ARTICLE DETAIL

资讯详情

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

ERNIE微调情感分析实战:句子级与属性级任务全流程解析

ERNIE微调情感分析实战:句子级与属性级任务全流程解析 简介一套面向自然语言处理开发者的情感分析完整实现基于百度预训练大模型ERNIE覆盖句子级与属性级两种粒度提供从数据预处理、模型加载、特征提取到分类器训练评估的可运行源码可直接用于实际项目或作为预训练模型应用模板。资源共24个文件含6个Python脚本、9个JSON文件、4个PDF说明文档及数据集压缩包25.54MB结构上分为基于ERNIE完成属性级情感分析和IMDB情感分析两个子项目便于按需学习。已有220人浏览学习。随包数据集包含标注的句子和属性PDF文档辅助理解模型原理与代码流程脚本覆盖情感分类、属性与观点抽取等关键环节适合入门与进阶者快速掌握预训练模型在情感分析中的落地方法也可迁移至其他自然语言处理任务并复用其中的关键流程。1. 情感分析从“规则”到“预训练”ERNIE解决的是什么样的任务做电商评论分析或者舆情监控的人多多少少都积累过一本翻车账本正则表达式把“没想象中差”当成差评词频统计把“格调有了就是太贵”算成好评。规则方法不是不能做而是每换一个领域特征就得重新设计补第100个规则时也得补第101个洞。预训练大模型时代这个任务的解法变成了一条标准流水线选一个中文预训练模型在标注数据上做微调。本方案要讲的是Python生态里用百度ERNIE完成情感分析跑通句子级和属性级两个任务。ERNIE在预训练阶段加入了中文实体与短语级别的语义理解做情感判断时比直接套通用BERT更容易收敛。接下来的章节会从环境、数据、代码到参数逐一展开每一步都给可复现的写法。2. 环境与数据准备装对依赖、看懂两份数据集的标注差异先把环境建好是半天的任务。因为PaddlePaddle的安装包分CPU和GPU两个版本且PaddleNLP的API会随着主版本变化最稳的做法是用conda环境把Python版本锁死依赖装在虚拟环境里而不是base环境。源码和数据集也尽量放在同一个独立目录里避免路径和缓存互相污染。2.1 版本组合与安装命令我自己在本地复现这套任务时用的组合是Python 3.8 PaddlePaddle 2.5.x PaddleNLP 2.6.x。为什么把Python锁在3.8Paddle的轮子在3.9和3.10上的兼容性虽然接近但个别依赖尤其是pybind11相关的在3.8上历史最干净遇到问题时的网上解决方案也最多。Python 3.11及以上暂时不要碰部分旧代码在算子注册上会直接把进程搞崩溃。conda create -n ernie_sentiment python3.8 -y conda activate ernie_sentiment # CPU版本适合先跑通流程 pip install paddlepaddle2.5.2 # GPU版本用下面这行需提前装好CUDA 11.8 # pip install paddlepaddle-gpu2.5.2 pip install paddlenlp2.6.2 pip install pandas numpy scikit-learn逻辑说明这里分了CPU和GPU两条路径。首次跑通代码的时候不需要上GPU拿CPU小数据验证接口再换GPU训练能少踩很多版本坑。paddlenlp里面自带ERNIE模型的加载和微调接口我们不需要手动下载权重文件from_pretrained会从模型库拉取并缓存到本地目录。参数说明Python环境名ernie_sentiment是自定义的paddlepaddle2.5.2是CPU版本paddlenlp2.6.2对应PaddlePaddle 2.5的API。如果你拿到一份旧项目代码且出现了“parameter not found”“Op(scale) error”这类报错第一件事就应该检查这两个包的版本而不是去改代码。依赖版本用途备注Python3.8运行环境3.9也可用但易出兼容性小毛病paddlepaddle2.5.x深度学习框架CPU版先跑通流程paddlenlp2.6.x预训练模型接口内置ERNIE权重加载pandas任一稳定版读取数据集无版本硬性要求scikit-learn任一稳定版切分数据与评估用于train_test_split2.2 句子级数据集的格式与读取代码句子级情感分析的数据集格式很简单最常见是tsv文本每行两列文本和标签。标签可以设计成0/1两分类负面/正面也可以设计成0/1/2三分类负面/中性/正面还可以对应五星评分映射到0-4五个类别。下文以三分类为例因为三分类更贴近真实评论一条“价格还行但质量一般般”往往不像规则系统那样被硬塞进正面或负面。import pandas as pd df pd.read_csv( data/sentence_level/train.tsv, sep\t, header0, names[text, label], encodingutf-8 ) print(df.head()) print(df[label].value_counts(normalizeTrue)) df[label] df[label].astype(int) classes_num df[label].nunique()逻辑说明用pandas读tsv必须显式指定sep\t和encodingutf-8否则Windows平台上读取中文很容易报UnicodeDecodeError。文件首行有列名时用header0没有列名时用names[text, label]直接指定。读取后先看一眼label分布再用astype(int)转数值模型接受的是数值标签。参数说明value_counts(normalizeTrue)输出各类别占比用于判断类别是否均衡。如果发现负面只占5%后面训练时就得考虑加权损失否则模型会无脑把所有样本预测成多数类。2.3 属性级数据集怎么组织才不亏属性级情感分析要拆开来说。以手机评论“屏幕显示效果很好但电池不太耐用”为例句子级模型只能看到整句的混合情感很可能被预测为中立甚至负面属性级模型需要输出两个三元组屏幕正面和电池负面。数据集就不能一行一个标签了而是一个句子对应多个属性-情感对。最小可用的属性级数据集有两种组织方式。第一种是“句对”方式第一列是句子第二列是属性词第三列是情感极性。第二种是把一个句子拆成多条样本每个样本是句子属性也就是说一条评论会出现在多行里。我建议按第二种组织因为后续迁移到句对输入的自然语言处理模型时最顺手。import pandas as pd df_absa pd.read_csv( data/aspect_level/aspect_train.tsv, sep\t, headerNone, names[text, aspect, polarity], encodingutf-8 ) print(df_absa.groupby(text).size().value_counts()) print(df_absa[polarity].value_counts()) polarity_map {negative: 0, neutral: 1, positive: 2} df_absa[polarity_id] df_absa[polarity].map(polarity_map)逻辑说明这个数据组织把一条评论拆成了多行每行只保留一个属性和对应情感。groupby(text).size().value_counts()能帮我们看平均每个句子有几条标注样本如果多数句子都只有一条属性标注那就要检查标注是否漏标了中性属性。情感极性映射成0/1/2后就能直接送进ERNIE做三分类。参数说明polarity字段最好用英文枚举值避免中文映射时出现前后不一致。如果你拿到的数据里用的是正/负/中映射表改成{正:2, 负:0, 中:1}即可。字段示例1示例2含义text屏幕显示效果很好但电池不太耐用屏幕显示效果很好但电池不太耐用原始评论文本aspect屏幕电池需要判断情感的属性polaritypositivenegative该属性对应正/负/中性2.4 验证集划分这一件事必须做一开始我常常贪图省事把全部数据拿去训练然后对着训练集loss沾沾自喜等拿到一批新数据才发现效果一塌糊涂。后来老老实实切出一折做验证集很多过拟合问题当场暴露。预训练模型的微调速度快验证集是最后的“后悔药”。from sklearn.model_selection import train_test_split train_texts, val_texts, train_labels, val_labels train_test_split( df[text].tolist(), df[label].tolist(), test_size0.1, random_state42, stratifydf[label] ) val_ds SentimentDataset(val_texts, val_labels, tokenizer, max_length128) val_loader DataLoader(val_ds, batch_size32, shuffleFalse)逻辑说明stratifydf[label]让划分前后各类别比例保持一致否则类别不平衡数据随机切分后验证集可能近乎全正例。验证集的shuffle必须设为False否则每次评估的样本顺序变化会导致指标轻微抖动难以判断当前改动是真是假。参数说明test_size0.1在小数据集几千条上可能让验证集样本太少建议比例放到0.2如果总数超过5万条再缩回0.1节省训练时间。random_state42固定随机种子保证重新运行时划分结果一致。这一章结束后至少有一个可自检的点两条命令行能解决环境两份数据集的结构能无脑复现。如果训练后出现loss不降先回头检查这里不用先去怀疑模型或代码。环境错一层后面的所有操作都会叠加出错这属于血泪经验。3. 句子级情感分析跑通用ERNIE微调一个评论分类器的完整代码句子级任务可以当作一个经典文本分类问题看待。ERNIE的最后一层隐藏状态代表整个句子的语义接一个全连接分类头就能输出每个类别概率。它的收敛速度和准确率都明显优于用word2vec、TF-IDF特征搭出来的传统模型尤其是在句子长度为10到100字的中文评论上。3.1 任务本质与分类头选择为什么后面只接一个Linear层ERNIE这类预训练模型在[CLS]位置输出的向量已经从预训练阶段学会了汇总全句信息。我们只需要把它接到一个num_classes的全连接层上。PaddleNLP的ErnieForSequenceClassification已经把这段封装好了不需要手动拼模型。这样做还有一个额外的好处预训练阶段的语义知识被完整保留微调只是“校准”而不是“从零学习”。from paddlenlp.transformers import ErnieForSequenceClassification, ErnieTokenizer model_name ernie-3.0-base-zh num_classes 3 model ErnieForSequenceClassification.from_pretrained( model_name, num_classesnum_classes ) tokenizer ErnieTokenizer.from_pretrained(model_name)逻辑说明from_pretrained会优先从本地缓存加载权重本地没有才会从模型库下载初次下载体积不小耗时看网络状况。num_classes3会自动替换模型顶部的分类头无须自己写Linear层。tokenizer负责把中文句子切成token序列并生成input_ids和token_type_ids两类输入。参数说明model_name可以换成ernie-3.0-medium-zh或ernie-3.0-mini-zh更小更快但效果略降。如果只是验证代码流程用mini版本能省一半显存最终压测再换回base。换模型名后tokenizer也要同步换不少人只换了模型名没换tokenizer跑起来报字表维度不匹配。3.2 tokenizer与Dataset把文本变成Paddle能吃的张量这里碰上的是第一个真正会反复调整的环节。最大长度max_length直接影响训练速度、显存和效果。常见做法是设128对短评论足够长文本场景要谨慎后文避坑章节会专门讲截断问题。把这条评论处理成一个固定长度的张量是后续训练稳定性的前提。import numpy as np from paddle.io import Dataset, DataLoader class SentimentDataset(Dataset): def __init__(self, texts, labels, tokenizer, max_length128): self.texts texts self.labels labels self.tokenizer tokenizer self.max_length max_length def __getitem__(self, idx): text self.texts[idx] labels self.labels[idx] encoded self.tokenizer( text, max_lengthself.max_length, truncationTrue, paddingmax_length, return_tensorspd ) return encoded[input_ids], encoded[token_type_ids], np.array([labels], dtypeint64) def __len__(self): return len(self.texts) train_ds SentimentDataset(df[text].tolist(), df[label].tolist(), tokenizer) train_loader DataLoader(train_ds, batch_size32, shuffleTrue)逻辑说明tokenizer里的return_tensorspd直接返回Paddle张量DataLoader拿到手就能送进模型避免在训练循环里手动转格式。paddingmax_length让同批次内所有样本等长方便矩阵运算代价是短样本有填充浪费。labels转成np.array后在外层放一个括号是为了之后batch维度统一成(batch_size, 1)。参数说明batch_size32在base模型上大概占5到7GB显存显存少就降到16CPU上跑可以调到8。shuffleTrue每个epoch打乱样本顺序避免模型学到数据排列里的顺序偏差。max_length设得过大只会浪费算力128对大多数电商评论已经覆盖90%以上的句子。3.3 训练循环优化器、学习率与warmup设置预训练模型微调有一个通行的规矩学习率不能像训练普通神经网络那样动辄0.01一般落在2e-5到5e-5之间而且前面要加warmup。原因是预训练权重已经收敛到较好状态学习率太大容易把学到的语义结构冲散也就是业内调侃的“灾难性遗忘”。import paddle import paddle.nn.functional as F from paddlenlp.transformers import LinearDecayWithWarmup learning_rate 3e-5 epochs 3 steps_per_epoch len(train_loader) total_steps steps_per_epoch * epochs warmup_steps int(total_steps * 0.1) scheduler LinearDecayWithWarmup( learning_ratelearning_rate, total_stepstotal_steps, warmupwarmup_steps ) optimizer paddle.optimizer.AdamW( learning_ratescheduler, parametersmodel.parameters(), weight_decay0.01 ) for epoch in range(epochs): model.train() total_loss 0.0 for step, batch in enumerate(train_loader): input_ids, token_type_ids, labels batch labels paddle.squeeze(labels, axis-1) logits model(input_ids, token_type_ids) loss F.cross_entropy(logits, labels) loss.backward() optimizer.step() scheduler.step() optimizer.clear_grad() total_loss loss.item() if step % 100 0: print(fepoch:{epoch} step:{step}/{len(train_loader)} loss:{loss.item():.4f}) avg_loss total_loss / len(train_loader) print(fepoch {epoch} done, avg_loss: {avg_loss:.4f})逻辑说明LinearDecayWithWarmup是PaddleNLP自带的学习率调度器前10%的step学习率从0线性升到设定值后90%线性降到接近0相当于给模型一个“先热身再冲刺”的节奏。loss.backward()之后必须加optimizer.clear_grad()Paddle是手动梯度清理漏了这一行梯度会在每个step上累加loss会越训越离谱。参数说明epochs3在这个任务上通常是收敛边界更多轮次未必提升F1反而可能过拟合训练集weight_decay0.01是微调标准值。打印loss时关注在epoch结束前是否下降到0.1附近——如果只降到0.7左右下不去多半是学习率偏大或者数据标签有噪声。3.4 评估与保存用准确率和F1两个视角看模型训练完成后看分类效果不能只盯着accuracy。情感分析数据集十有八九存在类别不平衡正面评论往往占大多数一个全投正面的模型也能拿到0.7以上的accuracy但业务上没有任何价值。正确做法是计算每个类别的precision、recall、F1再取macro平均。from sklearn.metrics import precision_recall_fscore_support def evaluate(model, loader): model.eval() all_preds [] all_labels [] with paddle.no_grad(): for batch in loader: input_ids, token_type_ids, labels batch labels paddle.squeeze(labels, axis-1) logits model(input_ids, token_type_ids) preds paddle.argmax(logits, axis-1) all_preds.extend(preds.numpy().tolist()) all_labels.extend(labels.numpy().tolist()) precision, recall, f1, _ precision_recall_fscore_support( all_labels, all_preds, averagemacro, zero_division0 ) acc sum(1 for p, l in zip(all_preds, all_labels) if p l) / len(all_labels) print(faccuracy: {acc:.4f}, macro-precision: {precision:.4f}, macro-recall: {recall:.4f}, macro-F1: {f1:.4f}) return all_preds, all_labels逻辑说明启用model.eval()并包裹paddle.no_grad()在评估阶段不计算梯度避免显存和耗时翻倍。将preds和labels转成numpy list交给sklearn计算指标。averagemacro把三类指标平均汇总类别不平衡时macro比accuracy诚实得多。zero_division0防止某个类别一条都没预测对时报除零警告。参数说明如果希望输出每个类别的单独指标可以把average换成None得到形状为(num_classes,)的三个数组特别适合排查“某个负面类别完全没召回”的情况。评估后保存模型用model.save_pretrained同时保存tokenizer否则推理阶段加载模型后无法还原词表。model.save_pretrained(./checkpoints/ernie_sentiment_sentence) tokenizer.save_pretrained(./checkpoints/ernie_sentiment_sentence)逻辑说明PaddleNLP的save_pretrained把权重和配置文件写入指定目录。推理时从同一目录用from_pretrained读取就能和训练时保持完全一致的词表和预处理逻辑。保存目录建议按项目名分二级目录避免多版本覆盖。4. 属性级情感分析把“好看但贵”拆成两个独立情感判定的实现属性级情感分析和句子级最大的不同在于训练样本的构造方式。“屏幕显示效果很好但电池不太耐用”这句话里模型需要对“屏幕”和“电池”分别给出情感极性。如果不把属性信息告诉模型模型天然分不开两段情感这也是属性级任务最常见的翻车根源。4.1 属性级任务拆解两个子任务缺一不可动手之前先搞清楚要做哪一步。属性级情感分析在学术上分两个子任务属性词抽取Aspect Term Extraction自动找出句子里的属性词或属性短语比如“屏幕”“电池”。属性级情感分类Aspect Sentiment Classification给定句子和属性词输出该属性的情感极性。放在工程落地里多数时候两个子任务都要做。可以先把属性词当作序列标注任务用ERNIE抽出来再把抽到的名词逐个跑情感分类。很多现成的开源项目只给第二段的标注数据所以项目里“数据集”这一项的质量往往决定了属性级最终能跑多远。4.2 用句对输入实现属性级情感分类把属性和上下文绑在一起最稳妥的做法是把句子和属性词拼成一个BERT式“句对”输入[CLS] 整句 [SEP] 属性词 [SEP]。在tokenizer里直接用text_pair参数构造。这里的关键是token_type_ids模型需要区分哪些token属于评论正文、哪些属于属性词这样Attention机制才能在不同片段之间做有方向的对齐。class AspectSentimentDataset(Dataset): def __init__(self, texts, aspects, polarities, tokenizer, max_length160): self.texts texts self.aspects aspects self.polarities polarities self.tokenizer tokenizer self.max_length max_length def __getitem__(self, idx): text self.texts[idx] aspect self.aspects[idx] polarity self.polarities[idx] encoded self.tokenizer( text, text_pairaspect, max_lengthself.max_length, truncationTrue, paddingmax_length, return_tensorspd ) return encoded[input_ids], encoded[token_type_ids], np.array([polarity], dtypeint64) def __len__(self): return len(self.texts)逻辑说明text_pairaspect让tokenizer生成两个句子段的token_type_ids。相比把整句和属性词直接拼接成一段文本喂进去句对结构给模型提供的归纳偏置更合理——模型能明确知道哪些位置是上下文、哪些位置是被评价的对象。微调阶段模型会逐渐学会把属性词附近的语义抠出来做极性判断。参数说明max_length建议放宽到160因为句对输入比单句多了一段属性词截断带来的影响也更明显。如果数据集里句子普遍不超过30个字120也够用不要无脑上256训练速度会成倍下降。4.3 训练复用句级代码改这几个参数就行属性级微调的代码和句子级几乎一样唯一的区别是num_classes改为3labels换成每个样本的属性极性。很多新手给属性级模型单独设计一套训练逻辑其实换汤不换药。先跑通再调参是这里最好的策略。model ErnieForSequenceClassification.from_pretrained( ernie-3.0-base-zh, num_classes3, dropout0.1 ) aspect_ds AspectSentimentDataset( df_absa[text].tolist(), df_absa[aspect].tolist(), df_absa[polarity_id].tolist(), tokenizer, max_length160 ) aspect_loader DataLoader(aspect_ds, batch_size16, shuffleTrue)逻辑说明dropout0.1是PaddleNLP模型默认值一般不用调。如果数据集很小少于1万条可以把dropout调到0.3抑制过拟合的效果立竿见影。batch_size16是因为句对样本每条约比单句长20到30个token显存压力大于句级任务。训练循环本身和3.3节完全一致优化器、warmup、损失函数都不需要动。唯一要提防的是训练数据量不足属性级任务对每个属性-情感对都要有足够样本如果某个属性只出现几十次模型很难学稳。这时候优先考虑在数据层面做增强而不是调模型结构。4.4 属性词兜底方案用词性筛选作为保底管道在真实项目中标注好的属性词未必齐全。这时候有一个低成本兜底用词性分析筛选名词短语作为属性候选再交给上面训练好的属性级分类器过滤。不需要额外训练一个序列标注模型也能跑出可用的结果。import jieba.posseg as pseg def extract_aspect_candidates(text, max_candidates5): words pseg.cut(text) candidates [] for word, flag in words: if flag.startswith(n) and len(word) 2: candidates.append(word) return candidates[:max_candidates] sentence 屏幕显示效果很好但电池不太耐用 print(extract_aspect_candidates(sentence)) # 屏幕, 效果, 电池, 耐用后两个需要过滤逻辑说明词性筛选只能作为候选生成器不能直接当最终属性因为序列里会混入“效果”这类宽泛名词和“耐用”这种被动词修饰的形容词。把候选喂给句对模型后模型对“效果”很可能输出中性对“电池”输出负面等于自动做了第二轮过滤。这个兜底方案的效果上限不如专门训练的属性抽取模型但胜在零标注成本。5. ERNIE微调避坑档案五个常见的翻车点与对应解法这章是给急着上线的人看的。以下五个坑我都踩过每个都按“现象、原因、解决”来写。如果某一条跟你的现场对上了直接照做通常能省半天时间。5.1 Windows下读取中文数据集报编码错现象在Windows下直接读训练tsv抛UnicodeDecodeError: gbk codec cant decode byte...。原因pandas在Windows默认使用系统编码GBK而数据文件是UTF-8。这是Python读取文本文件最经典的坑也最容易被人忽略——因为同一份代码在Linux上跑没有任何问题。解决读文件时显式传入encodingutf-8。如果你手里已经有了一批GBK编码的旧文件可以先用脚本统一转码不要靠pandas自动推断损失数据不可逆。df pd.read_csv(train.tsv, sep\t, encodingutf-8, enginepython)5.2 模型权重加载时报“参数名不匹配”现象从模型库下载好权重后实例化模型时抛parameter not found in checkpoint或[weight] shape mismatch。原因PaddlePaddle 2.4升到2.5之后PaddleNLP也换了主版本from_pretrained读取的检查点结构从旧版本格式变成了新格式模型缓存目录里残留旧版本文件时from_pretrained会优先读旧文件于是报参数名不匹配。解决把本地paddlenlp缓存目录下对应的ERNIE缓存文件夹删掉确保paddlepaddle和paddlenlp版本配对再重新下载。这条可以直接套用到任何“昨天还能跑今天突然不行”的诡异场景。5.3 类别不平衡导致训练时准确率虚高现象训练完成accuracy显示89%但报表里“负面”类别的precision为0。原因负面样本只占总数6%模型学到把所有样本预测为多数类准确率被正面样本拉高了。不看分类别指标这种假象很难被发现。解决先改用macro-F1评估再对损失函数做类别加权。加权公式是按类别样本数的倒数归一化权重counts df[label].value_counts().sort_index() class_weight (counts.sum() / (counts * len(counts))).to_numpy() loss F.cross_entropy( logits, labels, weightpaddle.to_tensor(class_weight, dtypefloat32) )逻辑说明class_weight让少数类在loss里占据更高比例模型不再偏向多数类。注意这个权重只在类别比例严重失衡超过10:1时使用正常分布下加了反而让公共类别变差。5.4 长文本截断把关键情感信息截掉了现象一篇500字的评论总体正面但最后一句是“总之不会再买了”模型预测成positive。原因默认truncationTrue从尾部截断保留开头关键的尾部转折被截掉了。预训练模型只看前128个token根本看不到“不会再来”这个决定性表述。解决改用truncation_sideleft从开头截去超出部分保留句尾。或者先把长文本按逗号句号分句对每句单独预测再用句级权重汇总。后者更稳但代码量稍多。tokenizer ErnieTokenizer.from_pretrained(model_name, truncation_sideleft)5.5 属性级模型对属性词“视而不见”现象句对模型训练完“屏幕显示效果很好但电池不太耐用”分别以“屏幕”和“电池”作为属性词预测时结果几乎都是正面。原因属性词在整个句对里占比太小模型在训练中把大部分注意力放在了评论正文上属性词的存在对最终分类头几乎没有贡献。如果训练数据里每个句子有多段情感而属性词又被截断模型就会学会“抄”整句的主情绪。解决两个方向可以组合使用。第一是把输入结构改成“属性词在前”的拼接方式让模型在处理正文前先看到被评价对象第二是训练时复制属性词替换成“attribute [MASK]”之类的增强信号逼迫模型依赖属性信息。# 属性词提前到句首的构造方式 encoded tokenizer( aspect, text_pairtext, max_lengthself.max_length, truncationTrue, paddingmax_length, return_tensorspd )我实测下来属性词前置于句首对小样本任务提升明显大约能挽回5到8个点的macro-F1。6. 用“访谈测试”验证模型真实水平再做一轮针对性微调训练完成之后先别急着上线。我的习惯是跑一轮“访谈测试”人工挑出二三十条在人类看来明显带情感倾向、但模型容易混淆的句子来核对。这一步能暴露评测集上完全看不出来的短板。6.1 构造硬负例集把模型的偏见逼出来硬负例集要覆盖几类模式带否定词、带转折、带委婉表达、带反讽。每类准备五条左右跑一遍模型预测统计判错的位置。hard_cases [ 没想象中那么差但也没多好, # 转折 这个价位就别指望有什么惊喜了, # 委婉否定 性价比高是真的服务差也是真的, # 双方面 也就那样吧不吹不黑, # 中性偏负 ] for text in hard_cases: inputs tokenizer(text, max_length128, truncationTrue, return_tensorspd) logits model(inputs[input_ids], inputs[token_type_ids]) pred paddle.argmax(logits, axis-1).item() print(text, -, [negative, neutral, positive][pred])逐条记录与人类预期不一致的地方你会发现失败往往集中在某一类模式上比如所有转折句都被判成正面所有委婉否定都被判成中性。找到共性后针对这个共性去扩充训练数据用1e-5的学习率额外微调两轮不要从头训。这样能保住原有能力只修正偏差点。走完这轮访谈测试模型才真正算“能用”而不是“在评测集上分数好看”。我接过不少情感分析项目发现最贵的部分从来不是训练本身而是减少模型和业务真实分布之间的间隙。每换一个领域我都会先用二三十条硬负例摸一遍底再决定要不要追加训练数据这个习惯帮我避开了很多翻车现场。希望帮到你。本文还有配套的精品资源点击获取
返回列表