ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从TF-IDF到BERT:虚假新闻检测系统全流程实战与避坑指南

从TF-IDF到BERT:虚假新闻检测系统全流程实战与避坑指南 简介本资源是一套面向中文社交媒体虚假新闻识别任务的完整项目源码适合自然语言处理初学者、机器学习课程设计者及毕业设计开发者参考。项目以微信文章标题为输入区分真实与虚假新闻覆盖传统机器学习、深度学习与BERT预训练模型三条技术路线并给出准确率、召回率、F1与AUC等评估指标。压缩包共92个文件约173KB以45个Python脚本为核心辅以zbak备份、xml配置、iml工程文件及ipynb笔记涵盖jieba分词、词袋与TF-IDF特征工程、随机森林、支持向量机、朴素贝叶斯、逻辑回归以及BERT微调等模块。其中逻辑回归结合词袋模型取得90.48%的准确率支持向量机配合TF-IDF在精确率上达到89%。已有66人学习读者可据此复现完整实验流程理解文本清洗、特征提取与模型集成的优化思路并借助网盘数据文件快速搭建Anaconda与PyTorch实验环境。1. 虚假新闻检测系统从标题到可运行方案的拆解虚假新闻检测系统说白了就是让机器帮你判断一条新闻是真是假。这件事在舆情监控、内容审核、事实核查场景里需求非常硬。我做过几个类似项目从最朴素的 TF-IDF 加逻辑回归到后来上 BERT 微调中间踩的坑比想象中多得多。这个标题覆盖了三个层次机器学习做基线、深度学习提效果、BERT 模型冲上限。适合谁看有 Python 基础、想做一个完整 NLP 落地项目的同学或者已经跑过 sklearn 但不知道怎么把 BERT 接进业务流程的工程师。整条链路我会按「数据怎么来、特征怎么建、模型怎么选、参数怎么调、上线怎么排错」讲清楚每一步都给可复现的命令和代码。你照着走能拿到一个可用的检测系统而不是一个只跑在 notebook 里的 demo。2. 数据准备与基线机器学习模型先把能跑通的版本做出来2.1 虚假新闻数据集从哪来、怎么清洗做检测系统数据是第一个卡点。公开数据集常见的有 Kaggle 上的 Fake and Real News Dataset、LIAR 数据集中文场景可以用微博辟谣数据或自建爬取。我一般会先确认三件事标签是否可靠、正负样本是否均衡、文本长度分布如何。很多数据集标签噪声很大比如把「讽刺性真实新闻」标成假新闻这种脏数据不处理后面模型再强也白搭。清洗流程我固定用这几步去 HTML 标签、去 URL、去特殊符号、统一编码、去重。下面是一个可复用的清洗脚本import re import pandas as pd def clean_text(text): if not isinstance(text, str): return # 去 HTML 标签 text re.sub(r[^], , text) # 去 URL text re.sub(rhttp[s]?://\S, , text) # 去特殊符号保留中英文和基本标点 text re.sub(r[^\w\s\u4e00-\u9fff.,!?;:], , text) # 压缩连续空白 text re.sub(r\s, , text).strip() return text df pd.read_csv(news.csv) df[content] df[content].apply(clean_text) df df.drop_duplicates(subset[content]) df df[df[content].str.len() 20] # 过滤过短文本 print(df[label].value_counts())逻辑说明clean_text函数按顺序处理 HTML、URL、特殊符号和空白顺序不能乱先去标签再去 URL 是因为有些 URL 藏在标签属性里。drop_duplicates去重很关键虚假新闻数据集里重复文本特别多不去重会导致训练集和测试集泄漏。len 20这个阈值是经验值太短的文本没有足够信息留着只会引入噪声。参数说明subset[content]指定按内容列去重value_counts()用来检查标签均衡性如果正负比超过 3:1后面训练时要加class_weightbalanced。2.2 用 TF-IDF 加逻辑回归跑通第一个基线别一上来就上 BERT。先用机器学习跑一个基线目的是确认数据管线没问题、评估指标有参考值。TF-IDF 加逻辑回归是我最常用的基线组合训练快、可解释、调参少。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( df[content], df[label], test_size0.2, random_state42, stratifydf[label] ) vectorizer TfidfVectorizer(max_features50000, ngram_range(1, 2), min_df3) X_train_vec vectorizer.fit_transform(X_train) X_test_vec vectorizer.transform(X_test) clf LogisticRegression(C1.0, max_iter1000, class_weightbalanced) clf.fit(X_train_vec, y_train) y_pred clf.predict(X_test_vec) print(classification_report(y_test, y_pred))逻辑说明TfidfVectorizer把文本转成稀疏向量ngram_range(1,2)同时考虑单词和双词组合对虚假新闻检测有帮助因为「据可靠消息」「内部人士透露」这类短语是强信号。stratify保证训练测试集标签比例一致。逻辑回归的class_weightbalanced自动处理样本不均衡。参数说明max_features50000控制词表大小太大容易过拟合min_df3过滤低频词C1.0是正则化强度的倒数值越小正则越强基线先用默认值。跑完这个基线你大概能拿到 0.85 到 0.92 的 F1具体看数据集质量。如果 F1 低于 0.8先回去查数据别急着换模型。2.3 机器学习基线的三个必调参数与评估陷阱基线跑通后有三个参数值得花时间调max_features、ngram_range、C。我一般用网格搜索快速扫一遍from sklearn.model_selection import GridSearchCV from sklearn.pipeline import Pipeline pipe Pipeline([ (tfidf, TfidfVectorizer()), (clf, LogisticRegression(max_iter1000, class_weightbalanced)) ]) params { tfidf__max_features: [30000, 50000, 80000], tfidf__ngram_range: [(1,1), (1,2)], clf__C: [0.1, 1.0, 10.0] } grid GridSearchCV(pipe, params, cv3, scoringf1, n_jobs-1) grid.fit(X_train, y_train) print(grid.best_params_)逻辑说明用 Pipeline 把向量化和分类器串起来避免在交叉验证时数据泄漏。scoringf1而不是 accuracy因为虚假新闻检测里漏判和误判代价不同F1 更均衡。评估陷阱要特别注意第一别用 accuracy 当唯一指标样本不均衡时 accuracy 会骗人第二测试集一定要和训练集同分布我见过有人用不同来源的数据做测试F1 直接掉 20 个点第三保存好 vectorizer线上推理时必须用同一个否则特征对不上。3. 深度学习模型用 CNN 和 LSTM 把文本特征吃得更透3.1 为什么机器学习基线不够用TF-IDF 加逻辑回归的瓶颈很明显它只看词频不理解语序和上下文。「狗咬人」和「人咬狗」在 TF-IDF 里几乎一样但语义完全相反。虚假新闻里大量存在这种语序敏感的表达比如「专家否认了谣言」和「专家否认了谣言」一个标点位置就改变意思。深度学习模型通过词嵌入和序列建模能捕捉这种上下文关系。另一个问题是特征工程成本。机器学习方案需要人工设计特征比如情感得分、标点密度、大写字母比例。深度学习端到端学习省去大量手工特征。但代价是训练数据需求更大、调参更玄学、解释性更差。我的建议是数据量低于 1 万条先用机器学习超过 1 万条再考虑深度学习。3.2 用 Keras 搭一个 CNN 文本分类器CNN 做文本分类的思路是用卷积核在词向量序列上滑动提取局部 n-gram 特征。相比 LSTMCNN 训练更快、并行度更好适合作为深度学习的第一个模型。import numpy as np from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, Conv1D, GlobalMaxPooling1D, Dense, Dropout MAX_WORDS 50000 MAX_LEN 300 EMBED_DIM 128 tokenizer Tokenizer(num_wordsMAX_WORDS, oov_tokenOOV) tokenizer.fit_on_texts(X_train) X_train_seq pad_sequences(tokenizer.texts_to_sequences(X_train), maxlenMAX_LEN, paddingpost) X_test_seq pad_sequences(tokenizer.texts_to_sequences(X_test), maxlenMAX_LEN, paddingpost) model Sequential([ Embedding(MAX_WORDS, EMBED_DIM, input_lengthMAX_LEN), Conv1D(128, 5, activationrelu), GlobalMaxPooling1D(), Dropout(0.5), Dense(64, activationrelu), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) model.summary() history model.fit(X_train_seq, y_train, epochs5, batch_size64, validation_split0.1, class_weight{0:1, 1:2})逻辑说明Tokenizer把词映射成整数 IDpad_sequences统一长度paddingpost在末尾补零。Conv1D(128, 5)表示 128 个卷积核、窗口大小 5能捕捉 5 个词以内的局部模式。GlobalMaxPooling1D取每个特征图的最大值把变长序列压成固定向量。Dropout(0.5)防过拟合。class_weight{0:1, 1:2}给假新闻类别更高权重因为漏判假新闻代价更大。参数说明MAX_LEN300覆盖大部分新闻长度太短截断信息太长增加计算量EMBED_DIM128是常用起点数据量大可以加到 256batch_size64在显存和训练稳定性之间取平衡。3.3 LSTM 与 CNN 的选型对比和调参要点LSTM 适合捕捉长距离依赖比如新闻开头和结尾的矛盾表述。但 LSTM 训练慢、容易梯度消失。我的经验是文本长度超过 500 词、需要建模长距离关系时用 LSTM短文本分类 CNN 足够。from tensorflow.keras.layers import LSTM, Bidirectional model_lstm Sequential([ Embedding(MAX_WORDS, EMBED_DIM, input_lengthMAX_LEN), Bidirectional(LSTM(64, return_sequencesFalse)), Dropout(0.5), Dense(64, activationrelu), Dense(1, activationsigmoid) ]) model_lstm.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy])逻辑说明Bidirectional同时从前向和后向读序列能捕捉双向上下文。return_sequencesFalse只取最后一个时间步的输出。调参要点LSTM 层数不要超过 2 层否则训练极慢units从 64 开始试学习率用1e-3如果 loss 震荡就降到1e-4。CNN 和 LSTM 可以堆叠先 CNN 提局部特征再 LSTM 提序列特征但调参复杂度翻倍新手先单独用。4. BERT 模型把检测效果推到上限4.1 BERT 微调为什么比从头训练更靠谱BERT 的核心价值是预训练。它在海量语料上已经学到了语法、语义和常识你只需要用几千条标注数据微调就能超过从头训练的 CNN/LSTM。虚假新闻检测里很多信号是隐式的比如「某研究显示」但没给出处、「据说」但没信源BERT 的注意力机制能捕捉这些模式。但 BERT 不是银弹。第一它需要 GPU显存至少 8G 才能跑 base 版本第二推理速度慢单条 50ms 左右高并发场景要加缓存或蒸馏第三中文场景要用 bert-base-chinese 或 RoBERTa-wwm-ext别用英文模型硬套。4.2 用 HuggingFace 微调 BERT 的完整代码import torch from torch.utils.data import Dataset, DataLoader from transformers import BertTokenizer, BertForSequenceClassification, AdamW from transformers import get_linear_schedule_with_warmup class NewsDataset(Dataset): def __init__(self, texts, labels, tokenizer, max_len256): self.texts texts self.labels labels self.tokenizer tokenizer self.max_len max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): encoding self.tokenizer( self.texts[idx], truncationTrue, paddingmax_length, max_lengthself.max_len, return_tensorspt ) return { input_ids: encoding[input_ids].squeeze(), attention_mask: encoding[attention_mask].squeeze(), labels: torch.tensor(self.labels[idx], dtypetorch.long) } tokenizer BertTokenizer.from_pretrained(bert-base-chinese) model BertForSequenceClassification.from_pretrained(bert-base-chinese, num_labels2) model.cuda() train_dataset NewsDataset(X_train.tolist(), y_train.tolist(), tokenizer) train_loader DataLoader(train_dataset, batch_size16, shuffleTrue) optimizer AdamW(model.parameters(), lr2e-5, weight_decay0.01) scheduler get_linear_schedule_with_warmup(optimizer, num_warmup_steps100, num_training_stepslen(train_loader)*3) model.train() for epoch in range(3): for batch in train_loader: optimizer.zero_grad() outputs model( input_idsbatch[input_ids].cuda(), attention_maskbatch[attention_mask].cuda(), labelsbatch[labels].cuda() ) loss outputs.loss loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() scheduler.step() print(fepoch {epoch} loss {loss.item():.4f})逻辑说明NewsDataset封装了 tokenizationtruncationTrue截断超长文本paddingmax_length统一长度。BertForSequenceClassification在 BERT 顶部加了一个分类头。AdamW是 BERT 微调的标准优化器lr2e-5是关键参数太大导致灾难性遗忘太小收敛慢。clip_grad_norm_防止梯度爆炸。warmup让学习率从 0 线性上升到设定值稳定训练初期。参数说明max_len256平衡信息量和显存batch_size16是 8G 显存的极限显存不够降到 8epoch3通常够用超过 5 容易过拟合weight_decay0.01是 BERT 微调的推荐值。4.3 BERT 推理加速与模型蒸馏的落地选择BERT 推理慢是上线时的最大障碍。我一般按这个顺序优化第一用torch.no_grad()关闭梯度速度提升 30%第二用 ONNX Runtime 或 TensorRT 导出再提升 2 到 3 倍第三如果还不够用 DistilBERT 或 TinyBERT 蒸馏精度掉 1 到 2 个点速度提升 3 到 5 倍。model.eval() with torch.no_grad(): outputs model(input_idsbatch[input_ids].cuda(), attention_maskbatch[attention_mask].cuda()) preds torch.argmax(outputs.logits, dim1)逻辑说明model.eval()切换 dropout 和 batchnorm 到推理模式torch.no_grad()不建计算图省显存和计算。选择建议QPS 低于 10直接用 BERTQPS 在 10 到 100用 ONNX 加速QPS 超过 100上蒸馏模型或加 Redis 缓存高频文本。5. 避坑与排查虚假新闻检测系统最常见的五个翻车点5.1 数据泄漏导致 F1 虚高现象离线 F1 跑到 0.98上线后掉到 0.7。原因训练集和测试集有重复文本或者同一事件的新闻被分到两边。解决按事件 ID 或时间划分数据集去重时用drop_duplicates加模糊匹配比如 SimHash 去重。5.2 标签噪声让模型学偏现象模型把「据新华社报道」判成假新闻。原因训练集里假新闻大量引用权威媒体模型学到了错误关联。解决人工抽检 500 条标签清洗明显错误的样本用置信学习confident learning自动找噪声标签。5.3 中文分词和 tokenizer 不匹配现象BERT 微调后效果不如 TF-IDF。原因用了英文 tokenizer 处理中文或者 jieba 分词后输入 BERT。解决中文场景统一用bert-base-chinese的 tokenizer不要自己分词后再喂给 BERT。5.4 推理时预处理不一致现象训练时 F1 0.95线上单条测试结果乱跳。原因训练用paddingmax_length线上用paddingTrue导致 attention mask 不一致。解决把预处理逻辑封装成函数训练和推理共用同一份代码。5.5 类别不均衡导致漏判现象假新闻召回率只有 0.6。原因正负样本 1:5模型偏向预测多数类。解决训练时加class_weight或者用 focal loss 替代交叉熵让模型关注难样本。6. 把 BERT 接进业务流程一个可上线的推理服务技巧模型训完只是开始真正落地要解决「怎么让业务系统调用」。我一般用 FastAPI 包一层 HTTP 服务配合 Redis 缓存高频文本。下面是一个最小可用的推理服务from fastapi import FastAPI from pydantic import BaseModel import torch from transformers import BertTokenizer, BertForSequenceClassification app FastAPI() tokenizer BertTokenizer.from_pretrained(bert-base-chinese) model BertForSequenceClassification.from_pretrained(./finetuned_bert) model.eval() model.cuda() class NewsRequest(BaseModel): text: str app.post(/predict) def predict(req: NewsRequest): inputs tokenizer(req.text, truncationTrue, paddingmax_length, max_length256, return_tensorspt) with torch.no_grad(): outputs model(input_idsinputs[input_ids].cuda(), attention_maskinputs[attention_mask].cuda()) prob torch.softmax(outputs.logits, dim1)[0][1].item() return {label: fake if prob 0.5 else real, confidence: round(prob, 4)}逻辑说明NewsRequest定义请求体predict函数做 tokenization 和推理返回标签和置信度。prob 0.5是默认阈值业务上可以调比如舆情场景宁可误判也要高召回阈值降到 0.3。参数说明max_length256要和训练时一致round(prob, 4)保留四位小数方便前端展示。一个实用技巧加一层规则兜底。BERT 对短文本和反讽文本容易翻车我一般会加几条规则比如文本长度小于 10 直接返回「无法判断」包含「纯属虚构」「辟谣」等关键词时降低假新闻概率。规则和模型结合线上 F1 能再提 2 到 3 个点。最后说个血泪教训别在测试集上反复调阈值。我见过团队把阈值调到测试集 F1 最高上线后完全失效。正确做法是留一个独立的验证集调阈值测试集只用一次。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表