中文谣言检测从数据清洗到模型评估的完整实践指南 简介面向中文谣言检测研究与毕业设计场景这份压缩包提供了完整的算法实现与论文配套材料。内容围绕谣言识别核心流程展开覆盖语料预处理、停用词过滤、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 GPUtextcnn 也能在 CPU 上跑但 epochs 要相应减少或者把训练集裁小一截先验证流程通不通。2.3 数据集选型字段、规模与划分方式决定了后续所有工作代码可以改模型可以换但数据集选不好后面全部白搭。中文谣言检测的公开语料大部分来自微博字段通常包含文本内容、标签谣言还是非谣言、发布时间有时候还有事件编号或转发量。先看数据长什么样import pandas as pd df pd.read_csv(data/rumor.csv, encodingutf-8-sig) print(df.dtypes) print(df[label].value_counts(normalizeTrue))encodingutf-8-sig是为了兼容带 BOM 的 CSV用默认utf-8读会解析出\ufeff这种隐藏字符normalizeTrue直接输出两类占比一眼就能看出数据是不是高度不平衡。字段结构里最容易被忽略的是事件编号。微博上一个谣言事件往往有几百条转发文本如果直接随机划分训练集和测试集同一个事件下的相似文本会同时出现在两边模型等于“开卷考试”测试分数虚高。正确的做法是让同一事件的所有文本都落在同一侧from sklearn.model_selection import GroupShuffleSplit gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, test_idx next(gss.split(df, df[label], groupsdf[event_id])) train_df df.iloc[train_idx] test_df df.iloc[test_idx]groups参数接收的是每条样本的事件编号切分时保证整组进训练或整组进测试。random_state42固定切分结果后面每次跑实验都用同一个种子结果才可比较。这一步不做到后面验证集指标忽高忽低时你会把大量时间浪费在排查模型上而问题其实出在数据划分。3. 从清洗到可训练样本中文文本的特征工程与模型输入数据集确定之后下一步是把原始文本变成模型能吃的东西。中文 NLP 的预处理和英文最大的区别在于没有天然空格分词符号和话题标签的干扰模式也不一样。这一章要解决的是“文本怎么洗、特征怎么提、模型怎么选”三件事。3.1 清洗顺序很关键话题、用户、链接和特殊符号的逐项处理微博文本里满是#话题#、用户、URL 和各种表情符号这些内容对谣言判定几乎没有正向贡献但会直接污染词向量和 TF-IDF 特征。我一般按固定顺序处理import re import jieba STOP_WORDS set(open(data/stopwords.txt, encodingutf-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(rhttps?://\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_features50000, ngram_range(1, 2), sublinear_tfTrue), LogisticRegression(max_iter1000) ) pipe.fit(train_texts, train_labels)max_features50000限定特征维度防止词表过大致使矩阵膨胀ngram_range(1, 2)同时考虑单词和相邻词组合因为谣言里“已辟谣”“紧急通知”这类双词搭配比单字更有语义sublinear_tfTrue对词频做对数压缩避免高频词主导相似度。如果后面要接深度模型Word2Vec 预训练是过渡方案。一个常见的做法是用 gensim 在自己的训练语料上训一套词向量然后把一条文本的所有词向量取平均作为句子向量from gensim.models import Word2Vec w2v Word2Vec(sentencestokenized_sentences, vector_size100, window5, min_count2, workers4) 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] * 100window5控制上下文窗口大小谣言里关键的触发词往往在前后几句话里min_count2过滤出现次数过少的词避免噪声词影响向量质量。这里要明确一点Word2Vec 平均向量在论文里是给深度模型做对比用的不是最终方案。3.3 深度模型怎么选TextCNN 打底、预训练模型兜底的组合深度模型我首选 TextCNN。它不是效果最好的但训练快、显存占用低、调参空间清晰非常适合作为毕业论文的主模型。下面是一份基础实现import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim128, num_filters128, filter_sizes(2, 3, 4), num_classes2): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) 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_idx0让填充位始终是零向量不参与词向量更新filter_sizes(2, 3, 4)分别抓取二元词组、三元词组和四元词组特征谣言经常以“据说”“紧急通知”这类固定短语开头多尺寸卷积核能同时捕捉不同长度的局部模式num_filters128是每个卷积核的输出通道数数值越大表达力越强但训练也越慢。训练循环里最值得调的参数是学习率和 Dropoutcriterion nn.CrossEntropyLoss(weightclass_weights) optimizer torch.optim.Adam(model.parameters(), lr2e-3)class_weights来自训练集标签占比的逆频率用来缓解谣言类样本偏少的问题。后面在避坑章里会专门讲这一步为什么关键。预训练模型作为上限验证使用和 TextCNN 是一个互补关系from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) model AutoModelForSequenceClassification.from_pretrained(bert-base-chinese, num_labels2)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[非谣言, 谣言], digits4))digits4保留四位小数论文里引用数据时精度足够。与此同时训练时给损失函数加上类别权重让模型对少数类更敏感from sklearn.utils.class_weight import compute_class_weight import numpy as np class_weights compute_class_weight(class_weightbalanced, classesnp.array([0, 1]), ytrain_labels) criterion nn.CrossEntropyLoss(weighttorch.tensor(class_weights, dtypetorch.float32))这一步能让谣言类的召回率显著提升。之后评估指标以 macro F1 为准而不是准确率。4.2 验证集指标忽高忽低随机种子与事件级泄漏的排查顺序现象同一份代码啥都没改连着跑三次验证集 F1 能差出五个百分点。原因一种是 PyTorch 没固定随机种子模型初始化参数每次不同另一种是数据划分是随机切分同一个谣言事件下的几百条相似转发同时出现在训练集和验证集里验证时模型其实在“背答案”指标自然虚高且不稳。解决先把随机种子钉死import random import numpy as np import torch def set_seed(seed42): 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 模型跑到第二个 epochCUDA out of memory或者 CPU 机器上跑了好几个小时一个 epoch 都没结束。原因序列长度设了 512batch size 设了 32显存直接打爆。CPU 机器上硬跑预训练模型本身也不现实。解决按显存量级调整序列长度和 batch size。下面是一个参考量级序列长度batch size适用显存128326~8 GB256168~10 GB512812 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: breakpatience3表示验证集 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[非谣言, 谣言], digits4)) print(confusion_matrix(y_true, y_pred))报告宏平均 F1macro F1而不是加权平均因为宏平均对少数类更公平能反映谣言类真实表现。三个口径里“谣言类召回率”尤其值得单独写进论文摘要这在谣言检测里比准确率更有业务意义。5.2 对比实验与消融实验用一张表讲完整个故事毕业论文的实验章不能只有模型 A 效果 95%、模型 B 效果 96% 这种堆数字。标准写法是先用一张表给出基线与主模型的对比再用消融实验说明每个模块的贡献。常用方法名称与指标示意如下数字以你自己复现结果为准方法准确率macro F1谣言类召回率说明TF-IDF LR0.870.740.68基线TextCNN0.910.820.76主线预训练模型0.930.870.82上限TextCNN去掉清洗0.880.760.70消融表格的意义是让读者一眼看出三条信息传统方法能到多少深度模型比传统方法提升多少清洗流程对结果贡献多少。消融实验每次只去掉一个环节证明这个环节确实起作用。5.3 图表与行文规范从混淆矩阵到训练曲线的写法论文里的图要保证矢量输出不要用手机拍屏幕的截图。混淆矩阵是谣言检测论文必备图import matplotlib.pyplot as plt from sklearn.metrics import ConfusionMatrixDisplay ConfusionMatrixDisplay.from_predictions(y_true, y_pred, display_labels[非谣言, 谣言], cmapBlues) plt.savefig(figures/confusion_matrix.pdf, bbox_inchestight)bbox_inchestight能裁掉多余白边。保存成 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 是为了只保留模型非常有把握的样本。半监督在谣言检测上有效的前提是只加正类谣言样本或只加高置信度样本宁缺毋滥否则伪标签噪声会把模型带偏。我做完这套题之后最大的习惯转变是先定指标口径再做数据清洗最后才碰模型结构。顺序反了的人往往在调模型上耗掉大半学期。你按这个顺序走即便中间某个环节翻车也能快速定位问题出在数据还是评估而不是在原地怀疑模型玄学。希望帮到你。本文还有配套的精品资源点击获取