简介:本资源是一套面向本科计算机/人工智能方向学生的毕业设计实战项目,聚焦电商与社交平台中虚假评论识别这一典型NLP应用场景,采用卷积神经网络(CNN)与LSTM混合架构实现高精度判别。压缩包共23个文件,包含6个核心Python源码(如数据预处理、词向量训练、模型构建与预测)、2个Word文档(含系统设计说明与使用手册)、1个H5模型文件、1个CSV标注语料及停用词表等,总大小仅2.9MB,轻量易部署。已有185人学习下载,适合作为课程设计参考、毕设开题原型或NLP入门实践素材。所有代码均经导师指导并严格调试通过,附带完整运行依赖(requirements.txt)与流程说明,涵盖从原始文本清洗、Word2Vec词嵌入、模型训练到结果预测的全流程,结构清晰、注释充分,便于理解神经网络在文本分类任务中的实际落地逻辑。 深夜下单前,你会盯着评论区一条条看吗?你会不会也在想:这条“质量很好,卖家诚信,下次还会再来”到底是真实用户体验,还是刷出来的假话?这其实不是一个人的疑问。电商、外卖、应用商店、影评网站,虚假评论几乎渗透到所有带评价功能的地方。我的本科毕业设计就选了“基于神经网络的虚假评论识别系统”这个方向。目标很直接:用Python实现一个能自动判断一条中文评论是真实还是虚假的系统,训练好模型,再给它包一个可以演示的界面。整个过程中踩了不少坑,也把这些坑变成了现在系统的一部分。这篇文章就把这套系统的完整设计思路、数据准备过程、模型选型逻辑、训练调优细节,以及源码包和使用文档的组织方式,全部梳理出来。觉得“神经网络”四个字不知道从哪儿下手的同学,可以参考一下。
1. 虚假评论识别:到底在识别什么
1.1 虚假评论不是只有“刷单好评”
虚假评论这个词听起来抽象,实际落到数据里形态非常多样。最常见的三种:第一种是水军好评,雇佣账号给商品打五星,内容复制粘贴,或者套用模板,“商家服务态度很好”“物流很快”“质量不错”这种话到处都是;第二种是恶意差评,为了打击同行或者勒索商家,故意编造质量问题;第三种是广告引流,在评论里夹带外链、联系方式、微信号。这三类在文本特征上都有迹可循,但规则方法很难全部覆盖。
做系统之前先把问题定义清楚:我做的分类是二分类,标签就两个,1代表虚假评论,0代表真实评论。有人可能会问,那中立的、模棱两可的评论怎么办?实际处理时我会把这些归为真实评论——如果模型无法判断真假,宁可信其真,因为虚假评论识别系统的核心目标是抓出那些明显可疑、高风险的评论。这个定位直接决定了后面标注策略和评估指标的选取。
1.2 为什么必须上神经网络
传统方案走的是关键词黑名单和情感分析。比如统计一条评论里出现了多少个“绝对”“最”“强烈推荐”这种词,或者用情感词典算一段文本的情感极性。这类方法在2010年前后比较流行,但问题非常明显:水军可以换词、换句式回避黑名单;情感分析只能算出情感倾向,算不出“是否撒谎”。同一个句子改几个词,感情色彩没变,规则就失效了。
神经网络走的是完全不同的一条路:它不需要人工设计特征,而是把评论变成向量,让模型自动学习“虚假评论长什么样”。这个学习过程有点像人看多了假评论之后形成的直觉,只不过模型靠的是统计规律。上下文、句式、用词习惯、与商品描述是否相关,都能被捕捉到。这也是毕设选这个方向的原因——技术上“有得挖”,又不至于深到做不出来。
1.3 这个系统整体做了什么
用一个流程来概括:用户输入一条评论,系统经过预处理,交给已经训练好的神经网络模型,输出两个数字——属于虚假评论的概率和属于真实评论的概率,概率高的一方就是最终判断。为了方便演示,我加了一个简单的Web页面,输入框贴一条评论,点按钮就能看到结果。这套东西拆开看每块都不算难,但合在一起就是一个完整的自然语言处理分类项目。
技术栈上,主体是Python + PyTorch,分词用jieba,Web端用Flask。为什么选PyTorch?毕设阶段社区资料多、代码写起来直观,调试也方便。后面所有内容都是基于这套技术栈展开的。
2. 技术选型:神经网络模型怎么定
2.1 先排除掉“看起来高级但没法落地”的方案
刚开始选型时,很多人第一反应是直接上BERT。BERT效果好,但本科毕设场景要冷静想一下:第一,你需要下载几百MB的预训练模型;第二,训练和推理对GPU有要求,很多同学只有一台普通笔记本;第三,答辩老师一定会问“BERT的原理你知道吗”,如果自己讲不清楚,反而吃力不讨好。所以我一开始就把纯BERT方案排除了。
但这不代表完全不用预训练思想。后面可以做对比实验:用Word2Vec预训练词向量初始化Embedding层,模型参数里就有一部分“预训练成果”,又能给老师讲清楚原理,属于性价比很高的折中。我实际做的时候,Word2Vec用的是gensim训练的中文词向量,训练集就是手上的评论语料,没有额外去下载大型语料,这样既可控又够用。
2.2 BiLSTM+Attention为什么是毕设优选
文本分类的神经模型主流就那几个:TextCNN、LSTM、BiLSTM、BiLSTM+Attention、BERT。TextCNN实现简单,但感受野有限,对长距离依赖抓得不好;LSTM能处理序列,但单向LSTM只能看上文,对“这句话前后矛盾”这类虚假评论特征不敏感;BiLSTM加了反向,能同时看上下文,再叠加一个注意力机制,让模型自动为句子中的每个词分配权重——虚假评论里那些“特别”“非常”“超级”这类情感强化词,往往会被分配更高权重。
| 模型 | 实现难度 | 训练速度 | 语义捕捉能力 | 毕设推荐程度 |
|---|---|---|---|---|
| TextCNN | 低 | 快 | 局部特征强,长距离弱 | 可以,偏简单 |
| LSTM | 中 | 中 | 有上文,无下文 | 一般 |
| BiLSTM+Attention | 中高 | 中 | 上下文+重点词,均衡 | 强烈推荐 |
| BERT | 高 | 慢(CPU) | 强 | 慎选 |
我的最终选择是BiLSTM+Attention。原因总结起来就是:效果能打、论文有东西可写、训练时间可控(CPU训练一个epoch大约几分钟),而且对注意力权重的可视化还能作为论文中的亮点展示。这个组合在中文文本分类里已经非常成熟,社区资料多,遇到问题搜得到。网上类似的毕设项目也很多,但大多数只给代码不讲为什么,我在这里把选型逻辑写清楚,就是希望大家不是“背下来”,而是真明白。
2.3 整体架构:数据流怎么走
系统从输入到输出分六个环节:原始评论 → 数据清洗 → 分词 → 词索引序列 → BiLSTM+Attention模型 → 分类输出。每个环节有自己独立的内容,写代码时也是独立的函数和文件。这套分层设计一开始可能觉得多余,但后来发现,只要改一个环节(比如分词换成更好的工具),其他部分不用动,对这个项目迭代帮助非常大。
这个架构也决定了源码包的组织方式。后面第5章会详细展开目录结构,这里先建立一个整体印象:数据环节全部在src/data_clean.py和src/tokenize.py里,模型在src/model.py里,训练、预测、评估各自独立成脚本,Web端单独放一个目录。清晰的分层让整个项目看起来不像是学生作业,更像一个可维护的小系统。
3. 数据准备:直接影响模型上限的环节
3.1 数据来源与标注策略
神经网络是“喂出来”的,数据质量决定模型上限。我的数据集主要来自两部分:一部分是公开的中文电商评论数据集,另一部分是自己手工整理的常见场景评论。把两者合并后,人工标注了一遍,虚假评论标注为1,真实评论标注为0。数据量上,最终保留了一万条左右,其中虚假评论大约占三成。为什么不五五开?因为现实场景中真实评论本来就比虚假评论多,全做成五五开反而是脱离实际。
这里要说一个重要经验:标注一定要有明确标准。比如“价格实惠,物流很快,就是包装有点破损”这种有褒有贬、符合真实体验的,归为真实评论;“卖家态度服务很好,全五星推荐,有需要的加微信xxx”这种夹带联系方式或明显模板化的,归为虚假评论。两个标注人如果标准不统一,后面模型学出来的东西会很乱。我因为是一个人标注,标准自洽性没问题,但如果有条件,两个人标注、不一致的讨论处理,效果会更好。
3.2 清洗流程:少一步都会让模型变傻
清洗是数据处理里最琐碎的环节,但最容易直接影响结果。我的清洗流程大致是:
- 去除重复评论。水军经常批量复制同一条内容,去重等于直接干掉一批明显样本。
- 去掉广告和外链。用正则匹配网址、微信号、QQ号等,匹配到就标记为广告类,而不是简单删除。
- 清洗HTML标签。部分评论是从网页里抓的,包含p、br等标签,用正则或BeautifulSoup去掉。
- 处理空白和特殊字符。多个空格压缩成一个,繁体转简体。
- 表情符号处理。中文评论里表情不算多,我选择直接删除;如果带表情的评论多,也可以统一替换成特殊标记词。
清洗完之后还有一个隐藏坑:中英文标点混排。很多评论里既有中文全角标点又有英文半角标点,比如“很好,”和“很好,”其实是同一个意思,分词和特征提取时如果不统一,同一个词会被当成两种形式。我的做法是把半角标点统一转成全角,再统一做正则清洗。这一步看似不起眼,但对后面模型的效果影响比想象中大,第7章还会单独说。
3.3 分词、词表与序列填充
中文不像英文天然按空格分词,必须用分词工具。我用的jieba分词,配置了自定义词典,把“商家”“物流”“客服”等评论常见词加入词典,避免被切得七零八落。词表构建时设置了min_count=2,也就是出现次数少于2次的词直接丢弃;词的个数限制在3万以内,减少向量空间的稀疏度。vocab.json单独保存下来,这个文件在预测阶段必须原样加载,第7章那个经典坑就和它有关。
序列填充是很多人第一次写会踩的坑:不同评论长度不同,神经网络要求输入长度固定。我定了一个最大长度max_len=100,超过100字的评论截断,不足100字的用0补齐。长度怎么定?我先把数据集的评论长度分布统计出来,发现大多数评论在50到120字之间,试了50、80、100、150几个候选,最后选定100。如果长度设太大,训练变慢还容易引入过多填充噪声;太小则丢失信息。这个长度分布统计的环节,建议做毕设的同学一定不要跳过。
4. 模型结构与训练细节
4.1 网络各层的作用和代码骨架
模型结构用PyTorch实现。核心代码如下:
import torch.nn as nn class FakeReviewClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_classes, dropout=0.5): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0) self.bilstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=2, batch_first=True, bidirectional=True, dropout=dropout) self.word_fc = nn.Linear(hidden_dim * 2, hidden_dim * 2) self.fc = nn.Linear(hidden_dim * 2, num_classes) self.dropout = nn.Dropout(dropout) def forward(self, x): emb = self.embedding(x) # [batch, seq_len, embedding_dim] out, _ = self.bilstm(emb) # [batch, seq_len, hidden*2] attn_weights = torch.softmax(self.word_fc(out), dim=1) context = (out * attn_weights).sum(dim=1) # [batch, hidden*2] output = self.fc(self.dropout(context)) # [batch, num_classes] return output简单说,Embedding层拿到的是每个词的索引,输出稠密词向量;BiLSTM读入整句,每个位置输出正向和反向拼接后的向量;Attention为每个位置的向量打分,做加权求和,得到整句话的向量表示;最后接全连接层和Softmax,输出属于真实评论和虚假评论的概率。这个Attention实现是简化版的加性注意力,用一层全连接直接做打分,效果已经够用。如果要做得更精细,可以尝试双线性注意力,但对毕设来说,简单版本更容易解释清。
4.2 训练参数与调优过程
我用Adam优化器,学习率初始设0.001,权重衰减weight_decay=1e-5。batch_size设64,epoch设30,但实际训练中几乎每轮都触发早停,通常在10到15轮之间就停了。为什么需要早停?因为训练集loss一直在降,但验证集在某个点之后开始回升,这个点就是模型开始过拟合的时刻,再往下训练没有意义。这个现象很多同学第一次跑模型时会看到,不要怀疑是代码写错了,这是过拟合的正常表现。
Dropout在BiLSTM和全连接层都加了0.5。一开始我把Dropout加在Embedding层后,效果并不好,后面调整到BiLSTM输出层和全连接层才明显改善。这个细节,如果大家自己复现时效果不对,可以检查一下Dropout的位置。
学习率衰减我采用ReduceLROnPlateau:当验证集损失连续两轮不下降时,学习率乘以0.5。实际使用中能有效帮助模型在后期更精细地收敛。训练完成后,把验证集上F1最高的那一轮模型权重保存下来,作为最终模型。
4.3 类别不平衡:不要一上来就focal loss
虚假评论与真实评论比例约3:7,并没有严重到离谱,但如果不处理,模型会倾向于把所有评论预测为真实评论,因为这样准确率已经很高了。我的做法是在损失函数里给虚假评论更高的权重,class_weight设为{0: 1.0, 1: 2.5}。这样模型对错误分类的虚假评论会惩罚更重,学习时会更在意那30%的样本。focal loss也试过,效果有改善但不明显,而且为毕设增加了复杂度,最后没有采用。如果做的是更不平衡的数据集(比如1:9甚至1:99),再考虑focal loss会更有必要。
这里想多说一句:很多教程会把类别不平衡说成必须用某种高级手段,但实际工程里,先试class_weight、调整阈值,往往就能解决大部分问题。把简单方法用到极致,比堆砌复杂模型更实用。
5. 源码包结构、使用文档与复现
5.1 源码包整体结构
这个毕设交付时是一个zip压缩包,里面的目录结构如下:
fake-review-system/ ├── data/ │ ├── raw/ # 原始评论数据 │ ├── processed/ # 清洗分词后的数据 │ ├── vocab.json # 词表文件 │ └── label_map.json # 标签映射 ├── src/ │ ├── data_clean.py # 数据清洗 │ ├── tokenize.py # 分词与序列化 │ ├── model.py # 网络结构定义 │ ├── train.py # 训练脚本 │ ├── predict.py # 单条预测脚本 │ └── evaluate.py # 评估脚本 ├── models/ │ └── model_best.pth # 训练好的模型权重 ├── web/ │ └── app.py # Flask演示页面 ├── README.md # 使用文档 └── requirements.txt # 依赖清单这个结构是做了一个多星期后调整出来的,最初所有代码堆在几个文件里,改动一次要翻半天。后来痛下决心按功能拆开,训练、预测、评估分离,维护起来会轻松很多。这里建议做毕设的同学,从第一天开始就按这个思路组织代码,不要等写了几百行再重构。
5.2 从零复现的完整步骤
拿到zip包后怎么跑起来,我在README里写了非常详细的步骤。这里也放到博客里供参考:
- 解压zip,进入项目目录。解压时如果提示“不是有效的zip文件”,绝大多数情况不是文件坏了,而是下载过程没完成或者解压工具兼容性问题,可以用7-Zip等工具再试一次。
- 安装Python 3.8以上版本(推荐3.9),执行
pip install -r requirements.txt。 - 如果只需要用训练好的模型,直接运行
python src/predict.py --text "这条评论是..."。 - 想自己重新训练,先确认
data/raw/下有原始数据,运行python src/train.py。训练完成后模型自动保存到models/目录。 - 启动Web演示界面:
python web/app.py,然后浏览器访问http://127.0.0.1:5000。
requirements.txt里的依赖包括:torch、numpy、pandas、jieba、flask、scikit-learn、gensim。版本都做了锁定,避免版本不一致导致运行报错。尤其是torch,CPU版本和GPU版本安装命令不一样,文档里我都标注了。
5.3 使用文档到底该写什么
使用文档是毕设评分的一部分,也是老师最容易翻的内容。我的README文档包含了六个章节:项目简介、环境要求、快速开始、代码结构说明、参数说明、常见问题。特别值得一提的是常见问题章节,我把自己在运行中遇到的高频问题都写了进去,比如“训练时CUDA out of memory怎么办”“预测时提示词表文件找不到怎么办”等。后来同学拿了这份文档自己去跑,几乎所有问题都能自己解决。使用文档的价值不在于字数多,而在于能不能让一个完全没接触过项目的人按步骤独立复现。
写文档的时候有个技巧:一边自己重新走一遍流程,一边记录每一步做了什么、遇到了什么问题。这样写出来的文档不是干巴巴的命令列表,而是真正能帮到人的指南。
6. 评估指标:模型好坏不是靠准确率一个数
6.1 准确率为什么“骗人”
先讲一个典型的误区:训练完模型,第一眼看准确率,95%,很高,觉得很成功。这个数字在类别不平衡的数据下没有太大意义。如果数据里真实评论占70%,虚假评论占30%,一个“无脑预测真实”的规则就能拿到70%的准确率。所以评估必须看每个类别的细分指标,尤其是虚假评论这一类,因为这才是系统的核心价值所在。
6.2 精确率、召回率与F1
对虚假评论识别这个任务,有两个错误方向需要分清:
- 把真实评论判成虚假:误杀,用户正常写个好评被系统标记为水军,体验很差。
- 把虚假评论判成真实:漏判,水军评论混进评论区,系统形同虚设。
精确率关心的是:预测为虚假的里面有多少真的虚假;召回率关心的是:所有虚假评论里有多少被找出来了。两者通常此消彼长,用F1来平衡。最终模型在测试集上的结果大致是:准确率90%左右,虚假评论的F1在0.86到0.89之间,真实评论的F1在0.93左右。对于毕设来说,这个成绩足够支撑一篇论文了。
| 指标 | 虚假评论 | 真实评论 |
|---|---|---|
| 精确率 | 0.88 | 0.93 |
| 召回率 | 0.85 | 0.95 |
| F1 | 0.86 | 0.94 |
不同随机种子下结果会有几个百分点的浮动,这很正常。汇报结果时最好跑三次取平均,论文里也这样写,显得严谨。
6.3 可视化混淆矩阵与注意力权重
为了让论文答辩更有说服力,我做了两个可视化。一个是混淆矩阵,用seaborn画出四格表,能直观展示模型在哪类样本上出错。比如我训练集里有些广告引流评论被误判为真实评论,看混淆矩阵就能定位到这类问题。另一个是注意力权重可视化,把评论里的每个词标上深浅不同的颜色,颜色越深表示模型对这个词越“在意”。水军的评论经过可视化后,“特别”“非常”“绝对”这些词往往特别显眼,答辩现场展示这个图,很容易让老师理解模型不是瞎猜。
7. 踩坑记录:这些坑比模型本身更值得写
7.1 模型保存后预测报错:词表不一致
这个问题困扰了我一个下午。训练结束,模型正常保存,换个脚本加载模型做预测,直接报错:索引越界或者unknown token。查了半天原因是:训练时构建的词表在预测时没有正确加载,预测脚本重新走了一遍分词和建词表流程,导致同一句话生成的分词结果和词索引跟训练时完全对不上。解决方法是:把vocab.json作为模型的一部分固定下来,预测脚本必须加载它,不允许重新构建。这个坑其实很经典,但文档里很少写清楚。
7.2 训练loss不下降,问题出在数据
训练第一个epoch,loss下不去,一直在2.3左右徘徊。当时第一反应是学习率太大,调小之后没用;又怀疑是模型结构写错了,对着代码查了半天也没发现逻辑问题。最后一行一行检查数据,发现问题出在label_map:真实评论的标签是0,虚假评论的标签是1,但在DataLoader里标签被错误地加了一个偏移,导致所有标签都变成了1和2,模型根本学不出映射关系。所以训练不收敛时,最先查的应该是数据读取和标签映射,而不是调参。
7.3 来自全角半角标点的隐形干扰
这个坑是在做错误分析时发现的:模型预测错的样本里,有一批评论只是把“好”替换成“好,”(半角逗号),预测结果就变了。原因在于清洗时没有统一标点符号,同一个词因为后面跟的标点不同,分词结果产生差异,模型把它当成了不同语境。后来在清洗流程里加了一步全角半角统一,错误率明显下降。教训是:数据预处理里看起来不起眼的细节,可能比调整模型结构更能提升效果。
7.4 用CPU训练时,epoch时间太长怎么办
并不是所有人都有GPU。我最初在笔记本上跑,一个epoch要十分钟,30个epoch就是五个小时,中途还容易内存溢出。后来做了两件事:第一,把最大序列长度从100降到80,实验发现对最终指标影响很小但速度提高了20%;第二,把训练集随机抽样出一部分做快速验证,确认pipeline没问题后再全量训练。这个思路其实和毕设节奏很匹配:先用小数据跑通流程,再用全量数据跑最终结果。
7.5 答辩老师最爱问的几个问题
这些问题我在实际答辩中基本都被问到了。提前准备好在心里打草稿,回答时不会慌。
- 为什么用BiLSTM+Attention而不是BERT?答:BERT效果好,但训练和推理成本高,毕设场景下BiLSTM+Attention在保证分类效果的同时更容易解释,且预训练词向量同样能引入语义信息。
- 数据是哪来的,标注可信吗?答:公开数据集和自建数据合并,人工按统一标准标注,标注标准在论文中写明。
- 模型是真的在理解语义,还是在高频词上碰运气?答:通过注意力可视化展示模型关注的重点词,通过消融实验(去掉Attention后效果下降)证明其作用。
- 如果实际部署,模型多久需要更新一次?答:评论语言会随时间变化,需要定期用新标注数据微调训练,或引入在线学习机制。
最后补一句个人体会。做这个毕设之前,我对神经网络的认知停留在某个课程作业里用TensorFlow跑过MNIST。真正按“数据—模型—训练—评估—部署”全流程走了一遍之后,才明白那些看起来高大上的系统,拆开之后其实就是一堆基础模块的组合,难点在于把它们串起来,以及在串的过程中发现问题、修掉问题。如果你正在做类似题目,不用急着直接上BERT或者追求SOTA,先把BiLSTM+Attention这套经典管线完整跑通,再去想怎么提升,会稳得多。这个项目里最值钱的东西不只是最终的准确率数字,而是那几次排查错误的经历——它们才是毕业设计真正的学习素材。
本文还有配套的精品资源,点击获取