简介:本资源是一份面向计算机及相关专业本科生的毕业设计实践项目,聚焦单细胞RNA测序数据的细胞类型注释算法实现,适用于人工智能、生物信息、自动化等方向的学生开展毕设、课程设计或科研入门。项目基于Python构建,含完整可运行源码、详细文档说明与结构化工程目录,已通过实际测试并获答辩平均96分高分评价,兼顾理论严谨性与工程可用性。压缩包共91个文件,主体为61个Python核心模块(涵盖预处理、模型构建、训练预测、数据读取、可视化等全流程),辅以7个XML配置文件、3个CSV样本数据及README.md等说明文档,整体仅227KB,轻量易部署。目前已有127人下载学习,内容覆盖scADL算法复现、GPU加速训练、UMAP/t-SNE降维可视化、标签编码与数据集融合等关键环节,并提供十余个独立测试脚本(如tensor_normalize_test.py、pca_GPU_test.py等),便于读者逐模块验证与二次开发。
1. 这不是又一个“调用Scanpy跑个UMAP”的毕设:它用PyTorch重写了单细胞注释的底层数据流,把scVI、Seurat里黑匣子般的预处理和模型训练拆成了可调试、可断点、可替换的Python模块
你见过多少份“基于单细胞RNA测序数据的细胞类型注释”毕设?十份里九份是 pip install scanpy + read_h5ad() + sc.tl.pca() + sc.tl.umap() + sc.tl.leiden() + sc.pl.umap(color='cell_type') —— 流程漂亮,答辩PPT炫酷,但代码里没有一行是你自己写的逻辑。而这份《基于单细胞RNA测序数据的细胞类型注释算法研究》毕业设计,核心不是调包,是重写数据加载链路、重构归一化张量、手写GPU加速的FC-Net分类器、并把scADL(Single-cell Adversarial Deep Learning)思想落地为可复现的PyTorch训练循环。它不依赖AnnData黑盒,所有预处理(log1p、batch correction模拟、gene filtering)、模型构建(含attention机制的双分支编码器)、训练策略(带梯度裁剪的对抗损失+分类损失联合优化)全部显式编码;答辩平均分96分不是靠PPT动画,是评审老师当场要求你切到train.py第142行,把loss_adv权重从0.3临时改成0.8,看模型在验证集上F1-score是否骤降——它真能动、真可控、真可debug。适合想把“单细胞分析”从“会跑流程”升级到“懂数据怎么进模型、梯度怎么反传、为什么batch size=64时CUDA out of memory”的计算机/生信本科生,也适合需要快速搭建可解释性注释pipeline的课题组研究生。
2. 从原始h5文件到GPU张量:scADL数据流的三阶段解耦设计与实操细节
2.1 数据加载层:read_datasets.py如何绕过AnnData,直取原始count matrix与label
传统单细胞流程高度依赖AnnData对象封装,但这也意味着你无法轻易干预其内部稀疏矩阵转换逻辑、无法在读取瞬间插入自定义filter(比如按基因表达方差动态剔除低变基因)。本项目采用原生h5py + numpy解耦加载,关键逻辑在read_datasets.py中:
def load_dataset_h5(path: str, gene_filter_threshold: float = 0.01, min_cells_per_gene: int = 10) -> Tuple[np.ndarray, np.ndarray, List[str]]: """ 直接读取10x格式h5文件,返回dense count matrix (n_cells x n_genes) 注意:不使用AnnData,避免隐式log1p或normalize 参数说明: - gene_filter_threshold: 基因在所有细胞中非零比例阈值(默认1%) - min_cells_per_gene: 基因至少在min_cells_per_gene个细胞中表达才保留 """ import h5py with h5py.File(path, 'r') as f: # 10x h5结构:'matrix'下有'data', 'indices', 'indptr', 'shape' data = f['matrix/data'][()] indices = f['matrix/indices'][()] indptr = f['matrix/indptr'][()] shape = f['matrix/shape'][()] # 构建scipy.sparse.csr_matrix再转dense(内存换可控性) from scipy.sparse import csr_matrix sparse_mat = csr_matrix((data, indices, indptr), shape=shape) dense_mat = sparse_mat.toarray() # 此刻仍是raw count # 按基因非零比例过滤(关键!避免后续log1p后大量0变inf) nonzero_ratio = np.count_nonzero(dense_mat, axis=0) / dense_mat.shape[0] valid_genes = nonzero_ratio >= gene_filter_threshold dense_mat = dense_mat[:, valid_genes] # 加载cell labels(假设h5中存于'obs/cell_type'路径) try: labels = [x.decode('utf-8') for x in f['obs/cell_type'][()]] except KeyError: # 若无label,生成dummy label(用于无监督预训练) labels = ['unknown'] * dense_mat.shape[0] return dense_mat.astype(np.float32), np.array(labels), list(f['var/gene_ids'][()])[:sum(valid_genes)]提示:此函数返回的是原始count matrix,未做任何log1p或scale。所有归一化操作被明确剥离到
preprocess.py中,确保你在调试时能清晰看到“输入是什么、中间态是什么、输出是什么”。若你的数据来自10x CellRanger输出,直接传入filtered_feature_bc_matrix.h5路径即可;若为loom或csv格式,需先用txt_to_csv.py或mtx_to_npy.py统一转为numpy array再喂入。
2.2 预处理流水线:preprocess.py里的四步不可跳过操作
preprocess.py不是简单的sc.pp.normalize_total()封装,而是将单细胞数据特性转化为PyTorch张量前的四步强约束处理:
- Raw count → Log1p转换:
np.log1p(counts),而非np.log(counts + 1),避免浮点精度误差; - Gene-wise z-score标准化:对每个基因(列)做
(x - mean) / std,使不同基因量纲一致——这是后续FC-Net收敛的关键; - Top 2000高变基因筛选:非简单按mean-variance关系,而是计算每个基因的离散度(dispersion):
dispersion = var / mean,取top-k(默认2000); - Tensor化与GPU搬运:
torch.tensor(...).float().to(device),且显式检查device是否为cuda,失败则报错而非静默退化。
def preprocess_sc_data(counts: np.ndarray, n_top_genes: int = 2000, device: torch.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')) -> torch.Tensor: """ 单细胞数据标准预处理流水线 返回:(n_cells, n_top_genes) 的float32 Tensor,已z-score标准化 """ # Step 1: Log1p log_counts = np.log1p(counts) # Step 2: Gene-wise z-score means = log_counts.mean(axis=0, keepdims=True) stds = log_counts.std(axis=0, keepdims=True) # 防止std为0导致除零 stds[stds == 0] = 1e-8 z_scored = (log_counts - means) / stds # Step 3: Dispersion-based gene selection dispersion = np.var(z_scored, axis=0) / (np.mean(z_scored, axis=0) + 1e-6) top_gene_idx = np.argsort(dispersion)[-n_top_genes:] z_scored = z_scored[:, top_gene_idx] # Step 4: To tensor & GPU tensor_data = torch.tensor(z_scored, dtype=torch.float32) if device.type == 'cuda': tensor_data = tensor_data.to(device) print(f"✅ Preprocessed data loaded to {device} ({tensor_data.shape})") else: print("⚠️ CUDA not available, using CPU") return tensor_data参数说明:
n_top_genes默认2000,但实际项目中建议先运行dataset_get_common_genes_test.py确认多个数据集交集基因数;device必须显式传入,因为train.py中会根据args.gpu_id动态设置,不能依赖torch.cuda.is_available()全局判断——这是多卡训练时的常见翻车点。
2.3 模型输入适配:LoadData_to_GPU_test.py验证你的数据是否真正“准备好”
别急着跑train.py。先执行code_test/LoadData_to_GPU_test.py,它会模拟真实训练时的数据搬运链路:
# code_test/LoadData_to_GPU_test.py from utils import get_device from read_datasets import load_dataset_h5 from preprocess import preprocess_sc_data if __name__ == "__main__": # 1. 加载原始h5 counts, labels, gene_names = load_dataset_h5("data/10x_pbmc.h5") print(f"Raw shape: {counts.shape}, labels: {len(labels)}") # 2. 预处理 device = get_device() # 读取args.gpu_id或fallback to cpu tensor_data = preprocess_sc_data(counts, n_top_genes=2000, device=device) # 3. 检查GPU内存占用(关键!) if device.type == 'cuda': print(f"GPU memory allocated: {torch.cuda.memory_allocated(device)/1024**3:.2f} GB") print(f"GPU memory reserved: {torch.cuda.memory_reserved(device)/1024**3:.2f} GB") # 4. 尝试小批量forward(验证tensor shape兼容性) from models import FC_Net model = FC_Net(input_dim=2000, hidden_dim=512, num_classes=len(set(labels))).to(device) batch = tensor_data[:32] # 取32个cell做测试 out = model(batch) print(f"✅ Forward pass success: output shape {out.shape}")运行此脚本,你会看到:
Raw shape: (10000, 32738)→ 原始PBMC数据规模✅ Preprocessed data loaded to cuda:0 (10000, 2000)→ 预处理完成GPU memory allocated: 0.15 GB→ 内存占用合理(若>1GB,说明n_top_genes设太大或batch_size未限制)✅ Forward pass success: output shape torch.Size([32, 8])→ 模型能接收该tensor
这一步是血泪经验:曾有同学跳过此测试,直接跑train.py,结果卡在DataLoader的collate_fn里报RuntimeError: expected scalar type Float but found Half——根源是preprocess.py中torch.tensor(...).float()被误删,而models.py里用了torch.float16。这个测试脚本就是你的“后悔药”。
3. 模型架构与训练策略:scADL_rebuild中的对抗学习如何提升跨数据集泛化能力
3.1 FC_Net主干网络:轻量但有效的双分支编码器设计
models.py中的FC_Net并非简单MLP,而是双分支结构:一个分支专注细胞类型分类(Classification Head),另一个分支学习域不变特征(Domain Discriminator),二者通过梯度反转层(Gradient Reversal Layer, GRL)耦合。这种设计让模型在训练时既学“这是T cell”,又学“别管这数据来自10x还是Smart-seq2”。
class FC_Net(nn.Module): def __init__(self, input_dim: int, hidden_dim: int, num_classes: int, num_domains: int = 2): super().__init__() self.encoder = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.Dropout(0.3), nn.Linear(hidden_dim, hidden_dim // 2), nn.BatchNorm1d(hidden_dim // 2), nn.ReLU() ) # Classification head self.classifier = nn.Linear(hidden_dim // 2, num_classes) # Domain discriminator (adversarial head) self.domain_discriminator = nn.Sequential( GradientReversalLayer(), # 关键!反向传播时梯度乘-λ nn.Linear(hidden_dim // 2, hidden_dim // 4), nn.ReLU(), nn.Linear(hidden_dim // 4, num_domains) ) def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: features = self.encoder(x) class_logits = self.classifier(features) domain_logits = self.domain_discriminator(features) return class_logits, domain_logits class GradientReversalLayer(torch.nn.Module): def __init__(self, lambda_factor: float = 1.0): super().__init__() self.lambda_factor = lambda_factor def forward(self, x): return x def backward(self, grad_output): return -self.lambda_factor * grad_output # 在backward时反转梯度为什么用双分支?单细胞数据最大的痛点是批次效应(batch effect):同一细胞类型在不同实验平台(10x vs Smart-seq2)上表达谱差异巨大。传统方法用
harmony或scanorama做后处理校正,而scADL_rebuild在训练时就让encoder学出对平台无关的特征表示。domain_discriminator越难区分数据来源(10x vs Smart-seq2),说明encoder提取的特征越鲁棒。
3.2 训练循环:train.py中对抗损失与分类损失的动态平衡
train.py的train_epoch()函数核心在于损失权重的动态调整。不是固定loss = 0.7*loss_cls + 0.3*loss_adv,而是根据当前epoch逐步增加对抗损失权重,让模型先学好分类,再学域不变:
def train_epoch(model, dataloader, optimizer, epoch, total_epochs, device): model.train() total_loss, cls_loss, adv_loss = 0, 0, 0 for batch_idx, (data, labels, domains) in enumerate(dataloader): data, labels, domains = data.to(device), labels.to(device), domains.to(device) # Forward cls_logits, domain_logits = model(data) # 分类损失(CrossEntropy) loss_cls = F.cross_entropy(cls_logits, labels) # 对抗损失(Domain分类,但目标是让domain_logits全等概率) # 即:希望domain_discriminator输出[0.5, 0.5],故用CrossEntropy最小化其与uniform分布的KL uniform_target = torch.full_like(domains, 1.0 / domains.size(1)) # [0.5, 0.5] loss_adv = F.cross_entropy(domain_logits, domains) # 注意:此处domains是真实标签(0 or 1) # 动态权重:epoch前期cls主导,后期adv增强 lambda_adv = 0.1 + 0.9 * (epoch / total_epochs) # 从0.1线性增至1.0 loss = loss_cls + lambda_adv * loss_adv optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) # 防止梯度爆炸 optimizer.step() total_loss += loss.item() cls_loss += loss_cls.item() adv_loss += loss_adv.item() return total_loss / len(dataloader), cls_loss / len(dataloader), adv_loss / len(dataloader)参数说明:
lambda_adv从0.1开始线性增长至1.0,确保模型不会因过早强调对抗而损害分类精度。torch.nn.utils.clip_grad_norm_是必加项——单细胞数据高维稀疏,梯度易爆炸,不加此行train.py大概率在epoch=3时loss突变为nan。
3.3 验证与早停:train_test.py中的F1-score驱动策略
train_test.py不只算accuracy,而是严格按细胞类型粒度计算macro-F1,并实现patience=10的早停:
def validate(model, dataloader, device, class_names): model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for data, labels, _ in dataloader: data, labels = data.to(device), labels.to(device) cls_logits, _ = model(data) preds = torch.argmax(cls_logits, dim=1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) # 计算macro-F1(各cell type F1取平均,防类别不平衡) from sklearn.metrics import f1_score f1_macro = f1_score(all_labels, all_preds, average='macro') # 输出混淆矩阵(仅前5类,防终端刷屏) from sklearn.metrics import confusion_matrix cm = confusion_matrix(all_labels, all_preds) print(f"Validation Macro-F1: {f1_macro:.4f}") print("Top-5 classes confusion:") for i, cls_name in enumerate(class_names[:5]): print(f" {cls_name}: {cm[i][:5]}") return f1_macro为什么用macro-F1不用accuracy?单细胞数据中,某些细胞类型(如"erythrocyte")可能仅占0.1%,若模型全预测为majority class(如"T cell"),accuracy仍可达99%,但F1=0。
train_test.py强制用macro-F1,逼模型学好每一类。
4. 避坑:那些让答辩前夜崩溃的5个真实踩坑记录
4.1 现象:train.py运行到第2个epoch就OOM(Out of Memory),GPU显存瞬间占满100%
- 原因:
preprocess.py中n_top_genes=5000,而你的GPU只有12GB显存;或train.py中batch_size=256未根据显存动态调整。 - 解决:
- 先运行
code_test/GPU-test.py确认可用显存:print(f"Total: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB") print(f"Available: {torch.cuda.memory_reserved(0) / 1024**3:.1f} GB") - 根据显存调整
options.py:# options.py parser.add_argument('--batch_size', type=int, default=64) # 12GB GPU推荐64 parser.add_argument('--n_top_genes', type=int, default=2000) # >3000需>=24GB
- 先运行
4.2 现象:predict.py输出全是同一类标签(如全为"NK cell")
- 原因:
predict.py中未加载训练好的best_model.pth,而是用了随机初始化的模型;或read_datasets.py加载的test数据未经过与train相同的preprocess(如train用了log1p,test没用)。 - 解决:
- 检查
predict.py第32行是否为:model.load_state_dict(torch.load("results/best_model.pth")) # 必须指定路径 - 确保test数据预处理调用同一
preprocess_sc_data()函数,且n_top_genes与train一致。
- 检查
4.3 现象:tsne_test.py生成的t-SNE图完全散乱,无聚类结构
- 原因:t-SNE输入的是未归一化的raw count,或
perplexity参数过大(>50)导致全局结构丢失。 - 解决:
- t-SNE必须输入
preprocess.py输出的z-scored tensor; perplexity设为15~30(tsne_test.py中默认25),n_iter=1000,learning_rate=200。
- t-SNE必须输入
4.4 现象:requirements.txt安装后import torch报错libcudnn.so.8: cannot open shared object file
- 原因:PyTorch版本与系统CUDA驱动不匹配(如装了
torch==1.13.1+cu117,但系统CUDA为11.6)。 - 解决:
- 查系统CUDA:
nvcc --version; - 到https://pytorch.org/get-started/locally/ 选对应CUDA版本的pip命令;
- 卸载旧版:
pip uninstall torch torchvision torchaudio,再重装。
- 查系统CUDA:
4.5 现象:label_merge_split_test.py报错ValueError: Found array with 0 sample(s)
- 原因:
datasets_capitalize_test.py中细胞类型名大小写不统一(如"t cell"和"T Cell"被当不同类),导致label_process_test.py合并时某类样本数为0。 - 解决:
- 运行
code_test/dataset_label_match_test.py检查label一致性; - 在
read_datasets.py中强制统一label:labels = [x.strip().title() for x in labels] # "t cell" → "T Cell"
- 运行
5. 模型部署与结果可视化:从predict.py到results/目录的完整交付物生成
5.1 一键预测:predict.py的三种输入模式与输出规范
predict.py支持三种输入方式,覆盖毕设答辩所有场景:
| 输入模式 | 命令示例 | 适用场景 | 输出文件 |
|---|---|---|---|
| 单h5文件 | python predict.py --input data/test_pbmc.h5 --model results/best_model.pth | 答辩现场演示,输入新数据 | results/predict_results/test_pbmc_pred.csv(含cell_id, pred_label, confidence) |
| CSV矩阵 | python predict.py --input data/test.csv --format csv --label_col cell_type | 课程设计交作业,提供表格 | results/predict_results/test_pred.csv |
| 目录批量 | python predict.py --input data/test_batch/ --format dir | 企业级批量预测(如临床样本) | results/predict_results/test_batch/xxx_pred.csv |
# 示例:用训练好的模型预测新PBMC数据 python predict.py \ --input data/10x_pbmc_test.h5 \ --model results/best_model.pth \ --output_dir results/predict_results \ --batch_size 128 \ --device cuda:0关键参数说明:
--output_dir:必须指定,否则结果写入./results/(可能覆盖训练日志);--batch_size:与train.py保持一致,避免GPU显存波动;--device:显式指定cuda:0或cpu,防止多卡机器默认选错卡。
5.2 结果解读:results/目录下5类核心文件的用途
predict.py运行后,results/predict_results/下生成结构化交付物:
| 文件名 | 格式 | 用途 | 技术要点 |
|---|---|---|---|
xxx_pred.csv | CSV | 细胞级预测结果 | 列:cell_id,pred_label,confidence(softmax最大值) |
confusion_matrix.png | PNG | 混淆矩阵热力图 | 使用seaborn.heatmap,标注F1-score |
umap_projection.png | PNG | UMAP降维可视化 | 颜色按pred_label着色,叠加真实label(若提供) |
feature_importance.npy | NPY | Top-50重要基因 | 通过torch.autograd.grad计算logits对input的梯度均值 |
prediction_report.txt | TXT | 宏观指标报告 | 包含Accuracy, Macro-F1, Per-class Precision/Recall |
# results/predict_results/xxx_pred.csv 示例 cell_id,pred_label,confidence AAACCTGAGAAACCAT-1,T Cell,0.923 AAACCTGAGAAACCGA-1,B Cell,0.876 AAACCTGAGAAACCTA-1,NK Cell,0.741注意:
feature_importance.npy不是SHAP或LIME,而是梯度法(Gradient × Input),计算效率高,适合单细胞高维场景。代码在predict.py第189行:# 计算每个基因对预测的贡献 grads = torch.autograd.grad(outputs=logits.max(), inputs=input_tensor, retain_graph=False)[0] importance = torch.abs(grads * input_tensor).mean(dim=0).cpu().numpy()
5.3 可视化增强:用tensorboard_test.py实时监控训练过程
tensorboard_test.py不是玩具,而是集成到train.py的生产级监控:
# 在train.py开头添加 from torch.utils.tensorboard import SummaryWriter writer = SummaryWriter(log_dir="logs/train_logs") # 在train_epoch()循环内添加 writer.add_scalar('Loss/Total', total_loss, epoch) writer.add_scalar('Loss/Classification', cls_loss, epoch) writer.add_scalar('Loss/Adversarial', adv_loss, epoch) writer.add_scalar('Metrics/Macro-F1', val_f1, epoch) writer.add_histogram('Gradients/encoder', model.encoder[0].weight.grad, epoch) # 启动tensorboard # $ tensorboard --logdir=logs/train_logs --bind_all启动后访问http://localhost:6006,你能看到:
- 标量曲线:Loss下降是否平滑?F1是否在val集上持续提升?
- 直方图:
encoder层梯度是否在[-0.1, 0.1]内?若超出,说明需要加大clip_grad_norm_; - PR曲线:各类别的Precision-Recall trade-off,答辩时可截图展示。
血泪经验:我带过的3届毕设学生,有2人因没开tensorboard,在答辩前发现模型从epoch=50开始F1 plateau,但loss还在降——其实是过拟合了。开了tensorboard,一眼看出val_loss在epoch=45后上升,立即加了Dropout和早停。
6. 进阶技巧:如何用这份代码快速支撑你的科研论文图表(附3个可直接抄的代码片段)
6.1 生成论文Figure 2a:跨数据集泛化能力对比图
你需要证明scADL比Scanpy baseline更强。code_test/del_list_test.py已封装好对比逻辑:
# code_test/del_list_test.py from sklearn.metrics import f1_score import numpy as np def compare_methods_on_domain_shift(train_dataset: str, test_dataset: str): """ 比较scADL vs Scanpy在跨平台数据上的F1-score train_dataset: "pbmc_10x", test_dataset: "pbmc_smartseq2" """ # 1. scADL预测(调用predict.py) !python predict.py --input data/{test_dataset}.h5 --model results/best_model.pth scadl_pred = np.loadtxt(f"results/predict_results/{test_dataset}_pred.csv", delimiter=",", skiprows=1, usecols=2, dtype=str) # 2. Scanpy baseline(需提前运行scanpy_pipeline.py) scanpy_pred = np.loadtxt(f"data/{test_dataset}_scanpy_pred.csv", delimiter=",", usecols=1, dtype=str) # 3. 加载真实label true_labels = np.loadtxt(f"data/{test_dataset}_labels.csv", delimiter=",", usecols=1, dtype=str) # 4. 计算macro-F1 scadl_f1 = f1_score(true_labels, scadl_pred, average='macro') scanpy_f1 = f1_score(true_labels, scanpy_pred, average='macro') print(f"scADL on {test_dataset}: {scadl_f1:.4f}") print(f"Scanpy on {test_dataset}: {scanpy_f1:.4f}") return scadl_f1, scanpy_f1 # 执行 scadl_f1, scanpy_f1 = compare_methods_on_domain_shift("pbmc_10x", "pbmc_smartseq2")输出即论文图:将
scadl_f1和scanpy_f1填入Excel,生成柱状图,标题:“scADL vs Scanpy在跨平台数据上的Macro-F1对比”。这是审稿人最爱看的baseline comparison。
6.2 提取论文Table 1:模型超参数与硬件配置清单
options.py中所有可调参数,就是你的Method部分表格:
| Parameter | Value | Description |
|---|---|---|
--batch_size | 64 | GPU显存12GB下的安全值 |
--n_top_genes | 2000 | 基于dispersion筛选的高变基因数 |
--hidden_dim | 512 | Encoder第一层隐藏单元数 |
--lr | 1e-3 | Adam初始学习率 |
--lambda_adv_start | 0.1 | 对抗损失起始权重 |
--lambda_adv_end | 1.0 | 对抗损失最终权重 |
--patience | 10 | 早停容忍epoch数 |
技巧:直接从
options.py复制粘贴,无需手动整理。答辩PPT的Method页,就放这张表+一句“所有超参数经网格搜索确定”。
6.3 生成论文Supplementary Figure:t-SNE中对抗特征的可视化证据
要证明scADL真的学到了域不变特征,需画t-SNE图中domain label的混合程度:
# code_test/tsne_test.py 中新增函数 def plot_domain_mixture(embedding: np.ndarray, domains: np.ndarray, save_path: str): """ embedding: (n_cells, 2) t-SNE坐标 domains: (n_cells,) array of 0/1 (10x=0, Smart-seq2=1) """ import matplotlib.pyplot as plt plt.figure(figsize=(8,6)) scatter = plt.scatter(embedding[:,0], embedding[:,1], c=domains, cmap='bwr', alpha=0.6, s=1) plt.colorbar(scatter, label='Domain (0: 10x, 1: Smart-seq2)') plt.title('t-SNE: Domain Label Mixture (scADL Encoder Features)') plt.xlabel('t-SNE 1') plt.ylabel('t-SNE 2') plt.savefig(save_path, dpi=300, bbox_inches='tight') plt.close() # 调用 from sklearn.manifold import TSNE tsne = TSNE(n_components=2, perplexity=25, random_state=42) embedding = tsne.fit_transform(features_cpu) # features_cpu来自train.py的encoder输出 plot_domain_mixture(embedding, domains_array, "results/tsne_domain_mixture.png")图注写作范式:“scADL encoder提取的特征在t-SNE空间中呈现高度混合(domain标签无明显聚类),证明其成功学习到域不变表示。”
从那以后我每次跑新数据,都强制走一遍code_test/LoadData_to_GPU_test.py和code_test/tsne_test.py,哪怕只是确认GPU显存够不够——因为96分的毕设,不是靠PPT炫技,是靠每一个tensor的shape都对得上、每一张图的坐标轴都标得准、每一次答辩提问都能切到.py文件第几行。希望帮到你。
本文还有配套的精品资源,点击获取