ARTICLE DETAIL

资讯详情

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

ERNIE情感分析实战教程:从句子级到属性级,用PaddleNLP构建中文情感分类模型

ERNIE情感分析实战教程:从句子级到属性级,用PaddleNLP构建中文情感分类模型 简介面向自然语言处理学习者和开发者以百度预训练模型ERNIE为核心的完整情感分析工程包覆盖句子级与属性级两个分析维度并搭配已标注数据集适合入门到进阶阶段的NLP实践。资源共24个文件以py训练与推理脚本、json数据格式、pdf说明文档、pyc缓存等为主整体约25.54MB目录按情感分析任务拆分便于定位代码与数据。已有220人学习下载。包内包含IMDB句子级情感分析与属性级情感分析两套可运行项目从数据预处理、模型加载、特征提取到分类器训练与评估均有对应脚本属性级分析还配有属性与观点抽取模块结合pdf文档可快速掌握基于ERNIE完成情感分类的完整落地流程也可作为其他预训练模型NLP任务的代码模板。1. 情感分析为什么绕不开 ERNIE句子级与属性级到底差在哪Python基于预训练大模型ERNIE完成的情感分析听起来规格很高实际落地时你只需要一条文本、一个标注好的数据集、一张GPU没有也行就能把中文情感分类做到九成以上的准确率。这个方向包含两条任务线句子级别判断整条评论是正面还是负面属性级别为“服务”“位置”“价格”这些具体方面分别打分——同一句“酒店服务不错但隔音糟糕”句子级只能给一个结果属性级要同时输出“服务正面、隔音负面”。它适合想快速搭建中文情感分类服务的后端工程师、准备做情感分析实验的研究生以及要在自己业务数据上微调模型的产品同学。接下来我把环境、数据、代码、踩过的坑一条条拆开。2. 环境与数据集准备用PaddleNLP加载ERNIE以及两种数据格式的约定中文情感分类的模型选型基本没有悬念BERT能吃中文但对中文语义的编码不够透彻。ERNIE 是百度开源的预训练大模型它的掩码策略覆盖了实体和短语层面比 BERT 的随机掩码更能保留“新瓶装旧酒”这类中文特有语义。更关键的是Python 生态里 PaddleNLP 把 ERNIE 系列全部打包成一行from_pretrained省去手工下载权重和转换格式的麻烦。这一章先把环境装明白再把数据切成统一格式避免后面训练脚本被各种小问题打断。2.1 python安装与依赖匹配先装 paddlepaddle再装 paddlenlp常见做法是在干净的虚拟环境里操作避免和已有项目打架。python 安装好之后用 venv 创建独立环境然后装两个核心包paddlepaddleCPU 或 GPU 版和 paddlenlp。安装顺序建议先 paddlepaddle 再 paddlenlp让 pip 按依赖关系自动匹配如果反过来pip 可能会为了满足依赖把已装好的包又换掉版本。python -m venv venv_ernie source venv_ernie/bin/activate # Windows 用 venv_ernie\Scripts\activate pip install --upgrade pip pip install paddlepaddle # GPU 机器装 paddlepaddle-gpu pip install paddlenlp装完别急着跑训练先做一次导入自检。paddlepaddle 和 paddlenlp 的版本匹配是翻车重灾区常见的现象是import paddlenlp时报底层算子找不到或者模型加载时提示某个 API 不存在。自检脚本如下import paddle import paddlenlp from paddlenlp.transformers import AutoTokenizer print(paddle.__version__) print(paddlenlp.__version__) tok AutoTokenizer.from_pretrained(ernie-3.0-base-zh, use_fasterFalse) print(len(tok.vocab))这里用use_fasterFalse值得单独说。新版 PaddleNLP 默认走 FasterTokenizer速度快但对操作系统和 Python 版本有额外依赖加载报错时退回传统 tokenizer 是最快的解决办法。vocab 长度打印出来说明 ERNIE 词表已加载环境这关就算过了。GPU 机器建议先跑一下nvidia-smi看显存8GB 显存跑 base 模型、max_length 128、batch 32 是可以接受的如果只有 CPU调参阶段先用 mini 版本模型跑通链路。2.2 数据集格式句子级两列、属性级三列先统一成 TSV数据集是这个项目最容易“看起来能用、一跑就炸”的部分。我习惯把所有数据统一成 UTF-8 编码的 TSV不用 CSV——中文评论里逗号太多CSV 的引号转义会让清洗脚本多写一屏。句子级Sentence-level表格textlabel酒店的服务还算不错1房间太小不值这个价0属性级Aspect-based表格textaspectlabel酒店的服务还算不错服务1酒店的服务还算不错位置0房间太小不值这个价价格0标签统一用 0 表示负面、1 表示正面。判断数据能不能直接用先统计文件行数和合法标签wc -l data/*.tsv awk -F \t {print $NF} data/senti_aspect.tsv | sort | uniq -c第二个命令把属性级数据的标签列单独抽出来统计如果出现 0、1 之外的取值说明标注不一致或文件里混入了空行训练前必须处理。公开数据集方面句子级常用酒店评论和微博情感两个经典中文语料属性级可以找 SemEval-2014 的餐厅/笔记本英文数据做原型验证或者直接用业务场景里人工标注的中文方面数据。规模上句子级一两万条足够微调出可用模型属性级每个 aspect 类别最好不少于 500 条否则容易过拟合。这里有一个非常隐蔽的切分坑属性级数据里同一条句子会对应多行不同 aspect 各一行。切分训练集和验证集时一定要按句子维度切不能按行数直接切。按行切会把同一句话的“服务”分到训练集、“位置”分到验证集造成数据泄漏验证指标虚高。我一般先把句子去重拿到列表按句子列表切分再把 aspect 行映射回去。2.3 用 tokenizer 验证一条文本看清 input_ids 和 token_type_ids拿到数据后不要急着写训练循环先用 tokenizer 打一条样本看看 ERNIE 到底把文本变成了什么。这一步能看到很多后期排查依赖的细节。from paddlenlp.transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(ernie-3.0-base-zh, use_fasterFalse) text 酒店的服务不错但隔音很糟糕 tokens tokenizer( text, text_pair服务, max_length32, truncationTrue, paddingmax_length, ) print(tokenizer.convert_ids_to_tokens(tokens[input_ids])) print(tokens[token_type_ids][:20])第一次跑的人一定要盯着token_type_ids看。前面的[CLS]和“酒店的服务不错”部分对应 0从中间[SEP]开始、属性词“服务”及后续对应 1padding 部分又回到 0。这个“句子0、属性1、padding0”的分布是后面属性级别情感分析的关键很多模型训练完不生效就是这一步没做对。input_ids则决定了文本截断位置——把 tokens 打印出来数一数最长样本在 max_length 下丢掉了哪些词。如果情感词经常被截掉第 5 章会讲怎么统计和解决。数据准备到这里告一段落。需要强调下载什么数据不重要重要的是每一行都能对应到上面的 TSV 结构数据格式统一训练脚本才能做到“换数据不换代码”。这个源代码项目之所以把数据集单独拎出来是因为情感分析里数据质量对结果的影响经常比模型结构还大。3. 句子级别情感分析完整训练脚本与三个关键参数句子级别的目标很直接给定一条文本输出 0 或 1。由于 ERNIE 本身已经把中文语义编码得很充分我们不需要在它上面堆复杂结构输出端接一个线性分类头就够了。这一章给出一个完整可跑的源代码流程从模型定义到验证并交代清楚三个最影响结果的关键参数。3.1 模型定义用 AutoModelForSequenceClassification 而不是裸 ErnieModel很多第一次写的人会试图加载ErnieModel后自己写 pooling 和全连接层这是不必要的。PaddleNLP 提供的AutoModelForSequenceClassification已经在[CLS]输出上挂好了分类头只需要传num_classes。它在内部会根据分类数自动初始化对应维度的全连接层不会出现输出维度对不上标签的情况。from paddlenlp.transformers import AutoModelForSequenceClassification model AutoModelForSequenceClassification.from_pretrained( ernie-3.0-base-zh, num_classes2, ) print(model)如果机器显存有限把模型名换成ernie-3.0-medium-zh或ernie-3.0-mini-zh其余代码不用改。选型上我一般这样定数据量少于 2 万条时用 base 性价比最高超过 5 万条或追求极致精度再考虑更大模型CPU 机器调参阶段先用 mini 跑通全流程最后再用 base 训一版。打印模型结构只是为了确认分类头挂上了不需要把完整输出看完。3.2 Dataset 与 DataLoader把 TSV 变成模型能消费的 batch训练脚本的核心是把上一章准备好的 TSV 读进来通过 tokenizer 转成input_ids、token_type_ids、attention_mask再按 batch 喂给模型。这里有一个容易忽视的细节Paddle 的 DataLoader 默认 collate 对普通列表能处理但对 Paddle Tensor 的行为在不同版本里有差异。为避免版本差异带来的随机报错我建议在 Dataset 里统一返回 tensor并显式写一个collate_fn。import paddle from paddle.io import Dataset, DataLoader from paddlenlp.transformers import AutoTokenizer class SentiDataset(Dataset): def __init__(self, file_path, tokenizer, max_len128): self.tokenizer tokenizer self.max_len max_len self.texts, self.labels [], [] with open(file_path, r, encodingutf-8-sig) as f: next(f) # 跳过表头 for line in f: parts line.rstrip(\n).split(\t) if len(parts) 2: continue self.texts.append(parts[0]) self.labels.append(int(parts[1])) def __len__(self): return len(self.texts) def __getitem__(self, idx): tokens self.tokenizer( self.texts[idx], max_lengthself.max_len, truncationTrue, paddingmax_length, ) return ( paddle.to_tensor(tokens[input_ids], dtypeint64), paddle.to_tensor(tokens[token_type_ids], dtypeint64), paddle.to_tensor(tokens[attention_mask], dtypeint64), paddle.to_tensor([self.labels[idx]], dtypeint64), ) def collate_fn(batch): input_ids paddle.stack([x[0] for x in batch]) token_type_ids paddle.stack([x[1] for x in batch]) attention_mask paddle.stack([x[2] for x in batch]) labels paddle.concat([x[3] for x in batch], axis0) return input_ids, token_type_ids, attention_mask, labels几个细节值得说明。文件打开用encodingutf-8-sig而非utf-8是因为 Windows 下生成的 TSV 常带 BOM 头不处理的话第一行文本会多出\ufeff字符。paddle.concat处理 labels是为了把每个 batch 里的[1]形状拼成[batch_size]而不是[batch_size, 1]否则计算损失时会报维度不匹配。paddingmax_length会带来一定计算浪费但让collate_fn保持简单想省显存可以改paddinglongest代价是每个 batch 序列长度不同加载时略微多花时间。3.3 训练循环学习率、warmup、epoch 怎么定训练循环本身不复杂复杂的是三个参数的取舍learning_rate、warmup 比例、epoch 数。预训练模型微调的标准学习率区间是 2e-5 到 5e-5大于 5e-5 容易出现灾难性遗忘小于 1e-5 则收敛太慢。warmup 比例 0.1 表示前 10% 的训练步数里学习率线性上升之后按线性衰减。epoch 数在情感分类任务上 3 个就够超过 5 个通常只会看到验证集指标停止增长。参数建议取值说明learning_rate2e-5 ~ 5e-5超过 5e-5 容易遗忘预训练语义warmup_ratio0.1前 10% 步数线性升温epochs3再多通常过拟合weight_decay0.01AdamW 默认正则强度batch_size16 ~ 64取决于显存和文本长度def evaluate(model, val_loader): model.eval() correct 0 total 0 with paddle.no_grad(): for input_ids, token_type_ids, attention_mask, labels in val_loader: outputs model(input_ids, token_type_idstoken_type_ids, attention_maskattention_mask) logits outputs.logits if hasattr(outputs, logits) else outputs preds paddle.argmax(logits, axis-1) correct (preds labels).sum().item() total labels.shape[0] return correct / total def train(model, train_loader, val_loader, epochs3): criterion paddle.nn.CrossEntropyLoss() lr_scheduler paddle.optimizer.lr.LinearDecay( learning_rate2e-5, total_stepsepochs * len(train_loader), warmup_ratio0.1, end_learning_rate0.0, ) optimizer paddle.optimizer.AdamW( learning_ratelr_scheduler, parametersmodel.parameters(), weight_decay0.01, ) for epoch in range(epochs): model.train() total_loss 0.0 for step, (input_ids, token_type_ids, attention_mask, labels) in enumerate(train_loader): outputs model(input_ids, token_type_idstoken_type_ids, attention_maskattention_mask) logits outputs.logits if hasattr(outputs, logits) else outputs loss criterion(logits, labels) loss.backward() optimizer.step() optimizer.clear_grad() total_loss loss.item() if step % 200 0: print(fepoch {epoch} step {step} loss {loss.item():.4f}) acc evaluate(model, val_loader) print(fepoch {epoch} val_acc {acc:.4f})这里hasattr(outputs, logits)分支是我反复踩过的兼容性写法PaddleNLP 2.x 早期版本直接返回 logits tensor之后的版本返回带logits属性的对象。不写这个分支换个版本代码就崩。attention_mask理论上可以不传ERNIE 内部会自动补全但显式传入能让 tokenizer 的 padding 与模型计算保持一致尤其是属性级任务里text_pair带来的 mask 变化这一步不能省。主流程把数据集切好、模型加载、开训if __name__ __main__: tokenizer AutoTokenizer.from_pretrained(ernie-3.0-base-zh, use_fasterFalse) train_ds SentiDataset(data/senti_sentence_train.tsv, tokenizer) val_ds SentiDataset(data/senti_sentence_dev.tsv, tokenizer) train_loader DataLoader(train_ds, batch_size32, shuffleTrue, collate_fncollate_fn) val_loader DataLoader(val_ds, batch_size64, shuffleFalse, collate_fncollate_fn) model AutoModelForSequenceClassification.from_pretrained(ernie-3.0-base-zh, num_classes2) train(model, train_loader, val_loader) model.save_pretrained(models/ernie_sentence) tokenizer.save_pretrained(models/ernie_sentence)batch size 的设法和文本长度强相关。ERNIE base 处理 128 长度时batch 32 在 8GB 显存上能跑超出就 OOM文本普遍在 50 字以内可以用 32 或 64。训练结束后save_pretrained会把模型权重和配置写到目录推理时直接from_pretrained加载不需要重新初始化 tokenizer。4. 属性级别情感分析把 aspect 拼进输入复用同一套分类管线属性级别情感分析Aspect-based Sentiment Analysis不是句子级任务的简单重复。它的输入多了一个维度属性词。任务要求对每个出现的方面分别给出情感极性模型必须看到“句子中的哪个部分对应哪个属性”。最常见方案是把句子和属性词用[SEP]拼到一起让 ERNIE 在编码时把属性词和句子中相关片段做语义对齐。这一章讲清楚这个方案为什么有效、代码怎么改、推理时如何覆盖未知属性。4.1 为什么句子级模型不能直接解决属性级需求回到开头那个例子“酒店的服务不错但隔音很糟糕。”句子级模型读完整个句子输出只能是“正面”或“负面”其中之一因为分类目标就是这样定义的。属性级任务要求的是“服务”这个方面是正面“隔音”这个方面是负面。如果把属性级数据直接丢给句子级模型训练会出现一个典型行为模型学到的是句子的整体情感偏置而不是细粒度判断。因为训练样本里“服务不错”大概率是好评“隔音糟糕”大概率是差评模型会倾向于用整体情绪做短路判断。真正暴露问题的是“服务不错但隔音糟糕”这种矛盾情感句句子级模型只能给出一个均值答案。这也是 ABSA 必须把 aspect 作为显式输入的原因——它不是让模型从句子反推属性而是把属性作为条件给进去。早期 ABSA 有一种做法是用两个编码器分别编码句子和属性再做融合效果不一定比拼接好还多出一倍显存开销。我一般直接用拼接式简单稳定毕竟 ERNIE 的注意力机制足够把属性词和句中相关内容对齐。源代码里只要在 tokenizer 调用时传入text_pair就完成了这一步。4.2 输入构造text_pair 把 aspect 送进 token_type_idsPaddleNLP 的 tokenizer 支持text_pair参数这正好是为 ABSA 设计的输入接口。句子放text属性词放text_pair编码后的token_type_ids里句子是 0、属性词是 1模型据此知道哪部分是对齐目标。from paddlenlp.transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(ernie-3.0-base-zh, use_fasterFalse) text 酒店的服务不错但隔音很糟糕 for aspect in [服务, 隔音, 价格]: tokens tokenizer(text, text_pairaspect, max_length32, truncationTrue, paddingmax_length) ids tokenizer.convert_ids_to_tokens(tokens[input_ids]) print(f{aspect}: {ids})这段代码会输出三组 token 序列。仔细看[SEP]的位置句子在第 1 个[SEP]后结束aspect 拼在它后面序列结尾再补一个[SEP]。第二个[SEP]和 aspect 之间的 token 在token_type_ids上对应 1正是模型区分“句子正文”和“待评属性”的边界。属性词不需要在句子中显式出现是这个方案的另一个优势——评论里从头到尾没写“价格”两个字但“太贵了”已经隐含了价格信息把“价格”作为 aspect 拼进去ERNIE 依然能给出合理预测这是词频统计模型做不到的。训练脚本和句子级几乎一样只需要让 Dataset 同时读 text 和 aspect 两列并在__getitem__里把 aspect 传给 tokenizerclass AspectDataset(Dataset): def __init__(self, file_path, tokenizer, max_len128): self.tokenizer tokenizer self.max_len max_len self.texts, self.aspects, self.labels [], [], [] with open(file_path, r, encodingutf-8-sig) as f: next(f) for line in f: parts line.rstrip(\n).split(\t) if len(parts) 3: continue self.texts.append(parts[0]) self.aspects.append(parts[1]) self.labels.append(int(parts[2])) def __getitem__(self, idx): tokens self.tokenizer( self.texts[idx], text_pairself.aspects[idx], max_lengthself.max_len, truncationTrue, paddingmax_length, ) return ( paddle.to_tensor(tokens[input_ids], dtypeint64), paddle.to_tensor(tokens[token_type_ids], dtypeint64), paddle.to_tensor(tokens[attention_mask], dtypeint64), paddle.to_tensor([self.labels[idx]], dtypeint64), )collate_fn直接复用第 3 章版本。模型定义不变num_classes依然取 2。唯一要注意的是max_length的分配因为句子和 aspect 拼接同一条样本会比句子级多占用若干 token。建议先用 2.3 的 tokenizer 脚本打印真实长度再决定 max_length。我一般取训练集中 95% 分位的长度加 8既不截断大多数样本也不让 padding 浪费显存。4.3 推理阶段候选属性列表逐条跑别一次塞进模型训练完成后ABSA 的推理方式和句子级有区别线上来的是文本加一个未知的属性集合你不知道文本里有哪些属性词更不知道哪些属性在训练集里见过。常见做法是预置一个候选属性列表把句子和每个候选属性分别拼一次逐个得到极性分数。import paddle from paddlenlp.transformers import AutoModelForSequenceClassification, AutoTokenizer model AutoModelForSequenceClassification.from_pretrained(./models/ernie_absa, num_classes2) tokenizer AutoTokenizer.from_pretrained(./models/ernie_absa, use_fasterFalse) idx2label {0: 负面, 1: 正面} def predict_aspect(text, aspects): results {} for aspect in aspects: tokens tokenizer( text, text_pairaspect, max_length128, truncationTrue, paddingmax_length, return_tensorspd, ) outputs model(**tokens) logits outputs.logits if hasattr(outputs, logits) else outputs probs paddle.nn.functional.softmax(logits, axis-1) label_idx int(paddle.argmax(logits, axis-1)[0]) results[aspect] (idx2label[label_idx], float(probs[0, label_idx])) return results if __name__ __main__: text 酒店位置很好但服务响应太慢价格倒是不贵 print(predict_aspect(text, [位置, 服务, 价格]))这段代码有两个讲究。第一padding 按max_length是为了让循环里的张量形状稳定如果你追求性能改成longest并对相同长度样本分组也可以但第一版建议怎么稳怎么来。第二softmax 后的置信度不要直接存成小数点后两位我见过太多人对着 0.52 和 0.49 纠结“模型是不是随机猜”——情感分类的置信度经常落在 0.5 附近边界样本本来就难判断真正要看的是整体指标。候选 aspect 列表建议覆盖业务里出现频次最高的 20 到 50 个属性冷启动阶段可以用词典挖掘句子里出现的候选词后续再人工补齐。5. ERNIE 情感分析避坑指南本地训练最容易翻车的 5 个问题这一章整理训练和交付中反复遇到、并且几乎每次都有人问的问题。每条按现象、原因、解决来写。如果你跑上面代码报错优先对照这里。我踩过其中最深的坑是 token_type_ids这不只是新手问题换版本、换 tokenizer 之后老手也会再犯。5.1 pip 装完 import 报错paddle 和 paddlenlp 版本打架现象按顺序安装后import paddlenlp报ModuleNotFoundError或者from paddlenlp.transformers import AutoTokenizer时提示某个底层模块不存在。原因paddlenlp 的不同大版本对 paddlepaddle 的版本有下限要求pip 只保证依赖存在不保证大版本兼容。解决先pip show paddlepaddle paddlenlp看版本号确认两者大版本一致2.x 配 2.x、3.x 配 3.x不一致就重装其中一方。如果不想动现有环境用 venv 单独起一个项目环境把训练和线上服务解耦。5.2 数据集带 BOM 头第一行文本出现 \ufeff现象训练时 loss 正常但验证集里第一行数据预测总是错的打印出来文本开头有个奇怪字符。原因Windows 下用记事本或 Excel 另存的 TSV 带 UTF-8 BOMopen用utf-8读进来后BOM 被当成普通字符留在字符串开头。解决读取文件时统一用encodingutf-8-sigPython 会剥掉 BOM如果数据已经混进去了清洗时line.replace(\ufeff, )兜底。5.3 长文本被 max_length 截断情感词正好丢在边界现象短文本验证集 acc 很高长文本预测结果近乎随机或者模型对某类超过 100 字的评论统一预测为同一类。原因max_length 设置过小情感词被截断模型只能看到前半段的事实性描述。解决不要拍脑袋设 128。先用 tokenizer 跑一遍训练集所有样本按 input_ids 长度画分布取 95% 分位作为 max_length。有时间可以改成“头尾截断”策略保留首尾各 60%对中文评论这种情感词常在结尾出现的场景效果提升明显。属性级任务记得把 aspect 的长度也算进去text_pair会占用额外 token。5.4 属性级模型训练完不同 aspect 预测结果完全一样现象ABSA 模型训完同一句下“服务”“价格”“位置”三个方面输出相同极性和句子级模型没有区别。原因tokenizer 调用时没有传text_pair或者 Dataset 里 aspect 列全为空导致属性词没进输入token_type_ids全部为 0。解决回到 2.3 的验证脚本打印一条样本的token_type_ids确认句子部分是 0、aspect 部分是 1。格式正确后再看 Dataset 读取的三列切分是否正确别贴错列索引。5.5 标签不均衡acc 虚高到 95% 但正样本全判错现象训练结束 acc 显示 95% 以上拿去一测负面评论几乎全部被判成正面。原因数据里正样本占 95%、负样本占 5%模型学到“全部分配给正样本”就能拿高 acc分类头没有区分能力。解决训练时打印混淆矩阵而不是只打印 acc观察负样本召回率。缓解手段有三个负样本上采样或正样本降采样损失函数改用带类别权重的CrossEntropyLoss如果两个类别样本量差距超过 10 倍先按业务价值重新审视标注标准而不是继续调模型。6. 模型验证与部署从 F1 到推理脚本少走两步都不行6.1 用混淆矩阵盯 F1而不是盯 acc情感分类是典型二分类任务但“分得对不对”和“业务上好不好用”是两回事。验证阶段我习惯同时输出 acc、precision、recall 和 F1其中 F1 和混淆矩阵最值得看。用 sklearn 是偷懒但正确的做法from sklearn.metrics import classification_report, confusion_matrix y_true [0, 1, 1, 0, 1, 1, 0, 0, 1, 1] y_pred [0, 1, 1, 0, 0, 1, 0, 1, 1, 1] print(classification_report(y_true, y_pred, target_names[负面, 正面])) print(confusion_matrix(y_true, y_pred))如果验证集上负面样本的 recall 明显低于正面说明模型偏向于“默认好评”这在中文评论场景非常常见。我的习惯是先看负类 recall再看整体 F1两个都稳了才敢上线。如果 F1 不理想先回去检查数据切分有没有泄漏、token_type_ids 对不对再调超参。6.2 再往前走一步从文本情感到多模态与生成式 ABSA到这里训练和部署流程已经完整。文本 ABSA 还有一个天然短板输入输出是“句子 属性 极性”的固定形态遇到细粒度观点词抽取比如提取“隔音糟糕”里的“隔音”就会力不从心。进阶方向是生成式方案用 ERNIE 的生成模型直接输出结构化情感标签或者把音频、图像信息加进来做多模态情感分析。就我的实践感受先把拼接式 ABSA 的准确率、速度、稳定性吃透能覆盖 80% 的业务场景再决定要不要上生成式。说一个我自己的血泪经验第一次做属性级情感分析时我把 aspect 直接拼在句子末尾没有用text_pair也没有检查token_type_ids训完只看到不同属性的预测全部一样。后来打印 token 序列才发现属性词混在句子里模型根本没有把它当作独立部分。那次之后凡是涉及 ABSA 的输入构造我都会先打印token_type_ids再开始训练。这个习惯帮我省掉了大量无效调参希望也能帮到你。本文还有配套的精品资源点击获取
返回列表