中文谣言检测实战:从特征工程到模型部署的完整指南
简介:一份围绕中文谣言检测的本科毕业设计完整资料,面向自然语言处理、机器学习方向的毕设学生与研究者。压缩包以论文配套代码为核心,覆盖从微博等平台数据爬取、预处理、分词、停用词过滤,到TF-IDF特征构建及逻辑回归、聚类、最终分类模型训练的完整流程。研究同时涉及谣言演化分析、实时检测机制与人工+自动应对策略,可帮助读者复现实验并理解中文谣言识别的关键环节。
整包共26个文件,含12个Python脚本、9个txt词典与中间结果、4个json数据集和1个readme说明文档,仅4.93MB,目录按数据处理、特征工程、模型训练分阶段编号(如wholeDataProcess、featureProcess、jiebaTM、tfidfTM、logisticFM),便于逐步对照学习。txt/json文件为各阶段提供停用词表、语料与特征数据支撑。
目前已有125人学习下载,对正在做中文谣言检测或其他NLP文本分类课题的本科生、开发者有直接参考价值。
1. 中文谣言检测的第一步:把论文题目翻译成可执行的任务
Chinese Rumor Recognition 对应到本科毕设,通常就是“中文谣言检测”:给一条中文文本,预测它是真实消息还是谣言。每年 GitHub 上都会冒出同名的毕业设计压缩包,里面内容大同小异——一份论文、一份实验代码、一个演示界面。但真正动手复现过的人会承认,这题的难点从来不在模型本身,而在数据标注口径不统一、类别不平衡、短文本噪声大这三件事上。这篇博客按毕设项目最常见的落地路径展开:先想清楚任务边界,再处理数据和特征,然后对比基线与深度模型,最后把评估指标和演示接口做扎实,末尾补几个答辩前真正用得上的技巧。适合正在复现或准备开题的学生,也适合需要把这套流程迁移到内容审核场景的工程师。
2. 中文谣言检测的数据准备:从原始标注到能训练的特征
2.1 常见中文谣言数据集有哪些,读进来要注意什么
做中文谣言检测,第一步是拿到带标签的文本。常见做法是直接用公开的微博谣言数据集,通常包含“真/假”二分类标签和原始微博文本;也有从辟谣平台(如腾讯较真、微博辟谣)抓取整理的三分类数据,标签为“真实/谣言/未证实”。这些数据集在 GitHub 上以 ChineseRumorDataset、微博谣言等关键词可以搜到,格式多为 CSV 或 JSON。
| 数据集方向 | 标签体系 | 文本来源 | 典型字段 | 使用注意 |
|---|---|---|---|---|
| 微博谣言数据集 | 真/假 二分类 | 微博短文本,带话题标签 | text、label、转发数、发布时间 | 需人工复核,存在历史时效性 |
| 辟谣平台整理集 | 真实/谣言/未证实 | 辟谣栏目长文 | title、content、verdict | 长文本为主,与微博短文本分布差异大 |
| 爬虫自建集 | 自定义 | 特定时间窗口的微博 | text、举报数、评论内容 | 标注成本高,批次一致性难控制 |
读数据时最常见的问题一是编码,二是标签不统一。中文 CSV 用 UTF-8 写、Excel 打开再另存,常变成 GBK,读取时用 encoding="utf-8-sig" 或 gbk 逐个试。标签字段里“谣言”和“假”可能是同一个含义,必须先统一映射:
import pandas as pd
df = pd.read_csv("rumor_data.csv", encoding="utf-8-sig")
df["label"] = df["label"].map({"真": 0, "真实": 0, "假": 1, "谣言": 1})
df = df.dropna(subset=["text"])
df = df.drop_duplicates(subset=["text"])
print(df["label"].value_counts())
这段代码做了三件事:统一二分类标签、剔除空文本、按文本内容去重。去重这一步尤其重要,因为微博谣言数据里大量存在转发的重复文案,不去重会导致训练集和测试集之间有同文重叠,模型表现虚高,答辩时被问到就能直接答出这个坑。做三分类同理,把“未证实”映射成单独的数字即可。建议在论文里单独写一段“数据清洗与标注口径说明”,把每条规则列出来,这比堆模型细节更容易拿分。
2.2 中文文本清洗:为什么先做保守的正则再做分词
中文谣言检测的文本清洗要克制。很多教程上来就删掉所有标点、表情、URL,但对谣言检测这个任务,URL 可以作为信源特征,感叹号数量也能反映情绪强度。我一般只做最小清洗:去掉微博话题标签、@用户、URL,保留中英文、数字和常用标点,再交给分词器。
import re
import jieba
def clean_text(text: str) -> str:
text = re.sub(r"#.*?#", "", text) # 去掉 #话题#
text = re.sub(r"@[\w\u4e00-\u9fa5]+", "", text) # 去掉 @用户
text = re.sub(r"https?://\S+", "", text) # 去掉 URL
text = re.sub(r"\s+", " ", text)
return text.strip()
def load_stopwords(path: str) -> set:
with open(path, encoding="utf-8") as f:
return {line.strip() for line in f}
def cut_and_filter(text: str, stopwords: set) -> list:
words = jieba.lcut(text)
return [w for w in words if w.strip() and w not in stopwords and len(w) > 1]
这里用 jieba.lcut 而不是 jieba.cut ,前者直接返回 list,省一次转换。停用词表网上有现成的中文停用词表,我通常再手动追加“转发、分享、链接”这类对谣言判断无意义的词。 len(w) > 1 会滤掉单字,但对“假、真、毒”这类单字情感词可能误伤,所以如果你的数据里单字词占比高,这一步可以取消。
分词结果建议直接落盘成中间文件,比如 train_seg.txt ,每行一条文本的空格分词结果。这样一来,后面切 TF-IDF、Word2Vec、TextCNN 用的是同一份预处理结果,不会因为改了某个环节导致三套特征对不上。
2.3 特征表示:TF-IDF、Word2Vec 和预训练向量怎么搭
特征通常备三套:TF-IDF 适合传统模型,Word2Vec 是深度模型的输入,预训练词向量或 BERT tokenizer 输出是加分项。TF-IDF 用 sklearn 一行搞定,关键是参数:
from sklearn.feature_extraction.text import TfidfVectorizer
seg_train = [" ".join(words) for words in train_corpus]
vectorizer = TfidfVectorizer(
max_features=50000,
ngram_range=(1, 2),
min_df=2,
max_df=0.9,
sublinear_tf=True,
)
X_train = vectorizer.fit_transform(seg_train)
参数说明: max_features=50000 限制特征维度,防止大词表把内存撑爆; ngram_range=(1,2) 让“不是/谣言”这类双词组合也能进入特征空间,对否定句极其关键; min_df=2 去掉只出现一次的单词; max_df=0.9 去掉出现在 90% 文本里的高频噪声词; sublinear_tf=True 用 1+log(tf) 代替原始词频,削弱长文本里高频词的压制。测试集只调用 vectorizer.transform ,绝不重新 fit,这是最常犯的泄漏错误。
Word2Vec 用 gensim 在训练集上自己训:
from gensim.models import Word2Vec
sentences = [cut_and_filter(clean_text(t), stopwords) for t in train_texts]
w2v = Word2Vec(sentences, vector_size=200, window=5, min_count=2, sg=0, epochs=10)
sg=0 表示 CBOW,小数据量下更快更稳; sg=1 是 skip-gram,对低频词更友好。毕设数据量通常只有几万条,用 CBOW 加 epochs=10 就够。训练好的向量喂给 TextCNN 时,记得把 Embedding 层初始化为 w2v.wv.vectors ,先在冻结状态下训几轮,再解冻微调。
3. 中文谣言检测的模型取舍:基线、深度模型与 BERT 微调
3.1 用逻辑回归和 SVM 快速建立基线
拿到 TF-IDF 特征后,先不要急着上深度学习。逻辑回归在中文谣言这种短文本分类上通常已经能拿到可用的 macro-F1,而且可解释性强,能把“哪些词让模型判定为谣言”直接写成答辩材料。建基线用 Pipeline 一步到位:
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
seg_data, y_data, test_size=0.2, random_state=42, stratify=y_data
)
pipe = Pipeline([
("tfidf", TfidfVectorizer(max_features=50000, ngram_range=(1, 2), min_df=2, sublinear_tf=True)),
("clf", LogisticRegression(C=1.0, class_weight="balanced", max_iter=2000)),
])
pipe.fit(X_train, y_train)
class_weight="balanced" 让模型按类别频率反向加权,缓解谣言样本占比低的问题; C=1.0 是默认正则强度,先跑通再在 [0.1, 1, 10] 上网格搜索。逻辑回归的好处是能直接看特征系数:
import numpy as np
coef = pipe.named_steps["clf"].coef_[0]
top_idx = np.argsort(coef)[-15:]
words = pipe.named_steps["tfidf"].get_feature_names_out()[top_idx]
print(list(zip(words, coef[top_idx])))
输出里排在前面的一般是“转发、速看、真相、扩散”这类词。把这些词挑进论文的案例分析段,比单纯贴准确率数字更有说服力。SVM 可以替换分类器做对照,线性核 SVC(kernel="linear") 效果和逻辑回归接近、训练更慢,但有些评审默认“SVM 是经典方法”,放一个对比表即可。
3.2 TextCNN 和 BiLSTM 怎么搭、超参数怎么定
深度模型里 TextCNN 是中文谣言检测最常用的结构:用多个不同宽度的卷积核并行提取局部 n-gram 特征,再池化拼接到一起。对短文本来说,TextCNN 的训练速度和稳定性都优于 BiLSTM,是论文里“深度方法”部分的首选。
from tensorflow.keras.layers import Embedding, Conv1D, GlobalMaxPooling1D, Concatenate, Dropout, Dense
from tensorflow.keras.models import Model
from tensorflow.keras import Input
inputs = Input(shape=(max_len,))
embedding = Embedding(vocab_size, 128, mask_zero=True)(inputs)
convs = [
GlobalMaxPooling1D()(
Conv1D(64, k, activation="relu", padding="same")(embedding)
) for k in (2, 3, 4)
]
x = Concatenate()(convs)
x = Dropout(0.4)(x)
outputs = Dense(2, activation="softmax")(x)
model = Model(inputs, outputs)
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
kernel_size 取 2、3、4,分别对应短语里的 2-gram 到 4-gram; padding="same" 保证每个卷积输出长度一致,否则短句子的特征在池化层会缩水; GlobalMaxPooling1D 对每个卷积核取最大响应,等价于“这段文本里有没有出现这类谣言模式”。词嵌入用上一节训好的 Word2Vec 初始化,比随机初始化收敛快得多。超参经验值列在下面,毕设阶段不用网格搜索,照抄再微调一两个即可:
| 超参数 | 常见取值 | 影响 |
|---|---|---|
| embedding_dim | 100~300 | 数据量小就选 100,防止过拟合 |
| 卷积核数量 | 64~128 | 越大表达能力越强,越容易过拟合 |
| kernel_size | (2, 3, 4) | 对应 n-gram 窗口,微博短文本用 2-4 即可 |
| max_len | 64~128 | 微博文本平均较短,128 覆盖绝大多数样本 |
| dropout | 0.3~0.5 | 0.5 是谣言数据集上的常见安全值 |
BiLSTM 只有在加上 attention 或者文本相对较长时才明显比 TextCNN 好。如果论文里必须有两个深度模型做对比,BiLSTM 可以作为“结构不同”的对照组,但不要期待它在微博短文本上碾压 TextCNN,这个结论本身就可以写进实验分析。
3.3 BERT 微调:什么时候值得做,怎么做省钱
把 BERT 微调当作最终对标模型是现在毕设的常见做法。中文谣言检测用 bert-base-chinese 就能拿到不错的结果,如果显卡显存有限,换 hfl/rbt3 这种小模型,精度损失不大,训练时间少一半。
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
ckpt = "hfl/rbt3"
tokenizer = AutoTokenizer.from_pretrained(ckpt)
def encode(example):
return tokenizer(example["text"], truncation=True, padding="max_length", max_length=128)
train_args = TrainingArguments(
output_dir="./rbt3_checkpoints",
learning_rate=2e-5,
per_device_train_batch_size=32,
num_train_epochs=3,
weight_decay=0.01,
logging_steps=200,
save_strategy="no",
)
trainer = Trainer(
model=AutoModelForSequenceClassification.from_pretrained(ckpt, num_labels=2),
args=train_args,
train_dataset=train_ds,
eval_dataset=eval_ds,
)
trainer.train()
max_length=128 在微博短文本场景通常足够,超长文本被截断后信息损失有限,但能显著降低显存占用。学习率 2e-5 是微调常见默认值,不要随便调大,否则会出现 loss 震荡。如果只有 CPU 或显存不足,可以冻结 encoder 只训练分类头,效果比从头跑 TextCNN 略好,训练时间可控。注意训练集和测试集必须在同一批切分数据上生成,避免样本重叠导致测试集虚高。
4. 中文谣言检测的评估与演示:别让准确率骗了你
4.1 用 macro-F1 和混淆矩阵定位模型短板
中文谣言数据集普遍类别不平衡:真实样本和谣言样本比例经常失衡。纯准确率在这种分布下没有意义——如果 70% 是真实消息,一个全部判真的模型准确率就有 70%,但在谣言检测场景等于没做。我一般用 macro-F1 做主指标,再配混淆矩阵看具体错在哪:
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
print(classification_report(y_test, y_pred, target_names=["真实", "谣言"]))
cm = confusion_matrix(y_test, y_pred)
ConfusionMatrixDisplay(cm, display_labels=["真实", "谣言"]).plot()
plt.savefig("confusion_matrix.png", dpi=150)
macro-F1 把每个类别的 F1 平均,类别不平衡时比 accuracy 更诚实。看混淆矩阵时重点看两个格子:真实误报成谣言的数量,和谣言漏报成真实的数量。对内容审核场景,漏报比误报代价大,宁可误报多一点;毕设演示时主动说明这个取舍,会让评审觉得你理解业务而不只是会调包。
4.2 三分类“未证实”类别怎么处理
很多公开谣言数据集不止二分类,还有“未证实”这个中间态。毕设里常见两种处理:一是直接删掉未证实样本只做二分类;二是保留三分类,单独报告每个类别的 F1。我建议保留三分类,因为“未证实”本身就是谣言检测场景的重要输出,删掉会浪费信息。
report = classification_report(y_test, y_pred, target_names=["真实", "谣言", "未证实"], output_dict=True)
for cls in ["真实", "谣言", "未证实"]:
print(cls, "F1 =", round(report[cls]["f1-score"], 4))
三分类时,未证实类别的 F1 通常最低,因为它的边界模糊:一条“传言称”的文本在不同标注者眼里可能是未证实也可能是谣言。论文里要把标注口径写清楚,比如“出现明确否定或官方辟谣的判为真实,出现无来源断言且为负面指向的判为谣言,其余无法判定归属的进入未证实”。口径写清楚后,模型对未证实的识别才有稳定的学习目标。
4.3 用 Flask 和 joblib 把模型封装成可演示接口
毕设答辩时,演示一个 Web 接口比跑一遍 notebook 更有说服力。常见做法是训练完把模型和向量器都存成 joblib,再用 Flask 起一个最小接口:
from flask import Flask, request, jsonify
import joblib
app = Flask(__name__)
pipe = joblib.load("rumor_model.pkl") # 完整 Pipeline,包含向量器和分类器
stopwords = load_stopwords("stopwords.txt")
@app.route("/predict", methods=["POST"])
def predict():
data = request.get_json()
text = clean_text(data.get("text", ""))
words = cut_and_filter(text, stopwords)
prob = pipe.predict_proba([" ".join(words)])[0]
return jsonify({"label": int(prob[1] > 0.5), "rumor_score": round(float(prob[1]), 4)})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
predict_proba 返回每个类别的概率,直接取谣言类概率做阈值判断,返回时保留原始分数,方便前端做“绿黄红”三档展示。模型和停用词表在模块加载时一次性读入,不要在每次请求里重新 load,否则并发一上来响应就卡死。启动后用 curl 验证接口:
curl -X POST http://127.0.0.1:5000/predict \
-H "Content-Type: application/json" \
-d '{"text": "网传某品牌饮用水检出致癌物,官方尚未回应"}'
返回 {"label":1,"rumor_score":0.93} 就说明链路通了。这里注意用 POST 而不是 GET,避免文本内容进访问日志;线上部署时再加鉴权和限流,毕设演示则不用过度设计。
5. 中文谣言检测落地的三个实用技巧
5.1 用阈值搜索替代默认 0.5
二分类模型默认取 0.5 作为正负类分界,但谣言检测里我更倾向在验证集上做阈值搜索。把验证集上每个候选阈值对应的 F1 算出来,选让 macro-F1 最大的阈值,再灌进接口:
import numpy as np
from sklearn.metrics import f1_score
probs = pipe.predict_proba(X_val)[:, 1]
best_th, best_f1 = 0.5, 0.0
for th in np.arange(0.3, 0.8, 0.05):
f1 = f1_score(y_val, (probs >= th).astype(int), average="macro")
if f1 > best_f1:
best_th, best_f1 = th, f1
print("best threshold", round(best_th, 2), "macro-F1", round(best_f1, 4))
阈值调完后,接口里的判断边界要同步更新,不要模型里一个阈值、演示代码里另一个阈值,这种不一致在答辩时会被一眼看穿。
5.2 给模型补充“立场无关”的辅助特征
纯文本词向量会把“据说”“传”“求证”这类词当成特征,但同样的词在真实新闻里也会出现。常见改进是拼接辅助特征:文本长度、URL 数量、感叹号数量、是否为转发句式。把这些数值特征和文本表征拼起来,再接一个分类层,能明显改善短文本的判别。这在论文里叫“多源特征融合”,是一个成本低、可写深度的加分点。
5.3 用错误样本反推模型弱点,而不是只调超参
训练结束后的第一件事,是把验证集里分错的样本打印出来,按错误类型归类。比如“真实被判为谣言”的样本多是带反问句的澄清帖,“谣言被判为真实”的样本多是伪权威口吻。把归因写进论文的误差分析一节,比盲目调参更有价值。最后一招是数据增强:对谣言句做同义词替换,保持核心实体不变,把训练集扩一到两倍,TextCNN 和 BERT 都能从中受益,但注意增强只作用在训练集上,验证与测试集保持原样。
更多推荐
所有评论(0)