简介:面向中文谣言检测研究与毕业设计场景,这份压缩包提供了完整的算法实现与论文配套材料。内容围绕谣言识别核心流程展开,覆盖语料预处理、停用词过滤、TF-IDF特征构建、多种分类模型训练与结果评估,包含从原始数据整理到最终模型调用的全部Python脚本,以及停用词表、类别标签、JSON语料等多类辅助文件,可支撑复现基于朴素贝叶斯、逻辑回归等方法的谣言检测实验。资源共26个文件,以12个.py脚本、9个.txt文本、4个.json数据和1个readme组成,整体仅4.93MB,目录结构清晰,便于按实验阶段顺序查阅。已有125人学习下载,适合NLP入门者或本科毕设学生参考其代码组织与实验思路。通过运行和研读脚本,可掌握中文文本分类的常用处理流程、特征工程技巧以及模型性能对比策略,为后续扩展深度学习谣言检测模型打下基础。
1. 一个中文谣言检测毕设压缩包,真正值钱的不是模型代码
从网盘或群里下载这个 zip 的,大多是想给自己的毕业设计找一条捷径。但真正拿这个题完整做过一轮的人会告诉你:这个压缩包里最值钱的不是那套神经网络代码,而是数据清洗和评估口径背后踩过的坑。Chinese Rumor Recognition,中文谣言检测,本质上就是给定一条中文文本(多数来自微博这类社交平台),判定它是谣言还是正常信息。二分类,模型成熟,公开语料也有,看起来很适合本科毕业设计。可一旦上手你就会发现,标签不平衡、事件级泄漏、文本时效性这几个问题,会从数据预处理一路跟到答辩前夜。这篇笔记就按我完整做通一套方案的顺序来写:先拆包看结构,再走通数据到模型的训练链路,最后把实验补成一篇能站住脚的论文。
2. 拆包之后先别急着跑代码:这个压缩包的真实内容与目录结构
拿到手的虽然只有一个 zip,但里面通常装着三类东西:论文定稿(PDF 或 Word)、代码目录、数据集目录。很多人第一时间去找 train.py 双击运行,这容易翻车。因为这份毕设的作者可能在校期间换过三个版本的数据集,代码里的路径还是绝对路径,不先看清结构直接跑,大概率报 FileNotFoundError。
2.1 论文正文和代码,先读摘要跟实验这两处
我拿到这类压缩包后的第一个动作不是看代码,而是先把论文的摘要和实验章扫一遍。摘要里通常会写“提出了一种基于 XX 的中文谣言检测方法,准确率达到 XX%”,但论文正文里真正决定可复现性的信息不在摘要,而在实验设置里。你需要核对三处:
第一处是数据集描述。论文实验章一般会写明用了多少条数据、谣言类占比多少、训练验证测试怎么切分。如果这里只写了总量和准确率,没有写划分比例和随机种子,那么复现出来的数字跟论文对不上很正常。
第二处是模型配置。TextCNN 还是双向 LSTM?预训练模型微调还是冻结特征?batch size 和序列长度是多少?这些在论文里可能只体现在一张表或一段文字里,但代码里一定有对应参数。
第三处是指标口径。是只用准确率,还是报告了精确率、召回率、F1?评估是在测试集上跑一次,还是用五折交叉验证取平均?口径不同,结果可以差出好几个点。
先读完这两处,你才能判断这份代码开到什么程度能跑通、哪些参数要按自己的机器重调。
2.2 代码目录常见组织方式:从解压到跑通最小命令
先把压缩包解压,再按层列一下文件,确认根目录长什么样:
unzip Chinese_Rumor_Recognition.zip -d rumor_proj cd rumor_proj find . -maxdepth 2 -type f | head -50这个命令做了两件事:-d rumor_proj指定解压目录,避免文件散落一地;find ... -maxdepth 2 -type f | head -50只列两层内的文件,先把顶层结构看清楚,不急着进任何子目录。
我见过比较标准的目录布局长这样:
rumor_proj/ ├── 论文_最终版.pdf ├── code/ │ ├── train.py │ ├── config.py │ ├── utils/ │ │ ├── preprocess.py │ │ └── data_loader.py │ └── models/ │ ├── textcnn.py │ └── bert_model.py └── data/ ├── train.csv ├── dev.csv └── test.csvdata 目录里三个 csv 直接对应训练、验证、测试划分,说明作者已经做了数据准备。最省事的方式是看根目录有没有 README,没有就看 train.py 的 main 函数入口。常见做法是有个 config.py 集中放路径和超参数,比散落在各文件里好调。
跑通的最小命令大概是这样的:
pip install -r requirements.txt python train.py --model textcnn --epochs 20 --batch_size 64 --lr 2e-3--model指定用哪套模型,textcnn是训练成本最低的深度模型;--epochs和--batch_size直接影响训练时长,显存不够就先把 batch_size 降到 32 再试;--lr是学习率,Adam 优化器下2e-3是个能快速看到收敛趋势的起点。如果你的机器没有 NVIDIA GPU,textcnn 也能在 CPU 上跑,但 epochs 要相应减少,或者把训练集裁小一截先验证流程通不通。
2.3 数据集选型:字段、规模与划分方式决定了后续所有工作
代码可以改,模型可以换,但数据集选不好,后面全部白搭。中文谣言检测的公开语料大部分来自微博,字段通常包含文本内容、标签(谣言还是非谣言)、发布时间,有时候还有事件编号或转发量。
先看数据长什么样:
import pandas as pd df = pd.read_csv('data/rumor.csv', encoding='utf-8-sig') print(df.dtypes) print(df['label'].value_counts(normalize=True))encoding='utf-8-sig'是为了兼容带 BOM 的 CSV,用默认utf-8读会解析出\ufeff这种隐藏字符;normalize=True直接输出两类占比,一眼就能看出数据是不是高度不平衡。
字段结构里最容易被忽略的是事件编号。微博上一个谣言事件往往有几百条转发文本,如果直接随机划分训练集和测试集,同一个事件下的相似文本会同时出现在两边,模型等于“开卷考试”,测试分数虚高。正确的做法是让同一事件的所有文本都落在同一侧:
from sklearn.model_selection import GroupShuffleSplit gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42) train_idx, test_idx = next(gss.split(df, df['label'], groups=df['event_id'])) train_df = df.iloc[train_idx] test_df = df.iloc[test_idx]groups参数接收的是每条样本的事件编号,切分时保证整组进训练或整组进测试。random_state=42固定切分结果,后面每次跑实验都用同一个种子,结果才可比较。这一步不做,到后面验证集指标忽高忽低时,你会把大量时间浪费在排查模型上,而问题其实出在数据划分。
3. 从清洗到可训练样本:中文文本的特征工程与模型输入
数据集确定之后,下一步是把原始文本变成模型能吃的东西。中文 NLP 的预处理和英文最大的区别在于:没有天然空格分词,符号和话题标签的干扰模式也不一样。这一章要解决的是“文本怎么洗、特征怎么提、模型怎么选”三件事。
3.1 清洗顺序很关键:话题、@用户、链接和特殊符号的逐项处理
微博文本里满是#话题#、@用户、URL 和各种表情符号,这些内容对谣言判定几乎没有正向贡献,但会直接污染词向量和 TF-IDF 特征。我一般按固定顺序处理:
import re import jieba STOP_WORDS = set(open('data/stopwords.txt', encoding='utf-8').read().split()) def clean_text(raw): raw = re.sub(r'#.+?#', ' ', raw) # 去掉微博话题 raw = re.sub(r'@[\u4e00-\u9fa5a-zA-Z0-9_]+', ' ', raw) # 去掉 @用户 raw = re.sub(r'https?://\S+', ' ', raw) # 去掉链接 raw = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', ' ', raw) # 只保留中文/英文/数字 words = [w for w in jieba.cut(raw) if w.strip() and w not in STOP_WORDS] return ' '.join(words)顺序不能乱。先去掉话题和 @,是因为它们会干扰后面的符号过滤;最后一步用取反字符集只保留中文、英文和数字,滤掉所有其他符号。分词放在最后,是因为 jieba 对带 # 和 @ 的串会切出无意义片段。
停用词表里有一个常见坑:不要把“不”“没”“未”这类否定词加进去。谣言文本里经常出现“官方未通报”“家属不承认”,如果把“未”和“不”删掉,句子的核心否定语义就丢了,模型很容易把辟谣文本误判成谣言。我用过的经验是:宁可停用词表短一点,也不要误伤否定词。
3.2 基线模型优先:TF-IDF 与 Word2Vec 的搭配和参数
很多本科生上来就跑 BERT,这不是好习惯。深度模型调参链路长、训练慢,一旦结果异常,很难判断是数据问题还是模型问题。稳妥的顺序是先跑一个传统特征加线性分类器作为基线,再往上叠深度模型。基线不是拿来凑数用的,它是你判断后续模型有没有真正变好的参照物。
TF-IDF 加逻辑回归是最经典的开局:
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline pipe = make_pipeline( TfidfVectorizer(max_features=50000, ngram_range=(1, 2), sublinear_tf=True), LogisticRegression(max_iter=1000) ) pipe.fit(train_texts, train_labels)max_features=50000限定特征维度,防止词表过大致使矩阵膨胀;ngram_range=(1, 2)同时考虑单词和相邻词组合,因为谣言里“已辟谣”“紧急通知”这类双词搭配比单字更有语义;sublinear_tf=True对词频做对数压缩,避免高频词主导相似度。
如果后面要接深度模型,Word2Vec 预训练是过渡方案。一个常见的做法是用 gensim 在自己的训练语料上训一套词向量,然后把一条文本的所有词向量取平均作为句子向量:
from gensim.models import Word2Vec w2v = Word2Vec(sentences=tokenized_sentences, vector_size=100, window=5, min_count=2, workers=4) def sentence_embedding(tokens): vecs = [w2v.wv[t] for t in tokens if t in w2v.wv] return sum(vecs) / len(vecs) if vecs else [0.0] * 100window=5控制上下文窗口大小,谣言里关键的触发词往往在前后几句话里;min_count=2过滤出现次数过少的词,避免噪声词影响向量质量。这里要明确一点:Word2Vec 平均向量在论文里是给深度模型做对比用的,不是最终方案。
3.3 深度模型怎么选:TextCNN 打底、预训练模型兜底的组合
深度模型我首选 TextCNN。它不是效果最好的,但训练快、显存占用低、调参空间清晰,非常适合作为毕业论文的主模型。下面是一份基础实现:
import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim=128, num_filters=128, filter_sizes=(2, 3, 4), num_classes=2): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.convs = nn.ModuleList([ nn.Conv1d(embed_dim, num_filters, k) for k in filter_sizes ]) self.dropout = nn.Dropout(0.3) self.fc = nn.Linear(num_filters * len(filter_sizes), num_classes) def forward(self, x): emb = self.embedding(x).transpose(1, 2) # 转成卷积需要的 (B, E, L) pooled = [torch.max(nn.functional.relu(conv(emb)), dim=-1).values for conv in self.convs] out = torch.cat(pooled, dim=-1) return self.fc(self.dropout(out))padding_idx=0让填充位始终是零向量,不参与词向量更新;filter_sizes=(2, 3, 4)分别抓取二元词组、三元词组和四元词组特征,谣言经常以“据说”“紧急通知”这类固定短语开头,多尺寸卷积核能同时捕捉不同长度的局部模式;num_filters=128是每个卷积核的输出通道数,数值越大表达力越强,但训练也越慢。
训练循环里最值得调的参数是学习率和 Dropout:
criterion = nn.CrossEntropyLoss(weight=class_weights) optimizer = torch.optim.Adam(model.parameters(), lr=2e-3)class_weights来自训练集标签占比的逆频率,用来缓解谣言类样本偏少的问题。后面在避坑章里会专门讲这一步为什么关键。
预训练模型作为上限验证使用,和 TextCNN 是一个互补关系:
from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese') model = AutoModelForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=2)bert-base-chinese是面向中文的开源预训练模型标识符,直接加载即可。但要注意,序列长度先设 128 起步,别上来就 512,显存不够会直接 OOM。一个稳妥的组合策略是:传统方法出基线,TextCNN 出主线,预训练模型刷上限。论文里三条线都全,对比实验讲得清楚,答辩时也站得住。
4. 训练与评估阶段的常见问题与排查记录
这一章是我自己在这个题目上踩过最多次的坑的记录。你在复现或自己重新实现时遇到的大多数“模型效果不好”,都不是模型结构的问题,而是数据或评估环节的某个细节没处理好。
4.1 准确率虚高、谣言类 F1 却拉胯:先检查指标口径
现象:训练日志里准确率一路爬到 95%,看起来很漂亮,但打印分类报告后发现,谣言类别的 F1 只有 0.3 左右,甚至更差。
原因:数据里非谣言类占比通常超过 70%,模型把所有样本都判成非谣言,准确率照样有 70% 打底。如果你用的数据里非谣言占比 90%,那模型全部预测非谣言也能拿到 90% 准确率。准确率在类别不平衡的场景下反映不了模型真实能力。
解决:只在训练日志里看准确率是不够的,必须看分类报告:
from sklearn.metrics import classification_report print(classification_report(y_true, y_pred, target_names=['非谣言', '谣言'], digits=4))digits=4保留四位小数,论文里引用数据时精度足够。与此同时,训练时给损失函数加上类别权重,让模型对少数类更敏感:
from sklearn.utils.class_weight import compute_class_weight import numpy as np class_weights = compute_class_weight(class_weight='balanced', classes=np.array([0, 1]), y=train_labels) criterion = nn.CrossEntropyLoss(weight=torch.tensor(class_weights, dtype=torch.float32))这一步能让谣言类的召回率显著提升。之后评估指标以 macro F1 为准,而不是准确率。
4.2 验证集指标忽高忽低:随机种子与事件级泄漏的排查顺序
现象:同一份代码,啥都没改,连着跑三次,验证集 F1 能差出五个百分点。
原因:一种是 PyTorch 没固定随机种子,模型初始化参数每次不同;另一种是数据划分是随机切分,同一个谣言事件下的几百条相似转发同时出现在训练集和验证集里,验证时模型其实在“背答案”,指标自然虚高且不稳。
解决:先把随机种子钉死:
import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)然后在数据切分上,按事件编号分组建训练集和验证集,而不是纯随机切。这一步在上一章的数据集选型里已经给了实现。排查顺序是:先看划分方式有没有按事件分组,再看有没有固定种子,最后才怀疑模型本身。大多数“复现不出论文效果”的问题都出在前两步。
4.3 预训练模型训练到一半就崩:显存、序列长度与早停的取舍
现象:BERT 模型跑到第二个 epoch,CUDA out of memory,或者 CPU 机器上跑了好几个小时一个 epoch 都没结束。
原因:序列长度设了 512,batch size 设了 32,显存直接打爆。CPU 机器上硬跑预训练模型本身也不现实。
解决:按显存量级调整序列长度和 batch size。下面是一个参考量级:
| 序列长度 | batch size | 适用显存 |
|---|---|---|
| 128 | 32 | 6~8 GB |
| 256 | 16 | 8~10 GB |
| 512 | 8 | 12 GB 以上 |
如果显存仍然不够,可以把梯度累积打开,用小 batch 等效大 batch,或者直接把序列长度截到 64 试跑通流程。同时加早停,验证集指标连续多个 epoch 不涨就停止训练:
best_f1 = 0 patience = 0 for epoch in range(epochs): model.train() # ... 训练循环 val_f1 = evaluate(model, val_loader) if val_f1 > best_f1: best_f1 = val_f1 torch.save(model.state_dict(), 'best_model.pt') patience = 0 else: patience += 1 if patience >= 3: breakpatience=3表示验证集 F1 连续三个 epoch 不创新高就停,省时间也防过拟合。
4.4 论文里缺三行数字:收 log、固定种子、定 best epoch
现象:实验跑完了,写论文时发现某个模型的准确率、F1 忘了记录,只能重新再跑一遍,运气不好又是几小时。
原因:训练时只在控制台看了输出,没存 log,也没保存模型,所有过程信息都丢了。
解决:从一开始就把每次实验的完整输出落到文件里:
python train.py --model textcnn --seed 42 | tee logs/textcnn_seed42.logtee同时把输出打到屏幕和 log 文件。日志文件按“模型名+种子编号”命名,后面论文里的每个数字都能对应到一次具体的训练记录。模型的最终权重也同步保存好,测试时用同一个 checkpoint,不重新训练:
model.load_state_dict(torch.load('best_model.pt')) y_pred = model.predict(test_texts)这样一来,论文实验表格里的每一行都能追溯到原始 log,答辩被追问时你能拿得出证据。
5. 把结果写成能毕业的论文:指标口径、对比实验与图表规范
模型跑通了,实验数据也有了,剩下最重要的一步是把它组织成一篇能通过评审的论文。很多人的代码能力没问题,论文却被反复打回,问题往往出在指标口径不统一、实验对比没有逻辑、图表不规范。
5.1 评测指标的三个口径:准确率、macro F1 与谣言类召回率
我见过不少毕业设计论文只写一个准确率,这是最容易被打回的点。谣言检测场景下类别不平衡,单独报告准确率说服力不够。评审老师第一眼会找 F1,第二眼会找谣言类的精确率和召回率。
调参阶段用验证集选模型,到论文里报告的数字必须以测试集上的一次正式评测为准,测试集不能反复用来调参,否则就是变相过拟合。
from sklearn.metrics import classification_report, confusion_matrix y_true = test_labels y_pred = loaded_model.predict(test_texts) print(classification_report(y_true, y_pred, target_names=['非谣言', '谣言'], digits=4)) print(confusion_matrix(y_true, y_pred))报告宏平均 F1(macro F1)而不是加权平均,因为宏平均对少数类更公平,能反映谣言类真实表现。三个口径里,“谣言类召回率”尤其值得单独写进论文摘要,这在谣言检测里比准确率更有业务意义。
5.2 对比实验与消融实验:用一张表讲完整个故事
毕业论文的实验章不能只有模型 A 效果 95%、模型 B 效果 96% 这种堆数字。标准写法是先用一张表给出基线与主模型的对比,再用消融实验说明每个模块的贡献。
常用方法名称与指标示意如下,数字以你自己复现结果为准:
| 方法 | 准确率 | macro F1 | 谣言类召回率 | 说明 |
|---|---|---|---|---|
| TF-IDF + LR | 0.87 | 0.74 | 0.68 | 基线 |
| TextCNN | 0.91 | 0.82 | 0.76 | 主线 |
| 预训练模型 | 0.93 | 0.87 | 0.82 | 上限 |
| TextCNN(去掉清洗) | 0.88 | 0.76 | 0.70 | 消融 |
表格的意义是让读者一眼看出三条信息:传统方法能到多少,深度模型比传统方法提升多少,清洗流程对结果贡献多少。消融实验每次只去掉一个环节,证明这个环节确实起作用。
5.3 图表与行文规范:从混淆矩阵到训练曲线的写法
论文里的图要保证矢量输出,不要用手机拍屏幕的截图。混淆矩阵是谣言检测论文必备图:
import matplotlib.pyplot as plt from sklearn.metrics import ConfusionMatrixDisplay ConfusionMatrixDisplay.from_predictions(y_true, y_pred, display_labels=['非谣言', '谣言'], cmap='Blues') plt.savefig('figures/confusion_matrix.pdf', bbox_inches='tight')bbox_inches='tight'能裁掉多余白边。保存成 PDF 而不是 PNG,印刷和放大都不会糊。
行文上有几个习惯可以帮你减少被打回的概率:数据集介绍单独一个小节,写清来源、总条数、正负比例、切分方式;损失函数和优化器配置若压缩包里的原论文没写,你在自己的论文里补齐;结论章只做工作总结和不足分析,不临时引入新数据和未跑的结果。
6. 再进一步:把时效信号和半监督加进谣言检测
如果你做完基础方案还有余力,这里有个很实用的进阶方向:给模型加上时间信号。谣言最核心的特性是时效性,一条内容在某个时间点是谣言,官方辟谣发布后同样一句话就不再是谣言。如果把发布时间充分利用起来,模型能学到“这条文本出现在辟谣前后,可信度不同”的规律。
一个简单的做法是构造时间差值特征,拼接进模型:
df['hours_since_first'] = (pd.to_datetime(df['publish_time']) - pd.to_datetime(df['first_seen_time'])).dt.total_seconds() / 3600hours_since_first表示这条文本距离该事件首次出现过去了多少小时。谣言扩散早期,文本语气往往更笃定、更情绪化;辟谣之后,再出现的相同内容通常自带“辟谣”“别信”这类词。把这个特征拼到文本特征后面,在开题或论文里讲“多模态特征融合”也很加分。
另一个值得尝试的方向是半监督扩充。公开谣言语料的标注量有限,模型很容易对低频表达不敏感。常见做法是:用已训练模型对无标注样本做预测,把置信度高于阈值、且和已有标注没重叠的样本挑出来加入训练集。
proba = model.predict_proba(unlabeled_texts)[:, 1] pseudo_idx = proba > 0.95 pseudo_texts = unlabeled_texts[pseudo_idx] pseudo_labels = np.ones(pseudo_idx.sum())阈值取 0.95 是为了只保留模型非常有把握的样本。半监督在谣言检测上有效的前提是:只加正类(谣言)样本或只加高置信度样本,宁缺毋滥,否则伪标签噪声会把模型带偏。
我做完这套题之后最大的习惯转变是:先定指标口径,再做数据清洗,最后才碰模型结构。顺序反了的人,往往在调模型上耗掉大半学期。你按这个顺序走,即便中间某个环节翻车,也能快速定位问题出在数据还是评估,而不是在原地怀疑模型玄学。希望帮到你。
本文还有配套的精品资源,点击获取