
简介一份面向Python毕业设计/课程设计的旅游景点方面级情感分析项目源码与语料库可用于理解从数据标注到模型评估的完整流程。项目基于Django框架实现后端覆盖数据库设计、NLP情感分析模型构建、前端界面展示等环节适合计算机相关专业学生作为毕业设计参考或实训课题。压缩包约70.56MB已吸引101人学习包含项目源码、说明文档及部署说明等核心内容。系统中集成BERT、LSTM或Transformer等深度模型或传统机器学习方法并附带经过标注的景点评论语料库便于直接训练和验证数据库层可利用Django ORM操作SQLite/MySQL完成评论与景点信息的存储管理。文档部分对系统架构、功能模块、操作流程与技术栈进行了详细梳理部署说明则涵盖Python/Django环境配置、gunicorn与Nginx反向代理等环节可帮助快速搭建可运行的演示系统。项目还涉及文本预处理、特征工程与模型调优等细节配合说明文档可逐步复现实验环境。1. 旅游景点方面级情感分析毕设选题为什么选它值得吗如果你的毕业设计题目是《旅游景点方面级情感分析》又恰好从学长手里接到一个带语料库和模型源码的 zip 压缩包你首先要弄明白它和普通的好评差评分类不是一回事。普通情感分析只回答“这条评论是好评还是差评”而方面级Aspect-Based情感分析要回答的是“大家觉得这个景点的交通方便吗、门票贵不贵、餐饮对不对胃口、风景值不值得专程跑一趟”。同样是“景区很美但门票太贵”这一句话景观方面是正面、门票方面是负面两个结果必须同时输出。这正是基于 Python 的毕业设计里较能体现 NLP 含金量的方向之一既有语料库这个“劳动量证明”又有模型源码这个“技术证明”还方便答辩时拿具体句子现场演示不用画一堆空饼。下面我把这个题目的语料库构建、模型选型、训练跑通和踩坑过程完整拆开讲。2. 从0搭建景点评论文本语料库数据来源、清洗与方面级标注2.1 语料库结构设计句子-方面-情感标签怎么存方面级情感分析的语料库最忌讳的就是把数据存成“一条评论一行、一个好评差评标签”那种老格式。因为一条景点评论里经常同时聊好几个对象比如“地铁能到门口但停车太难而且门票还涨价了”这一条里交通是正面的、停车算交通里的负面、门票是负面。如果只给一个整体标签模型永远学不会细粒度判断。我一般会把语料库设计成 CSV 或 JSONL核心字段固定为四列review_id、review_text、aspect_category、sentiment_label。review_text是原始评论句子aspect_category是预定义好的方面类别sentiment_label是情感极性用数字存0 负面、1 中性、2 正面。这样一条原始评论会按“方面-情感对”展开成多行例如review_idreview_textaspect_categorysentiment_label1001景色很美但门票偏贵景观21001景色很美但门票偏贵门票01002地铁直达景区很方便交通2实践里方面类别不要设太多56 个足够交通、门票、餐饮、景观、服务。有人硬要加“安全”“卫生”标注成本立刻翻倍而多数模型在这几个稀疏类别上很难学好所以建议新手先把这 5 类做扎实。情感极性也尽量别做五档很满意、满意、一般、不满意、很不满意标注一致性会崩毕设里的答辩老师更看重你能不能自圆其说三档最稳。2.2 公开评论抓取与清洗把杂乱的 HTML 变成干净的 DataFrame数据来源无非两条路公开数据集和自采数据。用现成中文情感分析数据集再改成方面级标注能省大量时间但搜索引擎上能找到的旅游领域 ABSA 公开语料少多数毕设还是靠自己写爬虫抓公开评论。需要注意抓取必须遵守目标网站的 robots 协议和访问频率只用于学习研究别用多线程并发压对方服务器。一个够用的抓取与清洗脚本核心其实就两个函数import re import time import pandas as pd from bs4 import BeautifulSoup def fetch_reviews(base_url, max_pages20): 抓取公开评论列表页返回评论文本 DataFrame。 rows [] for page in range(1, max_pages 1): url f{base_url}?page{page} resp requests.get(url, headers{User-Agent: Mozilla/5.0}, timeout10) soup BeautifulSoup(resp.text, html.parser) # 每条评论通常是一个带 review-item 类名的节点具体选择器按目标站调整 for item in soup.select(.review-item): text item.get_text(separator , stripTrue) if len(text) 8: rows.append({review_text: text, page: page}) time.sleep(1) # 单线程 延时避免给目标站造成压力 return pd.DataFrame(rows) def clean_review(text): 去掉 URL、多余空白和纯英文数字段保留中文主体。 text re.sub(rhttps?://\S, , text) text re.sub(r\s, , text).strip() # 过滤掉没有中文的文本长度小于5的没信息量 if len(text) 5 or not re.search(r[\u4e00-\u9fa5], text): return return textfetch_reviews里的参数max_pages控制抓取深度20 页一般能拿到几百到上千条原始数据具体数量看网站每页条数。time.sleep(1)是成本最低的保护措施把请求频率压到每秒一次否则容易触发反爬ip 被临时封了还得等。clean_review里那个len(text) 5的过滤门槛很关键景点评论里大量存在“不错”“还行”这种短文本保留它们会让后续方面标注变得非常离谱因为它们往往不包含任何方面词。清洗之后还需要去重和抽样质检df raw_df.drop_duplicates(subsetreview_text) df df[df[review_text].map(clean_review) ! ] print(f清洗后有效评论 {len(df)} 条)去重按评论文本整体来因为很多用户会直接复制别人的好评重复文本会让模型在验证集上虚高。清洗完成后下一步是把这些文本变成带标签的语料也就是标注。2.3 方面级标注规则预标注加上人工修正别硬纯手标纯人工标注 8000 条、每条还拆多个方面两个人标两周很正常。更高效的办法是先写一套关键词规则做预标注把它当成初稿再由人工在初稿上修正。这样既省时间又能让标注标准统一。ASPECT_RULES { 交通: [交通, 地铁, 公交, 停车, 打车, 堵, 方便, 直达], 门票: [门票, 票价, 收费, 贵, 性价比, 划算, 涨价, 值], 餐饮: [餐厅, 小吃, 吃饭, 饭菜, 美食, 贵, 好吃, 难吃], 景观: [风景, 景色, 景观, 好看, 美, 拍照, 值得, 一般], 服务: [服务, 导游, 态度, 工作人员, 热情, 差], } def rule_label(text): 返回这条评论命中的方面类别列表未命中的返回空列表。 matched [aspect for aspect, words in ASPECT_RULES.items() if any(word in text for word in words)] return matched规则的核心是召回率优先宁可让一个句子命中多个方面也不要漏掉真实方面。注意“贵”“值”“方便”这些词会同时出现在多个方面规则里这没问题因为预标注之后还要人工决定“贵”到底指门票还是餐饮。实际操作时可以让两个人独立修正同一批预标注数据再用一致性指标核对。from sklearn.metrics import cohen_kappa_score # ano_a 和 ano_b 是两位标注者对相同200条样本打的标签序列 kappa cohen_kappa_score(anno_a, anno_b) print(f标注一致性 Kappa {kappa:.3f})Kappa 大于 0.6 说明标注标准基本可用低于 0.4 就要重新统一标准这时候通常是“方面词”和“情感词”没分清楚。比如“景区太商业化了”该标景观还是服务这没有标准答案关键是在标注规范里明确讨论观感体验归景观讨论工作人员行为和设施服务归服务。把这条写进标注文档一致性立刻会提升。预标注和人工修正完成之后把数据切成 train / dev / test 三份比例 8:1:1 即可。切分时要用groupby保证同一条评论的所有方面记录进同一个集合不能按行随机切否则同一条评论的方面会同时出现在训练和验证集里模型等于提前见过原句效果虚高。这一步是很多人漏掉的坑。3. 构建方面级情感分析模型Python实现与关键参数3.1 模型选型为什么选择 BERT 微调而不是 LSTM方面级情感分析的经典路线有两条。传统做法是分词、去停用词、训练 Word2Vec再喂给 LSTM 或 TextCNN复杂度不高但对语料规模和标注质量要求极高一个“门票虽然贵但是景色值”这种反转句式就能让 LSTM 的准确率掉一截。另一条路是直接对 BERT 微调用预训练模型把句子编码成语义向量再用全连接层做三分类效果几乎“无脑”超过 LSTM参数调法也成熟稳定。毕设选题的核心诉求是“能跑通、能答辩、能解释”BERT 微调是当下最稳妥的选择。原因有三第一中文就用bert-base-chinese权重12 层 Transformer对短文本评论的语义理解远好于词向量平均第二不需要自己设计复杂的特征工程方面信息和句子一起喂进去即可第三答辩时能说清楚“利用预训练语言模型捕捉上下文语义”这一句话就比讲三天词向量有说服力。硬件上单张 8GB 显存的显卡就够用没有显卡也能用 CPU 跑通一个小 epoch只是训练时间会久一点。3.2 把句子和方面拼成模型输入tokenizer 的细节方面级情感分析在 BERT 下的常规做法是构造“句子对”也就是把评论句子和方面类别拼成一对输入中间用[SEP]分隔。对“景色很美但门票偏贵”这个句子如果预测“景观”方面输入就是[CLS] 景色很美但门票偏贵 [SEP] 景观 [SEP]标签是正面如果预测“门票”方面输入换成[CLS] 景色很美但门票偏贵 [SEP] 门票 [SEP]标签是负面。from transformers import BertTokenizer tokenizer BertTokenizer.from_pretrained(bert-base-chinese) def encode_example(review, aspect, max_len128): 将评论和方面编码成 BERT 输入带截断和填充。 enc tokenizer( textreview, # 第一个句子评论文本 text_pairaspect, # 第二个句子方面类别 max_lengthmax_len, paddingmax_length, # 不足128补 [PAD] truncationTrue, # 超过128截断 return_tensorspt # 返回 PyTorch 张量 ) return enc这里最关键的是text_pairaspect这个参数。把方面类别当成一个“句子”拼在评论后面模型才能在学习注意力权重时把评论里的“贵”和“门票”关联起来而不是简单把“贵”当成整体情感。max_len128对景点短评论通常足够抓到的长评会被截断但截断只砍尾部而评论的情感倾向经常在开头就表达清楚所以损失可控。如果评论平均长度特别短可以缩到 64训练速度明显加快我建议第一轮先用 64 试跑看验证集 F1 再决定要不要加长。3.3 自定义 Dataset 与 DataLoader把 CSV 批量化成张量编码函数准备好后要写一个 Dataset 类把 DataFrame 包装起来否则手动循环做 batch 既慢又容易出错。import torch from torch.utils.data import Dataset, DataLoader class ABSADataset(Dataset): def __init__(self, df, tokenizer, max_len128): self.df df.reset_index(dropTrue) self.tokenizer tokenizer self.max_len max_len def __len__(self): return len(self.df) def __getitem__(self, idx): row self.df.loc[idx] enc self.tokenizer( row[review_text], row[aspect_category], max_lengthself.max_len, paddingmax_length, truncationTrue, return_tensorspt ) return { input_ids: enc[input_ids].squeeze(0), attention_mask: enc[attention_mask].squeeze(0), labels: torch.tensor(row[sentiment_label], dtypetorch.long) }__getitem__里每个样本返回三个张量input_ids是 token 索引attention_mask让模型忽略[PAD]位置labels是三分类标签。return_tensorspt时返回的是[1, seq_len]形状的张量所以要squeeze(0)去掉 batch 维否则 DataLoader 会自动拼成[batch, 1, seq_len]模型直接报维度错误。dataset ABSADataset(train_df, tokenizer, max_len128) loader DataLoader(dataset, batch_size16, shuffleTrue, num_workers2)num_workers2在 Windows 下可能会出问题如果报多进程相关错误就把它改成 0用主进程加载数据速度慢点但更省心。shuffleTrue只在训练集上开启验证集和测试集必须设成False否则每次评估结果随机波动没法判断模型是不是真的在收敛。3.4 训练循环与超参数学习率、类别权重和早停模型结构直接用BertForSequenceClassification它内部已经包含了 BERT 编码器和分类头不需要自己写 Transformer 层。from transformers import BertForSequenceClassification, AdamW model BertForSequenceClassification.from_pretrained( bert-base-chinese, num_labels3 ) device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) # 类别权重负面0.8中性0.5正面1.0 # 点评数据里正面评论天然偏多给正面1.0、中性0.5能压低多数类 class_weights torch.tensor([0.8, 0.5, 1.0], devicedevice) loss_fn torch.nn.CrossEntropyLoss(weightclass_weights) optimizer AdamW(model.parameters(), lr2e-5) train_loader DataLoader(train_dataset, batch_size16, shuffleTrue) dev_loader DataLoader(dev_dataset, batch_size32, shuffleFalse) for epoch in range(5): model.train() total_loss 0.0 for batch in train_loader: batch {k: v.to(device) for k, v in batch.items()} outputs model(**batch) logits outputs.logits loss loss_fn(logits, batch[labels]) loss.backward() optimizer.step() optimizer.zero_grad() total_loss loss.item() avg_loss total_loss / len(train_loader) print(fepoch {epoch 1}, train loss {avg_loss:.4f}) # 每个 epoch 后在开发集上计算准确率用于早停和保存最优权重 model.eval() correct 0 total 0 with torch.no_grad(): for batch in dev_loader: batch {k: v.to(device) for k, v in batch.items()} logits model(**batch).logits preds torch.argmax(logits, dim-1) correct (preds batch[labels]).sum().item() total batch[labels].size(0) dev_acc correct / total print(fepoch {epoch 1}, dev acc {dev_acc:.4f})lr2e-5是 BERT 微调的经验起点太大容易把预训练权重冲坏太小收敛慢。类别权重[0.8, 0.5, 1.0]不是固定值要根据自己语料的类别分布算比例中性标签如果只占 10%权重可以调到 2.0把它的 loss 放大否则模型基本不会预测中性。训练时千万别只在 train loss 下降就以为收敛了每个 epoch 结束必须算一遍 dev acc连续两个 epoch 不上升就停掉保存 dev acc 最高的那一次权重这就是“早停”。3.5 评估指标别只看准确率要同时看 F1 和混淆矩阵景点评论方面级分类里准确率很具有欺骗性。如果语料里 60% 是正面模型全预测正面也能有 60% 准确率答辩时老师一问“负面类别预测成什么样”就露馅。from sklearn.metrics import classification_report, confusion_matrix def evaluate(model, loader, device): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for batch in loader: batch {k: v.to(device) for k, v in batch.items()} logits model(**batch).logits preds torch.argmax(logits, dim-1).cpu().tolist() all_preds.extend(preds) all_labels.extend(batch[labels].cpu().tolist()) report classification_report( all_labels, all_preds, target_names[负面, 中性, 正面], digits4 ) cm confusion_matrix(all_labels, all_preds) return report, cm report, cm evaluate(model, dev_loader, device) print(report) print(混淆矩阵) print(cm)classification_report会按负面、中性、正面分别给出 precision、recall、F1我们要重点关注“中性”类别的 F1。根据我的经验中性总是最难学的因为“还行”“一般”这种表达本身偏向模糊标注一致性也差。confusion_matrix的每一行是真实标签、每一列是预测标签如果发现负面大量被预测成正面就要回头检查是不是标注规则把“差”和“贵”的极性搞反了。4. 代码架构与跑通流程从 zip 到第一个可视化结果4.1 拿到压缩包后的源码结构应该有哪些目录和文件一个完整的方面级情感分析毕设项目压缩包解压后目录结构通常长这样project/ ├── data/ │ ├── raw/ # 原始抓取或收集的评论 │ ├── annotated/ # 标注完整的 CSV / JSONL │ └── split/ # 切分好的 train/dev/test ├── models/ │ ├── __init__.py │ ├── absa_model.py # BERT 模型封装 │ ├── train.py # 训练入口 │ └── predict.py # 预测入口 ├── utils/ │ ├── preprocess.py # 清洗、去重、预标注 │ ├── dataset.py # Dataset 与 DataLoader │ └── metrics.py # 评估指标 ├── checkpoints/ # 训练保存的最优权重 ├── requirements.txt └── README.md这个结构其实不是代码能跑起来的核心真正核心的是data/split/下的数据文件和models/train.py的训练逻辑。收到学长传的 zip 后第一时间不是去装环境而是先解压看requirements.txt和README.md确认 Python 版本要求。很多项目是在 Python 3.8 下开发的拿到手里用 3.11 跑torch版本冲突能折腾你一下午。4.2 跑通最小流程环境安装到训练启动我习惯先用 conda 建一个独立环境避免把开发机的 Python 环境搞得乱七八糟。下面的命令在 Windows 和 Linux 下通用只是 CUDA 相关配置略有不同。conda create -n absa python3.8 conda activate absa pip install -r requirements.txtrequirements.txt里通常包含这几项缺什么补什么torch1.13.0 transformers4.28.0 pandas numpy scikit-learn matplotlib tqdm装完依赖先用一个最小命令试跑训练确认代码链路没断再跑完整训练。python models/train.py \ --train_data data/split/train.csv \ --dev_data data/split/dev.csv \ --epochs 3 \ --batch_size 8 \ --max_len 64 \ --output_dir checkpoints/test_runepochs 3和batch_size 8是用来验证链路的最低配一套跑通大概几分钟。max_len 64能显著降低显存占用如果显卡只有 4GB这个参数是救命稻草。训练结束后checkpoints/test_run目录下会出现pytorch_model.bin和config.json这就是以后用来做推理和演示的模型权重。4.3 predict.py 推理脚本命令行输入一个句子就出结果训练流程跑通后最让答辩老师眼前一亮的是现场输入一句没见过的评论立刻输出每个方面的情感结果。predict.py的逻辑比train.py简单很多。import argparse import torch from transformers import BertTokenizer, BertForSequenceClassification MODEL_DIR checkpoints/test_run ASPECTS [交通, 门票, 餐饮, 景观, 服务] def predict(review, aspect): model BertForSequenceClassification.from_pretrained(MODEL_DIR) tokenizer BertTokenizer.from_pretrained(bert-base-chinese) enc tokenizer(review, aspect, return_tensorspt) model.eval() with torch.no_grad(): logits model(**enc).logits label_id torch.argmax(logits, dim-1).item() return [负面, 中性, 正面][label_id] if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--review, typestr, requiredTrue) parser.add_argument(--aspect, typestr, choicesASPECTS, requiredTrue) args parser.parse_args() result predict(args.review, args.aspect) print(f评论{args.review}) print(f方面{args.aspect}) print(f情感{result})python models/predict.py --review 地铁直达景区就是节假日停车太费劲 --aspect 交通 # 输出评论地铁直达景区就是节假日停车太费劲 # 方面交通 # 情感正面这个脚本每次运行都会重新加载模型如果做批量测试会很慢但胜在结构清晰答辩演示时不容易出意外。要批量预测时可以把模型加载提到全局只做一次再把预测封装成函数循环读取 DataFrame 逐行调用即可。另外提醒一句from_pretrained本地目录只认由save_pretrained保存的目录别把.bin文件单独丢在目录里就当完事config.json丢了也会加载失败。4.4 实验结果记录表不同超参组合的对比训练完成后要把在不使用复杂权重组合的情况下跑出的几个结果整理成表格答辩老师特别吃这一套。常用的记录表格式如下模型配置lrbatch_sizedev accdev macro-F1训练耗时LSTM baseline1e-3640.7240.7018 minBERT max_len 642e-5160.8120.78912 minBERT max_len 1282e-580.8250.80325 minBERT class_weight2e-5160.8310.82412 min记录时必须固定随机种子否则 PyTorch 的随机初始化会让每次训练结果都有波动对比就不公平。固定方式是在训练脚本开头加一句import numpy as np import random import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)注意公共基准放在train.py的开头这样每次复现实验才有可比性。深度学习训练确实有些玄学成分同一个脚本同一天跑两次都可能差一个点所以多跑几次取平均值的说服力远比单次高。5. 避坑与常见问题数据处理、训练收敛和部署的典型翻车现场5.1 现象分词后模型效果反而下降许多学生在跑 BERT 前会习惯性用 jieba 对评论做分词再把分好词的文本喂给BertTokenizer。结果训练完成后负面类别的 F1 比不做分词还要低 35 个点。原因是 BERT 的中文 tokenizer 本身是按字切分的预训练阶段就建立在“字”级别上再切一次词会把“门票”的语义打散成“门”“票”两个字反而让模型更难捕捉方面词。还有分词错误会直接污染注意力对齐。解决BertTokenizer的输入直接传原始文本任何地方都不做 jieba 分词。传统 LSTM 路线才需要分词走 BERT 路线就彻底忘掉这个习惯。5.2 现象显存 OOMbatch_size 降到 4 都报错用bert-base-chinese训练输入max_len128、batch_size16一张 4GB 显存的显卡在第 2 个 epoch 直接 OOM。把batch_size降到 4 仍然报错。原因max_len128会让每个 token 在 12 层 Transformer 里产生大量中间激活值这些激活值在反向传播前必须保存在显存里4GB 显存扛不住。解决先把max_len改到 64显存占用大约下降到原来的三分之一再把batch_size设 8配合梯度累积凑成等效 32 的 batch。如果还不行用混合精度训练torch.cuda.amp能把显存再砍一半。可以沿用下面的梯度累积写法accum_steps 4 for step, batch in enumerate(train_loader): outputs model(**batch) loss outputs.loss / accum_steps loss.backward() if (step 1) % accum_steps 0: optimizer.step() optimizer.zero_grad()5.3 现象训练 loss 在下降dev 的 macro-F1 却一直原地不动这种情况最容易让人怀疑代码写错了但实际上是类别分布出了问题。数据切分后正面样本占 60%负面占 25%中性只有 15%。模型一直在学“怎么猜中正面”因为把每条都预测成正面也能拿到 60% 准确率而 loss 里中性样本贡献太小梯度被正面样本淹没。解决在CrossEntropyLoss里传入weight参数按各类别样本量的倒数做归一化。比如负面、中性、正面的比例接近 2 : 1 : 4可以设置权重[1.0, 2.0, 0.5]放大少数类的梯度贡献。如果调权重后中性 F1 仍然低于 0.5那就是标注本身问题中性样本里混了大量“应该拆成两个具体方面”的句子需要回头改标注。5.4 现象模型对“虽然……但是……”这种句式总是全判错“虽然景色很美但是票价太高”这句话模型经常把门票方面也判成正面。原因是基础 BERT 微调对转折关系的捕捉能力有限尤其是训练语料里这种句式占比不高时。解决两方面同时下手。第一在标注阶段把这类句子里的“虽然”部分和“但是”部分看成一个整体但确保“但是”后的情感词权重更大用预标注规则把“但是/可是/不过”后 10 个字内的情感词单独提取作为补充特征拼接进输入。第二在训练数据里做数据增强把已有的反转句式复制一份把情感标签互换强制模型学到转折结构。这样处理后测试集上这类句式的准确率能从 50% 左右提到 75% 以上。5.5 现象conda 环境里import torch总提示 CPU 版本训练慢到怀疑人生用pip install torch默认装的是 CPU 版安装日志里能看到pytorch-cpu训练一个 epoch 要 40 分钟这时你会很想去网上找“加速教程”但更靠谱的路子是回头查 GPU 版安装。解决先运行nvidia-smi查看自己的 CUDA 版本再去 PyTorch 官网选择对应版本安装。比如 CUDA 11.8 环境就装pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118装完后跑torch.cuda.is_available()输出True才算成功。还有一步容易被忽略装完 GPU 版后必须重启 Python 进程否则旧版本模块还被占着等于没装。这不算玄学是 Python 包导入机制的经典坑。6. 把毕设做成加分项模型效果验证与多模态扩展模型训练到 dev macro-F1 在 0.80 左右已经是一个能拿得出手的毕设成绩但要想在答辩里显得有思考深度还要做两件事效果验证和扩展方向验证。效果验证最靠谱的做法是做 k 折交叉验证而不是只跑一次数据切分。把标注好的数据按评论 id 分 5 折每折都留出独立验证集跑 5 次取平均值。如果 5 次结果的 macro-F1 标准差超过 0.02说明模型对某几类样本不稳定需要检查那几类是哪些方面和情感的组合。另一个验证技巧是构造对抗样例专门测试模型的方面区分能力。我习惯准备 10 条含双方面反转句式的手工评论比如“停车很方便但门票涨价了”然后用训练好的模型分别预测交通和门票两个方面看结果是不是分别给出正面和负面。这个测试能直接暴露模型是否真的学到了方面级推理还是只学到了整句情感。多模态情感分析是目前业内相对热门的方向也是你能在毕设里提出的“未来工作”。景点评论天然带图片用户发“风景真好”时往往配一张蓝天白云照片。扩展方案不需要重新训练多模态模型可以把文本情感分析和图片情感分析结果做个加权融合。文本模型仍然用现在的 BERT图片部分可以用现成的 CLIP 模型对评论配图做零样本情感判断或者人工标注一部分图片情感极性。最后对比纯文本模型和图文融合模型在验证集上的 macro-F1如果能稳定提高 12 个百分点这个实验就很有价值。用 Gradio 搭一个可视化演示页面也很简单答辩现场输入句子、勾选方面直接出情感概率import gradio as gr def infer(review, aspect): enc tokenizer(review, aspect, return_tensorspt) model.eval() with torch.no_grad(): prob torch.softmax(model(**enc).logits, dim-1).squeeze() labels [负面, 中性, 正面] return {labels[i]: round(float(prob[i]), 4) for i in range(3)} demo gr.Interface( fninfer, inputs[ gr.Textbox(label评论文本, value停车方便但门票太贵), gr.Radio([交通, 门票, 餐饮, 景观, 服务], label选择方面, value交通) ], outputsgr.Label(num_top_classes3), title旅游景点方面级情感分析演示 ) demo.launch()这段代码几十行就能跑起一个本地网页服务答辩现场随手输入“停车方便但门票太贵”先选交通再选门票连续展示两个不同预测比任何 PPT 都直观。我当年做这个题目时最大的一次教训是急着搜“毕业设计从 github 抄来”结果拿到一个庞大但没写 README 的仓库根本不知道从哪开始跑。后来养成习惯任何源码包先花 15 分钟把数据格式和训练入口摸清楚再动环境。这个习惯帮我省了很多无用功也希望这篇文章把同样的问题意识传递给你。文本模型加上多模态扩展思路这个选题的完整度和技术纵深足够撑起一场有内容的答辩希望帮到你。本文还有配套的精品资源点击获取