尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于BERT和ResNet的多模态情感分析特征融合实践

基于BERT和ResNet的多模态情感分析特征融合实践 简介本资源是一套面向人工智能方向研究者与进阶学习者的多模态情感分析实战方案聚焦文本与图像双通道融合建模解决单模态方法在复杂情感识别中语义-视觉割裂的问题适用于情感计算、人机交互、社交媒体分析等场景。压缩包共49个文件含17个核心Python源码涵盖BERT文本编码、ResNet图像提取、多种融合模型如CMAC/HSTEC/OTE等、12个编译缓存文件、8个备份文件、3个数据集文本train/test、3个可视化结构图及README、LICENSE等辅助文档整体仅450KB轻量易部署。已有151人下载学习代码结构清晰分层data目录管理样本src下Models模块封装6类融合策略utils提供数据预处理与评估APITrainer统一训练流程。读者可直接复现跨模态注意力对齐、特征拼接与张量融合等关键技术并获得完整端到端训练配置与分阶段优化策略说明。 多模态情感分析这两年从我接触到的项目来看已经是很多团队在尝试的方向了。但真正落地的不会去搞那种特别复杂的跨模态预训练大模型更多还是基于成熟的单模态backbone做特征融合。我今天要分享的就是我自己在一个实际项目里沉淀下来的方案——用BERT处理文本、ResNet处理图像再通过不同的融合策略把两条模态的特征结合起来做情感分类包括完整的PyTorch代码和踩坑记录。这个组合不是最前卫的但绝对是最稳、最容易复现的适合刚切入多模态方向、或者想在现有情感分析系统里加一路图像信号的团队参考。1. 为什么要把文本和图像放在一起做情感分析1.1 单模态模型的天花板在哪里如果你只做纯文本情感分析哪怕用的是BERT或者更新的大模型也会碰到一个很尴尬的场景一条微博写的是这家店的服务态度真是绝了配图却是服务员黑着脸的照片。纯文本模型看到这句话大概率判成正面因为绝了在大部分语料里是正面的。但人眼一看图就知道这是反讽。反过来纯图像模型也有盲区。一张美食照片色调温暖、摆盘精致图像模型倾向判正面。但如果配文是等了两个小时菜还上错了图片再好看也没有用。这就是单模态的天花板文本擅长捕捉语义和反讽但缺乏视觉语境图像擅长捕捉场景和情绪氛围但读不懂文字背后的态度。多模态情感分析要解决的就是让模型同时看到这两路信号在语义和视觉之间找到一致或者冲突的信息再综合判断。1.2 BERT加ResNet为什么是最稳妥的起点多模态的backbone选型有很多组合比如ViT加RoBERTa、CLIP直接做zero-shot、甚至用多模态大模型做few-shot。但如果目标是落地一个可训练、可解释、可迭代的模型BERT加ResNet依然是我个人最推荐的起步方案。三个原因。一是两个模型的预训练权重都非常成熟。BERT有HuggingFace生态ResNet有torchvision官方权重下载、加载、迁移学习都是开箱即用不需要像训练ViT那样对数据量有很高要求。二是两者的结构天然适配特征提取。BERT输出的是每个token的上下文向量序列取CLS位就能拿到整句话的语义向量ResNet去掉最后的全连接层得到的是空间特征图经过池化就能变成整张图像的语义向量。两条分支的产物都是向量后面的融合层就很好设计了。三是社区里相关实现非常多出了问题容易排查。不像某些新出的多模态框架报错之后连issue都搜不到几条。做项目最怕的不是模型效果差而是出问题查无可查BERT加ResNet这条路线完全不存在这个烦恼。2. 整体方案设计从数据流视角拆解融合模型2.1 两条编码分支怎么设计先明确我们要做什么。输入是一对数据一张图片和一段文本。输出是情感类别常见的有二分类正面、负面或者三分类正面、中性、负面。我下面的代码以二分类为例但整体结构换成多分类也只是改一下分类头的输出维度。整体数据流是这样的文本经过BERT分词、编码取CLS位置的输出得到768维的文本向量。图像经过ResNet的卷积主干得到7x7x2048的特征图再做全局平均池化得到2048维的图像向量。文本向量和图像向量经过一个映射层统一到相同的维度然后进入融合模块。融合后的向量经过分类头输出情感类别的概率分布。这个流程看起来简单但有一个关键问题需要想清楚文本向量是768维图像向量是2048维维度不一致直接拼接当然可以但融合层的参数会被高维的图像特征主导。所以我在设计时会在两条分支后面各加一个全连接映射层把两者都映射到同一个固定的维度我项目里用的是512维再做融合。2.2 特征对齐不只是维度对齐维度对齐只是最表面的问题。更隐蔽的是特征分布的对齐。BERT出来的CLS向量本质上是经过层归一化、残差连接之后的高斯分布特征数值范围大概在-2到2之间。而ResNet最后一层卷积输出的特征经过ReLU激活全是非负值全局平均池化之后变成每个通道的平均激活值数值可能从0到十几不等。这两路特征的尺度和分布差异很大如果直接拼在一起梯度更新时高数值的特征会主导损失函数低数值的特征几乎学不到东西。我的做法是在两条分支的映射层后面各接一个LayerNorm。LayerNorm会把每个样本的特征向量归一化到均值为0、方差为1的分布相当于把两路特征拉到同一个量纲空间后面的融合层才能公平地学习两路信号。这一点是我在实验里对比过的加与不加LayerNorm在验证集上F1能差2到3个百分点。2.3 融合层位置的选择融合的位置决定了模型能学到什么样的跨模态交互。我用过一个很直观的类比早期融合就像两个人还没自我介绍就被塞进一个房间信息是混在一起的模型要靠自己慢慢理清谁是谁晚期融合就像两个人先各自介绍完再由主持人综合两人的发言做判断。我试过三种融合位置纯晚期融合文本和图像分别编码最后拼在一起过分类头。实现最简单但两路特征之间几乎没有交互模型学不到文本在说反话图像在证实这种跨模态关系。中期融合两条分支各自编码到中间层先做一次融合融合结果再继续往后送。适合设计复杂的注意力交互但结构复杂调参成本高。我最终采用的方案两条分支完整编码后在分类头之前做融合但融合模块内部不是简单的拼接而是用了带门控的注意力机制。这样既保持了结构简单又让两路特征在融合时有机会互相影响。下面我给出这个方案的具体代码。3. 数据准备与预处理容易被忽视却决定上限的环节3.1 文本模态的预处理流程文本侧的处理不复杂但有一个细节容易忽略。BERT的tokenizer是子词级别的原始文本必须经过它的分词、映射、padding、mask四步才能送进模型。直接用jieba分完词再套BERT的tokenizer会导致词表对不上这是新手最容易踩的坑。标准的流程是直接调用BertTokenizer.from_pretrained把原始文本传进去让tokenizer自己完成全部分词逻辑。还有一个需要定的参数是max_length。这个值直接影响训练效率和效果。我做过统计在社交媒体评论这种短文本场景95%以上的文本长度在64个token以内所以我设的是64。如果你处理的是电商长评论建议统计一下语料的长度分布再决定是128还是256。设得太大padding太多浪费显存又拖慢训练设得太小长文本被截断情感信息丢失。3.2 图像模态的预处理流程图像侧的标准预处理是Resize到224x224转Tensor再按ImageNet数据集的均值和标准差做归一化。这里有一个反直觉的点很多人觉得224x224损失了原始图像的高频细节尝试用448x448或者更大尺寸。但要注意ResNet在ImageNet上预训练时的输入就是224x224卷积核的感受野和池化结构都是围绕这个尺寸设计的。如果你把输入改成448模型能兼容但预训练权重里的统计特性其实没被充分利用。我实测过在情感识别这个任务上224已经足够。真正影响效果的不是分辨率而是图像的裁剪方式。3.3 数据增强与样本平衡策略视觉侧常规的随机水平翻转、随机裁剪我用上了但没用太强的增强。原因很实际情感分析的图像本身是自然场景照片过度增强会破坏原有的情绪氛围。比如一张冷色调的阴天照片是负面情绪的视觉信号你给它做一个剧烈的色彩抖动可能就变成暖色调了模型反而学错。文本侧我试过EDA同义词替换、随机插入、随机交换、随机删除效果不升反降。后来想明白了社交文本本来就短一句话里替换一个词就可能把反讽的语义破坏掉。所以文本侧我没有做任何增强全靠BERT的dropout机制提供正则化。样本不平衡是情感分析里非常常见的问题。负面评论在真实数据里通常占比很高正面评论相对少。简单的做法是在损失函数里加类别权重更稳的做法是配合WeightedRandomSampler做采样。两者可以叠加使用前者让模型对少数类的梯度更大后者让模型在训练时更频繁地看到少数类样本。4. 核心源码实现模型结构搭建与训练流程4.1 文本编码器BERT特征提取先定义文本编码器。我用的是bert-base-uncased加载预训练权重同时加了一个映射层把768维降到512维。这里还有一个设置需要解释freeze参数。如果数据量很少冻结BERT前几层的参数只训练后几层和映射层可以防止过拟合如果数据量足够不冻结让BERT全量微调效果通常更好。我的项目里数据量在几万条级别采用的是全量微调。import torch import torch.nn as nn from transformers import BertModel, BertTokenizer class TextEncoder(nn.Module): def __init__(self, model_namebert-base-uncased, embed_dim512, freezeFalse): super().__init__() self.bert BertModel.from_pretrained(model_name) if freeze: for param in self.bert.parameters(): param.requires_grad False self.fc nn.Linear(self.bert.config.hidden_size, embed_dim) self.norm nn.LayerNorm(embed_dim) def forward(self, input_ids, attention_mask): outputs self.bert(input_idsinput_ids, attention_maskattention_mask) cls_feat outputs.last_hidden_state[:, 0, :] # [batch, 768] feat self.fc(cls_feat) feat self.norm(feat) return feat取CLS位置是BERT做分类任务的标准做法。CLS位置的输出在预训练时就通过Next Sentence Prediction学习了整个输入序列的汇总信息所以用它的向量代表整句话是合理的。如果后续你想实验mean pooling和max pooling直接改动outputs.last_hidden_state的聚合方式就行其他代码不用动。4.2 图像编码器ResNet特征提取图像侧我用的是resnet50。torchvision里加载的ResNet默认带一个全连接分类层我们需要把它去掉只保留卷积主干然后在特征图后面加全局平均池化再接映射层。一个实现细节resnet.avgpool输出的是[batch, 2048, 1, 1]需要先squeeze掉最后两维才能变成[batch, 2048]否则全连接层会报维度不匹配的错。import torchvision.models as models class ImageEncoder(nn.Module): def __init__(self, embed_dim512, freezeFalse): super().__init__() resnet models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) self.backbone nn.Sequential(*list(resnet.children())[:-2]) # 去掉avgpool和fc self.pool nn.AdaptiveAvgPool2d((1, 1)) self.fc nn.Linear(2048, embed_dim) self.norm nn.LayerNorm(embed_dim) if freeze: for param in self.backbone.parameters(): param.requires_grad False def forward(self, images): feat_map self.backbone(images) # [batch, 2048, 7, 7] pooled self.pool(feat_map).squeeze(-1).squeeze(-1) # [batch, 2048] feat self.fc(pooled) feat self.norm(feat) return featAdaptiveAvgPool2d((1,1))的好处是输出尺寸固定不依赖输入特征图的大小这样即使你换了不同尺寸的输入代码也不用改。相比直接用resnet.avgpool这种方式更灵活。4.3 融合模块三种实现方式对比融合模块是整个模型的核心。我实现三种方案方便跑对比实验。第一种拼接融合。最简单两路特征拼接后过一个MLPMLP的隐层作用是学习两路特征的交叉关系。实现最快适合作为baseline。class ConcatFusion(nn.Module): def __init__(self, embed_dim512, num_classes2): super().__init__() self.mlp nn.Sequential( nn.Linear(embed_dim * 2, embed_dim), nn.ReLU(), nn.Dropout(0.3), nn.Linear(embed_dim, num_classes) ) def forward(self, text_feat, image_feat): combined torch.cat([text_feat, image_feat], dim-1) return self.mlp(combined)第二种门控融合。对两路特征分别计算一个权重用sigmoid函数把权重压缩到0到1之间然后做加权求和。这等于让模型自己决定当前样本里文本和图像谁更重要。对于文本是反讽、图像是证实这类样本门控机制能学到图像信号给的权重更高。class GatedFusion(nn.Module): def __init__(self, embed_dim512, num_classes2): super().__init__() self.gate nn.Linear(embed_dim * 2, embed_dim) self.classifier nn.Linear(embed_dim, num_classes) def forward(self, text_feat, image_feat): gate_value torch.sigmoid(self.gate(torch.cat([text_feat, image_feat], dim-1))) fused gate_value * text_feat (1 - gate_value) * image_feat return self.classifier(fused)第三种交叉注意力融合。这是我把注意力机制引入融合层的尝试。具体做法是把文本特征作为query图像特征作为key和value让文本在融合时去查看图像中最相关的部分同时图像也反过来查看文本。双向注意力能让两路特征在融合前充分交互。class CrossAttentionFusion(nn.Module): def __init__(self, embed_dim512, num_heads4, num_classes2): super().__init__() self.text_attn nn.MultiheadAttention(embed_dim, num_heads, batch_firstTrue) self.image_attn nn.MultiheadAttention(embed_dim, num_heads, batch_firstTrue) self.classifier nn.Linear(embed_dim, num_classes) def forward(self, text_feat, image_feat): text_proj text_feat.unsqueeze(1) image_proj image_feat.unsqueeze(1) text_updated, _ self.text_attn(text_proj, image_proj, image_proj) image_updated, _ self.image_attn(image_proj, text_proj, text_proj) fused (text_updated.squeeze(1) image_updated.squeeze(1)) / 2 return self.classifier(fused)把三种融合模块组合进完整模型class MultimodalSentimentModel(nn.Module): def __init__(self, embed_dim512, fusionconcat, num_classes2): super().__init__() self.text_encoder TextEncoder(embed_dimembed_dim) self.image_encoder ImageEncoder(embed_dimembed_dim) if fusion concat: self.fusion ConcatFusion(embed_dim, num_classes) elif fusion gated: self.fusion GatedFusion(embed_dim, num_classes) elif fusion cross_attn: self.fusion CrossAttentionFusion(embed_dim, num_classes) def forward(self, input_ids, attention_mask, images): text_feat self.text_encoder(input_ids, attention_mask) image_feat self.image_encoder(images) return self.fusion(text_feat, image_feat)4.4 数据加载与训练循环数据加载部分需要同时处理文本和图像。我写一个自定义Dataset最核心的是__getitem__方法里要返回四个值input_ids、attention_mask、image tensor、label。from torch.utils.data import Dataset, DataLoader from PIL import Image import torchvision.transforms as T class MMEmotionDataset(Dataset): def __init__(self, samples, tokenizer, max_len64): self.samples samples # 每个元素是 (text, image_path, label) self.tokenizer tokenizer self.max_len max_len self.image_transform T.Compose([ T.Resize(256), T.CenterCrop(224), T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def __len__(self): return len(self.samples) def __getitem__(self, idx): text, image_path, label self.samples[idx] encoding self.tokenizer( text, truncationTrue, paddingmax_length, max_lengthself.max_len, return_tensorspt ) image Image.open(image_path).convert(RGB) image_tensor self.image_transform(image) return { input_ids: encoding[input_ids].squeeze(0), attention_mask: encoding[attention_mask].squeeze(0), image: image_tensor, label: torch.tensor(label, dtypetorch.long) }训练循环采用标准的PyTorch流程。这里的优化器我用了AdamWBERT类模型对AdamW的适配度很好。学习率没有直接照搬BERT论文里的2e-5而是稍微调大到了3e-5因为后面接的ResNet分支和融合层是新初始化的参数默认情况下BertEmbedding层的迁移学习中会以不同的学习率更新这里一并说明不同模块用不同学习率是一个在迁移学习里很实用的小技巧。文本编码器的预训练参数调整幅度应该小一点图像编码器和融合层因为是从头训练或接近从头学习率可以适当大一点。我用的是param_group的方式optimizer torch.optim.AdamW([ {params: model.text_encoder.parameters(), lr: 3e-5}, {params: model.image_encoder.parameters(), lr: 1e-4}, {params: model.fusion.parameters(), lr: 1e-4} ], weight_decay0.01)这个设计的主要原因BERT预训练任务的分布和情感分类任务相差较大语义特征需要精细微调太大的学习率容易把原始的语义表示破坏掉所谓灾难性遗忘。ResNet的预训练特征比较通用并且图像情感信号相对低级它可以以稍大的步长适应新任务。这个细节在最终效果上比调融合层结构还明显。训练循环完整代码def train_one_epoch(model, dataloader, optimizer, criterion, device): model.train() total_loss 0 correct 0 total 0 for batch in dataloader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) images batch[image].to(device) labels batch[label].to(device) outputs model(input_ids, attention_mask, images) loss criterion(outputs, labels) optimizer.zero_grad() loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() total_loss loss.item() preds outputs.argmax(dim-1) correct (preds labels).sum().item() total labels.size(0) return total_loss / len(dataloader), correct / total在训练循环里我加了梯度裁剪clip_grad_norm_设为1.0。这个细节很多人会忽略但在多模态模型里尤其重要。因为两路Encoder的梯度尺度不同融合层又叠加了两种梯度容易出现梯度爆炸。加上裁剪之后训练稳定很多验证集的指标波动明显减小。评估阶段除了准确率还要看F1分数。情感分析的数据往往不平衡只看准确率会骗人。有方案用sklearn.metrics里的f1_score来做注意averagebinary。5. 融合策略对比实验什么方案最实用5.1 实验设置与超参数为了验证三种融合方案的实际效果我在同一个数据集上做了对照实验。数据集用的是我从公开社交媒体上收集的图文对清理之后剩1.2万条按8:1:1划分训练集、验证集、测试集。情感类别是二分类正面和负面。超参数设置如下参数取值文本最大长度64图像尺寸224x224融合层维度512注意力头数4batch size32训练轮数10早停策略验证集F1连续3轮不升则停止文本分支学习率3e-5图像分支学习率1e-4融合层学习率1e-4权重衰减0.01另外还加了一个纯BERT文本单模态模型和纯ResNet图像单模态模型作为参照。5.2 实验结果与差异分析测试集上的结果如下模型准确率F1BERT单模态78.3%77.2%ResNet单模态71.6%69.8%BERTResNet拼接融合84.5%83.6%BERTResNet门控融合85.2%84.4%BERTResNet交叉注意力融合86.1%85.3%从结果能看出几个信息。单模态对比BERT明显好于ResNet。这说明在社交媒体图文情感分析里文本语义的权重确实比视觉氛围更高。但这不代表图像分支没用——加了图像分支之后即使是最简单的拼接融合准确率也比纯BERT高了6个百分点。这说明文本和图像之间存在互补信息模型确实从图像信号里学到了文本没有提供的判断依据。三种融合方案里拼接是最弱的交叉注意力最强门控居中。差距大约是1到1.5个F1点不算大。这说明融合结构的作用不是颠覆性的尤其是在特征映射和归一化做得足够好的情况下简单的加法融合也能有不错的效果。但从实际使用的角度我最终没有选交叉注意力而是选了门控融合。原因有两个交叉注意力需要计算多头注意力矩阵训练速度慢了将近40%每轮epoch从30秒变成50秒左右而且它对batch size更敏感batch小的时候注意力学不稳定。门控融合的效果只比交叉注意力低不到1个点但训练速度快、结构简单、更容易调试。做项目不是堆SOTA是在效果和工程成本之间找平衡点。5.3 错误样本分析融合模型到底学到了什么我抽了50条测试集里融合模型判对而单模态模型判错的样本发现了几类典型的跨模态信息互补。一类是文本反讽配图证实。文本写服务真好配图是倒掉的饮料。单模态BERT判正面融合模型判负面。这类样本说明图像分支确实提供了语义之外的视觉证据帮助模型识别反讽。另一类是文本中性配图带情绪。文本就是一句到货了配图是明显破损的商品。文本本身没有情感极性但图像给出了强烈的负面信号。融合模型能正确判负面纯BERT只能靠猜。还有一类是文本给出情感但图像补充背景。文本是排队两小时终于吃上了配图是网红店门前的长队。单模态BERT判成负面因为排队两小时通常是负面体验但融合模型结合图像中的网红店、长队、人气旺等视觉线索判成了正面。这类样本体现了多模态的价值单看文本容易误判单看图像也容易误判只有结合起来才能还原完整的语境。6. 项目落地过程中遇到的坑与优化心得6.1 特征分布差异导致的训练不稳定问题这个坑是我印象最深的。第一次跑通模型时训练loss正常下降但验证集指标在epoch 4左右突然从84%掉到72%然后又慢慢恢复。排查了很久最终定位到是融合层的输入特征分布问题。BERT分支的特征和ResNet分支的特征幅度差异太大导致融合层在训练初期偏向学习图像分支的特征后来文本分支的梯度突然开始占主导特征分布发生了剧烈变化。解决办法就是前面说的在两条分支的映射层后都加LayerNorm把特征归一化到同一分布同时在训练时开启梯度裁剪。这两个改动加进去后训练loss曲线明显平滑验证集指标也没有再出现剧烈波动。6.2 冻结策略的真实效果我做了一套对比实验冻结BERT、冻结ResNet、两者全部微调。结论是在1.2万条数据量下冻结ResNet、微调BERT的效果最好。冻结ResNet相当于只把它当作一个固定的特征提取器训练参数少了一半收敛更快而且图像侧的任务相对简单固定的ResNet特征已经足够。冻结BERT不行因为语义特征和任务的相关性更强必须微调才能发挥效果。数据量如果到5万以上全部微调应该会慢慢反超。这个结论供参考不同的数据分布可能结果有差异。6.3 类别不平衡的坑别只看准确率项目初期我在训练时用了最常见的交叉熵损失没有做任何不平衡处理。结果测试集准确率有84%看起来很漂亮。但一看混淆矩阵就发现问题了负面样本的召回率高达96%正面样本的召回率只有61%。因为数据集里负面样本占比73%模型只要把所有样本都判成负面准确率就有73%稍微学一点东西就能到80%以上。但正面评论全被漏掉了。后来我加了类别权重把正面类别的权重调成负面类别的1.5倍正面样本的召回率从61%提升到了78%代价是负面样本的召回率降到91%总体F1反而提升了。这里要提醒的是做情感分析尤其是真实业务场景一定要先想清楚你要优化的是准确率、召回率还是F1。比如做舆情监控漏掉一条负面舆情可能比误报一条严重得多这时候就要重点优化负面类的召回率。6.4 训练技巧warmup、早停、细看loss曲线最后分享两个训练细节。第一个是warmupBERT类模型在训练初期需要一个学习率从0逐渐升到目标值的过程因为预训练模型在第一步就拿到一个很大的梯度更新反而容易破坏已经学好的特征。训练了3轮再开始衰减。第二个是早停多模态模型的验证集指标波动比单模态大我设置了连续3个epoch F1不上升就停止训练避免过拟合。一个额外的观察是交叉注意力融合比门控融合更容易过拟合验证集指标从epoch 6开始下降得更快所以如果选交叉注意力dropout可以适当加大一点。6.5 从源码视角看这个方案的下一步演进如果你已经跑通了这个项目想做得更好我觉得有三个顺理成章的演进方向。第一个是把ResNet换成ViT或Swin Transformer。图像分支从CNN换成Transformer之后两路特征都是Transformer结构融合层的设计更统一也可以直接复用BERT的Attention机制做跨模态交互。第二个是引入模态间对齐损失。目前融合层的训练信号完全来自分类损失没有显式的约束让文本特征和图像特征在语义空间里对齐。可以尝试类似CLIP的对比学习损失让匹配的图文对在特征空间里更近不匹配的更远。这个改动可以跟分类损失联合训练理论上能进一步提升融合效果。第三个是轻量化。如果项目要部署到线上BERT加ResNet的推理耗时可能扛不住高并发。可以尝试用蒸馏后的轻量BERT比如TinyBERT和轻量CNN比如MobileNetV3替换两个Encoder再配合ONNX导出和量化。这个过程和模型结构本身解耦融合层的代码不用改。我在项目里就做过一次替换推理延迟从120毫秒降到了35毫秒F1只降了1.2个点线上完全够用。从源码的角度看整个项目最核心的不是某一个模块的代码写得多花哨而是数据流的设计要清晰每一步的输入输出维度要了然于心。你在调试时最常做的事情就是打印每一层的shape确认数据从文本、图像到最终分类头的流动没有问题。把这个流程跑通了换模型、换融合方式、换任务都是替换某个模块的事。本文还有配套的精品资源点击获取
返回列表