这是一个生物信息学分析流程图,详细说明了如何从RNA测序数据中鉴定出候选的“长链基因间非编码RNA”(lincRNA)

以下是流程图的详细步骤解读:

  1. 起始数据 (Input Data)

    • scRNA-seq/snRNA-seq and bulk RNA-seq data

    • 解读:流程的起点是三种不同类型的RNA测序数据:单细胞RNA测序 (scRNA-seq)、单核RNA测序 (snRNA-seq) 和常规的批量RNA测序 (bulk RNA-seq)。使用多种数据源可以更全面地捕获转录本。

  2. 数据预处理 (Data Pre-processing)

    • FastQC, TrimGalore

    • 解读:这是标准的质控和清洗步骤。FastQC 用于检查原始测序数据的质量,TrimGalore 用于去除低质量的碱基和测序接头 (adapter)。

  3. 比对与转录本组装 (Alignment & Assembly)

    • HISAT2, StringTie

    • 解读HISAT2 是一个将清洗后的测序读长(reads)比对到参考基因组上的工具。StringTie 则根据比对结果,将这些读长组装成完整的转录本(transcripts)。

  4. 合并转录本 (Transcript Collection)

    • Known lncRNA transcripts (已知的lncRNA转录本)

    • Assembled transcripts (新组装的转录本)

    • 解读:这一步将上一步中新组装(可能包含未知的)转录本与数据库中已知的lncRNA转录本合并在一起,形成一个更完整的转录本集合。

  5. 定位 (Localization)

    • Locating in the intergenic regions (定位于基因间区)

    • 解读:这是定义 "intergenic" (基因间) 的关键步骤。流程会筛选出那些位于已知蛋白质编码基因 之间 的转录本。

  6. 过滤标准 (Filtering criteria)

    • 这是最核心的步骤,用于筛选出真正的 lincRNA。一个转录本必须同时满足以下所有五个条件:

  7. Length > 200 bp:长度必须大于200个碱基对。这是定义 "长链" (long) non-coding RNA (lncRNA) 的标准阈值。

  8. ORF < 100 aa:ORF (Open Reading Frame, 开放阅读框) 必须小于100个氨基酸。这是为了排除那些可能编码较短肽链的转录本。

  9. No coding potential:没有编码潜力。这一步会使用专门的生物信息学工具(如 CPC, CPAT, Pfam等)来预测该RNA序列是否具有被翻译成功能性蛋白质的潜力。

  10. No overlap with housekeeping ncRNAs:不与 "管家"非编码RNA(如 rRNA, tRNA, snRNA, snoRNA 等)重叠。这是为了排除那些已知的、具有特定结构或功能的非编码RNA。

  11. No known protein domains:不包含任何已知的蛋白质结构域。这是另一重保险,确保该转录本不编码蛋白质。

  12. 最终输出 (Output)

    • lincRNA candidates (lincRNA 候选者)

    • 解读:通过上述所有步骤筛选后,剩下的转录本被认为是高可信度的 lincRNA 候选者,可以用于后续的深入研究。

总结

该流程图展示了一个严谨的 lincRNA 鉴定流程:它首先从多种RNA-seq数据中组装转录本,然后将其定位到基因间区,最后通过一系列严格的过滤条件(长度、编码潜能、与已知RNA或蛋白结构域的重叠)来筛选出最终的 lincRNA 候选集。

宝可梦和数码宝贝分类问题——机器学习

github/mrok273 - 搜索

合并为一个poke代码

python

-- coding: utf-8 --

import hashlib import shutil from pathlib import Path

= 配置:按需修改 =

src1 = Path(r"C:\Users\32110\Desktop\ポケモン\poke_vs_digi\data\poke_new") src2 = Path(r"C:\Users\32110\Desktop\ポケモン\poke_vs_digi\data\poke_old") dst = Path(r"C:\Users\32110\Desktop\ポケモン\poke_vs_digi\data\poke_all")

IMG_EXTS = {".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp", ".tif", ".tiff"} dry_run = False # 先想看计划不实际复制 -> True;确认没问题再改回 False

def iter_images(folder: Path): for p in folder.rglob("*"): if p.is_file() and p.suffix.lower() in IMG_EXTS: yield p

def file_sha1(p: Path, block=1024 * 1024): h = hashlib.sha1() with p.open("rb") as f: while True: b = f.read(block) if not b: break h.update(b) return h.hexdigest()

def main(): assert src1.exists(), f"不存在:{src1}" assert src2.exists(), f"不存在:{src2}" dst.mkdir(parents=True, exist_ok=True)

 # 记录目标中已有文件的hash,避免重复拷贝
 print("扫描目标文件夹以建立去重索引……")
 seen = {}  # sha1 -> filename
 for q in dst.rglob("*"):
     if q.is_file() and q.suffix.lower() in IMG_EXTS:
         try:
             seen[file_sha1(q)] = q.name
         except Exception as e:
             print(f"[跳过损坏文件] {q}: {e}")
 ​
 total, copied, skipped_dup = 0, 0, 0
 for src in (src1, src2):
     print(f"\n处理:{src}")
     for p in iter_images(src):
         total += 1
         try:
             h = file_sha1(p)
         except Exception as e:
             print(f"[读文件失败] {p}: {e}")
             continue
 ​
         if h in seen:
             skipped_dup += 1
             # 可换成 verbose 打印:print(f"[重复] {p} == {seen[h]}")
             continue
 ​
         # 生成不重名的文件名
         name = p.name
         stem, suf = p.stem, p.suffix
         out = dst / name
         k = 1
         while out.exists():
             out = dst / f"{stem}_{k}{suf}"
             k += 1
 ​
         print(("[计划复制] " if dry_run else "[复制] ") + f"{p} -> {out}")
         if not dry_run:
             shutil.copy2(p, out)
         seen[h] = out.name
         copied += 1
 ​
 print("\n=== 总结 ===")
 print(f"发现图片:{total}")
 print(f"复制新文件:{copied}")
 print(f"跳过重复:{skipped_dup}")
 print(f"目标目录:{dst}")

if name == "main": main()

 ​
 ```python
 # -*- coding: utf-8 -*-
 #直接运行代码分类
 """
 run_poke_digi.py
 - 从 data/poke 与 data/digi 读取图片
 - 划分 train/val/test
 - 微调 ResNet18 并保存 best 模型
 - 输出测试集报告 & 全量逐图预测至 outputs/classification_result.csv
 """
 import os, json, random, csv, pandas as pd, numpy as np, torch
 from pathlib import Path
 from torch import nn
 from torch.utils.data import DataLoader, random_split
 from torchvision import datasets, transforms, models
 from sklearn.metrics import classification_report, confusion_matrix
 ​
 # ========= 配置 =========
 DATA_DIR = r"C:\Users\32110\Desktop\ポケモン\poke_vs_digi\data"
 SAVE_DIR = os.path.join(os.path.dirname(__file__) or ".", "outputs")
 os.makedirs(SAVE_DIR, exist_ok=True)
 ​
 IMG_SIZE = 224
 BATCH = 32
 EPOCHS = 6
 LR = 1e-3
 SEED = 42
 DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 USE_PRETRAINED = True   # 如果无网络下载权重失败,设为 False
 ​
 random.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)
 ​
 # ========= 变换 =========
 tfm_train = transforms.Compose([
     transforms.Resize((IMG_SIZE, IMG_SIZE)),
     transforms.RandomHorizontalFlip(),
     transforms.ColorJitter(0.2,0.2,0.2,0.1),
     transforms.ToTensor(),
     transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),
 ])
 tfm_eval = transforms.Compose([
     transforms.Resize((IMG_SIZE, IMG_SIZE)),
     transforms.ToTensor(),
     transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),
 ])
 ​
 # ========= 数据 =========
 full = datasets.ImageFolder(DATA_DIR, transform=tfm_train)
 assert len(full.classes)==2 and set(full.classes)==set(["poke","digi"]) or True
 num_train = int(0.8 * len(full))
 num_val   = int(0.1 * len(full))
 num_test  = len(full) - num_train - num_val
 train_set, val_set, test_set = random_split(
     full, [num_train, num_val, num_test],
     generator=torch.Generator().manual_seed(SEED)
 )
 val_set.dataset.transform = tfm_eval
 test_set.dataset.transform = tfm_eval
 ​
 train_loader = DataLoader(train_set, batch_size=BATCH, shuffle=True,  num_workers=0)
 val_loader   = DataLoader(val_set,   batch_size=BATCH, shuffle=False, num_workers=0)
 test_loader  = DataLoader(test_set,  batch_size=BATCH, shuffle=False, num_workers=0)
 ​
 ​
 idx_to_cls = {v:k for k,v in full.class_to_idx.items()}
 with open(os.path.join(SAVE_DIR, "label_map.json"), "w", encoding="utf-8") as f:
     json.dump(idx_to_cls, f, ensure_ascii=False, indent=2)
 ​
 # ========= 模型 =========
 if USE_PRETRAINED:
     weights = models.ResNet18_Weights.DEFAULT
 else:
     weights = None
 model = models.resnet18(weights=weights)
 model.fc = nn.Linear(model.fc.in_features, 2)
 model.to(DEVICE)
 ​
 opt = torch.optim.Adam(model.parameters(), lr=LR)
 crit = nn.CrossEntropyLoss()
 ​
 def run_epoch(loader, train=True):
     model.train() if train else model.eval()
     tot_loss, tot_correct, tot = 0.0, 0, 0
     with torch.set_grad_enabled(train):
         for x, y in loader:
             x, y = x.to(DEVICE), y.to(DEVICE)
             out = model(x)
             loss = crit(out, y)
             if train:
                 opt.zero_grad(); loss.backward(); opt.step()
             tot_loss += loss.item() * x.size(0)
             tot_correct += (out.argmax(1) == y).sum().item()
             tot += x.size(0)
     return tot_loss/tot, tot_correct/tot
 ​
 best_val, best_path = 0.0, os.path.join(SAVE_DIR, "best_resnet18.pth")
 for ep in range(1, EPOCHS+1):
     tr_loss, tr_acc = run_epoch(train_loader, True)
     va_loss, va_acc = run_epoch(val_loader,   False)
     print(f"Epoch {ep}/{EPOCHS} | train acc {tr_acc:.3f} loss {tr_loss:.3f} | val acc {va_acc:.3f} loss {va_loss:.3f}")
     if va_acc > best_val:
         best_val = va_acc
         torch.save(model.state_dict(), best_path)
 ​
 # ========= 测试报告 =========
 model.load_state_dict(torch.load(best_path, map_location=DEVICE))
 model.eval()
 y_true, y_pred = [], []
 with torch.no_grad():
     for x, y in test_loader:
         x = x.to(DEVICE)
         logits = model(x)
         y_true.extend(y.numpy().tolist())
         y_pred.extend(logits.argmax(1).cpu().numpy().tolist())
 ​
 print("Label map:", idx_to_cls)
 print("Confusion matrix:\n", confusion_matrix(y_true, y_pred))
 print(classification_report(y_true, y_pred, target_names=[idx_to_cls[0], idx_to_cls[1]]))
 print(f"Best model saved to: {best_path}")
 ​
 # ========= 导出全量逐图预测到 CSV =========
 # 对 data 目录下所有图片做一次前向并写到 outputs/classification_result.csv
 from PIL import Image
 exts = {".jpg",".jpeg",".png",".bmp",".webp",".tif",".tiff"}
 from PIL import Image
 ​
 def predict_one(path: Path):
     img = Image.open(path).convert("RGB")
     x = tfm_eval(img).unsqueeze(0).to(DEVICE)
     with torch.no_grad():
         p = torch.softmax(model(x), dim=1)[0]
         pred_idx = int(p.argmax().item())
         # 关键修改:用整数键访问
         label = idx_to_cls[pred_idx] if pred_idx in idx_to_cls else idx_to_cls[str(pred_idx)]
         return label, float(p[pred_idx].item())
 ​
 rows = []
 for fp in Path(DATA_DIR).rglob("*"):
     if fp.is_file() and fp.suffix.lower() in exts:
         label, conf = predict_one(fp)
         rows.append({"path": str(fp), "pred": label, "confidence": conf})
 ​
 csv_path = os.path.join(SAVE_DIR, "classification_result.csv")
 pd.DataFrame(rows).to_csv(csv_path, index=False, encoding="utf-8-sig")
 print(f"Predictions saved to: {csv_path}")
 ​
 ​
 ​
 ​

后续自己验证即可

解读疑难杂症

经典推导。它在说明:如果我们有一个“好”的训练数据集,那么在训练集上学到的“最好”的模型,在真实世界中也会“几乎”是最好的。

我们用一个“考试”的例子来解读这些数字。

1. 定义和数字实例

想象你是一个学生,准备一场满分100分的大型考试。

  • $D_{all}$ (所有数据):代表“题库”,包含了这场考试可能出现的所有题目(比如10,000道题)。

  • $D_{train}$ (训练数据):代表你的“练习册”,它只包含了题库中的一小部分题目(比如500道题)。

  • $h$ (模型/假设):代表你的一种“学习方法”(比如 $h_1$=“死记硬背”,$h_2$=“理解概念”)。

  • $L(h, D)$ (损失/错误):代表你用 $h$ 方法在 $D$ 数据集上的“错误率”

    • 例如:$L(h, D_{train}) = 10\%$,意思是用 $h$ 方法做“练习册”,错误率是10%(得了90分)。

    • 例如:$L(h, D_{all}) = 12\%$,意思是用 $h$ 方法去考“题库”,真实错误率是12%(真实水平是88分)。

  • $\delta$ (Delta/容忍度):一个你能接受的“差距”。我们就设 $\delta = 2\%$。


2. 我们的目标 (What do we want?)

$$L(h^{train}, D{all}) - L(h^{all}, D{all}) \le \delta$$

  • $h^{all}$:这是“最强学神”。TA用最牛的方法,在“题库”($D_{all}$)上能达到的最低错误率

    • 假设“最强学神”的真实错误率是 $L(h^{all}, D_{all}) = 5\%$ (真实水平95分)。这是理论上的天花板。

  • $h^{train}$:这是“你”。你是根据“练习册”($D_{train}$)学到的最好方法

    • 假设你的练习册上有两种方法可选:

      • 方法1(死记硬背)在练习册上错误率 10%。

      • 方法2(理解概念)在练习册上错误率 8%。

    • 你会选择错误率最低的方法2,所以 $h^{train}$ = “理解概念”,并且 $L(h^{train}, D_{train}) = 8\%$

  • $L(h^{train}, D_{all})$:这是你的“真实水平”。也就是用你学到的方法(理解概念)去考“题库”的真实错误率。这是我们最关心的,但也是未知的。

目标的简单解读:

我们希望 “你的真实水平” - “最强学神的水平” $\le 2\%$。

我们希望 $L(h^{train}, D_{all}) - 5\% \le 2\%$。

换句话说,我们的目标是让我们的真实水平(错误率)$L(h^{train}, D_{all}) \le 7\%$。

我们希望我们通过“练习册”学到的本事,和“最强学神”的本事,差距不能超过2%。


3. 如何实现目标?(What kind of $D_{train}$ fulfil it?)

要实现这个目标,我们需要一个什么样“练习册”($D_{train}$)呢?

答案是:这本练习册必须质量非常高,非常有代表性。

条件 (Condition):

$$|L(h, D{train}) - L(h, D{all})| \le \delta/2$$

  • $\delta/2$:既然 $\delta = 2\%$,那么 $\delta/2 = 1\%$。

  • $|...|$:代表“差距的绝对值”。

  • $L(h, D_{train})$:用 任意一种 方法 $h$ 在“练习册”上的错误率。

  • $L(h, D_{all})$:用 同一种 方法 $h$ 在“题库”上的真实错误率。

条件的简单解读:

这个条件要求我们的“练习册”($D_{train}$)必须好到这种程度:无论你用什么学习方法 ( $\forall h$ ),你在“练习册”上的错误率,和你去考“题库”的真实错误率,差距都不能超过 1%!

这意味着,你的练习册(训练数据)必须是“题库”(所有数据)的完美缩影,不能有偏差。


4. 推导过程 (The proof)

现在,我们假设我们的“练习册”真的满足了上面那个1%的苛刻条件,看看我们是否能达成2%的最终目标

我们来一步步分解这个不等式链条:

第1步:$L(h^{train}, D_{all})$

  • 这是我们最关心的“你的真实错误率”。

第2步:$\le L(h^{train}, D_{train}) + \delta/2$

  • 依据:根据“条件”,$L(h^{train}, D{all}) - L(h^{train}, D{train}) \le \delta/2$。

  • 数字解读:“你的真实错误率” $\le$ “你在练习册上的错误率” + $1\%$。

  • 我们已知 $L(h^{train}, D_{train}) = 8\%$ (你在练习册上表现最好)。

  • 所以:$L(h^{train}, D_{all}) \le 8\% + 1\% = 9\%$。

第3步:$\le L(h^{all}, D_{train}) + \delta/2$

  • 依据:$h^{train}$ (你) 是在“练习册”上表现最好的 ($L(h^{train}, D_{train})$ 最小)。所以你在练习册上的错误率,肯定 $\le$ “最强学神”在 这本练习册 上的错误率。

  • 数字解读:$L(h^{train}, D{train}) \le L(h^{all}, D{train})$。

    • 你的练习错误率 (8%) $\le$ 学神做你这本练习册的错误率。

    • (比如,学神的“真实水平”是5%错误率,但对这本练习册,他可能刚好错了6%)。

    • 所以:$8\% \le 6\%$ 是不成立的。我们换个数字。

    • 假设 $h^{all}$ (学神方法) 在 $D{train}$ (练习册) 上的错误率是 **$L(h^{all}, D{train}) = 5.5\%$**。(这完全可能,因为 $h^{train}$ 才是为 $D_{train}$ 优化的)。

    • 而你的 $h^{train}$ 在 $D{train}$ 上的错误率是 **$L(h^{train}, D{train}) = 4.5\%$**。(你更适应练习册)

  • 我们重新代入第2步和第3步:

    • $L(h^{train}, D{all}) \le L(h^{train}, D{train}) + \delta/2 = 4.5\% + 1\% = 5.5\%$

    • $L(h^{train}, D{train}) \le L(h^{all}, D{train})$,所以 $4.5\% \le 5.5\%$ (成立)。

    • 因此:$L(h^{train}, D_{all}) \le 5.5\% + 1\% = 6.5\%$。 (这一步是为了把 $h^{train}$ 替换成 $h^{all}$)

第4步:$\le L(h^{all}, D_{all}) + \delta/2 + \delta/2$

  • 依据:再次使用“条件”,这次用在“最强学神” $h^{all}$ 身上。

    • $L(h^{all}, D{train}) \le L(h^{all}, D{all}) + \delta/2$

  • 数字解读:“学神在练习册上的错误率” $\le$ “学神的真实错误率” + $1\%$。

    • 我们已知 $L(h^{all}, D_{all}) = 5\%$ (学神的真实水平)。

    • $L(h^{all}, D_{train})$ (假设是5.5%) $\le 5\% + 1\% = 6\%$。(这个条件成立)。

  • 代入第3步的结果

    • 我们刚推到 $\le L(h^{all}, D_{train}) + \delta/2$

    • 现在我们把 $L(h^{all}, D_{train})$ 替换掉:

    • $\le [L(h^{all}, D_{all}) + \delta/2] + \delta/2$

  • 代入数字

    • $\le [5\% + 1\%] + 1\%$

    • $\le 7\%$

第5步:$= L(h^{all}, D_{all}) + \delta$

  • 数字解读:$= 5\% + 2\% = 7\%$。

总结

我们从 $L(h^{train}, D_{all})$ (你的真实水平) 出发,通过一步步推导,最终证明了:

$L(h^{train}, D_{all}) \le 7\%$

这正好是我们最初的目标!($L(h^{train}, D{all}) \le L(h^{all}, D{all}) + \delta$)

核心思想:

只要我们能保证我们的“练习册”(训练集 $D_{train}$)质量足够好,能让 任何方法 的“练习分”和“真实分”差距都在1% ($\delta/2$) 以内,那么我们通过这本练习册学到的“最好方法”,其“真实水平”和“学神”的差距一定不会超过2% ($\delta$)。

这张图展示的是图神经网络 (GNN) 中最核心的一步:信息聚合 (Aggregation)

右下角的中文已经总结了它的目的:“矩阵乘法的意义是为了让节点获取周围节点的信息。这个步骤称之为聚合。”

我们用一个简单的例子来拆解这个计算过程:

1. 定义

  • A (Adjacency matrix - 邻接矩阵):这是一个“谁和谁是邻居”的列表。

    • A[i][j] = 1 表示节点 i 和节点 j 是邻居。

    • A[i][j] = 0 表示它们不是邻居。

  • H_0 (Initial Features - 初始信息):这是每个节点一开始拥有的信息。你可以把它想象成每个节点上贴的“数字标签”。

    • 节点1的标签是 [1, 1, 1]

    • 节点2的标签是 [2, 2, 2]

    • ...

    • 节点6的标签是 [6, 6, 6]

  • A.matmul(H_0) (矩阵乘法):这就是“聚合”的动作。

2. 用实例计算

我们来看看输出结果 tensor([...])第一行 [7, 7, 7] 是怎么算出来的

这一行代表节点1在“聚合”后获得的新信息

  1. 找到节点1的邻居:

    我们查看邻接矩阵 A 的第一行:[0, 1, 0, 0, 1, 0]。

    • 第2个位置是1 (A[0][1])

    • 第5个位置是1 (A[0][4])

    • 这表示:节点1的邻居是节点2和节点5

  2. 获取邻居的信息:

    根据“聚合”的规则,节点1的新信息 = 它所有邻居的旧信息相加。

    • 节点2的旧信息是:H_0[1] = [2, 2, 2]

    • 节点5的旧信息是:H_0[4] = [5, 5, 5]

  3. 相加(聚合)

    • 节点1的新信息 = [2, 2, 2] + [5, 5, 5] = [7, 7, 7]

    • 这和输出结果的第一行完全一致!


3. 再看一个例子

我们来算一下输出结果的第二行 [9, 9, 9] (节点2的新信息):

  1. 找到节点2的邻居:

    查看邻接矩阵 A 的第二行:[1, 0, 1, 0, 1, 0]。

    • 这表示:节点2的邻居是节点1、节点3和节点5

  2. 获取邻居的信息

    • 节点1的旧信息:H_0[0] = [1, 1, 1]

    • 节点3的旧信息:H_0[2] = [3, 3, 3]

    • 节点5的旧信息:H_0[4] = [5, 5, 5]

  3. 相加(聚合)

    • 节点2的新信息 = [1, 1, 1] + [3, 3, 3] + [5, 5, 5] = [9, 9, 9]

    • 这也和输出结果的第二行完全一致。

总结

A.matmul(H_0) 这个操作,本质上就是让每个节点(每一行)都去“查询”它的邻居是谁(由A矩阵定义),然后把所有邻居的“信息”(由H_0矩阵定义)加起来,作为自己的新信息。

请注意:图中的“Labelled graph”与它旁边的“Adjacency matrix”并不完全匹配(例如,图中节点1连接了3个邻居,但矩阵A的第一行显示它只有2个邻居)。这个计算是严格按照矩阵 AH_0 来执行的,图示可能只是一个概念上的说明。

这张图详细展示了反向传播(Backpropagation)的核心——求参数梯度。它以一个非常简单的神经网络为例,一步一步计算出了损失函数 $J$ 对参数 $w_{11}$ 的偏导数(梯度)。

以下是这张图的详细解读:

1. 神经网络模型(图的上半部分)

这是一个两层的前馈神经网络:

  • 输入 (Input):$x_1$

  • 第一层(隐藏层)

    • 输入 $x_1$ 乘以权重 $w_{11}$ 再加上偏置 $b_1$。

    • 通过一个Sigmoid激活函数,得到输出 $a_{11}$。

    • $a{11} = \text{sigmoid}(x_1 w{11} + b_1)$

  • 第二层(输出层)

    • 第一层的输出 $a{11}$ 乘以权重 $w{21}$ 再加上偏置 $b_2$。

    • 通过一个ReLU激活函数,得到输出 $a_{21}$。

    • $a{21} = \text{relu}(a{11} w_{21} + b_2)$

  • 预测值 (Prediction):$y$

    • $y = a{21}$ (网络的最终预测值就是 $a{21}$)

  • 损失函数 (Loss Function):$J(W, b)$

    • 根据推导的第三行 $\frac{\partial}{\partial y} (\frac{1}{2}(y-2)^2)$,我们可以看出损失函数是均方误差 (MSE) 的一半。

    • $J = \frac{1}{2}(y{\text{pred}} - y{\text{true}})^2 = \frac{1}{2}(y - 2)^2$

    • 这里的真实标签 (true value) 是 $2$

2. 目标:求 $w_{11}$ 的梯度

我们的目标是计算 $\frac{\partial J}{\partial w_{11}}$。

这个值告诉我们:$w_{11}$ 这个参数变化一点点,最终的损失 $J$ 会变化多少? 知道这个值后,我们才能使用梯度下降法来更新 $w_{11}$,以减小损失。

3. 推导过程:链式法则(Chain Rule)

反向传播的本质就是链式法则。我们要从最后(损失 $J$)一直“追溯”到最前(参数 $w_{11}$):

第 1-2 行:建立链式法则

$$\frac{\partial J}{\partial w{11}} = \frac{\partial J}{\partial y} \cdot \frac{\partial y}{\partial a{21}} \cdot \frac{\partial a{21}}{\partial a{11}} \cdot \frac{\partial a{11}}{\partial w{11}}$$

  • $\frac{\partial J}{\partial y}$:损失 $J$ 对预测 $y$ 的偏导。

  • $\frac{\partial y}{\partial a_{21}}$:预测 $y$ 对 $a_{21}$ 的偏导。

  • $\frac{\partial a{21}}{\partial a{11}}$:$a{21}$ 对 $a{11}$ 的偏导。

  • $\frac{\partial a{11}}{\partial w{11}}$:$a{11}$ 对 $w{11}$ 的偏导。

第 3-4 行:分步计算

  • $\frac{\partial J}{\partial y}$ = $\frac{\partial}{\partial y} (\frac{1}{2}(y - 2)^2)$ = $(y - 2)$

  • $\frac{\partial y}{\partial a_{21}}$ = $\frac{\partial a{21}}{\partial a{21}}$ (因为 $y = a_{21}$) = $1$

  • $\frac{\partial a{21}}{\partial a{11}}$ = $\frac{\partial \text{relu}(a{11} w{21} + b_2)}{\partial a_{11}}$

    • ReLU的导数:如果输入 $> 0$,导数为 $1$;如果输入 $\le 0$,导数为 $0$。

    • 从第6行 $y = 1.731$ 可知 $a_{21} > 0$,所以ReLU的导数为 $1$。

    • $\frac{\partial a{21}}{\partial a{11}} = 1 \cdot \frac{\partial (a{11} w{21} + b_2)}{\partial a{11}}$ = **$w{21}$**

  • $\frac{\partial a{11}}{\partial w{11}}$ = $\frac{\partial \text{sigmoid}(x_1 w{11} + b_1)}{\partial w{11}}$

    • Sigmoid的导数:$\text{sigmoid}'(z) = \text{sigmoid}(z) \cdot (1 - \text{sigmoid}(z))$

    • $\frac{\partial a{11}}{\partial w{11}} = \text{sigmoid}'(x_1 w{11} + b_1) \cdot \frac{\partial (x_1 w{11} + b_1)}{\partial w_{11}}$

    • = $[\text{sigmoid}(x_1 w{11} + b_1) \cdot (1 - \text{sigmoid}(x_1 w{11} + b_1))] \cdot x_1$

    • 因为 $a{11} = \text{sigmoid}(x_1 w{11} + b_1)$,所以这也等于 $a{11} \cdot (1 - a{11}) \cdot x_1$

第 5 行:组合所有导数

把上面计算的每一项乘起来:

$\frac{\partial J}{\partial w{11}} = (y - 2) \times w{21} \times \text{sigmoid}(x_1 w{11} + b_1) \cdot (1 - \text{sigmoid}(x_1 w{11} + b_1)) \cdot x_1$

第 6 行:代入“前向传播”的数值

在进行反向传播之前,必须先进行一次前向传播(forward pass)来得到所有中间值。这张图直接给出了这些值:

  • $x_1 = 1$ (输入)

  • $a{11} = \text{sigmoid}(x_1 w{11} + b_1) = 0.731$ (第一层输出)

  • $w_{21} = 1$ (第二层权重)

  • $y = a_{21} = 1.731$ (最终预测值)

  • 真实值 = $2$

代入:

$\frac{\partial J}{\partial w_{11}} = (1.731 - 2) \times 1 \times 0.731 \times (1 - 0.731) \times 1$

第 7-8 行:最终计算

$\frac{\partial J}{\partial w_{11}} = (-0.269) \times 0.731 \times (0.269) \times 1$

$\frac{\partial J}{\partial w_{11}} = -0.052895$

结果的意义:

这个负值 $-0.052895$ 意味着,如果增加 $w{11}$,损失 $J$ 会减少。因此,在梯度下降更新时, $w{11}$ 应该被增加。

(公式:$w{\text{new}} = w{\text{old}} - \eta \cdot \text{gradient}$,这里 $w{\text{new}} = w{11} - \eta \cdot (-0.052895)$,变成了加法)。

4. 关于 $b_1$ 的梯度

$\frac{\partial J(b_1)}{\partial b_1} = -0.052895$

“同理可得”,$b_1$ 的梯度和 $w_{11}$ 的梯度在数值上完全一样。为什么?

  • $b_1$ 的链式法则是:$\frac{\partial J}{\partial b_1} = \frac{\partial J}{\partial y} \cdot \frac{\partial y}{\partial a{21}} \cdot \frac{\partial a{21}}{\partial a{11}} \cdot \frac{\partial a{11}}{\partial b_1}$

  • 前三项都和 $w_{11}$ 的计算完全相同。

  • 最后一项 $\frac{\partial a{11}}{\partial b_1} = \frac{\partial \text{sigmoid}(x_1 w{11} + b_1)}{\partial b_1} = [\text{sigmoid}(\dots) \cdot (1 - \text{sigmoid}(\dots))] \cdot 1$

  • 而 $\frac{\partial a{11}}{\partial w{11}} = [\text{sigmoid}(\dots) \cdot (1 - \text{sigmoid}(\dots))] \cdot x_1$

唯一的区别是 $\frac{\partial a{11}}{\partial b_1}$ 乘以 $1$,而 $\frac{\partial a{11}}{\partial w_{11}}$ 乘以 $x_1$。

因为在这次计算中,输入 $x_1 = 1$,所以两个导数最终的计算结果完全相同。

这张图是在解释一个著名的数学问题:如何构建一个“2元2级德布鲁因(De Bruijn)序列”

以下是详细解读:

1. 提问(左侧)

  • 问题:在一个有4个扇区的转盘上,放入两个0和两个1。当你顺时针转动转盘时,你有一个“观察窗”始终只能看到相邻的两位数。如何摆放这四个数,才能让你每转动一次,看到的两位数都完全不一样

  • 目标:4次转动(包括起始位置),需要分别看到 00, 01, 10, 11 这四种所有可能的两位二进制数,且每种只出现一次。

2. 解决方案(右侧的图)

右侧的图叫作德布鲁因图(De Bruijn graph),它是解决这个问题的钥匙。

  • 节点 (Nodes):图中有两个节点,分别是 01

    • 在“2元2级”问题中,节点代表所有可能的 $N-1$ 位数。这里 $N=2$,所以节点代表所有可能的1位数,即 "0" 和 "1"。

  • 有向边 (Edges):图中有四条带箭头的边,分别是 00, 01, 10, 11

    • 这些边代表了我们要寻找的所有 $N$ 位数(这里是2位数)。

    • 边的“起点”和“终点”由节点决定:一条标签为 XY 的边,会从节点 X 指向节点 Y

    • 例如

      • 00:从 0 指向 0 (自循环)

      • 01:从 0 指向 1

      • 10:从 1 指向 0

      • 11:从 1 指向 1 (自循环)

3. 如何从图中找到答案

我们的目标是找到一条“欧拉回路”(Eulerian path)—— 这是一条“一笔画”的路径,它必须走过所有的4条边,且每条边只走一次,最后回到起点。

这个路径上你访问的节点的顺序(或者边的第一个数字的顺序)就是转盘上数字的排列方式。

让我们来“走”一遍:

  1. 起点:我们从节点 0 开始。

  2. 第1步:走 00 这条边。

    • 路径:0 → 0

    • 我们得到的序列的第一个数是 0

    • 我们看到了 00

  3. 第2步:现在我们在节点 0,走 01 这条边。

    • 路径:0 → 1

    • 我们得到的序列的第二个数是 0

    • 我们看到了 01

  4. 第3步:现在我们在节点 1,走 11 这条边。

    • 路径:1 → 1

    • 我们得到的序列的第三个数是 1

    • 我们看到了 11

  5. 第4步:现在我们在节点 1,走 10 这条边。

    • 路径:1 → 0

    • 我们得到的序列的第四个数是 1

    • 我们看到了 10

  6. 结束:我们回到了起点 0,并且所有4条边都走完了。

最终答案:

我们按顺序走的边的第一个数字组合起来,就是这个序列:0, 0, 1, 1。

验证:

把 0 0 1 1 顺时针放在转盘上。

  1. 观察窗看到:00

  2. 转一下,看到:01

  3. 再转一下,看到:11

  4. 再转一下(从最后的1绕回开头的0),看到:10

所有四种组合都看到了,问题解决!

(P.S. 存在另一个解:0110。你可以试试从节点0出发,先走01这条边,也能找到这个解。)

文献阅读

使用 RNAhybrid 软件,通过计算“结合能”来预测 miRNA 会“粘”在靶基因的哪个位置。

当然可以。这张图的核心意思是:使用 RNAhybrid 软件,通过计算“结合能”来预测 miRNA 会“粘”在靶基因的哪个位置。

我们来举一个实例,一步一步解读这个预测过程。

1. 预测的两个主角:miRNA 和靶基因

首先,你需要两个RNA序列(用A, U, G, C表示):

  1. miRNA 序列 (短)

    • 这是一条很短的RNA,大约22个碱基长。我们虚构一个叫 miR-123 的miRNA。

    • miR-123 序列: 5'-AUGUACUGAGCUAGUACUGGA-3'

  2. 靶基因 mRNA 序列 (长)

    • 这就是图中的“靶基因 E75”。miRNA通常结合在基因的3'UTR区域,所以我们拿E75基因的3'UTR序列(或像图中提到的编码区)作为靶标。这是一个非常长的序列,可能有几千个碱基。

    • E75 序列 (片段): ...GCAUUCAUGAUC**AUGAUGA**CCAU...

2. 预测的核心原理:寻找“最速配”的结合点

RNAhybrid 软件的工作就像一个“自动配对”程序。它会拿着短的 miR-123 序列,在长的 E75 序列上一格一格地滑动匹配,寻找“粘”得最牢固的区域。

这个“牢固”程度主要由两个规则决定:

规则一:寻找“种子区域” (Seed Region)

  • miRNA的第2到第8个碱基(从5'端开始数)被称为“种子区域”。

  • miR-123 的种子区域: 5'-A**UGUACUG**AGCUAGUACUGGA-3' (即 UGUACUG)

  • 软件会优先寻找 E75 序列上能与这个“种子区域”完美反向互补的地方。(A配U,G配C)

  • miR-123 种子: UGUACUG

  • 完美互补序列: ACAUGA C

  • 软件在 E75 序列上扫描,发现了一个非常匹配的区域!

    ...GCAUUCAUGAUCAUGAUGACCAU...

    这里的 AUGAUGA 和 ACAUGA C 非常接近。

规则二:计算“最小自由能” (Minimum Free Energy, MFE)

  • 一旦找到了一个不错的“种子”匹配点,RNAhybrid 就会计算整个miRNA短链与该区域结合的总能量

  • RNA的碱基配对会释放能量。配对越完美(A-U, G-C),释放的能量越多,结构越稳定。

  • 这种稳定性能量值用“自由能”表示,单位是 kcal/mol这个值越负(比如 -30 kcal/mol),代表结合越稳定,越牢固。

  • “错配”(比如A和C相对)或“凸起”(一个链上有碱基,另一链上没有)会增加能量,使结合不稳定。

3. 预测结果:一个实例

RNAhybrid 软件会输出一个类似这样的结果,告诉你“最佳结合点”在哪里:

 靶标: E75基因 (位置 310-335)
 miRNA:  miR-123
 ​
   靶标 5' ...AUUCAUGAUCAUGAUGACCAU... 3'
           ||||| |  |||||||  ||
   miRNA  3' AGGUCGUAGCUAGUCAUGUA... 5'
        (种子区)
 ​
 能量 (MFE): -28.5 kcal/mol
解读这个结果:
  1. 位置:软件告诉你,在 E75 基因序列的第310到335个碱基位置,找到了一个最好的结合点。

  2. 配对

    • | 竖线代表完美的 G-C 或 A-U 配对。

    • : (图中未显示,但很常见)代表稍弱的 G-U 配对。

    • 空格代表“错配”或“凸起”。

  3. 种子区域:你可以看到,在miRNA的种子区域(右侧),几乎是完美配对的。这是结合的核心。

  4. 能量值 (-28.5 kcal/mol):这是关键数字。这个值足够“负”,表明这是一个非常稳定、非常可能的结合位点。

总结

就像您图中所说,研究人员就是用 RNAhybrid 做了这个计算。

  1. 他们输入了“靶基因 E75”的完整序列。

  2. 他们输入了三个不同的 miRNA 序列。

  3. 软件分别计算了这三个 miRNA 在 E75 上的所有可能结合点,并返回了能量最低(最稳定)的那几个。

  4. 最终,他们“初步确定了三个miRNA与靶基因E75”的结合位点,因为这些位点的计算能量值很低(比如低于-25 kcal/mol),并且具有良好的“种子区域”配对。

cora数据集做图神经网络

 ​
 # -*- coding: utf-8 -*-
 import os, random
 import numpy as np
 import matplotlib.pyplot as plt
 import networkx as nx
 import torch
 from torch_geometric.datasets import Planetoid
 import torch_geometric.transforms as T
 ​
 # ====== 配置 ======
 MODEL_NAME = "gcn"   # 你也可以改成 "gat"/"sage",对应前面保存的 pred_gat.npy / pred_sage.npy
 OUT_DIR = "cora_viz_outputs"
 DRAW_MODE = "ego"    # "full"=整图 | "sample"=随机子图 | "ego"=k-hop 自中心子图
 SUBSET_N = 600       # 随机采样子图尺寸(DRAW_MODE="sample" 时生效)
 EGO_CENTER = None    # 指定中心节点 id(None=自动选一个度最高或随机的)
 EGO_K = 2            # k-hop 半径
 SEED = 42
 random.seed(SEED); np.random.seed(SEED)
 ​
 # ====== 载入数据与预测 ======
 dataset = Planetoid(root="Planetoid", name="Cora", transform=T.NormalizeFeatures())
 data = dataset[0]
 y_true = data.y.numpy()
 test_mask = data.test_mask.numpy()
 ​
 pred_path = os.path.join(OUT_DIR, f"pred_{MODEL_NAME}.npy")
 if not os.path.exists(pred_path):
     raise FileNotFoundError(f"未找到预测文件:{pred_path},请先跑 cora_gnn_viz_all.py 生成。")
 y_pred = np.load(pred_path)  # 全图的预测
 ​
 # ====== 构图(PyG -> NetworkX)======
 edge_index = data.edge_index.numpy()
 G = nx.Graph()
 G.add_nodes_from(range(data.num_nodes))
 edges = list(zip(edge_index[0], edge_index[1]))
 # Cora 是无向图,去重
 G.add_edges_from((u, v) for u, v in edges if u != v)
 ​
 # ====== 选择子图 ======
 if DRAW_MODE == "full":
     H = G
     title_suffix = " (full graph)"
 elif DRAW_MODE == "sample":
     # 随机选 SUBSET_N 个节点诱导子图
     nodes = np.random.choice(G.number_of_nodes(), size=min(SUBSET_N, G.number_of_nodes()), replace=False)
     H = G.subgraph(nodes).copy()
     title_suffix = f" (random {H.number_of_nodes()} nodes)"
 else:
     # ego:选择一个中心节点(默认:度最大的测试集节点;否则随机一个测试集节点)
     if EGO_CENTER is None:
         # 选测试集里度最大的节点当中心;若找不到就随机一个
         test_nodes = np.where(test_mask)[0]
         if len(test_nodes) == 0:
             EGO_CENTER = random.randrange(G.number_of_nodes())
         else:
             center_deg_pairs = [(n, G.degree[n]) for n in test_nodes]
             center_deg_pairs.sort(key=lambda x: x[1], reverse=True)
             EGO_CENTER = center_deg_pairs[0][0]
     ego_nodes = nx.ego_graph(G, EGO_CENTER, radius=EGO_K).nodes()
     H = G.subgraph(ego_nodes).copy()
     title_suffix = f" (ego k={EGO_K}, center={EGO_CENTER}, |V|={H.number_of_nodes()})"
 ​
 print(f"Subgraph: |V|={H.number_of_nodes()}, |E|={H.number_of_edges()}")
 ​
 # ====== 准备颜色映射 ======
 # 方案 A:按“是否预测正确”着色(正确=绿,错误=红),非测试集=灰
 colors = []
 for n in H.nodes():
     if test_mask[n]:
         colors.append("#2ca02c" if y_pred[n] == y_true[n] else "#d62728")
     else:
         colors.append("#bbbbbb")
 ​
 # 方案 B(可选):按真实类别着色
 # 取消下方注释即可用类别调色
 # import matplotlib.cm as cm
 # cmap = cm.get_cmap('tab10', dataset.num_classes)  # 7 类
 # colors = [cmap(y_true[n]) for n in H.nodes()]
 ​
 # 节点大小:测试集更大,非测试集更小(视觉区分)
 sizes = [30 if test_mask[n] else 10 for n in H.nodes()]
 ​
 # ====== 布局(spring 或 kamada_kawai,节点多时 spring 更稳)======
 if H.number_of_nodes() <= 1200:
     pos = nx.spring_layout(H, seed=SEED, k=None)
 else:
     pos = nx.spring_layout(H, seed=SEED, k=0.05)  # 大图适当调弹簧长度
 ​
 # ====== 绘图 ======
 plt.figure(figsize=(10, 8))
 nx.draw_networkx_nodes(H, pos, node_color=colors, node_size=sizes, linewidths=0.0)
 nx.draw_networkx_edges(H, pos, width=0.5, alpha=0.25)
 plt.axis("off")
 plt.title(f"Cora network{title_suffix}\nGreen=Correct (test), Red=Wrong (test), Gray=Non-test", fontsize=12)
 ​
 # 保存与展示
 os.makedirs(OUT_DIR, exist_ok=True)
 out_name = f"cora_net_{MODEL_NAME}_{DRAW_MODE}.png"
 plt.tight_layout()
 plt.savefig(os.path.join(OUT_DIR, out_name), dpi=300)
 plt.show()
 print(f"[Saved] {os.path.join(OUT_DIR, out_name)}")
 ​
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐