简介:面向自然语言处理方向学生的本科毕业设计论文资源,针对中文网络谣言识别问题,提供从数据清洗、分词、特征提取到文本分类建模的完整实现方案。项目基于清华大学开源中文谣言数据集,将谣言检测定义为二分类任务,覆盖词袋模型、TF-IDF、主题模型等特征构建方法,以及逻辑回归、SVM、随机森林等分类算法,并包含准确率、精确率、召回率与F1等评估指标。资源共28个文件,以12个Python脚本为主体,辅以11个文本文件、4个JSON数据和1个Markdown说明文档,分别对应模型代码、预处理后的语料与中间结果、项目使用说明,压缩包整体仅4.93MB。已有59人学习下载。借助该资源可以快速复现中文谣言检测实验,了解自然语言处理在虚假信息治理中的落地流程,也可作为毕业设计、课程项目或算法对比研究的参考基线。
1. 中文谣言检测:为什么文本二分类不够用
中文谣言检测在工程上并不是一个“喂数据出模型”的简单二分类任务。同样一句“某地发生事故”,换一个时间戳、换一个发布账号,传播路径完全不同;而且谣言文本往往在事件早期和辟谣信息高度相似,仅仅靠词频统计很难拉开差距。做毕业设计时最容易被卡住的地方,不是选什么算法,而是数据怎么洗、特征怎么对齐、标签从哪来——这套中文谣言检测项目恰好把整条链路都拆开了:从清华开源的中文谣言数据集 Chinese_Rumor_Dataset 出发,依次完成数据合并、标签化、分词、停用词过滤、TF-IDF 向量化、主题建模、逻辑回归分类和聚类分析。对于想复现一个完整 NLP 文本分类流程的从业者,或者准备把“文本分类”作为毕设主题的学生,这份资源的价值在于它是一份可以逐文件对照执行的全过程代码包。
2. 从原始语料到干净特征:谣言库的预处理链路
2.1 数据集长什么样:Chinese_Rumor_Dataset 与 datap 目录
项目的数据源头是清华大学开源的中文谣言数据集,仓库地址为 https://github.com/thunlp/Chinese_Rumor_Dataset.git 。这个数据集的最大特点不是“量大”,而是字段结构完整:每条记录包含原始文本、事件标签、时间戳、发布者信息等。实际训练时并不会直接使用 GitHub 上的原始 json 文件,而是先在本地做一轮清洗,把散落在不同目录下的数据统一合并成alldata.json,再进一步拆出用于监督学习的data.json和data_1.json。
这里要理解datap目录的作用。它存放的是经过二次处理的中间产物,包括classList.txt(类别标签列表)、textList.txt(文本列表)、logPro.txt(概率对数结果)、tfidfPro.txt(TF-IDF 特征结果)、scoreList.txt(得分列表)、tagList.txt(标签列表)等。这些 .txt 文件是 Python 脚本运行时生成的检查点,不是人工维护的静态资源。文本分类项目最容易犯的错是把中间产物当成训练数据,正确做法是先搞清每个文件的写入方和消费方。本项目中生成方是1_wholedataProcess.py、2_addtag.py、3_1_rumorProcess.py,消费方是5_x系列和6_x系列脚本。
2.2 数据处理的执行管线:1_wholedataProcess.py 到 3_2_featureProcess.py
整个项目的脚本命名有清晰的主线编号,执行顺序从 1 到 8。对应数据处理的脚本关系如下:
| 脚本 | 职责 | 主要输出 |
|---|---|---|
1_wholedataProcess.py | 合并原始 json,统一字段 | alldata.json |
2_addtag.py | 为每条文本打谣言/非谣言标签 | data.json、标签列表 |
3_1_rumorProcess.py | 提取谣言正样本并格式化 | 清洗后的谣言样本集 |
3_2_featureProcess.py | 文本特征初筛、统计 | 特征词文件与统计信息 |
5_1_preTM.py/5_2_stopwordTM.py | 分词前准备、停用词加载 | 分词中间语料 |
5_3_jiebaTM.py | jieba 分词 | jieba.txt |
5_4_tfidfTM.py | TF-IDF 向量化 | tfidfPro.txt |
6_1_preFM.py | 特征矩阵预处理 | 可直接训练的特征矩阵 |
6_2_logisticFM.py | 逻辑回归训练与预测 | logPro.txt |
7_clusterModel.py | 聚类分析 | 聚类标签与中心 |
8_finalModel.py | 最终模型评估 | 评估结果与最终输出 |
按这个顺序执行,前四个脚本起着“数据入口”的作用。1_wholedataProcess.py里最常见的操作是把原始数据集中的多个 json 块拼成一个数组,再统一键名。以实际数据为例,原始记录中谣言和非谣言字段的命名可能不一致,直接读入会报 KeyError。处理这类问题的代码模式如下:
import json raw_data = [] for line in open("original.json", "r", encoding="utf-8"): line = line.strip() if not line: continue try: item = json.loads(line) # 统一字段名:原始数据里可能叫 "text" 也可能叫 "content" text = item.get("text") or item.get("content") or "" label = item.get("label") or item.get("isRumor") or "unknown" raw_data.append({"text": text, "label": label}) except json.JSONDecodeError: continue with open("alldata.json", "w", encoding="utf-8") as f: json.dump(raw_data, f, ensure_ascii=False, indent=2)这里的关键点是 json 解析时的健壮性。很多入门实现直接json.load()整个文件,遇到一个坏行就整体崩溃。更稳妥的做法是逐行读取并捕获异常,坏数据跳过不是“偷懒”,而是谣言文本本身噪声极大——网页抓取的文本里混着表情符号、URL、@用户等噪声,不能指望数据源是干净 JSON。脚本整体对文本类型采用ensure_ascii=False,避免中文被转成\uXXXX造成下游读取不便。
2.3 停用词表与 jieba 分词:中文分词的边界
中文谣言检测里分词的结果直接影响特征质量。5_2_stopwordTM.py会读取stopword.txt和stop_word.txt两个停用词文件。保留两份不同命名的文件,说明来源不同——一份来自公开停用词表,一份是自定义补充。常见做法是将两者合并去重,形成一套“基础停用词 + 领域自定义停用词”。
jieba 分词在这个项目中承担主力。默认jieba.cut(text)采用精确模式,适合文本分类场景。但谣言文本容易混入“@用户”“#话题#”等半结构化内容,直接分词会产生大量无效 token。一个合适的分词与清洗过程如下:
import jieba import re stopwords = set() for w in open("stopword.txt", "r", encoding="utf-8"): w = w.strip() if w: stopwords.add(w) def clean_and_tokenize(text): # 去掉 URL、@用户、#话题# 等半结构化噪声 text = re.sub(r"http\S+", "", text) text = re.sub(r"@\S+", "", text) text = re.sub(r"#\S+#", "", text) # 只保留中文、英文和数字,中文标点直接丢弃 text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", " ", text) words = jieba.lcut(text) return [w for w in words if w.strip() and w not in stopwords]停用词过滤不能做得太激进。“不”“没”“难道”这类否定词在谣言文本中往往是关键线索,例如“医院辟谣称没有死人”和“医院确认有人死亡”,去掉否定词后语义完全翻转。因此用通用停用词表时长词要保留,短词要谨慎。实践中的经验是自定义停用词表主要覆盖无效虚词和人称代词,而不是所有两个字的词。分词完成后,每一条文本对应一串词列表,写入jieba.txt供后续 TF-IDF 使用。
3. TF-IDF 与主题特征:谣言分类的特征工程
3.1 为什么词袋模型之后还要 TF-IDF
文本不能直接输入逻辑回归,需要先转成数值向量。最简单的词袋模型统计每个词在文档中出现的次数,但这样做有两个问题:高频无意义词权重过大,长文档天然更容易获得高分。TF-IDF 通过“词频 × 逆文档频率”来削弱常见词的干扰,突出在少数文档中出现但能区分类别的词。设想谣言文本里经常出现“紧急”“扩散”“千万别转”这类情绪性词汇,同时文章正文里“记者”“来源”等词频繁出现,TF-IDF 可以让这两类词的权重明显分开。
项目里5_4_tfidfTM.py承担了 TF-IDF 向量化的任务,tfidfPro.txt保存计算结果。用 scikit-learn 实现时,TfidfVectorizer本身就支持最大特征数、ngram 范围等参数控制。具体实现可以写成:
from sklearn.feature_extraction.text import TfidfVectorizer import joblib corpus = [] with open("jieba.txt", "r", encoding="utf-8") as f: for line in f: line = line.strip() if line: corpus.append(line.replace(" ", " ")) vectorizer = TfidfVectorizer( max_features=10000, # 只保留 TF-IDF 值最大的 1 万个词,控制维度 ngram_range=(1, 2), # 保留单个词和相邻双词,捕捉“不实消息”这类短语 min_df=2, # 词至少在 2 篇文档中出现,过滤垃圾 token max_df=0.9, # 出现在 90% 以上文档中的词视为通用词,丢弃 sublinear_tf=True # tf 取 1+log(tf),降低热门词的绝对优势 ) X = vectorizer.fit_transform(corpus) joblib.dump(vectorizer, "tfidf_vectorizer.pkl")关键参数值得展开解释。max_features=10000是维度上限,原始词典规模可能在 5 万以上,但低频词对逻辑回归几乎没有贡献,反而增加过拟合风险。min_df=2表示只在一条文本中出现过的词直接丢弃。max_df=0.9是反向过滤,比如“我们”“大家”这类在大多数文档中都出现的词,对区分谣言没有意义。sublinear_tf用对数平滑削弱高词频差异,是一种在短文本分类中效果稳定的常用配置。
ngram_range=(1, 2)在谣言检测中尤其重要。单字词“谣”没有意义,双字词“辟谣”才有区分度;“有”“人”分开看无意义,合在一起“有人”在谣言文本里往往连接“看到”“听到”等传闻性表达。双词组的引入代价是维度显著增加,因此配合max_features一起使用。
3.2 主题模型:把文档折叠成话题分布
TF-IDF 给每个词一个权重,但词与词之间的关联没有被利用。主题模型(Topic Modeling)则把文档视为多个话题的混合体,输出的是“这篇文本有多大比例在谈卫生事件、多大比例在谈政策回应”。项目中5_1_preTM.py到5_4_tfidfTM.py的主题建模部分,用 LDA 为每条文本额外生成一组主题分布特征,再拼接到 TF-IDF 特征矩阵后面。
LDA 是生成式模型,对输入格式有要求——它希望输入是“词袋”形式,而不是 TF-IDF 权重矩阵。gensim 的实现方式很直接:
from gensim import corpora, models # dictionary_corpus 是上一步分词结果,格式为 list[list[str]] dictionary = corpora.Dictionary(doc_list) # 过滤低频词和超高频词,保持主题独立性 dictionary.filter_extremes(no_below=3, no_above=0.8) bow_corpus = [dictionary.doc2bow(doc) for doc in doc_list] lda_model = models.LdaModel( corpus=bow_corpus, id2word=dictionary, num_topics=20, # 主题数量,语料量小就少设 iterations=50, passes=10 ) topic_features = lda_model[bow_corpus]num_topics=20是经验起点。中文谣言数据集规模通常在几万条量级,20 个主题足够覆盖突发事件、医疗健康、食品安全等常见类别。主题数太多会出现一个主题只对应少数文档,逻辑回归反而学到噪声。no_above=0.8和 TF-IDF 里的max_df目的一致,都是丢弃出现在绝大多数文档中的通用词,否则 LDA 会把它们单独抽成一个“废话主题”。
主题特征可以和 TF-IDF 特征横向拼接,也可以在分类后做概率融合。简单做法是把 topic_features 转为稠密矩阵,用scipy.sparse.hstack与 TF-IDF 稀疏矩阵拼接后喂给逻辑回归。
3.3 特征融合的维度管理与实际效果
把 TF-IDF 和主题特征拼接后,特征矩阵从稀疏变半稀疏,逻辑回归训练速度下降,但准确率通常会提升。这是因为谣言文本的判别线索往往同时依赖词汇级别和话题级别的信息——词汇级别抓“造谣”“求证”这样的关键词,话题级别抓“文本属于哪个事件背景”。类不平衡问题在这里开始显现:非谣言样本通常远多于谣言样本,模型会倾向于把所有样本判成多数类。
| 特征来源 | 特征含义 | 对区分谣言的典型贡献 |
|---|---|---|
| TF-IDF 词权重 | 词汇层面的判别性 | 抓住“辟谣”“紧急”“求证”等高频词 |
| TF-IDF 双词组 | 短语层面的共现模式 | 识别“经核实”“不实消息”等固定搭配 |
| LDA 主题分布 | 文档级话题归属 | 结合事件背景判断文本是否属于官方通报 |
| 文本长度特征 | 字数、句子数 | 谣言文本往往短且情绪化 |
| 符号特征 | 感叹号、问号数量 | 情绪表达强度是辅助信号 |
这里有一个容易被忽略的实现细节:拼接特征后必须记录特征名列表。如果后续要输出“哪些词对谣言判断影响最大”,特征名映射一旦丢失,权重系数就无法对应到具体词。因此训练前保存一份feature_names = vectorizer.get_feature_names_out(),拼接后维护一个完整的特征名列表,这对答辩展示和后续调参都很关键。
4. 分类模型、评估与调参:逻辑回归的实际表现
4.1 为什么选逻辑回归而不是 SVM 或随机森林
项目最终分类器用的是逻辑回归,而不是 SVM 或随机森林。逻辑回归在文本分类任务里的优势很明确:高维稀疏特征下训练快,模型输出是概率值,可以直接用于置信度过滤和阈值调整。SVM 适合小样本高维场景,但训练复杂度随样本量增长明显变慢;随机森林对稀疏特征不敏感,很容易在小样本下过拟合。逻辑回归还有一个附加价值——权重系数可解释,每个词的权重直接反映它对谣言倾向的贡献方向。对于毕业设计答辩来说,能说清楚“模型为什么判断这条文本是谣言”非常重要。
6_1_preFM.py的作用是把前面得到的tfidfPro.txt、主题特征、标签文件整合为一个X_train, y_train可直接训练的数据结构。这里常见的坑是训练集和测试集特征维度不一致,原因是特征矩阵的构建过程没有先 fit 再 transform,而是对全量数据一起 fit 了。正确做法是先用训练集 fit 向量化器,再用训练好的向量化器 transform 测试集。
4.2 逻辑回归训练与概率输出:6_2_logisticFM.py 和 8_finalModel.py
6_2_logisticFM.py里的核心逻辑可以用以下代码表示:
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, classification_report import joblib X_train, X_test, y_train, y_test = train_test_split( X_combined, y_labels, test_size=0.2, random_state=42, stratify=y_labels ) model = LogisticRegression( C=1.0, # 正则化强度的倒数,越小正则越强 class_weight="balanced", # 自动根据类别占比调整权重 solver="liblinear", # 适合中小规模稀疏数据,L1/L2 均可 max_iter=1000 ) model.fit(X_train, y_train) y_pred = model.predict(X_test) y_prob = model.predict_proba(X_test)[:, 1] print(classification_report(y_test, y_pred)) joblib.dump(model, "logistic_rumor_model.pkl")class_weight="balanced"是处理谣言负样本占比过大的常用手段。它按照n_samples / (n_classes * np.bincount(y))为每个类自动分配权重,让少数类样本在损失函数中获得更高权重。solver="liblinear"适合几万维特征、几万条样本的场景,如果样本量到百万级,可以换saga。C=1.0是默认值,在文本分类里往往需要调小到 0.1 甚至 0.01,因为一万维以上的特征矩阵非常稀疏,逻辑回归很容易在少量强特征上过度自信,更强的 L2 正则能压低这种风险。
4.3 评估指标与阈值调整的细节
项目在评估阶段输出准确率、精确率、召回率和 F1 分数,这些指标在类别不平衡时的含义需要准确理解。准确率高不代表模型好——如果谣言样本只占 10%,全预测为“非谣言”也有 90% 准确率。真正需要关注的是谣言类(正类)的召回率和精确率。
| 指标 | 含义 | 谣言检测场景下的优先级 |
|---|---|---|
| Accuracy | 全部样本中预测正确的比例 | 仅供参考,容易虚高 |
| Precision | 预测为谣言中真实谣言的比例 | 高,避免误伤正常发言 |
| Recall | 真实谣言中被模型找出的比例 | 最高,漏检比误报后果更严重 |
| F1 | 精确率和召回率的调和平均 | 平衡指标,模型选型时用 |
通常的做法是直接使用class_weight="balanced"之后再观察 F1 值。如果谣言类召回率仍然太低,可以考虑调整决策阈值,逻辑回归默认以 0.5 为分界,但在不平衡场景下应改为在验证集上搜索最优阈值:
import numpy as np best_threshold = 0.5 best_f1 = 0.0 for threshold in np.arange(0.3, 0.7, 0.05): y_pred_adj = (y_prob >= threshold).astype(int) f1 = f1_score(y_test, y_pred_adj, pos_label=1) if f1 > best_f1: best_f1 = f1 best_threshold = threshold print(f"best threshold = {best_threshold:.2f}, F1 = {best_f1:.4f}")阈值搜索必须在验证集进行,不能用测试集。先划分训练集、验证集、测试集,用训练集拟合参数,用验证集选择阈值,最后才能用测试集报告最终效果。毕业设计里常见的问题是直接拿全部数据求最优阈值,导致结果虚高,真实泛化能力无从知晓。
5. 混淆矩阵之外:谣言检测落地时的四类典型毛病
最后一章不讲新模型,聊四个在复现这套代码时最容易翻车的工程问题。
第一个是特征泄漏。2_addtag.py在合并标签时,如果处理的是带时间顺序的事件数据,直接用train_test_split(random_state=42)随机划分会把同一条事件的爆发期和辟谣期文本分别放进训练集和测试集。谣言检测任务是时间敏感的,正确做法是按事件 ID 或时间戳分组划分,保证同一条事件的文本不会跨集合出现。当年做复现时被这一点坑过:模型评估 F1 有 0.9,换到新时间段的语料后直接掉到 0.6。
第二个是分词歧义处理。谣言文本里有很多网络新词和缩写,例如“yyds”“u1s1”,jieba 默认词典不认识,会拆成单个字母数字。解决方法是在分词前加载自定义词典,把网络热词强制视为一个 token。jieba.add_word("yyds")之后,该词在 TF-IDF 里成为一个独立特征,否则“y”“yd”“ds”这类碎片会成为噪声特征。
第三个是类别标记的置信度问题。tagList.txt里的标签来自原始数据集的标注规则,但标注本身存在不确定性——同一事件的文本,发布时间和语境变化可能导致标注结果不同。训练前应当对标签分布做一次可视化和统计,如果某个事件下谣言与非谣言比例极端失衡,建议单独分析而不是直接纳入训练。这是“数据质量优先于模型复杂度”最典型的表现。
第四个是特征文件的重新生成策略。tfidfPro.txt、logPro.txt这类中间文件一旦生成,重新运行后不会自动更新。如果改动分词或停用词逻辑,必须删除旧的中间文件再从 5_x 系列脚本开始重新执行。项目中脚本按编号依次执行,但编号之间的文件依赖关系没有用 Makefile 或 shell 脚本固化,实际运行时容易漏跑。复现时最省心的方式是按照编号顺序完整跑一遍,观察每个 .txt 是否生成成功,再进入下一阶段。
整个项目最后可以通过8_finalModel.py输出分类报告和若干条谣言样本的预测概率,用这些概率分布反推模型在哪类文本上犹豫不决,再针对性地补充训练样本或调整特征。这才是中文谣言检测项目里真正值得沉淀的部分——特征和模型都能抄,而排错和迭代的思路,只能在实际运行中积累。
本文还有配套的精品资源,点击获取