
简介面向国科大深度学习课程的大作业合集包含手写数字识别、猫狗分类、自动写诗与情感分析四个完整项目主要面向计算机相关专业正在完成课程设计或期末作业的学生也适合需要项目实战练习的初学者。压缩包共七十六个文件大小约十六点七四兆字节以源代码、编译缓存文件为主同时包含实验报告、说明文档、训练图片与数据文件四个项目分目录存放结构清晰便于逐项学习与二次开发。现已有二百五十四人浏览学习。作者在校期间完成并获导师认可评审九十八分属于高分作品。通过该资源可以了解图像分类、文本生成、情感分析任务的完整建模流程包括数据准备、模型搭建、训练调优与结果评估也能学习到规范的代码组织方式与实验报告撰写思路。1. 从MNIST到情感分析一份深度学习作业覆盖的四条技术线一份深度学习期末作业如果只跑一个MNIST手写数字识别学到的只是调包但如果把图像分类、迁移学习、文本生成、文本分类四个任务放进同一份工程那基本就覆盖了课程的核心面板。这套国科大深度学习课程作业合集正是这样的结构手写数字体识别、猫狗分类、自动写诗、情感分析四个独立项目每个项目都有main.py入口、数据目录、模型定义和实验报告评审分98分。对正在做课程设计或者期末大作业的人来说它最值得拿走的不是某个模型的准确率而是一套完整的代码组织方式数据怎么加载、模型怎么定义、训练参数怎么配、实验报告怎么补。下面按四条线逐一拆开讲连修改思路一起说。2. 手写数字体识别用CNN把基线准确率做到99%以上2.1 网络结构选型LeNet-5为什么够用手写数字体识别这个任务是MNIST28×28的灰度图10个类别。用全连接网络也能跑到95%左右但参数量大而且把二维结构强行展开成向量会丢掉像素之间的空间关系。卷积层通过局部感受野和共享权重保留邻域信息所以哪怕用90年代经典的LeNet-5验证集准确率也能稳定在99%以上。LeNet-5由两层卷积加三层全连接组成参数量不到全连接网络的十分之一训练很快作为课程作业的第一个模型正合适。import torch import torch.nn as nn class LeNet5(nn.Module): def __init__(self): super(LeNet5, self).__init__() self.conv nn.Sequential( nn.Conv2d(1, 6, kernel_size5, padding2), nn.ReLU(), nn.MaxPool2d(2, 2), nn.Conv2d(6, 16, kernel_size5), nn.ReLU(), nn.MaxPool2d(2, 2) ) self.fc nn.Sequential( nn.Linear(16 * 5 * 5, 120), nn.ReLU(), nn.Linear(120, 84), nn.ReLU(), nn.Linear(84, 10) ) def forward(self, x): x self.conv(x) x x.view(x.size(0), -1) return self.fc(x)第一层卷积用padding2保证28×28输入经过卷积后尺寸不变池化两次后特征图变成5×5所以全连接输入维度是16×5×5。这里的ReLU替换了早期LeNet用的sigmoid训练更不容易饱和。如果你想在作业报告里写得漂亮一点可以补充一句卷积核共享权重减少了参数量池化带来了平移不变性。2.2 数据加载与归一化的细节MNIST在torchvision里直接有封装但加载时要注意归一化。直接用ToTensor()会把像素从0到255缩放到0到1这只是一个基础处理。对MNIST来说更常见的做法是把像素均值0.1307和标准差0.3081减除一遍让数据分布接近标准正态模型收敛更快。from torchvision import datasets, transforms import torch.utils.data transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_set datasets.MNIST(root./data, trainTrue, transformtransform, downloadTrue) train_loader torch.utils.data.DataLoader(train_set, batch_size64, shuffleTrue)root./data把数据缓存到当前目录重复训练时不会重复下载。shuffleTrue让每个epoch的batch顺序都不同避免模型记住样本顺序。batch_size设为64这个值在MNIST上不会太大也不会让梯度更新太碎。实际作业里如果显存不够可以考虑降到32准确率影响很小。2.3 训练脚本里值得照抄的参数配置课程作业的main.py通常只承担训练和验证两个职责参数配好了结果基本不会跑偏。我一般用Adam优化器学习率0.001损失函数用交叉熵跑10个epoch。优化器和损失函数的代码如下optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.CrossEntropyLoss() for epoch in range(10): for x, y in train_loader: out model(x) loss criterion(out, y) optimizer.zero_grad() loss.backward() optimizer.step()交叉熵损失自带softmax操作所以网络最后一层不用额外接softmax。zero_grad()每次迭代前清空梯度否则梯度会累加loss.backward()计算出梯度optimizer.step()用梯度更新参数。例如下表是手写数字识别实验里最常用的一组参数想拿高分的同学可以直接抄。参数项推荐值说明输入尺寸1×28×28MNIST灰度图卷积核5×5经典LeNet配置优化器Adam学习率自适应收敛快学习率0.001过高会震荡过低收敛慢batch_size64平衡内存占用和梯度稳定性epochs10再多有些过拟合验证集准确率约99.2%依随机种子有浮动3. 猫狗分类小数据集上的数据增强与迁移学习3.1 两千张图片带来的过拟合问题猫狗分类的常规数据集有25000张图片但课程作业为了避免大家训练太久通常只会给每个类别几百到一千张。样本量一旦压到几千随机初始化从头训练一个深层CNN表现往往是训练集准确率接近100%验证集掉到80%以下。这就是典型的过拟合模型记住了训练图像的具体纹理甚至背景没有学会猫和狗的本质区别。解决办法有两条路一是用数据增强扩大有效训练样本二是用迁移学习借用ImageNet上已经学到的视觉特征。3.2 数据增强不只是翻转和裁剪PyTorch里做数据增强是在transforms中组合若干随机操作。最简单也最有效的三件套是随机裁剪、水平翻转和颜色扰动。from torchvision import transforms train_transforms transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ])RandomResizedCrop(224, scale(0.7, 1.0))表示随机裁剪原图面积的70%到100%再缩放到224×224这相当于模拟了猫狗在画面中大小不一的情况。RandomHorizontalFlip对猫狗是安全的因为左右翻转不改变物体类别。ColorJitter给亮度、对比度加扰动避免模型依赖颜色信息。需要注意验证集只做Resize和Normalize不做随机增强否则验证结果不稳定这一点经常被初学者漏掉。3.3 迁移学习把预训练权重变成全新特征提取器数据增强能缓解过拟合却无法从零学到足够强的特征。迁移学习的思路是直接加载在ImageNet上预训练好的ResNet18或VGG16把最后一层全连接换成自己的二分类头。import torchvision.models as models import torch.nn as nn model models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, 2)model.fc.in_features读取原模型最后一层的输入维度通常是512然后接一个输出为2的全连接层。这里有两种训练策略一是冻结特征层只训练新的全连接头适合数据量特别少的情况二是解冻所有层做微调效果更好但需要更小的学习率比如0.0001否则容易破坏预训练权重。在实际作业里我一般先用冻结方式跑3个epoch看看损失下降趋势再解冻微调5个epoch。3.4 训练策略与评估指标猫狗分类是二分类且两个类别样本均衡直接用准确率作为指标就行不用太在意召回率和精确率。训练时batch_size取32或64都可以优化器仍然用Adam。下表是一次课程作业中三种方案的典型表现可以看出来迁移学习带来的提升非常明显。方案训练数据验证集准确率训练耗时从零训练ResNet18每类1000张约85%40分钟冻结特征层训练每类1000张约93%15分钟全量微调ResNet18每类1000张约97%30分钟从零训练的准确率低不是因为代码写错而是数据量撑不起模型复杂度。如果作业里需要提出改进点可以对最后一层全连接加Dropout或者在数据增强中加入RandomRotation(5)让猫狗图像的角度变化再大一点。4. 自动写诗从字符级LSTM到有风格的文本生成4.1 为什么用字符级模型而不是词级自动写诗和文本分类不一样输出是变长的字符序列。如果按词建模型词表会膨胀到几万甚至几十万而且一首五言绝句只有20个字按词切分后序列长度很短模型很难学到韵律。字符级模型把每个汉字当做一个token词表通常只有几千模型可以在更短的序列里捕捉字与字的共现关系。LSTM又通过门控机制解决了普通RNN在长序列上的梯度消失问题所以作业里普遍用的是字符级LSTM。4.2 语料处理与序列构造训练文本是一整份诗歌库第一步是建立字符到数字索引的映射表然后用滑动窗口切成固定长度的序列。窗口长度一般取20到30太长会增大LSTM的计算量太短学不到句间依赖。切分代码和标签构造如下char_list sorted(set(text)) char2idx {c: i for i, c in enumerate(char_list)} idx2char {i: c for c, i in char2idx.items()} max_len 20 sequences [] for i in range(len(text) - max_len): seq text[i:i max_len] sequences.append([char2idx[ch] for ch in seq]) # 标签是seq整体右移一位即每个位置都预测下一个字符滑动窗口每次移动一个字符能让同一个诗出现在不同起始位置的变体中大幅增加训练样本。注意切分后每个样本的x是seq[:-1]y是seq[1:]因为LSTM在每个时间步都要输出下一个字符的预测。这一步最容易出错训练前最好打印一个样本检查一下映射关系。4.3 LSTM模型定义与训练模型结构是Embedding 多层LSTM Dense输入字符索引序列输出每个时间步在词表上的概率分布。import torch.nn as nn class PoetryLSTM(nn.Module): def __init__(self, vocab_size, embed_dim128, hidden_dim256, num_layers2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim) self.lstm nn.LSTM(embed_dim, hidden_dim, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_dim, vocab_size) def forward(self, x): emb self.embedding(x) out, _ self.lstm(emb) return self.fc(out)Embedding把字符索引映射为128维的稠密向量。num_layers2让LSTM堆叠两层第二层在更高抽象级别上组合第一层的输出但层数再增加会显著拉长训练时间。训练时把x和y都展开成一维用交叉熵损失计算每一个时间步的预测误差。4.4 temperature采样控制创造力的旋钮模型训练好以后生成诗不是简单取概率最大的字符那样只会不断重复高频组合。常见做法是对输出logits除以一个temperature参数再计算softmax概率最后按概率采样。def sample_char(model, char_tensor, temperature0.8): with torch.no_grad(): logits model(char_tensor)[0, -1, :] logits logits / temperature probs torch.softmax(logits, dim-1) next_idx torch.multinomial(probs, 1).item() return next_idxtemperature越小概率分布越尖锐生成结果越保守temperature越大分布越平坦词语组合越跳跃。下表是不同温度下的实际效果在报告里可以作为实验对比小节。temperature生成特点0.6押韵稳定但容易套用原句0.8自然度和创造性平衡1.2偶有新词但经常不通顺自动写诗这个任务重点不在模型有多深而在数据处理和采样策略。作业报告甚至可以只优化一个点用temperature0.8采样把生成的诗打印到assets目录配合生成样例分数不会低。5. 情感分析从词向量到TextCNN的文本分类实战5.1 任务定义与评价指标选择情感分析是NLP情感分析里最基础的二分类任务在这个作业里是对中文评论做正面和负面的判断。相比前面三个任务文本分类的输入长度不固定而且中文没有天然空格分词。评价指标最常用的是准确率但如果训练集中正负样本不均衡要看F1分数。课程作业的数据集一般都平衡准确率足够说明问题。模型选择上有两种方案TextCNN和LSTM我会先说明预处理再给TextCNN实现最后对比。5.2 文本预处理与词表构建中文文本需要先分词。分词工具用jieba最省事然后去停用词、去掉空格、控制序列最大长度。注意词表要加入PAD和UNK因为训练时batch内句子要补成相同长度推理时可能出现新词。import jieba def tokenize(text, stopwords): words [] for w in jieba.lcut(text): if w.strip() and w not in stopwords: words.append(w) return words # 假设vocab已经建好把词映射成索引 def sentence2ids(words, vocab, max_len50): ids [vocab.get(w, vocab[UNK]) for w in words[:max_len]] ids [vocab[PAD]] * (max_len - len(ids)) return idstokenize逐词过滤空白和停用词sentence2ids固定输出长度为max_len超出的截断不足的用PAD补齐。这里把PAD的特殊含义留给了之后的embedding层通常在模型里设置padding_idx0让补位字符不参与梯度更新。5.3 TextCNN模型实现要点TextCNN的思路是用多个不同宽度的卷积核在词向量序列上滑动每个卷积核相当于提取一组n-gram特征。宽度2、3、4分别对应相邻词、三词短语、四词短语模式然后对每个卷积输出做全局最大池化得到固定维度的特征向量。class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim100, num_filters128, num_classes2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.convs nn.ModuleList([ nn.Conv2d(1, num_filters, (kernel, embed_dim)) for kernel in (2, 3, 4) ]) self.fc nn.Linear(num_filters * 3, num_classes) def forward(self, x): x self.embedding(x) # [B, T, E] x x.unsqueeze(1) # [B, 1, T, E] convs [torch.relu(conv(x)).squeeze(3) for conv in self.convs] pools [torch.max_pool1d(c, c.size(2)).squeeze(2) for c in convs] out torch.cat(pools, dim1) return self.fc(out)unsqueeze(1)把词向量序列变成卷积层要求的四维输入。三种卷积核并行计算最后把三个池化结果拼成一个长度为384的向量再送入全连接层。这里num_filters128表示每种卷积核有128个filter维度越大特征越丰富但也更容易过拟合。5.4 和LSTM基线对比情感分析作业里如果时间允许可以把LSTM也跑一遍做对比。LSTM更擅长捕捉远距离依赖比如“虽然画面一般但非常感人”这种转折关系TextCNN训练快并且在短文本上通常准确率更高。下表是比较常见的课程作业结果。模型序列长度上限验证集准确率训练时间TextCNN50约91%5分钟LSTM50约89%12分钟我一般会把TextCNN放在models目录下把LSTM也保留在utils或者models里这样实验报告就能写“尝试了两种模型最终选择TextCNN”。这也是课程作业拿高分的一个通用思路有对比、有取舍而不是只给一个结果。如果数据集里还有评论对应的商品图片甚至可以把图像特征和TextCNN输出拼接做简单的多模态情感分析这在课程报告中很加分。6. 作业代码的工程化改造从assets到全新项目的复用套路6.1 先读目录再跑通入口拿到源码包之后我习惯先看目录结构而不是直接运行。这套作业里每个子项目都有main.py、assets、data、models和utils。assets通常放实验报告和生成的结果图data放数据集models定义网络结构utils放数据处理等公共函数。运行之前先建立好虚拟环境安装依赖。一般不需要版本完全一致PyTorch 1.13以上都能跑。6.2 设置随机种子并保存最佳模型课程作业评审最讨厌每次跑结果不一样。要可复现第一件事是设置随机种子。PyTorch下这样写import random import numpy as np import torch def set_seed(seed2024): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)set_seed要放在导入数据、初始化模型、生成任何随机参数之前。另一件值得做的事是边训练边保存验证集最优的模型可以在验证时比较准确率然后torch.save(model.state_dict(), best.pth)。这样每次实验都能回到最好的结果。6.3 替换数据接口把作业改成课程设计复用这套代码改造新项目时最省力的方式是只换数据接口。手写数字识别和猫狗分类的main.py可以互相改数据加载换成自己的图片文件夹模型输出类别改成目标数自动写诗和情感分析的关键是替换语料库和预处理函数。常见做法是继承torch.utils.data.Dataset写一个自己的__getitem__把utils里的加载函数替换掉网络结构和训练循环基本不动。这样改造后新任务从配置到跑通只需要一下午。最后一个具体技巧是训练时关闭调试打印把loss和acc写进TensorBoard每过几个epoch记录一次。实验报告里直接贴训练曲线比贴训练日志直观得多。对于这种课程作业工程结构的完整度往往比模型复杂度更影响分数写报告时把随机种子设置和最佳模型保存过程写进附录评审会认为你考虑到了可复现性。本文还有配套的精品资源点击获取