资讯动态

从CNN到多模态:四个必练的深度学习核心项目

发布时间:2026/9/8 13:24:21 来源:尧图企业网站定制
AI 算法工程师的成长路径里有一组项目出现频率非常高VGG/ResNet 做图像分类图像风格迁移做特征分布控制TextCNN 做文本分类图像字幕生成把视觉和语言拼到一起。这四个深度学习核心项目恰好覆盖了 CNN 基础、特征提取、损失设计、序列建模和多模态入口不是面试前背结构就完事的东西。我建议每个想转算法方向的人都亲手跑通一遍。适合谁刚学完 Python 和 PyTorch 基础的学生、做 CV 但没碰过 NLP 的工程师以及准备把课程 Demo 整理成可提交项目的开发者。最值得关注的不是某一个模型有多深而是你能通过这套组合学会一件事如何从数据、训练、验证、日志、报错里判断一个深度学习项目是不是真的可控。1. 先搞清楚这四个项目各自在练什么很多人把这四个项目当成“四个模型”来学这是最大的误区。它们真正的作用是一条能力链路先用 VGG/ResNet 看懂图像特征再用风格迁移理解特征分布然后用 TextCNN 把视野拉到文本最后用图像字幕生成把图像和文本串起来。按这个顺序学比单独刷十个模型更稳。1.1 VGG/ResNet不是背结构是理解特征提取的底座VGG 的核心思路很朴素连续用 3x3 卷积堆叠配合 max pooling 逐步降低分辨率最后接全连接层做分类。VGG16、VGG19 的区别只是层数不同结构上没有太多花哨的东西。正因为结构简单VGG 非常适合用来理解“卷积层提取到什么特征”这个问题。ResNet 解决的是更深网络难训练的问题。层数加深后训练误差反而可能变大这被称为退化问题。ResNet 通过残差连接让每一层可以学习“相对于输入的增量”也就是把输出从 F(x) 变成 F(x) x。这个变化看起来小实际影响很大。它让网络可以训练得更深也成了很多后续模型的通用骨架。在工程实践里我不建议一上来就死记 VGG 和 ResNet 每一层输出尺寸。你需要记住的是几件更核心的事预训练模型通常使用 ImageNet 的均值和标准差做归一化换数据集时不要丢掉这一步。分类头要按自己的类别数替换ResNet 是 model.fcVGG 是 model.classifier。数据量小的时候优先用预训练模型做微调而不是从头训练。这四个项目里VGG 和 ResNet 不只是分类模型。图像风格迁移会用到 VGG 的中间层特征图像字幕生成的编码器可以直接用 ResNet。所以这一关过了后面会省很多事。1.2 图像风格迁移把“内容”和“风格”拆开风格迁移通常不是训练一个新网络而是用预训练好的 VGG 网络提取特征然后对一个输入图片的像素做优化。内容特征很好理解就是图片经过某层卷积后的 feature map。风格特征更抽象一些通常用 Gram 矩阵表示。Gram 矩阵计算的是不同特征通道之间的相关性可以把它粗略理解为“某种风格纹理的统计指纹”。把内容图片的某层特征和风格图片的 Gram 矩阵同时约束住就能让生成图既保留内容结构又呈现风格纹理。总损失一般长这样loss content_weight * content_loss style_weight * style_loss tv_weight * tv_loss其中 tv_loss 是 total variation loss用来让生成结果更平滑减少噪点。学习风格迁移时最值得体会的不是代码而是“损失函数不一定需要标签”这件事。分类任务需要真实标签风格迁移只需要定义一张图应该像内容图、应该具备风格图的特征分布。这种用特征去定义损失的方式在后面很多生成模型里都会被反复用到。1.3 TextCNN把文本当成图像来处理TextCNN 的核心思路是文本经过 embedding 之后变成形状为 (batch_size, seq_len, embed_size) 的张量。转置一下就可以把它当成一个“一维图像”去做卷积。不同的卷积核大小相当于不同长度的 n-gram 窗口比如 kernel_size2 看相邻两个词kernel_size3 看相邻三个词。TextCNN 的逻辑并不复杂但它是一个特别适合入门 NLP 分类任务的项目。相比 RNN它训练更快结构更直观调参也更简单。对于短文本分类、情感分析、意图识别这类任务TextCNN 依然是很强的基线。做中文文本分类时要先决定用字还是用词。用字的好处是不需要分词简单直接用词能带上更多语义但要先做分词还要处理词表大小。第一次跑通时我建议先按字切分减少变量。1.4 图像字幕生成第一道多模态门槛图像字幕生成英文叫 image captioning任务是输入一张图片输出一句描述性文本。和前面的项目都不一样这个任务的输入是图像输出是序列。你需要把图像特征编码成一个向量然后用 LSTM 或者 GRU 一个词一个词地生成句子。这就构成了一个经典的 encoder-decoder 结构编码器CNN 提取图像特征。解码器RNN/LSTM 根据图像特征和已经生成的词预测下一个词。训练目标交叉熵损失让模型预测词的概率尽量接近真实标注。第一次做这个项目时很多人会被数据集和数据加载搞晕。因为图片和文本是两种不同格式需要建立 image_id 和 caption 的对应关系还要处理词表、未知词、填充、句子长度不一致等问题。这些正是多模态项目最常出现的工程坑。这四个项目串起来就是从“看懂图像”到“看懂文本”再到“图像和文本互相转换”的完整过程。2. 环境准备和数据集怎么选环境问题看起来是最基础的但实际项目中报错最多的往往不是模型而是版本不匹配、数据路径错误、图片打开失败、文本编码乱码。先把环境固定住后面四个项目才不会互相干扰。2.1 硬件和软件版本先跑通再升级如果你是第一次跑这套项目不要一上来就配最强的设备。更稳妥的做法是先用 CPU 跑通一个小样例确认模型前向、损失计算、验证流程都没问题再用 GPU 跑完整训练。常见配置可以参考下面这张表资源入门起步相对舒服的配置CPU能跑训练慢多核 CPU 会好一些内存8GB16GB 以上GPU可选NVIDIA GPU显存 6GB 以上磁盘10GB 可用空间30GB 以上依赖方面以 PyTorch 为例我建议先确认 Python、CUDA、PyTorch 三者兼容。不要随手复制最新版本因为最新版不一定适配你本机的 CUDA。pip install torch torchvision numpy pillow matplotlib pandas如果你的环境是内网装不了在线包就要提前准备离线 wheel 包或者让维护环境的同事统一安装。这个环节看起来和算法无关但很多人恰恰卡在这里。2.2 数据集和目录组织别把时间浪费在数据加载上这四个项目的数据集可以这样选VGG/ResNet 图像分类CIFAR-10、猫狗分类或者你自己整理的小型图片集。图像风格迁移准备一张内容图、一张风格图即可比如一张街景照片和一张名画。TextCNN 文本分类THUCNews 的子集、购物评论情感分类或者自定义的中文短文本。图像字幕生成Flickr8k 或 COCO 的少量子集不要一开始就处理整个 COCO。目录结构建议按项目分开deep-learning-practice/ ├── 01_classification/ ├── 02_style_transfer/ ├── 03_textcnn/ └── 04_captioning/每个项目目录里再拆 data、models、checkpoints、outputs。这样做的好处是出问题时你能很快定位是数据问题、模型保存问题还是运行日志问题。否则所有文件堆在一个目录里后期会很痛苦。3. 按顺序跑通四个项目下面按实际操作顺序拆一遍。每个项目我只给出关键代码片段和要注意的点完整训练逻辑需要你自己补全。3.1 项目一VGG/ResNet 图像分类先用 ResNet18 做分类因为它比 ResNet50 轻跑得也快。第一次运行时建议用小数据集、小 batch、少 epoch确认流程能通再逐步加大。import torch import torch.nn as nn from torchvision import models, transforms model models.resnet18(pretrainedTrue) num_classes 10 model.fc nn.Linear(model.fc.in_features, num_classes) transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ])这里最容易忽略的是 Normalize。如果你使用 ImageNet 预训练权重输入图片就必须按 ImageNet 的均值和标准差归一化。如果不做模型的输出会非常差而且你很难判断是模型问题还是数据问题。训练时至少分训练集和验证集。不要用同一份数据既训练又验证否则看到的准确率是虚高的。常见做法是把数据按 8:2 或 7:3 划分每个 epoch 结束在验证集上跑一次保存验证集上表现最好的模型。如果 CIFAR-10 数据量较小ResNet18 微调十几个 epoch 就能看到明显效果。如果你的任务不是图片分类而是检测或分割这一块的重点仍然是把它当特征提取器用后面的项目会依赖这个能力。3.2 项目二基于 VGG 的图像风格迁移风格迁移不需要训练模型需要定义损失并优化图片本身。建议用 VGG19 的中间层特征通常内容层取靠后的conv4_1或conv5_1风格层取多层。语法上Gram 矩阵可以这样实现def gram_matrix(x): b, c, h, w x.shape features x.view(b, c, h * w) return features.bmm(features.transpose(1, 2)) / (c * h * w)内容损失用内容特征和生成图特征之间的 MSE风格损失用 Gram 矩阵之间的 MSE。每次迭代时把生成图片交给 VGG 提取特征算损失再对生成图片执行 backward。优化器更新的是图片张量不是网络参数。这里有几个实践建议内容图和风格图最好先缩放到相同尺寸否则 VGG 前向之后特征大小不一致处理起来很绕。风格权重通常比内容权重大很多因为 Gram 矩阵的数值范围比较大需要平衡。每 50 或 100 次迭代保存一张生成图肉眼看效果比看 loss 更直观。风格迁移的 loss 数值没有绝对标准不同风格图、不同内容图差异很大。判断标准是内容结构是否清晰、风格纹理是否明显、图像是否出现大量噪点。如果出现花屏可以增加 tv_weight。3.3 项目三TextCNN 文本分类TextCNN 的网络结构可以写得很短import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_size, num_filters, num_classes, kernel_sizes(2, 3, 4)): super().__init__() self.embedding nn.Embedding(vocab_size, embed_size, padding_idx0) self.convs nn.ModuleList([ nn.Conv1d(embed_size, num_filters, k) for k in kernel_sizes ]) self.fc nn.Linear(num_filters * len(kernel_sizes), num_classes) def forward(self, x): emb self.embedding(x).transpose(1, 2) pooled [] for conv in self.convs: c torch.relu(conv(emb)) c torch.max_pool1d(c, c.size(2)).squeeze(2) pooled.append(c) return self.fc(torch.cat(pooled, dim1))这份代码里Embedding 用 padding_idx0意思是填充位置的向量不会参与更新。数据加载时文本序列长度不一致需要 pad 成一个 batch 内的固定长度。最简单的方式是用torch.nn.utils.rnn.pad_sequence把每条样本 padding 到当前 batch 的最大长度。做文本分类数据清洗比网络结构更影响结果。首先要检查文本编码Windows 下常见 gbk用 utf-8 打开会乱码。其次要确定标签类别数量与 model 输出维度一致。最后要确认输入是整数索引不是字符串。很多人把中文文本直接传进模型报错后还以为是卷积层写错了。TextCNN 的典型训练状态是前几个 epoch loss 快速下降之后逐渐平稳。如果验证集准确率一直上不去优先检查数据标签是否均衡、词表是否过大、学习率是否太高。3.4 项目四图像字幕生成小模型第一次做图像字幕生成不要追求完整 COCO 效果。可以只取少量图片和一个固定词表先用小模型跑通生成流程。简单做法是编码器用 ResNet18去掉最后的全连接层输出一个图像特征向量。解码器用单层 LSTM输入是词向量。训练时使用 teacher forcing也就是每一步都把真实词作为输入。测试时使用 greedy decoding把上一步预测的词作为下一步输入。关键代码结构参考class CaptionDecoder(nn.Module): def __init__(self, feature_dim, embed_size, hidden_size, vocab_size): super().__init__() self.embedding nn.Embedding(vocab_size, embed_size) self.lstm nn.LSTM(embed_size, hidden_size, batch_firstTrue) self.fc nn.Linear(hidden_size, vocab_size) def forward(self, image_feature, captions): emb self.embedding(captions) # 简化处理把 image_feature 拼在序列最前面 # 也可以用 image_feature 初始化 LSTM 的 hidden state outputs, _ self.lstm(emb) return self.fc(outputs)实际项目中你会遇到一个很常见的问题batch 里每条 caption 长度不一样。直接塞进 DataLoader要么 padding要么使用 pack_padded_sequence。我建议先使用 padding 跑通再去处理更复杂的性能优化。评估字幕生成质量时不要只看交叉熵。应该实际保存几条生成的句子和参考字幕放在一起对比。指标可以看 BLEU但 BLEU 也不是万能的它更偏向 n-gram 重叠不一定能完全反映语义质量。4. 关键参数、训练策略和结果判断跑通四个项目之后下一步是学会调参和判断训练状态。这一阶段才是真正拉开差距的地方。4.1 图像任务里要盯的参数参数影响我的调整习惯image_size越大信息越多但显存和时间上升先小后大不一开始就 448batch_size影响梯度和显存显存不够就减半同时观察收敛learning_rate过大会震荡过小收敛慢预训练微调用 0.0001 到 0.001epochs训练轮次用小数据集先试看验证集是否还能涨数据增强防止过拟合ResNet 分类建议加随机翻转、裁剪做风格迁移时重点不是学习率多大而是 content_weight 和 style_weight 的比例。风格权重高生成图更“像”风格图但内容结构可能丢内容权重高结构清晰但风格不够明显。实际调参时我习惯固定一张内容图和一张风格图把权重做成变量保存多组结果对比。4.2 文本任务里要盯的参数TextCNN 的常用参数包括 embed_size、num_filters、kernel_sizes、dropout。embed_size 越小训练越快但表示能力弱num_filters 越多模型越容易记住训练数据也更容易过拟合。建议先保持默认 128 左右跑通后再调。文本分类里类别不均衡是很容易被忽略的问题。如果某个类别只有几十条另一些类别有一万条模型会倾向于预测多数类。处理方式可以是重采样、调整类别权重或者收集更均衡的数据。先看混淆矩阵再决定怎么处理。图像字幕生成里max_caption_length 也要提前设置。不要等训练时才发现句子长度差异巨大。词表需要保留pad、bos、eos、unk四种特殊 token。如果你训练时没加未知词处理测试时碰到新词就会直接崩。4.3 怎么判断训练是否正常而不是只看 lossloss 下降不一定代表结果好loss 不降也不一定代表模型坏了。最有效的判断是同时看训练集和验证集训练 loss 降验证 loss 也降正常。训练 loss 降验证 loss 不降或上升过拟合需要增强数据、加 dropout、降低模型容量。训练 loss 和验证 loss 都不降可能学习率太小、数据有问题、模型设计有 bug。训练一开始 loss 就是 NaN优先检查输入数据有没有异常值、学习率是否过大、标签是否越界。对风格迁移我强烈建议每轮迭代都保存图片。不要只看终端里的 loss因为风格迁移的 loss 和人类主观感受不是严格对应的。画面是否干净、物体轮廓是否可辨认这些只能靠肉眼判断。5. 常见报错和排查顺序项目跑不通时很多人第一反应是改模型但大多数问题其实不在模型。遇到问题时按这个顺序查效率最高。5.1 先看现象再看输入最后动参数我常用的排查顺序是看报错发生在哪个阶段数据加载、模型前向、loss 计算、反向传播还是验证过程。看输入数据的形状、类型、取值范围。先 print 一个 batch确认 shape 和标签对不对。看依赖版本和路径。路径不存在、目录没权限、权重没下载成功这些比模型 bug 常见得多。看参数。batch_size 太大、学习率太大、序列 padding 错误都会引发千奇百怪的问题。最后才怀疑模型结构。不要一上来就重构网络。5.2 四个项目各自的典型坑项目常见现象优先检查VGG/ResNet准确率很低是否做 Normalize、是否改对了分类头、训练集和验证集是否混在一起风格迁移生成图花屏图片尺寸是否一致、tv_loss 是否太小、风格权重是否过高TextCNNshape 不匹配文本序列是否 padding、词表索引是否从 0 开始、padding_idx 是否设置图像字幕生成生成的句子全是重复词是否漏掉结束符、训练是否过拟合、词表是否太小GPU 显存不足时不要马上靠增加模型并行解决。先把 batch_size 减半把 image_size 调小把 DataLoader 的 num_workers 调低通常都能缓解。如果数据太大优先做采样不要一次性把所有图片读进内存。还有一个常见问题是权重下载失败。内网环境尤其容易遇到。解决办法是提前把预训练权重下载到本地然后用 torch.hub 指定缓存目录或者在代码里直接加载本地路径。这不是模型问题是网络和路径问题。6. 从 Demo 到工程化的几个建议四个项目都能跑通后你手里就已经有了四个可演示的“小项目”。但如果只是想跑通价值还不够。要把它们变成真正可复用的工程资产还需要补几件看似琐碎但很关键的事。6.1 模型保存、日志和复现我建议每个项目都做 checkpoint而不是只保存最后一步的权重。一个完整的 checkpoint 至少包含模型参数、优化器参数、当前 epoch、最优验证指标。这样断点续跑时不会丢进度也能回溯哪个版本效果最好。torch.save({ model: model.state_dict(), optimizer: optimizer.state_dict(), epoch: epoch, best_acc: best_acc, }, checkpoint.pt)复现同样重要。固定随机种子、固定数据划分文件、固定数据增强顺序以后才能解释某一次实验为什么效果变好或变差。否则实验结果不可复现等于没做实验。6.2 把推理过程封装起来训练完后不要只写训练脚本还要有一个干净的推理脚本或推理函数。推理时要处理几个和训练不一样的问题输入图片要经过和训练时相同的预处理。文本输入要做相同的 tokenize、padding。模型要切换到 eval 模式并用 torch.no_grad() 包住前向过程。输出类别要映射回原始标签名称而不是输出数字编号。把这些封装成一个函数比如 predict_image(image_path) 或 predict_text(text)后续接业务接口会非常省事。如果只是做一个课程项目这段封装也会让你的代码可读性高很多。6.3 下一步怎么扩展跑完这四个项目后你可以往几个不同的方向继续深入如果想继续做 CV可以把 ResNet 换成 ViT、Swin Transformer或者加入注意力机制。如果想继续做 NLP可以把 TextCNN 换成 BERT、Ernie 这类预训练语言模型。如果想继续做多模态可以把图像字幕生成里的 LSTM 换成 Transformer甚至尝试 CLIP 的思路。但我不建议你为了“追热点”而直接跳到太新的模型。先把这四个基础项目吃透把数据加载、训练、验证、推理、排查这一套动作练熟再上新模型你会发现自己踩的坑会少很多。把这四个项目完整跑通之后你会慢慢形成一种判断力看到一个新模型时会先问输入输出是什么数据要怎么做预处理训练时应该如何验证线上推理时哪些环节会变慢或出问题。这套判断力比多背十个模型的结构更有用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价