ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于提示学习的多模态情感分析系统:从模板设计到工程落地

基于提示学习的多模态情感分析系统:从模板设计到工程落地 简介针对多模态情感分析中常见的模态缺失问题这套基于提示学习与扩散模型的完整实现提供了可直接运行的深度学习工程覆盖MOSI、MOSEI、IEMOCAP、SIMS四个主流数据集融合文本、音频、视觉三模态信息。相比传统MULT模型改造后的PromptModel引入生成式提示和模态特定提示并结合扩散模型进行跨模态信息生成可处理从单模态缺失到多模态缺失共7种情况在四个数据集上均有良好表现。压缩包共24个文件以23个Python脚本为主涵盖数据加载、模型构建、训练评估、结果可视化等全流程另附1张结构示意图整体仅179KB轻量但功能完整适合作为毕业设计或情感分析方向的研究基线。目前已有125人学习下载适合具备一定深度学习基础、希望快速复现和二次开发该系统的读者。1. 基于提示学习的多模态情感分析系统为什么我不再先训分类头我在情感分析任务上已经有两年多不先训分类头了。起因是一次电商评论的翻车一条“景色真美加班到凌晨也算值了”配一张夕阳图传统文本分类器直接判成正面但用户真正想表达的是疲惫和自嘲。文本与图像情感不一致的样本恰恰是单模态模型的死穴也是基于提示学习的多模态情感分析系统要解决的核心问题。这套系统的思路是把情感分类改造成预训练模型更擅长的填空或生成任务让模型在少样本条件下也能稳定输出正面、负面、中性同时让文本和图像共享同一个预测通道。它适合三类人在小数据上快速出效果的算法工程师、做多模态方向的学生、以及想把实验代码落成在线服务的开发者。2. 方案选型提示学习与多模态融合的三个关键决定动手写代码之前先想清楚选型。多模态情感分析不是把两个单模态模型拼在一起就行提示学习也不只是加一个模板。这个章节讲三个我每次搭建系统都会先做的决定任务形式、模态对齐方式、以及基座模型。2.1 为什么选提示学习而不是传统微调少样本与类目迁移两个理由传统微调的做法是在BERT或RoBERTa后面接一个随机初始化的分类头用交叉熵去训一个固定类别的分类器。问题出在两个地方。第一少样本场景下随机初始化的分类头没有预训练知识兜底很容易过拟合到训练集的表层特征比如“退款”这个词一出现就倾向负面换个说法“钱没退回来”就失效。第二类别一旦扩展比如从三分类扩成七分类整个分类头要重新随机初始化、重新训练之前的权重全部作废。提示学习把任务形式改成预训练模型熟悉的模样。情感分析最常见的做法是把它变成一个掩码语言模型任务给定“文本{text} 图片{image} 情感是[MASK]。”这样的模板让模型在[MASK]位置输出一个情感词再把情感词映射到标签。这样就不需要新增分类头直接复用预训练模型的MLM解码头。RoBERTa和BERT在预训练阶段见过大量“X是Y”的句式对填空有天然的建模能力所以少样本下有更好的起点。对比一下两种方案在工程上的差别维度传统微调提示学习新增参数随机初始化分类头无或仅少量软提示向量少样本表现容易过拟合依赖预训练知识更稳类别扩展重新训练分类头改verbalizer词表即可可解释性黑匣子可以看[MASK]处的词分布与多模态融合的契合度分类头难以吸收图像特征图像特征可直接拼入序列提示学习内部还分硬模板和软模板。硬模板是“文本[TEXT] 图片[IMG] 情感是[MASK]。”这种人工写死的句子稳定、可解释数据量小的时候不会乱飘。软模板是在embedding层上拼接一组可学习的向量取代硬模板里的自然语言部分上限更高但更容易过拟合而且调起来看不到直觉。我一般用硬模板起步如果数据量超过5000条再考虑软模板。2.2 模态对齐文本-图像怎么进同一个模型多模态情感分析的技术难点不在分类器而在模态对齐。文本是离散token图像是连续张量两者直接拼接会让模型学到模态偏置而不是语义关联。常见做法有三种。第一种是早期融合图像经过视觉encoder得到特征向量后通过一个投影层映射到文本encoder的隐藏维度再作为特殊token拼进文本序列。这样文本和图像在同一个transformer层里做self-attention交互最充分但实现复杂度高对显存和训练数据量都有要求。第二种是晚期融合文本和图像各自走独立的encoder最后把[CLS]向量和图像特征拼起来过分类器。实现最简单但交互太少对“图文冲突”这种样本几乎无能为力。第三种是交叉注意力用一层cross-attention让图像特征去查询文本特征。效果好但代码量多一层。我实际落地用的是第一种的一个变体图像经过CLIP的视觉encoder得到特征向量过一层线性投影作为“图像token”插入文本序列中[MASK]标记的前面。这样MLM head在预测[MASK]时能同时看到文本和图像信息和提示学习的模板天然契合。重要的是投影层必须加非线性我见过有人直接用线性层把CLIP的特征压到BERT的隐藏维度结果特征空间完全对不上效果反而比纯文本差。这三种融合方式在成本上的差别也很明显。晚期融合几乎不增加训练时间早期融合大约增加20%到30%交叉注意力如果同时更新两个encoder训练时间直接翻倍。小数据量场景下我强烈建议把视觉encoder冻结只训练投影层和文本encoder。多模态模型不是端到端训出来的而是“冻结预训练特征、只学对齐”先跑通再逐步解冻。2.3 基线模型与参数量权衡一张表选好你的起点基座选型直接决定显存占用和训练时长。我常用的三个组合如下组合视觉编码器文本编码器显存占用batch16序列128适用场景轻量基线CLIP ViT-B/32RoBERTa-base8-10GB单卡试跑、快速验证中端主力CLIP ViT-B/16DeBERTa-v3-base12-14GB正式实验、少样本调优高配组合CLIP ViT-L/14RoBERTa-large20GB以上数据量大、追求SOTA新手我建议直接上轻量基线24GB的消费级显卡就能跑。不要一开始就用BLIP-2这类生成式模型效果虽好但推理成本高而且情感标签这种低语义粒度的任务用判别式模型反而更直接。CLIP的视觉特征经过投影后已经足够表达图像的情感氛围除非你要处理的是广告牌、截图这种文字密集的图像才需要考虑OCR分支或者更重的视觉模型。选择基座还有一个容易忽略的坑tokenizer的词表。RoBERTa-base的中文词表里“开心”和“生气”会被分词器切成子词如果你在verbalizer里用了“开心”这个词计算概率时需要把分词后所有子词的概率做平均或求和否则模型可能在“开”这个字上给高分但在“心”上给低分导致标签概率被算错。这个问题后面还会详细讲。3. 数据准备与预处理多模态样本怎么组织才不翻车模型选型定了接下来是数据。多模态情感分析的数据组织比单模态麻烦得多因为一个样本可以是一条文本、一张图、一段音频且模态之间不是一一对应。这一章讲文本清洗、图像增强、以及数据集划分三个最容易出错的地方。3.1 文本清洗与情感标签对齐正则与emoji的取舍多模态情感分析里的文本通常来自公开评论、客服对话或社交媒体噪声很大。我的清洗策略不是把文本“擦干净”而是保留情感信号。URL和用户名对情感判断几乎无贡献但它们会干扰分词所以要替换成占位符emoji不是噪声在情感分析里是强信号直接丢掉会让负面样本更难识别我的做法是映射成特殊标记。以下是我常用的清洗函数import re def clean_text(raw: str) - str: # URL和用户名换成占位符避免分词器把它们拆成碎片 raw re.sub(rhttps?://\S, [URL] , raw) raw re.sub(r\w, [USER] , raw) # emoji映射为语义标记保留情感强度 emoji_map { : [LAUGH], : [HAPPY], : [ANGRY], : [CRY], : [SAD] } for k, v in emoji_map.items(): raw raw.replace(k, v) # 其余非中英文、非数字、非基础标点一律去掉 raw re.sub(r[^\w\u4e00-\u9fa5。!? ], , raw) # 连续多个空格压缩成一个 raw re.sub(r\s, , raw).strip() return raw这段代码有三个细节值得说明。URL换成[URL]而不是直接删除是为了保留句子的位置关系避免“这个[URL]太糟糕了”变成“这个太糟糕了”之后句法结构受损。emoji映射成[LAUGH]这类标记本质上是让模型学到“文本表情”的组合信号而不是只学文本。最后的正则里我特意保留了中文逗号、句号、感叹号和问号感叹号和问号在情感分析中是重要线索比如“你居然这么做”和“你居然这么做。”的情感强度完全不同。清洗之后做标签对齐。三分类通常用positive、neutral、negative但如果业务方要求识别“生气”“失望”“惊喜”这类二级情感我建议在verbalizer里给每个二级情感准备三到五个同义词而不是直接让模型输出二级标签。原因是RoBERTa和BERT这类MLM模型的输出空间是整个词表直接预测“失望”这个词的难度远高于预测“差”这个词。3.2 图像变换与增强别把氛围感增强没了图像预处理的坑比文本更深。很多人把ImageNet分类的那套增强直接搬过来结果发现效果不升反降。原因是情感图像的语义不在物体上而在氛围上。一张晚霞图你把它随机裁剪到只剩一朵云情感可能从“宁静”变成“无感”你把色调偏移调大原本温暖的氛围直接变冷。我实际用的图像变换如下from torchvision import transforms image_transform transforms.Compose([ # 先统一缩放再做随机裁剪避免目标区域过小 transforms.Resize((256, 256)), transforms.RandomResizedCrop(224, scale(0.8, 1.0), ratio(0.9, 1.1)), # 颜色扰动只做轻微调整hue基本不碰 transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.1, hue0.02), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])这里scale设为0.8到1.0意味着每次裁剪至少保留原图80%的内容保证主要场景和氛围不被切碎。hue只给0.02因为色调偏移强烈影响情感判断。RandomResizedCrop的ratio设置成0.9到1.1尽量保持矩形接近原图宽高比防止构图被破坏。我没有加RandomHorizontalFlip左右翻转在某些场景下没问题但如果是带文字的截图或者有方向性的图像比如箭头指向翻转会引入错误语义。上下翻转我从来不用。如果图像里包含大量文字商品截图、广告牌建议把图像resize到更大尺寸再切比如Resize到384否则文字糊掉后图像模态基本失去价值。3.3 数据集划分按item分组避免数据泄漏多模态数据泄漏是个隐蔽的坑。假设一条商品评论有3张配图你随机把其中2张分进训练集、1张分进验证集模型在验证集上的表现会虚高因为视觉特征已经被训练集里的同款图像“剧透”了。正确做法是保证同一个item下所有模态属于同一条数据要么都进训练集要么都进验证集。我的数据目录长这样data/ train.jsonl val.jsonl test.jsonl images/ 0001_1.jpg 0001_2.jpg 0002_1.jpg每个item有唯一的item_idjsonl里每行是一个样本包含item_id、text、image_path、label。划分代码from sklearn.model_selection import GroupShuffleSplit import json items [] labels [] item_ids [] for line in open(data/all_samples.jsonl, encodingutf-8): item json.loads(line) items.append(item) labels.append(item[label]) item_ids.append(item[item_id]) # 按item_id分组保证同一个item的样本不会同时出现在训练和验证集 gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(items, labels, groupsitem_ids)) with open(data/train.jsonl, w, encodingutf-8) as f: for i in train_idx: f.write(json.dumps(items[i], ensure_asciiFalse) \n) with open(data/val.jsonl, w, encodingutf-8) as f: for i in val_idx: f.write(json.dumps(items[i], ensure_asciiFalse) \n)GroupShuffleSplit的groups参数传item_id列表它保证同一组内的样本被划分到同一边。random_state固定42这样每次复现实验时划分不变。这里的test_size是0.2但如果数据量只有几百条我建议test_size调到0.3甚至更高因为多模态模型的验证集必须包含足够的“图文冲突”样本否则验证结果没有参考价值。4. 模型实现与训练从Prompt模板到可复现的训练脚本数据和选型都确定了这一章进入核心实现。我会按模板构造、模型结构、训练参数三个层面逐步展开代码可以直接复制改路径跑通。4.1 构造Prompt模板硬模板与软模板的取舍模板是提示学习的灵魂。情感分析模板要回答两个问题图像特征插在哪、[MASK]放哪。我的模板是“文本{text} 图片{image} 情感是[MASK]。”其中{image}在预处理阶段会被替换成图像token[MASK]放在句尾。为什么放在句尾而不是中间因为情感是整句话的总结性判断放在句尾更符合语言习惯模型预测时能看到完整的上下文。构造模板的代码from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(hfl/chinese-roberta-wwm-ext) class SentimentPromptBuilder: def __init__(self, tokenizer, max_len128): self.tokenizer tokenizer self.max_len max_len self.template 文本{text} 图片{image} 情感是[MASK]。 def build(self, text: str) - dict: # 先把文本部分填入模板image位置占位后续在embedding层替换 prompt_text self.template.replace({text}, text).replace({image}, [IMG]) encoded self.tokenizer( prompt_text, truncationTrue, max_lengthself.max_len, return_tensorspt ) # 找到[MASK]的token位置训练和推理都要用 mask_pos (encoded[input_ids][0] self.tokenizer.mask_token_id).nonzero().item() return { input_ids: encoded[input_ids], attention_mask: encoded[attention_mask], mask_pos: mask_pos }这段代码里[IMG]作为一个特殊token需要提前加到tokenizer的词表里并在模型embedding层里有对应的embedding向量。mask_pos是[MASK]在input_ids中的位置训练时取该位置的logits。注意mask_token_id的判断用的是nonzero()如果[MASK]被分词器拆开这个位置就错了所以这行代码可以当作一个防御性检查如果返回的不是一个标量说明模板句子被tokenizer处理的方式不符合预期需要回看模板写法。硬模板的优点是稳定缺点是表达力有限。数据量超过5000条后我会在embedding层上额外加一组可学习的soft prompt向量soft_prompt torch.nn.Parameter(torch.randn(8, 768) * 0.01)这8个向量会拼在input_ids的embedding前面和硬模板共存。soft prompt让模型有机会学到硬模板之外的隐性特征但代价是训练不稳定需要把学习率降到硬模板的十分之一。我的经验是小数据用纯硬模板大数据才加软提示不要一开始就上软提示。4.2 模型结构与前向传播一个可落地的PyTorch实现模型结构分为三块文本encoder提取token特征视觉encoder提取图像特征投影层对齐后拼接到文本序列。视觉encoder用CLIP的ViT权重冻结文本encoder用RoBERTa最后一层解冻微调。核心代码如下import torch import torch.nn as nn class PromptMultimodalSentimentModel(nn.Module): def __init__(self, text_encoder, visual_encoder, hidden_size768, img_token_pos0): super().__init__() self.text_encoder text_encoder # RoBERTa模型含base_model和lm_head self.visual_encoder visual_encoder # CLIP的ViT默认冻结 self.image_proj nn.Sequential( nn.Linear(visual_encoder.embed_dim, hidden_size), nn.GELU(), nn.Dropout(0.1) ) # 图像token的embedding插入位置在[MASK]之前 self.image_embedding nn.Parameter(torch.randn(1, 1, hidden_size) * 0.02) def forward(self, input_ids, attention_mask, image_features, mask_pos): # 文本encoder前向得到token级隐状态 text_outputs self.text_encoder.base_model( input_idsinput_ids, attention_maskattention_mask ) text_hidden text_outputs.last_hidden_state # [B, L, H] # 图像特征经投影层变成[1, H]的向量再扩展为token形式 img_hidden self.image_proj(image_features).unsqueeze(1) # [B, 1, H] # 把图像token插入到mask_pos位置之前 fused torch.cat([ text_hidden[:, :mask_pos, :], img_hidden, text_hidden[:, mask_pos:, :] ], dim1) # 复用MLM head取[MASK]位置logits mask_logits self.text_encoder.lm_head(fused[:, mask_pos, :]) # [B, vocab_size] return mask_logits这个实现有几个关键点。第一图像特征作为token插入文本序列而不是和[CLS]拼接目的是让[MASK]位置的预测能直接attend到图像token。第二visual_encoder的embed_dim必须和hidden_size匹配CLIP ViT-B/32的embed_dim是512RoBERTa-base的hidden_size是768所以投影层把512映射到768。第三mask_pos在插入图像token之后文本序列的实际长度变成L1但mask_pos本身不变因为图像token插在它前面预测时只需要对第mask_pos个位置的输出做解码即可。因为视觉encoder被冻结整个模型的训练参数量大幅下降。我通常把文本encoder最后两层解冻其他层全部冻结这样在保持稳定性的同时给模型留出适应多模态特征的空间。如果完全不微调文本encoder模型很难学会把图像token的信息利用起来。4.3 训练参数与损失函数参数怎么设、失败时看什么训练时模型输出的是整个词表的概率分布我们需要把分布映射到三个标签上。映射关系叫verbalizer。我给每个标签准备多个同义词计算时取这些词概率的均值label_to_words { positive: [好, 赞, 不错, 满意], negative: [差, 糟糕, 生气, 失望], neutral: [一般, 还行, 平淡] } # 把词映射成token id注意中文词可能被切成子词这里用平均值处理 word_to_ids {} for label, words in label_to_words.items(): ids [] for w in words: tokens tokenizer.encode(w, add_special_tokensFalse) ids.extend(tokens) # 一个词可能对应多个子词token word_to_ids[label] ids损失函数不是直接对整个词表算交叉熵而是只对三个标签对应的词作用softmaxfor batch in dataloader: logits model(**batch) # [B, vocab_size] label_scores [] for label, ids in word_to_ids.items(): # 取该标签所有同义词子词概率的均值作为标签得分 label_scores.append(logits[:, ids].mean(dim1)) label_scores torch.stack(label_scores, dim1) # [B, 3] loss nn.CrossEntropyLoss()(label_scores, batch[labels])这里有个容易踩的细节多个同义词的token长度不同直接拼接id列表后logits[:, ids]得到的是一个变长的张量mean(dim1)会正确得到均值。但要小心同一个词被分多次时比如“不满意”被分成“不”和“满意”取均值会把两个子词同等对待这不够合理。更精细的做法是按词分组先取子词均值再取同义词均值但实验发现差异不大工程上先用简单平均即可。训练参数我用的是这样一组基线AdamW优化器学习率2e-5warmup比例0.1weight_decay 0.01batch_size 16训练轮数10。视觉encoder完全冻结投影层的学习率可以设到5e-5因为它是随机初始化的需要学更快一点。如果显存不足先用梯度累积accumulation_steps设为2等价于batch_size 32。fp16混合精度在PyTorch 2.0以后已经是默认选项可以直接打开。训练过程中重点盯两个指标训练loss和验证集F1。如果loss在前三个epoch内不下降先检查mask_pos位置是否算对再检查verbalizer词的token id是否真的存在于词表。如果loss在下降但验证集F1不变大概率是过拟合把dropout调到0.2、早停patience设为3即可。5. 避坑与排查多模态提示学习常见的五个翻车现场这个项目我前前后后做了三轮迭代踩过的坑比写出来的代码多。这一章把最典型的五个问题按“现象-原因-解决”写清楚你照着排查能省好几个晚上。5.1 loss不降或模型总预测neutral现象训练loss在前几个epoch几乎不下降模型对所有样本都预测neutralF1在0.3左右徘徊。原因最常见的是verbalizer里的词被分词器切碎后某个标签对应的token id为空。比如“还行”在roberta-wwm-ext的词表里可能被切成“[无]”和“[SEP]”导致模型在[MASK]位置的概率分布无法作用到正确的id上。另一个可能原因是模板里的[MASK]不是真正的mask_token_id比如模板里写的是[MASK]但tokenizer不认识这个写法把它当成普通文本。解决先把模板输入tokenizer并打印token id确认mask_pos能正确找到。然后逐个标签词打印encode后的id确认没有空列表。代码加一个断言for label, ids in word_to_ids.items(): assert len(ids) 0, f{label} 对应的token id为空换个词5.2 加图片后效果反而比纯文本差现象单文本模型的F1是0.78加上图像后掉到0.72多模态反而拖后腿。原因图一CLIP的特征空间和RoBERTa的特征空间本身不对齐图像token插入序列后MLM head在预测[MASK]时被不相关的图像特征带偏。图二图像被粗暴resize到224x224如果原图是截图或带文字的广告图文字细节直接糊掉图像模态成了噪声。解决先冻结视觉encoder只训练投影层500步让投影层先把CLIP特征“翻译”到RoBERTa的空间。然后把文本encoder解冻一起训练。对截图类样本可以考虑在模板里加一个“图片中包含文字”的标记或者干脆把图像模态换成OCR文本效果比强行融合好得多。我后来在投影层后面加了一个LayerNorm特征对齐速度明显加快。5.3 显存OOM与训练速度过慢现象batch_size设16一张24GB的显卡直接OOM。代码还在跑数据加载显卡占用已经满了。原因视觉encoder虽然冻结但每一轮前向传播仍然要跑一遍ViT图像特征没有缓存。ViT在224x224输入下显存占用约2-3GB加上RoBERTa的激活值batch一放大就爆。解决离线把所有图像的特征提出来存成npy文件训练时直接读特征不跑视觉encoder。这样显存占用立刻降30%以上速度也翻倍。import numpy as np import torch from tqdm import tqdm vit.eval() all_features [] with torch.no_grad(): for path in tqdm(image_paths): img load_image(path).unsqueeze(0) # [1, 3, 224, 224] feature vit(img) # [1, 512] all_features.append(feature.cpu().numpy()) np.save(data/cached_image_features.npy, np.stack(all_features))训练开始时只需要按索引读取这一份npy文件。注意如果用了数据增强缓存方案只适用于视觉encoder冻结的情况一旦解冻ViT缓存就失效了。5.4 少样本时verbalizer选词不合适现象训练集只有200条模型在训练集上F1有0.9验证集只有0.4且错误集中在某个标签上。原因verbalizer的选词和真实样本风格不匹配。比如业务数据里用户习惯说“垃圾”“坑”但verbalizer里用的词是“差”“糟糕”导致模型在[MASK]位置输出了“垃圾”但词表映射里没有这个词概率被浪费了。解决用无标注数据统计高频情感词再补进verbalizer。具体做法是先把训练集文本里的形容词和情绪词做个词频统计选出top词作为候选标签词。更省事的方法是给每个标签准备10个以上的词宁可重复不可遗漏。词多了之后不会显著增加计算量因为只需要取均值。5.5 图文冲突样本下模型被文本带跑现象构造一批“文本负面但图像正面”的冲突样本模型90%预测负面图像信息几乎没有起作用。原因文本序列长度通常是几十个token图像token只有一个在self-attention中文本token的注意力权重总和远大于图像token图像信号被淹没。这是token级融合的天然缺陷。解决第一种方法是给图像token的embedding乘以一个可学习的缩放因子初始值设1.0让模型自己学会放大视觉信号。第二种是在训练集里刻意加入5%到10%的冲突样本强制模型学习不一致条件下的决策。第三种是加一个门控融合层在预测前用图像特征计算一个门控值控制文本logits和图像logits的加权比例。我实际用的是第二种加第三种组合冲突样本测试集的F1从0.45提到了0.66。6. 从离线实验到可用服务推理加速与效果验证模型跑通之后要面对的是上线问题。多模态系统的推理比单模态慢一倍甚至更多瓶颈几乎都在图像编码上。一个被我反复验证有效的做法是把图像特征缓存和在线推理结合起来。图片不同于文本同一个商品的配图在一周内几乎不变完全没必要每次请求都跑一遍ViT。我在服务里加了一层特征缓存import numpy as np feature_cache {} def get_image_feature(image_id): if image_id in feature_cache: return feature_cache[image_id] feat vit(load_image(image_id)) feature_cache[image_id] feat return feat在缓存命中率80%的场景下推理P95延迟能从380ms降到120ms。如果并发量高再加一个LRU缓存淘汰策略防止缓存无界增长。文本部分把RoBERTa用torch.compile()包起来在动态shape下能省10%到20%的延迟再大就考虑导出ONNX但BERT系列的收益不如CNN明显。验证方法上我习惯在离线测试之外单独构造一个冲突样本测试集。做法是用两个训练好的单模态模型一个文本、一个图像分别预测挑出两个模型预测不一致的样本人工确认后作为多模态系统的专项测试集。多模态系统在这个测试集上的F1必须比最好的单模态模型高至少8个百分点否则说明融合没有真正生效。这个测试集平时不参与训练只在迭代时用来验收。最后一件事不要只盯着F1。上线后我收到最多的反馈是“明明预测对了但用户觉得不对”。后来排查发现是推理时的模板和训练时的模板不一致训练用“情感是[MASK]。”推理时写成了“情感为[MASK]。”导致分布偏移。从那以后我要求训练和推理共用同一个模板配置类不允许手写。这套系统从选型到落地我迭代了三轮最大的教训是多模态系统的坑不在模型能力而在特征对齐、缓存设计和验证集构造这些“看起来不重要”的环节。希望帮到你。本文还有配套的精品资源点击获取
返回列表