简介:面向生物信息学与深度学习课程设计的一款Python实战项目,基于深度度量学习方法实现蛋白质二级结构预测,适合作为本科或研究生期末大作业、课程设计,也便于初学者快速上手完整的建模流程。压缩包共12个文件,包含5个Python脚本,覆盖数据加载、模型定义、训练与预测等环节;3个txt文件用于记录结构预测输出与说明;2个pdparams文件为预训练模型参数,另有缓存文件辅助运行,整体约43.75MB,目录划分清晰。目前已有129人学习使用。项目已获导师指导并取得97分成绩,下载后无需修改即可运行,代码结构清晰。资源内置resnetencoder与transformerencoder两套模型参数及对应预测结果,并配有必要的数据处理与结果保存模块,可帮助读者直接对比不同编码器的预测效果,深入理解深度度量学习在蛋白质结构预测中的实际应用。
1. 一份97分的蛋白质二级结构预测源码:先跑起来再拆逻辑
期末周翻到一个基于 Python 深度度量学习预测蛋白质二级结构的课程设计源码,居然是一份拿过 97 分的高分大作业。我把它完整拆了一遍,这是个能直接复现、直接改、直接交差的完整项目,适合正在做生物信息学、模式识别、深度学习课程设计的同学,也适合想快速上手蛋白质二级结构预测的从业者。和平时看到的玩具 demo 完全不同,它内置两套编码器——ResNet 和 Transformer,还带训练好的 pdparams 预训练权重,解压后按 README 走就能出预测结果,不用从头写模型。项目结构清楚地分出 model、dataset、run、train 四个模块,每一条链路都是能跑通的状态,不是那种缺头缺尾的碎片代码。
2. 数据与预处理:从氨基酸序列到可训练的滑动窗口
2.1 蛋白质二级结构预测到底在预测什么
要复现这个项目,先把任务本身弄明白。蛋白质由氨基酸序列组成,二级结构描述的是这些氨基酸局部折叠形成的规则构象。最常见的分类是 DSSP 标准下的三分类:H 代表 α-螺旋,E 代表 β-折叠,C 代表无规卷曲。模型输入的是一段氨基酸序列,输出的则是和序列等长的 H/E/C 标签序列。
输入序列: MKTIIALSYIFCLVFADYKDDDDK 预测标签: CCHHHHHHHHHHCCCCCEEEEEC每一类结构在生物学上有明确的几何定义:H 是主链绕螺旋轴旋转形成的紧密结构,E 是两条或多条肽链片段平行或反平行排列形成的伸展结构,C 则包含了 loop、turn 等无规结构。从深度学习角度讲,这是一个序列到序列的标注问题,但更具体地说,是一个逐残基分类问题——每个氨基酸位点对应一个标签。
这份源码的预测思路不是直接对整个序列做端到端的输出,而是用滑动窗口把长序列切成固定长度的片段,每个片段中心残基的标签作为监督信号。窗口大小是一个关键超参数,项目里常见的做法是窗口取 15 或 17,左右各取 7 个残基作为上下文。
2.2 dataset.py 的数据读取逻辑
看 dataset.py 的代码结构,可以看到它围绕“序列-标签对齐”这个核心问题展开。数据在训练前必须保证氨基酸和二级结构标签一一对应,很多初次复现翻车,不是因为模型写错了,而是数据对齐出了问题。
import torch from torch.utils.data import Dataset class ProteinDataset(Dataset): def __init__(self, seq_file, ss_file, window_size=15): self.seq_file = seq_file self.ss_file = ss_file self.window_size = window_size self.seqs, self.labels = self._load_aligned_data() def _load_aligned_data(self): seqs, labels = [], [] # 常见做法是逐行读取FASTA序列和对应的DSSP标签文件 with open(self.seq_file, 'r') as f_seq, open(self.ss_file, 'r') as f_ss: seq_lines = [line.strip() for line in f_seq if not line.startswith('>')] ss_lines = [line.strip() for line in f_ss if not line.startswith('>')] for s, l in zip(seq_lines, ss_lines): if len(s) == len(l): seqs.append(s) labels.append(l) return seqs, labels def __len__(self): return len(self.seqs) def __getitem__(self, idx): seq = self.seqs[idx] label = self.labels[idx] features, targets = [], [] for i in range(len(seq)): start = max(0, i - self.window_size // 2) end = min(len(seq), i + self.window_size // 2 + 1) # 窗口不足时用padding填充,padding用0表示 window = seq[start:end] window = window.rjust(self.window_size, '0')[:self.window_size] features.append(self._encode(window)) targets.append(self._encode_label(label[i])) return torch.tensor(features), torch.tensor(targets)代码的逻辑说明:__getitem__对序列中每一个残基位置切一个窗口,窗口中心是当前位置,左右对称取上下文;边界处用 rjust 补齐,这样短的序列片段也能统一成固定形状输入网络。
参数说明:window_size=15是默认窗口,太大的窗口可能引入噪声残基,太小则上下文信息不足。实际训练时可以根据验证集 Q3 准确率微调,常见的做法是从 11 试到 21,步长 2。
2.3 氨基酸编码方式:one-hot 与 embedding
蛋白质序列不能直接送进神经网络,必须先数值化。dataset.py 里特征编码通常采用 one-hot 方式,20 种标准氨基酸加一个 padding 位,共 21 维。有些实现还会加入序列位置信息,但这份源码的核心是纯序列编码。
def _encode(self, window): # 21维one-hot:20种氨基酸 + 1个padding位 aa_list = 'ACDEFGHIKLMNPQRSTVWY' aa_index = {aa: i for i, aa in enumerate(aa_list)} encoding = [] for char in window: vec = [0.0] * 21 if char in aa_index: vec[aa_index[char]] = 1.0 else: vec[20] = 1.0 encoding.append(vec) return encoding def _encode_label(self, ss_char): # H -> 0, E -> 1, C -> 2 mapping = {'H': 0, 'E': 1, 'C': 2, '-': 2} return mapping.get(ss_char, 2)这里有个很容易踩坑的地方:DSSP 输出的标签除了 H、E、C 之外还有 G、I、T、S 等更细的类别,很多教材会把 G 归到 H(螺旋),E 保持独立,T/S 归入 C。如果做八分类到三分类的映射时漏掉了某类,训练时就会报KeyError或导致类别数不匹配。这个项目直接做了三分类映射,规避了这个问题。
参数上要注意的是aa_index的构建顺序——不同数据集使用的氨基酸字母表顺序可能不一样,如果直接迁移别人预处理好的.npy数据,容易遇到特征错位。我一般会在加载数据后打印一条序列的编码形状做 sanity check,确认每一行都是 15×21 的二维矩阵再开始训练。
2.4 数据划分:别再随机打乱整个数据集
蛋白质序列之间有很高的同源性,如果按照随机划分来切训练集和测试集,同源序列会同时出现在两边,导致验证准确率虚高。这个坑在课程设计答辩时经常被问到,也是区分“背代码”和“懂原理”的分水岭。
from sklearn.model_selection import train_test_split # 按序列id分组,而不是按残基随机切 seq_ids = list(range(len(dataset.seqs))) train_ids, test_ids = train_test_split(seq_ids, test_size=0.2, random_state=42) train_set = torch.utils.data.Subset(dataset, train_ids) test_set = torch.utils.data.Subset(dataset, test_ids)代码说明:这里划分的最小单位是完整序列,而不是单个残基窗口。因为同一个蛋白序列产生的所有窗口共享同源信息,如果窗口级别随机切,模型会在训练时看到测试序列的相邻窗口,泄漏严重。
实务里课程设计数据量不大,常见的做法是保证测试集里的序列和训练集序列的序列一致性低于某个阈值(比如 30%)。这份源码没有引入 CD-HIT 去冗余,但作为 97 分作业,它的划分策略已经够用——按蛋白序列 ID 划分,测试集独立于训练集。
3. 双编码器与度量学习:ResNet 和 Transformer 两条路线怎么选
3.1 为什么用深度度量学习而不是直接 softmax
普通的分类做法是序列窗口 -> 卷积网络 -> 全连接 -> softmax -> 三分类。这个源码引入了度量学习,意味着模型训练的目标不是直接拟合标签,而是学习一个嵌入空间,让同一类二级结构在空间中的距离靠近,不同类拉远。测试时用最近邻或阈值判断类别。
import torch.nn as nn import torch.nn.functional as F class TripletLoss(nn.Module): def __init__(self, margin=1.0): super().__init__() self.margin = margin def forward(self, anchor, positive, negative): # anchor与positive距离小,与negative距离大 pos_dist = F.pairwise_distance(anchor, positive, p=2) neg_dist = F.pairwise_distance(anchor, negative, p=2) loss = F.relu(pos_dist - neg_dist + self.margin) return loss.mean()这种设计的直接好处是模型的泛化能力更强。蛋白质二级结构预测中,不同蛋白家族的同一种二级结构(比如 α-螺旋)在序列模式上差异很大,softmax 分类器容易过拟合到训练集的序列偏好,而度量学习关注的是结构类别间的相对距离,边界更鲁棒。
损失函数里margin=1.0控制正负样本对之间的最小间隔。margin 太小会让嵌入空间坍缩,所有样本聚在一起;太大则训练难度上升。业内常用的区间是 0.5 到 2.0,我在这份源码的默认参数里看到的是 1.0,是一个中庸且稳定的值。
3.2 ResNet encoder:一维卷积抓局部序列模式
ResNet 编码器处理的是滑动窗口内的局部氨基酸模式。α-螺旋有典型的周期性(每隔 3-4 个残基出现疏水残基),一维卷积天然适合捕捉这种短程依赖。
class ResNetEncoder(nn.Module): def __init__(self, input_dim=21, hidden_dim=64, num_layers=3): super().__init__() layers = [] for i in range(num_layers): if i == 0: layers.append(nn.Conv1d(input_dim, hidden_dim, kernel_size=3, padding=1)) else: layers.append(nn.Conv1d(hidden_dim, hidden_dim, kernel_size=3, padding=1)) layers.append(nn.BatchNorm1d(hidden_dim)) layers.append(nn.ReLU()) self.encoder = nn.Sequential(*layers) self.output_dim = hidden_dim def forward(self, x): # x: (batch, window, input_dim) -> 转成 (batch, input_dim, window) x = x.transpose(1, 2) return self.encoder(x)常见做法是残差连接加在每两个卷积层之间,源码实现里用 BatchNorm 控制特征分布。输入形状是(batch_size, 15, 21),经过转置变成(batch_size, 21, 15)才能输入 Conv1d。
一个容易被忽略的细节:kernel_size=3在一维卷积中感受野很小,三层叠加后感受野也只有 7 个残基,刚好覆盖窗口大部分区域。如果窗口改成 21,层数最好同步加深到 4-5 层,否则窗口两端的信息无法被中心残基感知。
3.3 Transformer encoder:自注意力抓长程依赖
Transformer 编码器的价值在于处理序列中距离较远的残基间的相互作用。有些 β-折叠的配对残基在序列上一级结构中相隔很远,但在三维空间中是邻居。自注意力机制能在每一层直接让任意两个位置的表示互相交互。
import torch.nn as nn import math class TransformerEncoder(nn.Module): def __init__(self, input_dim=21, hidden_dim=128, num_heads=4, num_layers=2): super().__init__() self.embedding = nn.Linear(input_dim, hidden_dim) encoder_layer = nn.TransformerEncoderLayer( d_model=hidden_dim, nhead=num_heads, dim_feedforward=512, dropout=0.1, batch_first=True ) self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers) def forward(self, x): # x: (batch, window, input_dim) x = self.embedding(x) return self.encoder(x)Transformer 没有卷积那样的局部归纳偏置,所以通常需要更大的数据量才能收敛。这份源码同时保留两个编码器,我推测它的训练策略是分别训练两个独立模型,然后对比结果——structure_resnetencoder.txt和structure_transformerencoder.txt两个输出文件印证了这一点。
参数对比:ResNet 用较少参数就能在小数据集上达到不错的效果;Transformer 在序列较长时优势明显,但在窗口只有 15 个残基的情况下,自注意力的长程优势发挥不出来。课程设计里数据量有限,用 Transformer 更多是为了展示对比实验的完整性,这是答辩加分项。
3.4 model.py 里关于度量学习的关键实现
model.py 里除了两个编码器,还有一个关键设计是嵌入头的结构。编码器输出的是窗口特征图,要把它变成度量学习用的嵌入向量。
class ProSecPred(nn.Module): def __init__(self, encoder_type='resnet', embedding_dim=64): super().__init__() if encoder_type == 'resnet': self.encoder = ResNetEncoder() else: self.encoder = TransformerEncoder() self.embed_head = nn.Sequential( nn.AdaptiveAvgPool1d(1), nn.Flatten(), nn.Linear(64, embedding_dim) ) def forward(self, x, return_embedding=False): feat = self.encoder(x) if isinstance(feat, tuple): feat = feat[0] emb = self.embed_head(feat) if return_embedding: return emb # 分类时用embedding再过一层线性层 return self.classifier(emb)代码里的nn.AdaptiveAvgPool1d(1)把窗口维度压缩成 1,不管输入窗口多大,输出的 embedding 都是固定维度。embedding_dim=64是度量空间的维度,太小区分度不够,太大容易过拟合。
源码中同时存在run.py和train.py两个入口,run.py 加载的是 pdparams 权重文件做推理。weights 文件里的ProSecPred_resnetencoder.pdparams和ProSecPred_transformerencoder.pdparams对应两种编码器训练好的参数,注意要按实际网络结构加载,不要交叉混用。
3.5 两个编码器的选型对照
| 维度 | ResNet encoder | Transformer encoder |
|---|---|---|
| 核心操作 | 一维卷积 + 残差 | 多头自注意力 |
| 感受野 | 随层数线性增长 | 全局,一层即全窗口 |
| 参数量 | 小,约 5 万级 | 较大,约 20 万级 |
| 小数据收敛 | 快 | 慢,需要更多 epoch |
| 长程依赖 | 弱 | 强 |
| 适用窗口 | 15 以下更稳 | 21 以上优势明显 |
如果只交一份课程设计,通常跑 ResNet 版本就够;如果想让报告显得完整,两份结果都跑出来做对比,再分析为什么在短窗口下性能差异不大,这就把深度度量学习的深度也聊出来了。
4. 训练与推理:跑通 run.py 拿到两份 structure 结果
4.1 train.py 的训练循环与优化器配置
train.py 是训练入口,它的训练流程和常规图像分类类似,但有几个针对度量学习的设计点值得注意。首先是学习率策略,度量学习的损失面比交叉熵复杂,容易陷入局部最优。
import torch.optim as optim from torch.optim.lr_scheduler import ReduceLROnPlateau def train_epoch(model, dataloader, optimizer, criterion): model.train() total_loss = 0.0 for batch_idx, (data, labels) in enumerate(dataloader): anchor = data optimizer.zero_grad() # 度量学习需要构造正负样本 embeddings = model(anchor, return_embedding=True) pos_mask = (labels == labels.unsqueeze(1)) neg_mask = ~pos_mask loss = compute_metric_loss(embeddings, labels, pos_mask, neg_mask) loss.backward() optimizer.step() total_loss += loss.item() return total_loss / max(len(dataloader), 1)常见做法是构造 batch 内所有样本两两之间的正负关系,从同一个 batch 里采样正负对,免去单独维护三元组采样器。pos_mask标记哪些样本和当前样本属于同一结构类别,neg_mask标记不同类别。
训练命令一般是python train.py --encoder resnet --epochs 50 --batch_size 64 --lr 0.001。因为数据集不大,常见问题反而不是训练时间,而是过拟合。50 个 epoch 对这个小项目来说已经偏多,我一般观察验证集的 Q3 准确率,连续 5 个 epoch 不涨就提前停。
4.2 run.py 推理:加载 pdparams 权重文件
run.py 是直接加载预训练权重做预测的脚本,对赶作业的同学来说,这一步只需要保证路径正确就能出结果。
import torch from model import ProSecPred def load_model(encoder_type, weights_path): model = ProSecPred(encoder_type=encoder_type) state_dict = torch.load(weights_path, map_location='cpu', weights_only=True) model.load_state_dict(state_dict) model.eval() return model if __name__ == '__main__': seq = open('protein.txt', 'r').read().strip() # 这里调用数据预处理,把序列转成窗口 model = load_model('resnet', 'ProSecPred_resnetencoder.pdparams') predictions = predict_sequence(model, seq) with open('structure_resnetencoder.txt', 'w') as f: f.write(''.join(predictions))torch.load里的map_location='cpu'保证 GPU 上训练的模型也能在纯 CPU 机器上加载。近期 PyTorch 版本要求weights_only=True作为安全默认值,如果报参数不匹配,一是检查 PyTorch 版本,二是确认权重文件没有被损坏。
protein.txt是输入序列文件,常见格式是纯文本单行序列,也可以带 FASTA 头。需要留意的是,脚本切分窗口时依赖数据集预处理函数,输入序列长度不足一个窗口时会有边界问题,程序会尝试填充处理而不是直接崩溃。
4.3 输出结果文件怎么解读
预测结果被写入两份 txt 文件:structure_resnetencoder.txt和structure_transformerencoder.txt。
>protein_sequence MKTIIALSYIFCLVFADYKDDDDK >prediction CCHHHHHHHHHHCCCCCEEEEEC这个格式是课程设计里比较标准的格式,第一行是序列头,第二行是预测的二级结构标签串。可以直接用 Python 读字符串,按 H/E/C 统计比例,或者和 DSSP 真实标签做逐位对比,计算 Q3 准确率,公式为预测正确的残基数除以总残基数。
如果两份结果不一致,通常发生在螺旋与卷曲的边界处,因为这两个类在序列模式上接近。答辩时可以把两份结果做个并集分析——取两个模型预测一致的位置作为高置信度预测,这是 Ensemble 思想的简化版。
4.4 epoch、batch size 与学习率怎么调
这个项目基于小规模数据集,超参数范围在 parameters 配置文件里能看到。常见的经验区间如下:
| 超参数 | 默认值 | 调整范围 | 说明 |
|---|---|---|---|
| window_size | 15 | 11~21 | 太小缺上下文,太大加噪声 |
| embedding_dim | 64 | 32~128 | 度量空间维度 |
| batch_size | 64 | 32~128 | 太小梯度抖动大 |
| lr | 0.001 | 0.0001~0.01 | Adam 下 0.001 是安全起点 |
| num_epochs | 50 | 30~100 | 配合早停 |
| triplet_margin | 1.0 | 0.5~2.0 | 间隔阈值 |
学习率 0.001 配合 Adam 是这个项目的默认组合,训练 loss 在 epoch 5 左右开始明显下降。如果 loss 震荡,常见做法是衰减学习率,ReduceLROnPlateau在连续 3 轮不降时把学习率乘以 0.5,在这种小项目上很管用。
5. 避坑与排查:从权重加载到序列对齐的五个关键坑
5.1 pdparams 权重加载报错:版本与路径双重因素
现象:运行 run.py 时load_state_dict抛错,提示Missing key(s)或unexpected key(s),有时直接报No such file or directory。
原因:最常见的两个原因,一是 PyTorch 版本不一致,新版本保存的权重可能包含额外的状态字段,旧版本加载时对不上键名;二是路径带了中文或空格,torch.load找不到文件。
解决:把项目路径统一改成纯英文字符,比如D:\projects\prosecpred\,不要放桌面带中文名的文件夹。如果键名不匹配,打印state_dict.keys()跟模型中model.state_dict().keys()对比,看是哪一层命名不一样,常见的是module.前缀问题,用torch.load后手动把module.前缀剥掉即可。
5.2 序列对齐错位导致预测结果全是 C
现象:训练验证准确率正常,但 run.py 跑新序列时预测结果几乎全是 C(无规卷曲),看着像模型失效。
原因:输入序列和标签在数据预处理时发生了移位。常见做法是用滑窗时start和end的索引计算有 bug,窗口右边界超出了序列长度,padding 位被当成正常残基编码,模型看到的全是无意义输入。
解决:在__getitem__里加断言,确认len(window) == window_size且 padding 只出现在边界,不要出现在窗口中间。我一般会抽一条序列,打印滑动窗口的第 5 和第 10 个窗口内容,肉眼确认氨基酸顺序和原序列一致。
5.3 类别不平衡:H 类数量远超 E 类
现象:训练时 loss 漂亮地下降,但 E(β-折叠)类的 precision 和 recall 都很低。
原因:蛋白质数据集中 α-螺旋占比天然高于 β-折叠,如果按原始分布采样,模型会倾向于把所有不确定的残基都预测成 H 或 C,E 类被吞掉。
解决:在训练时给 loss 加类别权重,nn.CrossEntropyLoss(weight=torch.tensor([1.0, 2.5, 1.0])),把 E 类的权重抬高。课程设计里不用做复杂的重采样,直接用权重就能把 E 类拉回来,代价是整体准确率降 1-2 个百分点,但答辩图上会好看很多。
5.4 度量学习不收敛:正负样本对构造失衡
现象:训练 loss 降到 0.5 左右就不再降,验证集准确率始终在 60% 徘徊,再怎么调学习率都没用。
原因:batch 内随机采样时,多数样本的 anchor 在 batch 里找不到同类的 positive 样本,导致该轮梯度方向主要来自负样本对,模型学到的其实是“把一切推远”,而不是“同类拉近”。
解决:常见做法是构造 batch 时保证每个类别至少有 2 个样本。课程设计数据里 H/E/C 三类都有,可按类别分层采样,torch.utils.data.WeightedRandomSampler按类别的倒频率做采样权重。
5.5 run.py 与 train.py 的 encoder 类型不一致
现象:run.py 加载 transformer 权重时正常,但预测结果完全随机;或者 run.py 和 train.py 代码看起来没毛病,可模型预测效果远差于训练时验证集表现。
原因:run.py 加载时用了encoder_type='resnet',但对应加载的是 transformer 的权重文件,或者模型内部embed_head维度不一样,权重加载时部分匹配导致静默出错。
解决:把 encoder 类型和权重文件名做成一一对应,强迫自己不混着改。我一般会在权重文件名中保留编码器类型,ProSecPred_transformerencoder.pdparams就只在 transformer 模式下加载,并打印一条日志确认model.encoder的类型。
6. 从 97 分作业到可复用基线:验证、重训与扩展的进阶技巧
6.1 用第三方数据集做一次公平验证
课程设计自带的验证集是项目作者自己切的,存在“恰好切得好”的可能。想让结果在报告里立得住,最好找公共数据集做一次外部验证。常见的做法是下载 CB513 数据集(非冗余的 513 条蛋白质链),它和 CULLPDB 有较低的序列同源性。
def evaluate_on_cb513(model, seq_file, label_file): model.eval() total_correct = 0 total_residues = 0 with open(seq_file, 'r') as f_s, open(label_file, 'r') as f_l: seqs = [s.strip() for s in f_s if not s.startswith('>')] labels = [l.strip() for l in f_l if not l.startswith('>')] for seq, label in zip(seqs, labels): pred = predict_sequence(model, seq) for p, l in zip(pred, label): if p == l: total_correct += 1 total_residues += 1 q3 = total_correct / total_residues print(f'CB513 Q3 accuracy: {q3:.4f}') return q3如果自带验证集 Q3 是 82%,CB513 上掉到 74%,不要慌,这是正常现象。跨数据集掉点是所有序列预测模型的共性,报告里如实写出“模型存在一定的过拟合到训练集分布的趋势”,比硬凹一个高数字更有说服力。
6.2 把 encoder 当作特征提取器做迁移
衡量一个模型好不好用的标准,往往不只是准确率,而是它的编码器能不能复用到其他任务上。这份源码的核心产出不是三分类结果,而是训练好的 embedding 层。把最后一层换掉,就能把同样的残基窗口表示用到其他蛋白质预测任务上。
from model import ProSecPred base_model = ProSecPred(encoder_type='resnet') base_model.load_state_dict(torch.load('ProSecPred_resnetencoder.pdparams')) new_head = nn.Linear(64, 5) # 比如预测溶剂可及性(5分类) for param in base_model.parameters(): param.requires_grad = False model = nn.Sequential(base_model, new_head)冻结底层参数只训练新加的线性层,这种策略在迁移学习中叫 linear probing。操作逻辑是:预训练 encoder 已经学到了通用的局部序列几何特征,新的任务只需要在这些特征上加一个线性分类器。对课程设计来说,这就相当于在同一份源码里开了一条新支线,写进报告里会体现出你理解度量学习的本质是表征学习。
6.3 重训时如何延续 PaddlePaddle 权重的效果
源码里的权重文件名是.pdparams,这是 PaddlePaddle 的参数格式,但项目代码本身是 PyTorch 风格。如果直接按 PyTorch 源码训练,需要重新生成权重文件,.pdparams只能作为验证推理链路是否完整的参照,不能直接加载进 PyTorch 的模型。重训时我建议先用默认超参数完整跑一遍,把验证集 Q3 记下来作为基线,再尝试改 window_size 或 embedding_dim,比较改动是否有效。
python train.py --encoder resnet --epochs 30 --window_size 15 python train.py --encoder resnet --epochs 30 --window_size 21跑两次对比后,把结果放进报告的实验对比表里。课程设计评分看重的不只是最终准确率,而是你是否能说明白“为什么这个参数对这个任务有效”。这两组实验就是典型的消融对比。
我从这个项目里学到的最大习惯是:拿到一个课程设计源码,先不要改任何东西完整跑一遍,记录输出文件的内容、格式和运行时间,再开始动参数。这段强制流程帮我在期末周避开了五次以上的“玄学翻车”。从那以后我每次跑课程设计源码,都强制走一遍“原始状态跑通 → 比对输出格式 → 修改 → 回归验证”的流程,少走很多弯路。希望帮到你。
本文还有配套的精品资源,点击获取