深度学习项目实操——区分癌症与正常样本

部署运行你感兴趣的模型镜像

深度学习项目实操——区分癌症与正常样本

本文档从项目开始,搭建机器学习模型,用于属于博主的个人练手项目,也欢迎大家指正

项目内容

我们需要做的:

  1. 准备表达矩阵和标签,构建输入
  2. 构建模型,训练模型
  3. 验证模型能力,使用模型进行预测
一、准备输入

​ 先中之先把所有要用的库一口气加载了

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset, random_split
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_auc_score, accuracy_score, roc_curve
import matplotlib.pyplot as plt

​ 我们准备好 Exp.txt以及 Label.txt前者是表达矩阵,后者为标签信息,然后准备好验证集, Val.txt以及 Val_label.txt前者为验证用的表达矩阵以及对应的标签。这些数据很好准备,可以在TCGA或者GEO中下载不同中的表达矩阵,然后将正常样本和癌症样本的信息储存在 Label.txt中,第一列为样本名,第二列为标签信息,博主这里将癌症样本标记为1,正常样本标记为0,大家可以根据自己喜好。

​ 随后我们就可以将我们准备的文件读入,可以通过以下代码:

def load_data(exp_file, label_file, val_file, val_label_file):
    # 读取表达矩阵和标签
    X = pd.read_csv(exp_file, sep='\t', index_col=0)
    y = pd.read_csv(label_file, sep='\t').set_index("ID")
    X_val = pd.read_csv(val_file, sep='\t', index_col=0)
    y_val = pd.read_csv(val_label_file, sep='\t').set_index("ID")
    # 如果列名为基因名,行名为样本名,注释掉这一行
    X = X.T
    X_val = X_val.T

    # 取样本交集
    common_ids = X.index.intersection(y.index)
    common_val_ids = X_val.index.intersection(y_val.index)

    common_f = X.columns.intersection(X_val.columns)

    if len(common_ids) == 0:
        raise ValueError("没有找到 X 和 Y 的共有样本")
    if len(common_val_ids) == 0:
        raise ValueError("没有找到验证集中 X 和 Y 的共有样本")
    if len(common_f) == 0:
        raise ValueError("没有找到验证集与训练集相同的特征")

    # 只保留共有样本,并确保顺序一致
    X = X.loc[common_ids, common_f]
    y = y.loc[common_ids]
    X_val = X_val.loc[common_val_ids, common_f]
    y_val = y_val.loc[common_val_ids]
    # 打印信息方便检查
    print(f"样本匹配完成:共有 {len(common_ids)} 个样本")
    print(f"   X.shape = {X.shape}, y.shape = {y.shape}\n")
    print(f"样本匹配完成:共有 {len(common_val_ids)} 个样本")
    print(f"   X.shape = {X_val.shape}, y.shape = {y_val.shape}")

    return X.values, y["Label"].values, X_val.values, y_val["Label"]


# 读入数据
X_train, y_train, X_val, y_val = load_data("Exp.txt", "label.txt", "Val.txt", "Val_label.txt")

这里只return .values的原因是Dataloader只接受数字信息,不接受字符串信息,方便后续数据转化成Tensor和进行DataLoader

# 数据标准化
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_val = scaler.transform(X_val)

# 转换为 Tensor
X_train = torch.tensor(X_train, dtype=torch.float32)
y_train = torch.tensor(y_train, dtype=torch.float32)
X_val = torch.tensor(X_val, dtype=torch.float32)
y_val = torch.tensor(y_val, dtype=torch.float32)


# 转化为 DataLoader
train_dataset = TensorDataset(X_train, y_train)
val_size = int(0.2 * len(train_dataset))
train_size = len(train_dataset) - val_size

train_dataset, val_dataset = random_split(train_dataset, [train_size, val_size], generator=torch.Generator().manual_seed(42))

train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False)

​ 这里为什么需要进行TensorDataLoader呢?进行Tensor的原因是我们需要把数据转化为机器看得懂的语言,进行运算。DataLoader则是分批读取数据,事实上如果你不使用DataLoader,可以通过以下流程完成相同的功能:

  1. 从训练集里挑选32条数据
  2. 传入模型
  3. 等待模型吃完
  4. 再进行下一次循环…

​ 所以DataLoader相当于将上述流程打包好的一个函数,能够帮我们完成这些过程,在这之中batch_size为每次读入数据的大小,shuffle选择是否打乱顺序,True为打乱顺序。在训练集中,为了让模型学习到真正的规律,而不是记住数据,我们会打乱顺序;而在验证集中,我们不希望顺序随机带来的偶然因素,所以选择固定顺序,确保结果可重复。

二、模型构建
MLP神经网络
class MLP(nn.Module):
    def __init__(self, input_dim):
        super(MLP, self).__init__()
        self.net = nn.Sequential(
            nn.Linear(input_dim, 512),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(512, 256),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.net(x)

model = MLP(input_dim=X_train.shape[1])
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

定义MLP神经网络模型,使用两层隐藏层,一层输出层,线性全连接层作为模型骨架。

# 损失函数和优化器
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=1e-5, weight_decay=1e-5)

这里我们做得是二分类问题,所以我们选择nn.BCELoss作为损失函数,Adam作为优化器。

在这里提供一些常见的损失函数和优化器,以及他们的应用场景:

损失函数
损失函数适用场景说明
nn.MSELoss()回归问题(预测数值)计算预测值与真实值的平方差 常用于基因表达预测、药物响应值预测等
nn.BCELoss()二分类问题(输出为概率)计算预测概率与真实标签(0/1)的交叉熵 要求最后一层用 Sigmoid
nn.BCEWithLogitsLoss()二分类和上面的类似,但自动处理 Sigmoid,更稳定(数值不容易溢出)
nn.CrossEntropyLoss()多分类问题(如0,1,2,3类)自动结合 Softmax,适合多类别任务(如不同癌症亚型)
nn.L1Loss()回归问题,对异常值不敏感计算绝对误差,抗异常值能力强
nn.KLDivLoss()分布对齐(如知识蒸馏、生成模型)比较两个概率分布的差异
nn.NLLLoss()多分类 + log softmax 输出用于输出已取 log 的概率
nn.MarginRankingLoss()排序/相似性任务比如预测两个样本哪个“更相似”
nn.CosineEmbeddingLoss()表征学习/对比学习想让相似样本的向量角度更接近
优化器
优化器特点适用场景
SGD传统梯度下降;稳定但慢小数据集、调参实验
SGD(momentum=0.9)加动量,减少震荡稳定学习、收敛更快
Adam自动调节学习率;收敛快默认首选优化器(通用)
AdamW改进版 Adam,权重衰减独立控制深度模型训练首选(BERT, CNN等)
RMSprop平滑梯度,适合循环网络RNN、LSTM 常用
Adagrad每个参数独立学习率稀疏数据(NLP、基因序列)
Adadelta自动调整学习率,无需初始值小样本、鲁棒性任务
Lion (2023新)Meta提出,训练更快大模型优化,稳定性高
模型训练
num_epochs = 50
best_auc = 0
best_val_loss = float('inf')
for epoch in range(num_epochs):
    model.train()
    total_loss = 0
    for Xb, yb in train_loader:
        Xb, yb = Xb.to(device), yb.to(device)
        preds = model(Xb).flatten()
        loss = criterion(preds, yb)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    avg_train_loss = total_loss / len(train_loader)

    model.eval()
    total_val_loss = 0
    with torch.no_grad():  # 验证集不需要梯度
        for Xv, yv in val_loader:
            Xv, yv = Xv.to(device), yv.to(device)
            preds = model(Xv).flatten()
            loss = criterion(preds, yv)
            total_val_loss += loss.item()

    avg_val_loss = total_val_loss / len(val_loader)

    # 每个epoch验证
    val_auc, val_acc, _, _ = evaluate(model, val_loader)
    print(f"Epoch {epoch+1:03d} | \nTrainLoss={total_loss/len(train_loader):.4f} | ValLoss={avg_val_loss:.4f}\n"
          f"ValAUC={val_auc:.4f} | ValACC={val_acc:.4f}")
    if avg_val_loss < best_val_loss:
        best_val_loss = avg_val_loss
        torch.save(model.state_dict(), "best_model.pth")
        print("best model saved")

​ 模型训练的核心内容是这一段:

for Xb, yb in train_loader:
        Xb, yb = Xb.to(device), yb.to(device)
        preds = model(Xb).flatten()
        loss = criterion(preds, yb)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    avg_train_loss = total_loss / len(train_loader)

​ 这里的 train_loader 是一个 PyTorch 的 DataLoader,负责把训练数据分批(batch)送入模型,Xb 是输入特征,yb 是对应的标签。Xb.to(device)将数据送到device中,在这里是GPU。model(Xb) 是把这一批输入 Xb 送入模型,得到预测值 preds。在将预测值送入criterion()中得到loss,调用 loss.backward() 把梯度累加到参数的 .grad 属性上,最后optimizer.step()更新参数。

​ 总的来说,训练的核心就是数据输入 → 模型预测 → 计算损失 → 更新参数 → 统计平均损失

三、模型验证
# 外部验证
model.load_state_dict(torch.load("best_model.pth"))
val_loader_ext = DataLoader(TensorDataset(X_val, y_val), batch_size=64, shuffle=False)
val_auc, val_acc, y_true_ext, y_pred_ext = evaluate(model, val_loader_ext)
print(f"外部验证集:ValAUC={val_auc:.4f} | ValACC={val_acc:.4f}")

# 绘制ROC曲线
fpr, tpr, _ = roc_curve(y_true_ext, y_pred_ext)
plt.plot(fpr, tpr, label=f"AUC={val_auc:.3f}")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve - External Validation")
plt.legend()
plt.savefig("ROC_curve_external.png", dpi=300)
plt.show()

print("模型训练完成,已保存最佳模型。")

​ 最后是模型验证阶段,这段因为没有调参内容,如果对这部分不太懂直接用gpt生成代码效果也很不错,最终在独立验证集中的表现如下(后面学习率和调的epoch忘记是多少了,不过练手项目也不纠结了)

在这里插入图片描述

您可能感兴趣的与本文相关的镜像

PyTorch 2.8

PyTorch 2.8

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值