尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

方面级情感分析课程作业全解析:基于BERT的ABSA实现与微调

发布时间:2026/9/28 23:27:16

资讯中心
01
ARTICLE

方面级情感分析课程作业全解析:基于BERT的ABSA实现与微调

方面级情感分析课程作业全解析:基于BERT的ABSA实现与微调
简介基于Python的方面级别情感分析课程作业压缩包面向自然语言处理课程学习者与课程设计人员聚焦评论或文本中特定方面词的情感极性识别任务可采用预训练语言模型完成训练与推理适合入门NLP与情感分析实践。包内共含7个文件以2个Python脚本为核心分别实现数据预处理和模型微调预测配套shell脚本方便一键运行另有依赖清单、说明文档、许可证及Git忽略文件等压缩包整体约14KB体积轻量、结构清晰。目前已有368人学习下载。读者可直接获取一套完整可跑的实验代码与项目目录组织涵盖从原始文本清洗、特征处理到模型微调、结果输出的完整链路文档与脚本注释可辅助理解关键步骤便于复现实验、完成课程设计或按需修改扩展对掌握方面级别情感分析的基本流程有切实帮助。1. 方面级别情感分析这份 NLP 课程作业解决的不只是打分问题方面级别情感分析Aspect-Level Sentiment Analysis和普通情感分析最大的区别在于它不关心整句话是正面还是负面而是关心句子里的每个具体对象各自是什么情感。比如“食物很棒但服务很慢”这句话整句情感是复杂的但方面级别任务要求模型对“食物”预测 positive对“服务”预测 negative。这个基于 Python 的 NLP 课程作业 zip 包就是一套完整的方面级别情感分析实现数据预处理、模型微调、预测脚本全部齐活适合正在做 NLP 课程设计、需要跑通一个 baseline 的学生也适合想快速上手 ABSA 任务的开发者。我拆完这份资源后发现它的代码量不大但工程链路完整从 process_data.py 到 fine_tune_predict.py 再到 run.sh每一步都是可以照着改的。2. 先看数据和预处理process_data.py 里的 BIO 标签与对齐逻辑2.1 解压后先分清主程序和实验材料把 zip 解压后目录结构里几个文件的分工要先理清楚别一上来就盯着 fine_tune_predict.py 看。这份资源的核心文件是 process_data.py数据预处理、fine_tune_predict.py模型微调与预测、run.sh一键运行脚本、requirements.txt依赖清单另外还有 README.md 和 LICENSE。README 里通常会写数据格式说明和运行步骤我建议第一步先把 README 读完作者在里头还留了详细博客地址如果哪段代码没看懂对着博客文章过一遍效率高很多。这个项目的任务类型是方面项抽取与情感分类联合解决数据格式一般是“句子 方面词 情感极性”三件套。process_data.py 做的就是把这个三件套转换成模型能吃的张量。我一般在拆这种课程作业时会先跑一遍 process_data.py把输出打印出来看看中间结果长什么样这比直接读源码快得多。2.2 process_data.py 的三步转换逻辑process_data.py 的核心逻辑可以拆成三步第一步加载原始数据第二步用 tokenizer 把句子转成 input_ids第三步构造与 input_ids 对齐的标签序列。第三步是最容易出问题的地方因为 BERT 的分词器会把一个词拆成多个 subword方面词的标签不能简单地对到原始词级别而是要展开到 token 级别。# 伪代码示意标签展开逻辑 def align_labels_with_tokenizer(text, aspect, label, tokenizer, max_len): # 对完整句子做 tokenize拿到 offset mapping 才能把字符级位置映射到 token 级 encoding tokenizer( text, truncationTrue, max_lengthmax_len, return_offsets_mappingTrue ) offsets encoding[offset_mapping] labels [O] * len(encoding[input_ids]) # 根据 aspect 在句子里的字符位置把对应 token 的标签设为 B / I start text.find(aspect) end start len(aspect) for idx, (s, e) in enumerate(offsets): if e start or s end: continue if labels[idx] ! O: labels[idx] I else: labels[idx] B return encoding[input_ids], encoding[attention_mask], labels这里的关键是 return_offsets_mappingTrue 这个参数。BERT tokenizer 默认只返回 input_ids 和 attention_mask要拿到每个 token 对应原文的哪个字符区间必须显式开启 offsets mapping。很多课程作业的预处理脚本在这块偷懒直接按空格切分做对齐遇到标点粘连或者大小写变化就翻车。另外 labels 初始化为全 O第一次命中 aspect 区间时置为 B之后再命中就置为 I这是标准的 BIO 标注方案。2.3 截断策略与标签对齐改参数前先搞懂一个约束处理长文本时truncation 策略和标签对齐是一对矛盾。如果句子被截断而 aspect 恰好落在被截掉的那一段标签就全丢了模型学了个寂寞。我一般在处理这类 ABSA 数据时会把截断策略从默认的右截断改成保留 aspect 所在区间或者干脆保证 aspect 完整的情况下再截断。# 更稳妥的截断方式动态调整截断窗口 def safe_truncate(text, aspect, tokenizer, max_len): aspect_start text.find(aspect) aspect_end aspect_start len(aspect) # 如果 aspect 在句子后半部分则截断左侧而不是右侧 if aspect_start max_len * 0.6: # 截断左侧时需要在 tokenize 时用 truncation_sideleft encoding tokenizer( text, truncationTrue, truncation_sideleft, max_lengthmax_len, return_offsets_mappingTrue ) else: encoding tokenizer( text, truncationTrue, max_lengthmax_len, return_offsets_mappingTrue ) return encodingtruncation_sideleft 这个参数是 transformers 库提供的默认是 right改成 left 后句子开头会被截掉保留结尾部分。实践中我一般根据方面词在句子中的相对位置动态决定截断方向。这个细节在跑小数据集时看不出差别但一旦语料里出现长评论模型效果会明显分化。2.4 预处理环节的输入输出格式速查跑完 process_data.py 后输出一般会落在三个文件里train_features、dev_features、test_features每个样本包含 input_ids、attention_mask、labels 三件套。如果这份资源里还用了 segment_ids那说明模型结构是双输入模式句子和方面词分别作为两个 segment 输入。具体看代码里的 DataProcessor 类怎么写的。字段形状说明input_ids[batch, max_len]tokenizer 输出的 token id 序列attention_mask[batch, max_len]1 表示有效 token0 表示 paddinglabels[batch, max_len]BIO 标签与 input_ids 一一对应aspect_mask[batch, max_len]标记哪些 token 属于方面词用于 attention 池化如果看到 aspect_mask 这个字段说明模型用的是“对方面词区域做池化”的方案而不是把所有 token 的 hidden state 直接送进分类器。这个细节决定了后面模型结构的设计。3. 模型与微调把 ALSA 拆成三个组件看 fine_tune_predict.py3.1 为什么用预训练语言模型做底座而不是纯 BiLSTM方面级别情感分析早期的主流方案是 BiLSTM Attention后来被 BERT 系模型全面替代的转折点在于ABSA 任务对上下文语义的把握要求极高“食物很棒但服务很慢”这种转折句BiLSTM 需要很强的记忆能力才能同时捕捉两个方面的情感而 BERT 凭借自注意力机制天然能建模这种长距离依赖。这份课程作业里使用预训练语言模型说明作者默认选择了更稳妥的技术路线。这里要分清一个问题这个任务不是用 BertForSequenceClassification 直接对整个句子做分类而是要把每个 token 的隐状态拿出来结合方面词的位置做聚合。如果用 BertForSequenceClassification模型会丢失“方面词在哪”这个关键信息相当于把 ABSA 降级成了句子级情感分类。3.2 ALSA 模型结构BERT 编码 注意力池化 Softmax 分类拆开 fine_tune_predict.py 里的模型定义一般会看到三段式结构BERT 作为 encoder 输出每个 token 的 hidden state然后一个 attention pooling 层把方面词对应的 token 隐状态聚合为一个向量最后接一个线性层做三分类positive / negative / neutral。import torch import torch.nn as nn from transformers import BertModel, BertTokenizer class ABSA_Model(nn.Module): def __init__(self, bert_path, num_labels3): super().__init__() self.bert BertModel.from_pretrained(bert_path) self.dropout nn.Dropout(0.3) # attention pooling给每个 token 学一个权重 self.attn nn.Linear(self.bert.config.hidden_size, 1) self.classifier nn.Linear(self.bert.config.hidden_size, num_labels) def forward(self, input_ids, attention_mask, aspect_mask): # 取 BERT 最后一层隐状态: [batch, seq_len, hidden] outputs self.bert( input_idsinput_ids, attention_maskattention_mask ) last_hidden outputs.last_hidden_state # 只用 aspect 区域的 token 做池化 aspect_hidden last_hidden * aspect_mask.unsqueeze(-1) # attention 权重归一化只算 aspect 区域内 attn_scores self.attn(aspect_hidden).squeeze(-1) attn_scores attn_scores.masked_fill( aspect_mask 0, float(-inf) ) attn_weights torch.softmax(attn_scores, dim-1) # 加权求和得到句子表征 pooled torch.sum(attn_weights.unsqueeze(-1) * aspect_hidden, dim1) logits self.classifier(self.dropout(pooled)) return logits这个结构的精妙之处在于 aspect_mask 既参与了表征计算又参与了 attention 权重的 masking。把 aspect 区域外的 token 的 attention 分数置为负无穷softmax 之后权重自动归零模型就只能从方面词相关的 token 里提取信息。这个设计比直接 mean pooling 效果好的原因在于方面词内部的 subword 重要性并不一样比如 “not good” 里 “good” 和 “not” 对情感判断的贡献权重应该不同attention 机制可以学出这个差异。3.3 训练参数设置学习率、batch size、epoch 的合理区间fine_tune_predict.py 里默认的训练参数一般是 BERT 微调的标准配置但课程作业的语料规模通常比较小直接套用标准参数很容易过拟合。我建议关注以下几个关键参数的联动调整。参数典型值调整方向learning_rate2e-5 ~ 5e-5小数据集用 2e-5防止 loss 震荡batch_size8 ~ 32显存够就 16不够降为 4 并用梯度累积num_epochs3 ~ 6数据量少于 2000 条时不超过 5warmup_ratio0.1前 10% 步数线性升温max_length64 ~ 128评论类语料 64 够用长文本调到 128# fine_tune_predict.py 中的训练循环关键段 from transformers import AdamW, get_linear_schedule_with_warmup optimizer AdamW(model.parameters(), lr2e-5) total_steps len(train_dataloader) * num_epochs scheduler get_linear_schedule_with_warmup( optimizer, num_warmup_stepsint(total_steps * 0.1), num_training_stepstotal_steps ) for epoch in range(num_epochs): for batch in train_dataloader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) aspect_mask batch[aspect_mask].to(device) labels batch[labels].to(device) logits model(input_ids, attention_mask, aspect_mask) loss nn.CrossEntropyLoss()(logits, labels) loss.backward() optimizer.step() scheduler.step() optimizer.zero_grad()这里比较容易被忽略的是 scheduler.step() 必须紧跟 optimizer.step() 调用。如果忘了 scheduler学习率始终保持初始值后面几个 epoch 会一直在损失函数附近震荡。另外在数据处理部分一个 batch 里的 labels 不再是在预处理时展开的 BIO token 级标签数组而是每个样本只保留一个情感极性标签——因为模型已经把方面词信息融入 embedding最后直接做三分类。这个转变要搞清楚BIO token 级标签是给抽取任务用的三分类标签是给情感分类用的两者在不同阶段起作用。3.4 预测与结果落盘fine_tune_predict.py 的推理路径推理路径里一个值得留意的细节是模型预测时要让 input_ids 和 aspect_mask 保持和训练时完全一致的处理方式不能训练时用了截断而预测时不截断。我习惯把预处理逻辑抽成一个函数训练和预测共用同一个入口。fine_tune_predict.py 里预测部分的输出通常是每条样本的预测标签和概率分布可以保存成 JSON 或 CSV方便后续做误例分析。如果脚本里写的是直接打印到控制台我建议改成写文件——后面要分析错误案例时你会回来感谢这个决定。4. 一键运行的工程细节run.sh 和 requirements.txt 的环境坑4.1 run.sh 脚本里到底做了什么run.sh 的核心价值在于把环境安装、预处理、训练、预测串成一条流水线。打开一看内容通常是为 Python 创建虚拟环境、pip 安装依赖、执行 process_data.py再执行 fine_tune_predict.py。这里有个常见的坑如果 run.sh 里写死了 python3 而不是用当前虚拟环境的解释器路径在 conda 环境里执行可能会调到系统自带的 Python导致 import torch 直接报 ModuleNotFoundError。#!/bin/bash # run.sh 的关键步骤 # 创建虚拟环境并激活 python3 -m venv venv source venv/bin/activate # 升级 pip 并安装依赖 pip install --upgrade pip pip install -r requirements.txt # 执行数据预处理 python process_data.py # 执行训练与预测 python fine_tune_predict.py我一般会提醒自己source venv/bin/activate 之后后续的 python 命令必须确认用的是 venv 里的解释器用 which python 验证一下最稳妥。如果你在 Windows 上跑这份资源bash 脚本无法直接执行需要用 Git Bash 或者 WSL或者在 Visual Studio Code 的终端里手动逐行执行这些命令。关于 vscode python 环境配置最简单的做法是 CtrlShiftP 打开命令面板选择 Python: Select Interpreter指定到 venv 目录下的解释器之后运行脚本时自动使用该环境。4.2 requirements.txt 的版本锁定思路requirements.txt 里通常包含 torch、transformers、pandas、numpy、scikit-learn 这些基础库。拆这份资源时我注意到版本号如果写的是 torch1.8 这种宽松范围可能会在最新环境中安装出不兼容的 torch 2.x而 transformers 的版本差异会直接影响模型加载 API 的兼容性。比如 BertModel.from_pretrained 在 transformers 4.x 里返回的 outputs.last_hidden_state 字段名没变但如果你用了太旧的 3.x 版本字段名可能出现变化。依赖库建议版本区间说明torch1.10 ~ 2.02.0 以上需注意 CUDA 版本匹配transformers4.21 ~ 4.344.30 以上 API 稳定无破坏性变更pandas1.3 ~ 2.02.0 对 CSV 读取行为微调scikit-learn0.24 ~ 1.2仅用于评估指标计算如果不想纠结版本可以直接 pip install -r requirements.txt 装完跑一段训练报什么错再针对性降级。不过更稳妥的做法是按上面表格的区间手动锁版本避免最新版库的破坏性更新影响课程作业验收。torch 的 CPU 版本在本地调代码时够用但训 BERT 十几个小时也未必出一个 epoch能用 GPU 就用 GPU。4.3 CPU 推理的最低保底策略很多课程作业场景没有独显可用但又要交实验结果。此时有两个办法一是调小 max_length 和 batch_size让模型在 CPU 上能跑完一整个 epoch二是直接跳过训练加载作者提供的预训练权重做预测演示。这份资源里没有附带权重文件所以重点说第一种。在 CPU 上跑时max_length 从 128 降到 64batch_size 设为 4num_epochs 设为 1虽然指标会略降但能跑通全流程。如果要交实验报告在表格里注明“受限于本机 CPU 环境训练 1 个 epoch”这是完全可以接受的。4.4 三个版本不一致的经典报错跑这份资源时最常遇到的三个环境报错分别是import torch 时 CUDA 无法初始化、transformers 库版本太新导致 AutoModel 相关类不存在、tokenizer 加载时缺少 sentencepiece 包。这三个问题在 requirements.txt 里其实都有体现但作者用的版本可能和你本机环境冲突。# 查看当前环境的 torch 和 CUDA 版本是否匹配 python -c import torch; print(torch.__version__, torch.cuda.is_available())如果 cuda.is_available() 返回 False而你确认自己装了 CUDA 版 torch大概率是驱动与 CUDA 版本不匹配重装对应版本的 PyTorch 即可。这个检查和 Python 安装环境是否混用是两件事但本质上都是环境一致性问题——虚拟环境能帮你隔离掉大部分麻烦。5. 避坑指南我在这份作业上翻过的五个车5.1 训练 loss 下降到 0.7 左右就卡住不动了现象是 loss 前几个 step 降得很快然后就停在一个平台期准确率也在 60% 上下徘徊怎么调学习率都没用。原因是情感三分类的类别分布不均neutral 类样本占比特别高模型倾向于全预测 neutral 来压低 loss。解决办法是给 CrossEntropyLoss 传入 class_weight或者在预处理时对样本做重采样。# 给损失函数加类别权重 from sklearn.utils.class_weight import compute_class_weight class_weights compute_class_weight( class_weightbalanced, classesnp.array([0, 1, 2]), ytrain_labels ) class_weights torch.tensor(class_weights, dtypetorch.float).to(device) loss_fn nn.CrossEntropyLoss(weightclass_weights)注意 compute_class_weight 的 y 要是原始标签数组不能是 one-hot 编码。加了类别权重后 loss 的绝对值会比之前大不要慌这是正常的看准确率不要看 loss 绝对值。5.2 预测结果全是一个类别现象是训练指标看着正常但 fine_tune_predict.py 跑完测试集输出结果 90% 都是 positive。原因通常是模型学到的其实是句子级信号而不是方面级信号——比如语料里 positive 的句子恰好都长得很像模型找到了更简单的捷径特征。这种问题在课程作业的小数据集上特别容易复现。解决方法是切断捷径检查训练集里是否出现同一个模板句反复出现或者方面词和情感极性高度相关的语料。另一个常用做法是把模型最后一层改成对 aspect 区域做 attention 池化后加一个对抗扰动比如对词向量做 adversarial training能有效防止模型偷懒。5.3 GPU 显存 OOM 报错现象是第一个 batch 就跑爆显存报 CUDA out of memory。原因是 batch_size 设了 32而 BERT-base 在这个任务上每个样本的有效序列长度比想象中长。解决方法是把 batch_size 降到 4 或 8利用梯度累积模拟大步长。如果还爆炸检查 max_length 是否过长有些长评论样本会把整个 batch 的 padding 长度拖长出现一个 batch 比其他 batch 多占用好几倍显存。可以在 DataLoader 里按序列长度排序同一个 batch 内样本长度接近能显著减少 padding 浪费。5.4 run.sh 在 Windows 上直接报错现象是双击 run.sh 没有反应或者用命令行执行报 bad interpreter。原因是 .sh 脚本是 Linux 格式Windows 的 cmd 不能直接执行需要 Git Bash 或 WSL。如果用的是 vscode python 环境配置最简单的方式是在 vscode 的终端里选 Git Bash 作为默认 shell然后执行 bash run.sh。要注意换行符的问题Windows 下 Git 会把 LF 自动转成 CRLF导致 bash 脚本执行时报错在 .gitignore 里排除或者用 git config core.autocrlf false 关掉自动转换。5.5 模型加载权重时网络超时现象是运行 fine_tune_predict.py 时卡在 from_pretrained 这一步十几分钟不动然后报 ConnectionError。原因是 BERT 预训练权重需要从远程仓库拉取网络状况不好的情况下很容易超时。解决方法是离线下载在能访问外网机器上把 BertModel.from_pretrained(bert-base-uncased) 的缓存目录整个拷到本地然后换个本地路径引用。或者直接在代码里改成从本地目录加载# 离线加载 BERT 权重 model BertModel.from_pretrained(./bert-base-uncased)前提是你已经把权重文件解压到项目目录下了。还有一种做法是用 ALBERT 的 tiny 版本替换 BERT-base模型体积缩小到十分之一加载速度快很多缺点是准确率略有下降但做课程作业完全够用。6. 进阶验证不只是看 acc——用误例分析反推模型短板跑通训练和预测只是第一步真正能拉开档次的是验证环节。课程作业里如果只用 accuracy 一个指标老师看到的只是“你调通了模型”但如果你能说出“模型在哪些样本上错了、为什么错、怎么改”这就能体现出你对任务的理解深度。我一般会把预测结果与真实标签逐条对比按四个方向归类误例。第一类是转折句误判。比如“食物很好但等位太久”模型对“等位时间”这个 aspect 预测成了中性甚至正面原因可能是语料里“等”相关的表达不够多模型没学会把“久”和负面联系起来。改进方式是给这类样本做数据增强把转折句模板手动构造出来加进训练集。第二类是指代消解错误比如“这家店的披萨不错它的饼底很薄”模型对“饼底”这个 aspect 预测出错因为它不知道“它”指代披萨。这一类问题在小数据集上基本无解但可以在报告里指出来说明你理解了模型边界。第三类是显式否定漏检“服务不算差”里的“不算差”和“差”之间的否定关系没有学到这需要模型对否定词的敏感度。第四类是方面词本身的情感歧义“价格”在有的语境里是正面价格实惠在另一个语境是负面价格偏高模型依赖上下文才能判断。还有一个具体的技巧把验证集的预测结果按 aspect 做分组统计比如把预测错误的样本按“餐厅”“服务”“价格”“环境”归类看哪一类错误率最高。如果是跟“服务”相关的错误率显著高于其他类别那说明训练语料里服务类的样本不足或者服务类的情感表达方式更多样。把这个分析结果写进实验报告比任何 fancy 的模型结构说明都有说服力。如果想要进一步优化可以把预训练模型从小型号换到 RoBERTa但对应的训练时间也会增长数倍在课程作业的时间预算内未必划算建议先用误例分析确认瓶颈。从那以后每跑完一份 NLP 实验我都会强制走一遍“预测结果落盘 误例归类 按方面分组统计”这个流程哪怕只是十分钟的小实验也一样做每次都能发现模型的下一个翻车点。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。