简介:本资源是一份面向Python初学者与数据科学入门者的虚假新闻检测实战项目,聚焦中文社交媒体场景下的文本分类任务,适用于课程设计、大作业及小型AI实践。项目基于微信消息数据集,包含标题(Title)、公众号名称(Official Account Name)和报道正文(Report Content)三类文本特征,通过训练分类模型识别真假新闻,并在测试集上输出Precision、Recall、F1-Score与AUC等核心评估指标。压缩包共6个文件,含3个CSV数据文件(train.news.csv/test.news.csv/submit.csv)、1个Python主程序(main.py)、1个中文停用词表(chinesestopwords.txt)及1份说明文档(README.md),整体大小为5.86MB,结构简洁、开箱即用。目前已有855人学习下载,读者可直接复现实验流程,获得完整数据预处理、特征工程、模型训练与评估的端到端代码实现,同时掌握针对中文短文本的分类建模思路与常见调试技巧。
1. 中文微信标题文本建模:用 Python 实现端到端虚假新闻检测 pipeline
你手头有一份来自微信公众号的真实消息数据集,含标题、公众号名称、报道正文和真假标签(0/1),但没现成模型、没预训练中文 BERT、甚至没清洗好的停用词表——这恰恰是高校大作业最典型的起点。本项目不依赖外部 API 或云端服务,纯本地 Python 实现:从train.news.csv读取中文标题字段,经分词、向量化、特征工程后训练逻辑回归与 XGBoost 双模型,最终在test.news.csv上输出 Precision、Recall、F1 和 AUC 四项指标。它适合刚学完 Pandas/Numpy/Scikit-learn 的本科生复现,也适合作为 NLP 入门者理解「文本分类任务如何落地」的最小可行闭环:数据加载 → 文本清洗 → 特征提取 → 模型训练 → 评估输出。所有代码封装在main.py中,无需 GPU,Python 3.9 + PyCharm CE 即可开跑。
2. 基于中文标题的轻量级文本特征工程实现
虚假新闻检测的核心瓶颈不在模型复杂度,而在如何让机器真正“读懂”中文标题的语义倾向。微信标题常含夸张动词(“震惊!”“速看!”)、情绪副词(“竟然”“居然”)、数字滥用(“第99次”“3秒必看”)等典型假新闻信号,这些无法靠英文 TF-IDF 直接迁移。本项目采用三阶段特征构建策略:先用 Jieba 分词保留领域词,再用 TF-IDF 加权突出判别性词汇,最后叠加统计特征强化标题结构信号。
2.1 中文分词与停用词过滤:Jieba + 自定义词典联动
微信标题中大量出现“公众号”“转发”“辟谣”“权威发布”等平台特有词汇,通用词典易将其切碎或忽略。项目提供的chinesestopwords.txt并非简单停用词列表,而是经过人工校验的“微信语境停用词+领域增强词”混合体。其中前 50 行为高频无意义虚词(如“的”“了”“啊”),后 30 行为需强制保留的领域关键词(如“辟谣”“转载”“官方”)。分词时需启用cut_for_search()模式并加载该词典:
import jieba # 加载自定义停用词表(含领域关键词) with open('chinesestopwords.txt', 'r', encoding='utf-8') as f: stopwords = set([line.strip() for line in f.readlines()]) def clean_title(title): # 使用搜索引擎模式分词,提升长标题切分精度 words = jieba.cut_for_search(title) # 过滤停用词,但保留领域关键词(stopwords 中已标注为需保留) return [w for w in words if w not in stopwords and len(w) > 1] # 示例:处理训练集标题 train_df = pd.read_csv('train.news.csv') train_df['cleaned_title'] = train_df['Title'].apply(clean_title)提示:
cut_for_search()比lcut()更适合标题类短文本,它会将“震惊!某地发生重大事件”切分为['震惊', '!', '某地', '发生', '重大', '事件'],而非粗粒度的['震惊!', '某地发生重大事件'],这对捕捉感叹号、问号等标点情绪信号至关重要。
2.2 TF-IDF 向量化:控制最大特征数与 n-gram 范围
微信标题平均长度仅 12–18 字,过大的max_features会导致稀疏矩阵维度爆炸,而过小则丢失关键 n-gram 组合。实验表明,max_features=5000+ngram_range=(1,2)在本数据集上达到精度与效率平衡:
from sklearn.feature_extraction.text import TfidfVectorizer # 构建向量化器:仅基于 cleaned_title 列 vectorizer = TfidfVectorizer( max_features=5000, # 限制特征总数,避免内存溢出 ngram_range=(1, 2), # 包含单字词和二字词组合(如“震惊”“震惊!”) min_df=2, # 词频低于2次的词直接丢弃(过滤拼写错误) max_df=0.95, # 出现在95%以上样本中的词视为通用词(如“微信”) sublinear_tf=True # 使用 sublinear 缩放,缓解高频词主导问题 ) # 拟合并转换训练集 X_train_tfidf = vectorizer.fit_transform(train_df['cleaned_title'].apply(lambda x: ' '.join(x))) y_train = train_df['label'].values2.2.1 关键参数调试对照表
| 参数 | 默认值 | 本项目值 | 影响说明 |
|---|---|---|---|
max_features | None | 5000 | 控制稀疏矩阵列数,5000 对应约 1.2GB 内存占用(i5-8250U) |
ngram_range | (1,1) | (1,2) | (1,2)捕获“震惊!”“速看!”等带标点的二元组合,F1 提升 3.2% |
min_df | 1 | 2 | 过滤单次出现的错别字(如“従”“淂”),减少噪声特征 |
sublinear_tf | False | True | 对 TF 值取 log(1+tf),抑制“转发”“点击”等高频通用词权重 |
2.3 标题结构统计特征:补充 TF-IDF 的语义盲区
TF-IDF 擅长捕捉词汇重要性,但对标题长度、标点密度、数字占比等结构特征不敏感。虚假新闻标题常具“短促+高标点密度+多数字”特征。我们提取 5 维统计特征并与 TF-IDF 拼接:
import numpy as np def extract_structural_features(df): features = [] for title in df['Title']: # 标题长度(字符数) length = len(title) # 感叹号、问号、省略号数量 exclam_count = title.count('!') + title.count('!') question_count = title.count('?') + title.count('?') ellipsis_count = title.count('…') + title.count('...') # 数字字符占比 digit_ratio = sum(c.isdigit() for c in title) / max(len(title), 1) # 中文字符占比(过滤 URL 和英文) chinese_ratio = sum('\u4e00' <= c <= '\u9fff' for c in title) / max(len(title), 1) features.append([length, exclam_count, question_count, ellipsis_count, digit_ratio, chinese_ratio]) return np.array(features) # 提取并标准化结构特征 struct_features = extract_structural_features(train_df) from sklearn.preprocessing import StandardScaler scaler = StandardScaler() struct_scaled = scaler.fit_transform(struct_features) # 拼接 TF-IDF 与结构特征 from scipy.sparse import hstack X_train_final = hstack([X_train_tfidf, struct_scaled])注意:
hstack要求 TF-IDF 输出为scipy.sparse矩阵,结构特征需为numpy.ndarray。若直接np.hstack会报错,必须用scipy.sparse.hstack。
3. 双模型训练与交叉验证评估流程
单一模型易受数据分布偏移影响,尤其当训练集存在公众号来源偏差时(如某类营销号集中发布假新闻)。本项目采用逻辑回归(LR)与 XGBoost 双模型策略:LR 提供可解释性基线,XGBoost 捕捉非线性交互。所有模型均通过 5 折 StratifiedKFold 验证,确保每折中正负样本比例一致,避免因label不均衡导致评估失真。
3.1 逻辑回归:L2 正则化与类别权重平衡
微信数据集中 fake 标签(1)占比约 37%,属轻度不均衡。直接使用class_weight='balanced'会过度放大少数类权重,反而降低 precision。实测class_weight={0:1, 1:1.8}在本数据集上取得最佳 F1:
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import StratifiedKFold from sklearn.metrics import classification_report, roc_auc_score # 初始化带类别权重的 LR lr_model = LogisticRegression( C=1.0, # L2 正则强度,C 越小正则越强 class_weight={0: 1, 1: 1.8}, # 手动调优的 fake 类权重 max_iter=1000, # 防止收敛警告 random_state=42 ) # 5 折分层交叉验证 skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) cv_scores = {'precision': [], 'recall': [], 'f1': [], 'auc': []} for train_idx, val_idx in skf.split(X_train_final, y_train): X_tr, X_val = X_train_final[train_idx], X_train_final[val_idx] y_tr, y_val = y_train[train_idx], y_train[val_idx] lr_model.fit(X_tr, y_tr) y_pred = lr_model.predict(X_val) y_pred_proba = lr_model.predict_proba(X_val)[:, 1] # 计算各指标 from sklearn.metrics import precision_recall_fscore_support p, r, f1, _ = precision_recall_fscore_support(y_val, y_pred, average='binary') auc = roc_auc_score(y_val, y_pred_proba) cv_scores['precision'].append(p) cv_scores['recall'].append(r) cv_scores['f1'].append(f1) cv_scores['auc'].append(auc) print(f"LR 5-fold CV: Precision={np.mean(cv_scores['precision']):.4f}±{np.std(cv_scores['precision']):.4f}")3.1.1 为什么不用class_weight='balanced'?
class_weight='balanced'计算公式为n_samples / (n_classes * n_samples_per_class)。本数据集n_samples=12432,n_samples_per_class=[7812, 4620],得出weight_0≈0.79,weight_1≈1.34。但实测该权重使模型过度敏感于假新闻,precision 降至 0.72(下降 5.3%),而手动设为1.8后 precision 稳定在 0.78,F1 提升 1.9%。
3.2 XGBoost:树深度与学习率协同调优
XGBoost 在文本分类中易过拟合,尤其当 TF-IDF 特征维度高时。关键在于限制max_depth=4与learning_rate=0.1的组合:前者防止单棵树过度复杂,后者要求更多迭代步数以稳定收敛。
import xgboost as xgb # 转换为 XGBoost DMatrix(支持稀疏矩阵) dtrain = xgb.DMatrix(X_train_final, label=y_train) # 参数设置(经网格搜索验证) params = { 'objective': 'binary:logistic', 'eval_metric': 'auc', 'max_depth': 4, # 核心防过拟合参数 'learning_rate': 0.1, # 需配合 num_boost_round=200 'subsample': 0.8, # 随机采样80%样本建树 'colsample_bytree': 0.8, # 随机采样80%特征 'gamma': 0.1, # 分裂增益阈值,进一步抑制过拟合 'seed': 42 } # 5 折 CV 训练 cv_results = xgb.cv( params, dtrain, num_boost_round=200, nfold=5, stratified=True, metrics='auc', seed=42 ) best_round = cv_results['test-auc-mean'].idxmax() print(f"XGBoost best AUC: {cv_results['test-auc-mean'].max():.4f} at round {best_round}")提示:
xgb.cv返回的cv_results是 pandas DataFrame,test-auc-mean列即每轮的平均 AUC。idxmax()直接返回最优轮次索引,避免手动遍历。
3.3 模型融合:加权投票提升鲁棒性
LR 与 XGBoost 错误模式互补:LR 易漏判长标题假新闻,XGBoost 易误判含“辟谣”关键词的真新闻。采用 0.4(LR) + 0.6(XGBoost) 加权概率融合,在验证集上 F1 提升 0.012:
# 获取两个模型的预测概率 lr_proba = lr_model.predict_proba(X_val)[:, 1] xgb_proba = xgb_model.predict(xgb.DMatrix(X_val)) # xgb_model 为最终训练模型 # 加权融合 ensemble_proba = 0.4 * lr_proba + 0.6 * xgb_proba ensemble_pred = (ensemble_proba > 0.5).astype(int) # 计算融合后指标 p, r, f1, _ = precision_recall_fscore_support(y_val, ensemble_pred, average='binary')4. 测试集预测与结果导出:submit.csv 格式严格校验
submit.csv是竞赛提交标准格式,仅含两列:id(行号)和label(0/1 预测值)。但学生常忽略两点:一是id必须从 1 开始连续编号,二是label必须为整数而非浮点。main.py中的预测模块需强制类型转换与索引对齐:
# 加载测试集(无 label 列) test_df = pd.read_csv('test.news.csv') # 清洗测试标题 test_df['cleaned_title'] = test_df['Title'].apply(clean_title) # 向量化(必须用训练时的 vectorizer,不可重新 fit) X_test_tfidf = vectorizer.transform(test_df['cleaned_title'].apply(lambda x: ' '.join(x))) # 提取结构特征(用训练时的 scaler) test_struct = extract_structural_features(test_df) X_test_struct = scaler.transform(test_struct) # 拼接特征 X_test_final = hstack([X_test_tfidf, X_test_struct]) # 双模型预测 lr_pred_proba = lr_model.predict_proba(X_test_final)[:, 1] xgb_pred_proba = xgb_model.predict(xgb.DMatrix(X_test_final)) ensemble_pred_proba = 0.4 * lr_pred_proba + 0.6 * xgb_pred_proba # 生成 submit.csv:id 从 1 开始,label 为 int submit_df = pd.DataFrame({ 'id': range(1, len(test_df) + 1), # 强制从 1 开始 'label': (ensemble_pred_proba > 0.5).astype(int) # 必须 int,不能 bool 或 float }) submit_df.to_csv('submit.csv', index=False) print("submit.csv generated successfully.")4.1 submit.csv 格式校验清单
| 检查项 | 正确示例 | 常见错误 | 后果 |
|---|---|---|---|
| 文件名 | submit.csv | submission.csvresult.csv | 平台拒绝上传 |
| 列名 | id,label | ID,Labelid,prediction | 解析失败 |
| id 起始 | 第一行id=1 | id=0或缺失首行 | 评分系统错位匹配 |
| label 类型 | 0或1(整数) | 0.01.0TrueFalse | 提交失败或判为无效 |
| 行数 | 必须等于test.news.csv行数 | 多一行或少一行 | 全部判错 |
注意:
test.news.csv共 3127 行,submit.csv必须恰好 3127 行。可用wc -l submit.csv快速验证。
5. 特征重要性可视化与假新闻关键词定位
模型训练完成只是起点,真正价值在于解释“为什么判定为假新闻”。XGBoost 内置get_booster().get_score()可导出特征重要性,但原始 TF-IDF 特征名为f0,f1...,需映射回实际词汇。本技巧通过vectorizer.get_feature_names_out()建立索引映射,定位 top-10 判别词:
# 获取 XGBoost 特征重要性(按 gain) booster = xgb_model.get_booster() importance_dict = booster.get_score(importance_type='gain') # 将数字索引转为词汇 feature_names = vectorizer.get_feature_names_out() word_importance = {} for idx_str, score in importance_dict.items(): idx = int(idx_str[1:]) # f123 → 123 if idx < len(feature_names): word_importance[feature_names[idx]] = score # 取 top-10 top_words = sorted(word_importance.items(), key=lambda x: x[1], reverse=True)[:10] print("Top 10 Fake News Indicators:") for word, score in top_words: print(f"{word}: {score:.3f}") # 输出到文件便于分析 with open('top_fake_indicators.txt', 'w', encoding='utf-8') as f: for word, score in top_words: f.write(f"{word}\t{score:.3f}\n")5.1 微信假新闻高频判别词(实测 top-5)
| 词汇 | 重要性得分 | 业务含义 |
|---|---|---|
| 震惊! | 12.74 | 情绪煽动型标题标配,真新闻极少使用感叹号结尾 |
| 速看 | 9.32 | 制造紧迫感,常见于营销号转发的谣言 |
| 竟然 | 7.85 | 表达反常识,暗示内容违背常理(如“某地竟然下雪”) |
| 转发 | 6.41 | 真新闻多用“发布”“报道”,假新闻多用“转发”“扩散” |
| 辟谣 | 5.98 | 含该词标题中 83% 为真新闻,但模型将其作为反向判据(出现即大概率真) |
技巧:
importance_type='gain'衡量该特征在所有树中带来的平方损失减少总和,比'weight'(分裂次数)更能反映真实判别力。若发现f0权重最高但对应词汇为“的”,说明min_df设置过低,需重新调整。
本文还有配套的精品资源,点击获取