☰
ERNIE属性级情感分析源码实战:从IMDB到ABSA抽取与分类
2026/9/29 9:04:29 网站建设 项目流程

简介:本资源面向NLP初学者与进阶开发者,提供基于百度预训练大模型ERNIE的情感分析完整实战方案,覆盖句子级与属性级两类任务。包内共24个文件,以json数据配置、py源码脚本、pdf说明文档为主,另有少量pyc缓存与license等辅助文件,压缩包约25.54MB,目录按任务模块划分清晰。已有220人学习下载。读者可获取数据预处理、ERNIE模型加载、特征提取、分类器构建、训练调优与预测评估的全流程代码,并借助配套数据集完成训练、验证与测试。属性级部分还涉及属性与观点抽取,便于理解细粒度情感极性判断;句子级部分则以IMDB影评为例演示整体情感倾向分类。整体可作为预训练模型落地NLP任务的模板,帮助快速迁移至其他文本分类场景。

1. 拿到这份 ERNIE 情感分析源码,先别急着 pip install

很多做 NLP 的朋友第一次接触属性级情感分析(ABSA),都是被「一句话里同时夸了屏幕、骂了续航」这种场景逼出来的。句子级情感分析只能告诉你整句是正面还是负面,但真实业务里,一条评论往往包含多个评价对象,每个对象的情感极性还不一样。这份emotional-analysis-master资源包,就是冲着这个痛点来的:它用 Python 加百度 ERNIE 预训练模型,把句子级和属性级两条任务线都跑通了,源码、数据集、脚本一应俱全。

它适合谁?如果你已经会写 Python、装过 PyTorch 或 PaddlePaddle,想找一个能直接跑起来、又能拆开改的 ABSA 入门工程,这份代码值得花一个下午拆一遍。但如果你连虚拟环境都没配过,建议先把 Python 安装和 vscode python 环境配置这两件事搞定再回来。资源包里分了两个独立目录:一个做 IMDB 句子级情感分析,一个做属性级情感分析,后者又拆成「属性&观点抽取」和「属性级情感分类」两步。下面我按实际拆包顺序,把每个环节的参数、坑和验证方法讲清楚。

2. 环境与依赖:把 ERNIE 跑起来需要哪些前置条件

2.1 为什么选 ERNIE 而不是直接上 BERT

ERNIE 和 BERT 同属 Transformer 编码器架构,但 ERNIE 在预训练阶段引入了知识增强:它不只做随机掩码,还对实体、短语做整段掩码,让模型学到更完整的语义单元。放到属性级情感分析里,这个差异很关键。ABSA 需要模型理解「这家店的服务很周到,但价格偏高」中「服务」和「价格」各自对应的情感词,ERNIE 对短语边界的敏感度更高,抽取属性词和观点词的准确率通常比同量级 BERT 好一截。

常见做法是直接用transformers库加载nghuyong/ernie-3.0-base-zh或ernie-2.0-base-en,前者对应中文任务,后者对应英文 IMDB。资源包里两个目录分别覆盖了这两种场景,你按数据集语言选模型即可。

2.2 依赖安装与版本锁定

先建虚拟环境,再装依赖。不要全局装,否则后面换模型版本会互相污染。

python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install torch transformers datasets scikit-learn pandas numpy tqdm

参数说明:torch建议 1.13 以上,transformers用 4.30 左右比较稳,太新的版本有时会改BertTokenizer的默认行为。datasets不是必须,但读 IMDB 的 csv 或 tsv 时比手写open()省事。装完后跑一句验证:

from transformers import AutoTokenizer, AutoModel tok = AutoTokenizer.from_pretrained("nghuyong/ernie-3.0-base-zh") model = AutoModel.from_pretrained("nghuyong/ernie-3.0-base-zh") print(tok.tokenize("这家餐厅的服务很好,但价格太贵"))

如果输出里能看到##开头的子词切分,说明 tokenizer 正常。注意:第一次运行会下载权重,国内网络环境下建议提前把模型缓存到本地,或者用cache_dir参数指定路径。

2.3 目录结构与脚本职责

拆开压缩包后,核心文件分布如下:

路径职责
基于ERNIE完成IMDB情感分析/main.py句子级二分类训练与推理入口
基于ERNIE完成IMDB情感分析/utils.py数据加载、tokenize、指标计算
基于ERNIE完成属性级情感分析/main.py属性级任务总入口
基于ERNIE完成属性级情感分析/属性&观点抽取.py从句子中抽属性词和观点词
基于ERNIE完成属性级情感分析/属性级情感分类.py对每个属性做极性分类
dataset/训练、验证、测试数据

先跑 IMDB 那条线,因为它最简单,能帮你确认环境和模型加载没问题。再跑属性级,那条线依赖前一步的抽取结果。

3. 句子级情感分析:从 IMDB 数据到可复现的训练脚本

3.1 数据预处理的关键参数

IMDB 数据集是英文影评,二分类(正面/负面)。资源包里的utils.py通常包含一个load_data函数,把文本和标签读成列表。你需要关注三个参数:max_length、padding、truncation。

from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") def encode(texts, labels, max_len=256): enc = tokenizer( texts, padding="max_length", truncation=True, max_length=max_len, return_tensors="pt" ) enc["labels"] = torch.tensor(labels) return enc

逻辑说明:padding="max_length"会把所有句子补到 256 个 token,短句补零,长句截断。IMDB 影评平均长度在 200 词左右,256 能覆盖大部分样本。如果你显存不够,降到 128 会丢信息,但训练快一倍。truncation=True必须开,否则遇到超长评论会直接报错。

3.2 模型构建与分类头

ERNIE 本体输出的是每个 token 的隐状态,做句子分类要取[CLS]位置的向量,再接一个全连接层。

import torch.nn as nn from transformers import AutoModel class SentimentClassifier(nn.Module): def __init__(self, model_name, num_labels=2): super().__init__() self.encoder = AutoModel.from_pretrained(model_name) self.dropout = nn.Dropout(0.3) self.classifier = nn.Linear(self.encoder.config.hidden_size, num_labels) def forward(self, input_ids, attention_mask): out = self.encoder(input_ids=input_ids, attention_mask=attention_mask) cls_vec = out.last_hidden_state[:, 0, :] return self.classifier(self.dropout(cls_vec))

参数说明:hidden_size对 base 模型通常是 768,num_labels=2对应正负二分类。Dropout(0.3)是防止过拟合的常规操作,如果训练集很大可以降到 0.1。注意attention_mask一定要传,否则 padding 的零会被模型当成真实 token 参与注意力计算,准确率会掉得莫名其妙。

3.3 训练循环与学习率设置

from torch.optim import AdamW from transformers import get_linear_schedule_with_warmup optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01) scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=100, num_training_steps=len(train_loader) * 3 )

逻辑说明:预训练模型微调时学习率不能大,2e-5是 BERT 系模型的经典值,ERNIE 也适用。weight_decay=0.01做 L2 正则。warmup让学习率在前 100 步线性上升,避免一开始就大步更新破坏预训练权重。训练 3 个 epoch 通常足够,再多会过拟合。每个 epoch 后在验证集上算准确率和 F1,如果 F1 连续两轮不升,就停。

3.4 推理与结果验证

训练完保存state_dict,推理时加载回来,对单条文本做预测:

model.eval() with torch.no_grad(): logits = model(input_ids, attention_mask) pred = torch.argmax(logits, dim=-1)

验证方法:拿几条明显正负面的影评手动测,比如 "This movie is a masterpiece" 应该输出正面,"Waste of time" 输出负面。如果结果反了,检查标签映射是不是{0: neg, 1: pos},很多数据集是反的,这是血泪经验。

4. 属性级情感分析:抽取与分类两步走的工程实现

4.1 属性&观点抽取的任务定义

属性级情感分析的第一步是从句子中找出「评价对象」和「评价词」。比如「这家餐厅的服务很好,但价格太贵」,属性词是「服务」和「价格」,观点词是「好」和「贵」。资源包里的属性&观点抽取.py通常用序列标注思路,把每个 token 标成 B-ASP、I-ASP、B-OPI、I-OPI、O 五类。

常见做法是复用 ERNIE 编码器,接一个Linear(hidden_size, num_tags)做 token 级分类。数据标注格式一般是 BIO,你需要确认dataset里的标签文件和代码里的label2id对得上。

4.2 抽取模型的训练与解码

class SpanExtractor(nn.Module): def __init__(self, model_name, num_tags=5): super().__init__() self.encoder = AutoModel.from_pretrained(model_name) self.classifier = nn.Linear(self.encoder.config.hidden_size, num_tags) def forward(self, input_ids, attention_mask): out = self.encoder(input_ids=input_ids, attention_mask=attention_mask) return self.classifier(out.last_hidden_state)

逻辑说明:输出维度是[batch, seq_len, num_tags],训练时用CrossEntropyLoss,注意要把 padding 位置的 loss 忽略掉,设ignore_index=-100。解码时用argmax得到每个 token 的标签,再按 BIO 规则合并成完整 span。这里有个坑:中文 tokenizer 会把一个词切成多个子词,合并时要按字符偏移还原,不能直接按 token 下标截原文。

4.3 属性级情感分类的输入构造

抽取出属性词后,第二步是判断每个属性对应的情感极性。输入格式通常是[CLS] 句子 [SEP] 属性词 [SEP],让模型聚焦到该属性相关的上下文。

def build_pair(sentence, aspect): return f"{sentence} [SEP] {aspect}" encoded = tokenizer( build_pair("这家餐厅的服务很好,但价格太贵", "价格"), max_length=128, truncation=True, padding="max_length", return_tensors="pt" )

参数说明:max_length=128对单句加属性词足够。[SEP]是 ERNIE 的句子分隔符,tokenizer 会自动转成对应 id。分类头和句子级一样,只是num_labels可能变成 3(正面/负面/中性)。训练时学习率同样用2e-5,但 batch size 可以小一点,因为输入变长了。

4.4 两步串联的完整推理流程

实际使用时,先跑抽取模型拿到(aspect, opinion)对,再把每个 aspect 喂给分类模型:

aspects = extract_aspects(sentence) for asp in aspects: polarity = classify(sentence, asp) print(f"属性:{asp},情感:{polarity}")

验证方法:构造一条包含两个属性的句子,看输出是否分别给出不同极性。如果两个属性输出一样,检查分类模型的输入里 aspect 有没有被正确拼接,很多时候是拼接顺序反了导致模型忽略属性词。

5. 避坑与排查:跑这份源码最容易翻车的五个地方

5.1 模型下载卡住或报 ConnectionError

现象:from_pretrained一直转圈,最后抛连接错误。原因:默认从境外源拉权重,网络不稳定。解决:提前用cache_dir指定本地缓存目录,或者手动下载pytorch_model.bin、config.json、vocab.txt放到同一文件夹,再用本地路径加载。

5.2 标签映射反了导致准确率 50% 以下

现象:训练 loss 正常下降,但验证准确率一直在 0.5 附近。原因:数据集里0表示正面、1表示负面,但代码里写反了。解决:打印前 10 条样本的文本和标签,人工确认映射关系,改label2id字典。

5.3 中文 tokenizer 切分导致 span 偏移错位

现象:抽取出的属性词在原文里对不上,多一个字或少一个字。原因:ERNIE 中文 tokenizer 按字切分,但代码用 token 下标去截原字符串。解决:用tokenizer.convert_ids_to_tokens配合offset_mapping还原字符位置,或者直接用tokenizer.decode再匹配。

5.4 显存不足 OOM

现象:训练到一半报 CUDA out of memory。原因:max_length设太大或 batch size 太高。解决:先把 batch size 降到 8,再把max_length从 256 降到 128,同时开gradient_accumulation_steps=2模拟大 batch。如果还不行,换ernie-3.0-nano这种小模型先跑通流程。

5.5 推理时忘记 model.eval() 和 torch.no_grad()

现象:单条推理结果每次都不一样,或者显存越用越多。原因:模型还在训练模式,Dropout 和 BatchNorm 没切到推理状态,且梯度一直在累积。解决:推理前固定写model.eval()和with torch.no_grad():,这两行是后悔药,少一行都出玄学结果。

6. 进阶技巧:用 offset_mapping 把抽取结果还原成可读文本

属性抽取跑通后,你拿到的是一串 token 级标签,但业务方要的是原文里的属性词。这一步如果处理不好,前面训练再准也白搭。我一般会强制走一遍offset_mapping还原流程,下面是一个可直接抄的片段:

def decode_spans(text, offsets, pred_ids, id2label): spans = [] current = None for idx, (start, end) in enumerate(offsets): if start == end: # 特殊 token,跳过 continue label = id2label[pred_ids[idx]] if label.startswith("B-"): if current: spans.append(current) current = {"type": label[2:], "start": start, "end": end} elif label.startswith("I-") and current: current["end"] = end else: if current: spans.append(current) current = None if current: spans.append(current) return [(s["type"], text[s["start"]:s["end"]]) for s in spans]

逻辑说明:offsets是 tokenizer 返回的字符级偏移,每个 token 对应原文的[start, end)区间。遇到B-开标签就新建 span,遇到I-就延长当前 span 的结束位置,遇到O就收尾。这样还原出来的属性词和观点词直接是原文子串,不会出现多字少字。

参数上要注意:return_offsets_mapping=True必须在 tokenizer 调用时显式打开,否则拿不到 offsets。另外中文 tokenizer 对空格和标点的处理跟英文不同,建议在拼接输入时保留原始标点,不要提前做去除停用词,否则偏移会整体错位。

验证方法很简单:拿 20 条测试集样本,把还原出的 span 和人工标注对比,如果字符级完全匹配率低于 90%,就回去检查 tokenizer 的do_lower_case和strip_accents设置。我自己的习惯是每次换模型或换数据集,都先跑一遍这个小验证脚本,确认偏移对齐了再开始训练。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询