
简介本资源面向NLP初学者与进阶开发者提供基于百度预训练大模型ERNIE完成情感分析的完整Python实现方案覆盖句子级与属性级两类情感分析任务可用于产品评论挖掘、舆情监控等场景。压缩包共24个文件约25.54MB以json数据配置、py源码脚本、pdf说明文档为主另含少量pyc缓存与license等辅助文件目录按任务模块划分清晰。已有220人学习下载。读者可获取数据预处理、ERNIE模型加载、特征提取、分类器构建、训练调优到预测应用的完整代码链路并配套训练、验证、测试数据集便于直接复现句子级与属性级情感分类实验。项目还包含属性与观点抽取等扩展脚本可作为预训练模型落地NLP任务的参考模板帮助理解如何借助transformers库调用ERNIE权重并完成端到端情感分析流程。1. 从一句吐槽到属性级拆解ERNIE 情感分析到底在做什么电商评论里有一句「屏幕很惊艳但续航拉胯到想砸手机」。句子级情感分析模型大概率给你一个「中性」或者「略负面」可运营真正想知道的是屏幕是正向、续航是负向而且负向强度远大于正向。这就是句子级和属性级情感分析的分水岭。Python 基于预训练大模型 ERNIE 完成的情感分析任务核心价值就在于用一套代码同时覆盖这两个粒度句子级判断整句极性属性级把评价对象和评价词配对后逐项打分。它适合谁适合手里有中文评论、客服对话、问卷开放题想快速跑出可解释结果又不想从零训 BERT 的工程师。数据集通常包含标注好的句子极性样本和属性-极性三元组样本源代码则把数据读取、ERNIE 微调、预测、评估串成一条流水线。下面我按「先立住原理、再动手复现、最后讲坑」的顺序把这条流水线拆开讲清楚。2. ERNIE 做中文情感分析的原理与选型理由2.1 为什么中文情感分析优先考虑 ERNIE 而不是 BERTBERT 原版是字符级掩码中文里「苹」和「果」单独被遮住模型学到的更多是字与字的共现而不是词与短语的语义。ERNIE 在预训练阶段引入了知识增强的掩码策略会遮住「苹果」「续航」「拉胯」这类完整语义单元让模型在编码阶段就具备词组级别的语义感知。情感分析恰恰极度依赖短语极性比如「不贵」和「不便宜」字面接近但极性相反ERNIE 对这类搭配的表示更稳。从工程角度看ERNIE 系列在中文任务上的开箱效果通常比同规模 BERT 好一截微调所需标注量更少。常见做法是选ernie-3.0-base-zh或ernie-3.0-medium-zhbase 精度更高medium 推理更快显存吃紧时优先 medium。选型时不要只看榜单要看你的文本长度分布——评论短句多medium 足够客服长对话多base 更稳。2.2 句子级与属性级任务的形式化差异句子级情感分析是单标签分类输入一句文本输出 positive / negative / neutral。建模时取 ERNIE 的[CLS]向量接一个全连接层交叉熵损失。属性级情感分析ABSA要复杂一层常见拆成两个子任务属性词抽取和属性情感分类。工程上更常用的是「属性-极性」配对即给定句子和已知属性词判断该属性对应的极性。形式化为输入[CLS] 句子 [SEP] 属性词 [SEP]让模型在编码时把属性词作为条件输出该属性的极性。这样一套编码器可以同时服务两个任务只是输入拼接方式不同。提示如果你的数据里属性词没有标注需要先做属性抽取这属于另一个任务不要和情感分类混在一个损失里硬训否则两个任务互相拖累。2.3 微调策略冻结层数、学习率与批次怎么定ERNIE 微调不是所有参数一起猛冲。血泪经验是底层编码器学习率要小分类头学习率要大。常见做法是分层学习率底层用2e-5分类头用1e-4。批次方面句子级任务 batch size 设 32 或 64属性级因为输入更长batch size 降到 16配合梯度累积补回等效批次。冻结层数上数据量小于 5000 条时建议冻结底部 6 层只微调顶部 6 层加分类头能明显抑制过拟合。数据量上万再全量微调。训练轮数一般 3 到 5 轮超过 5 轮验证集 F1 往往开始掉这就是典型的过拟合信号。3. 把源代码跑起来环境、数据与训练脚本3.1 环境准备与依赖安装先确认 Python 版本ERNIE 的 PaddleNLP 路线对版本敏感建议 3.8 到 3.10。安装命令如下# 创建独立环境避免和系统包冲突 conda create -n ernie_sa python3.10 -y conda activate ernie_sa # 安装深度学习框架与 NLP 工具库 pip install paddlepaddle-gpu2.6.0 -i https://mirror.baidu.com/pypi/simple pip install paddlenlp2.7.0 pip install scikit-learn pandas numpy逻辑说明paddlepaddle-gpu是训练底座paddlenlp提供 ERNIE 模型和分词器。参数上2.6.0和2.7.0是经过验证能互相兼容的组合版本错配最常见的报错是AttributeError: module paddlenlp.transformers has no attribute ...。如果你只有 CPU把第一行换成paddlepaddle2.6.0但训练时间会拉长数倍属性级任务不建议纯 CPU 跑。3.2 数据集结构与读取代码数据集一般分两个文件句子级样本和属性级样本。句子级是text,label两列属性级是text,aspect,label三列。读取时统一成列表方便后续切分。import pandas as pd from sklearn.model_selection import train_test_split def load_sentence_data(path): # 句子级text 为评论文本label 为 0/1/2 对应负/中/正 df pd.read_csv(path, encodingutf-8) texts df[text].astype(str).tolist() labels df[label].astype(int).tolist() return train_test_split(texts, labels, test_size0.2, random_state42) def load_aspect_data(path): # 属性级text 为原句aspect 为属性词label 为该属性极性 df pd.read_csv(path, encodingutf-8) pairs list(zip(df[text].astype(str), df[aspect].astype(str))) labels df[label].astype(int).tolist() return train_test_split(pairs, labels, test_size0.2, random_state42)逻辑说明random_state42保证每次切分一致方便复现。参数上test_size0.2是常规比例数据量小于 2000 条时建议改成 0.3让验证集更有统计意义。注意属性级返回的是(text, aspect)元组列表后面拼接输入时要用到。3.3 句子级训练脚本与关键参数import paddle from paddlenlp.transformers import ErnieForSequenceClassification, ErnieTokenizer from paddle.io import DataLoader, Dataset MODEL_NAME ernie-3.0-medium-zh tokenizer ErnieTokenizer.from_pretrained(MODEL_NAME) model ErnieForSequenceClassification.from_pretrained(MODEL_NAME, num_classes3) class SentenceDataset(Dataset): def __init__(self, texts, labels, max_len128): self.texts, self.labels, self.max_len texts, labels, max_len def __getitem__(self, idx): enc tokenizer(self.texts[idx], max_seq_lenself.max_len, truncationTrue, paddingmax_length) return enc[input_ids], enc[token_type_ids], self.labels[idx] def __len__(self): return len(self.texts) train_texts, val_texts, train_labels, val_labels load_sentence_data(sentence_data.csv) train_ds SentenceDataset(train_texts, train_labels) val_ds SentenceDataset(val_texts, val_labels) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) val_loader DataLoader(val_ds, batch_size64) optimizer paddle.optimizer.AdamW( learning_rate2e-5, parametersmodel.parameters(), weight_decay0.01) loss_fn paddle.nn.CrossEntropyLoss() for epoch in range(4): model.train() for input_ids, token_type_ids, labels in train_loader: logits model(input_ids, token_type_idstoken_type_ids) loss loss_fn(logits, labels) loss.backward() optimizer.step() optimizer.clear_grad() print(fepoch {epoch} done)逻辑说明max_seq_len128覆盖绝大多数评论超过会被截断。batch_size32是句子级任务的稳妥值显存 8G 以上都能跑。learning_rate2e-5是 ERNIE 微调的经典起点太大容易震荡太小收敛慢。weight_decay0.01抑制过拟合。训练轮数 4 轮配合验证集早停更稳。3.4 属性级训练脚本输入拼接与损失计算class AspectDataset(Dataset): def __init__(self, pairs, labels, max_len160): self.pairs, self.labels, self.max_len pairs, labels, max_len def __getitem__(self, idx): text, aspect self.pairs[idx] # 拼接成 [CLS] 句子 [SEP] 属性词 [SEP] enc tokenizer(text, text_pairaspect, max_seq_lenself.max_len, truncationTrue, paddingmax_length) return enc[input_ids], enc[token_type_ids], self.labels[idx] def __len__(self): return len(self.pairs) train_pairs, val_pairs, train_labels, val_labels load_aspect_data(aspect_data.csv) train_ds AspectDataset(train_pairs, train_labels) train_loader DataLoader(train_ds, batch_size16, shuffleTrue) for epoch in range(4): model.train() for input_ids, token_type_ids, labels in train_loader: logits model(input_ids, token_type_idstoken_type_ids) loss loss_fn(logits, labels) loss.backward() optimizer.step() optimizer.clear_grad()逻辑说明text_pairaspect是属性级任务的关键分词器会自动插入[SEP]分隔。max_len160比句子级长因为要容纳句子加属性词。batch_size16是因为输入变长、显存占用上升。属性级和句子级可以共用同一个ErnieForSequenceClassification只是数据集不同这也是这套源代码能一套模型跑两个任务的原因。4. 评估、调参与踩坑排查4.1 评估指标准确率不够要看宏平均 F1情感分析类别不平衡很常见中性样本往往偏少。只看准确率会被多数类带偏。评估时用宏平均 F1它把每个类别的 F1 等权平均少数类表现差会立刻暴露。from sklearn.metrics import f1_score, classification_report def evaluate(model, loader): model.eval() preds, golds [], [] for input_ids, token_type_ids, labels in loader: logits model(input_ids, token_type_idstoken_type_ids) preds.extend(paddle.argmax(logits, axis1).numpy().tolist()) golds.extend(labels.numpy().tolist()) print(classification_report(golds, preds, digits4)) return f1_score(golds, preds, averagemacro)逻辑说明averagemacro是关键词换成micro就退化成准确率。classification_report能直接看到每个类别的精确率和召回率定位是哪个极性拖后腿。如果中性类 F1 明显低说明模型分不清「中性」和「弱正向」这时候要么补中性样本要么把中性并入正向做二分类。4.2 三个必调参数学习率、最大长度、批次参数推荐范围调大后果调小后果学习率1e-5 ~ 5e-5损失震荡不收敛收敛慢欠拟合最大长度128 ~ 256显存涨速度降长句被截断丢信息批次大小16 ~ 64显存溢出梯度噪声大不稳学习率是首要调参对象先固定 2e-5 跑一轮看损失曲线。如果前 100 步损失上下跳降到 1e-5如果损失几乎不动升到 5e-5。最大长度看你的文本长度分布用分词后长度覆盖 95% 样本即可不必盲目拉到 512。批次大小受显存限制显存不够就用梯度累积累积 2 步等效批次翻倍。4.3 避坑与常见问题排查现象一训练损失正常下降验证集 F1 从第 2 轮开始掉。原因过拟合数据量小加上全量微调。解决冻结底部 6 层或加 dropout 到 0.3或把训练轮数压到 3 轮。现象二属性级预测时同一句话不同属性输出完全一样。原因属性词没有真正参与编码常见于拼接时把属性词放错位置或者分词器把text_pair忽略了。解决打印一条样本的input_ids解码结果确认[SEP]后确实跟着属性词。现象三推理时报out of memory但训练时正常。原因推理没开no_grad中间激活值一直累积。解决推理包在with paddle.no_grad():里并把 batch size 调小。现象四中文标点导致分词异常出现大量[UNK]。原因数据里混入全角空格或不可见字符。解决读数据后统一做text.replace(\u3000, ).strip()再送分词器。现象五句子级和属性级共用一个模型实例切换任务后效果骤降。原因两个任务的数据分布不同交替训练会互相干扰。解决各自训练一个模型实例或先训句子级再在属性级上继续微调不要在一个循环里混着喂。5. 进阶技巧用属性级结果反哺句子级以及一个验证习惯一个实用技巧是把属性级预测结果聚合回句子级。做法是对一句话跑出所有属性的极性按极性加权求和负向权重给 1.5正向给 1.0得到句子级极性。这样句子级结果有了可解释的来源运营能看到「为什么判负面」。代码上只需在推理后加一层聚合def aggregate_aspects(aspect_results): # aspect_results: [(aspect, polarity, prob), ...] score 0.0 for _, polarity, prob in aspect_results: weight 1.5 if polarity 0 else 1.0 # 0 为负向 score (1 if polarity 2 else -1) * prob * weight return 2 if score 0.3 else (0 if score -0.3 else 1)逻辑说明prob是模型置信度乘权重后累加。阈值 0.3 是经验值可以在验证集上网格搜索。这个聚合不是替代句子级模型而是给句子级结果加一层解释两者不一致时优先信属性级因为它的证据更细。验证习惯上我一般会留 50 条人工核对样本每次改完参数先跑这 50 条看错误模式有没有变化。别只看整体 F1要看错的是哪类句子。有一次我把学习率从 2e-5 调到 3e-5整体 F1 涨了 0.5 个点但人工核对发现「双重否定」全错这种涨点就是虚的。后来我把双重否定样本单独抽出来加权才真正稳住。这套流程跑下来句子级和属性级都能落地关键在别偷懒跳过人工核对。希望帮到你。本文还有配套的精品资源点击获取