简介:本资源是一份面向计算机专业本科生的课程设计级实战项目,聚焦互联网虚假新闻检测这一典型NLP+机器学习应用场景,适用于期末大作业、课程设计及项目能力强化训练。项目基于Python实现多层感知机(MLP)分类模型,完整覆盖数据预处理、特征工程、模型训练与评估全流程,配套详细文档说明与可运行源码,助力学习者深入理解文本分类任务的技术落地路径。压缩包共21个文件,包含4个核心Python脚本(preprocess.py、fit.py、optimize.py、predict.py)、3个训练好的MLP模型文件、2个CSV数据集、2个PKL向量器、4个XML配置/日志文件,以及DOCX报告、MD说明和IDEA项目配置等,整体92.72MB,结构规范、模块清晰。目前已有104人下载学习,提供从环境搭建到结果预测的端到端实践支持,特别适合需快速复现高分课程设计成果的学习者参考与拓展。
1. 为什么用 MLP 做虚假新闻检测?不是模型越深越好,而是数据越脏越要“掐住脖子”调参
你手头有一份带标签的新闻文本数据集(比如 FakeNewsNet、LIAR 或自爬的微博/公众号样本),想快速验证“某条标题+正文是否大概率是编的”,而不是堆 BERT 大模型跑三天等结果——这时候,一个结构干净、训练快、可解释性尚可的多层感知机(MLP)反而是更务实的选择。它不追求 SOTA 指标,但能让你在 2 小时内从零跑通 pipeline:文本清洗 → 特征向量化 → MLP 训练 → 二分类输出(真/假),且每个环节参数都可控、错误可定位。本项目不是教你怎么发论文,而是帮你交大作业时:不卡 GPU、不报 CUDA 错、不被老师问“你这个 embedding 是怎么来的”当场哑火。适合 Python 基础扎实(会 pip install、读 pandas DataFrame、写 for 循环)、但没系统学过 NLP 的本科生;也适合想快速验证业务逻辑、后续再替换为 Transformer 的工程师。核心不在“多炫”,而在“每一步都能 debug”——比如 vectorizer.max_features=5000 这个数,你改完立刻能看到 vocab_size 变化,而不是黑匣子吐出个 loss 下降曲线就以为成了。
2. 从原始文本到数值特征:TF-IDF 不是玄学,是必须亲手调的三道闸门
虚假新闻检测的瓶颈,从来不在模型层,而在文本到数字的转换过程。MLP 只认 float32 数组,而新闻是长短不一、夹杂 emoji、URL、标点混乱的字符串。直接扔进 CountVectorizer?90% 的特征维度会被“的”“了”“和”这种停用词霸占,模型根本学不到“夸大其词”“无信源引用”“情绪动词堆砌”这类判别信号。所以必须分三步“掐脖子”式控制特征生成:
2.1 文本清洗:删 URL、标准化空格、保留关键标点
不要用正则一股脑删所有标点——感叹号“!”、问号“?”在虚假新闻中高频出现(如“震惊!99% 人不知道…”),是有效信号。真正该删的是:
- 完整 URL(
https?://\S+)→ 替换为<URL>占位符 - 连续空白符(
\s+)→ 统一为单空格 - 全角标点 → 转半角(避免“。”和“.”被当不同 token)
- 数字统一归一化(
[0-9]+→<NUM>),防止“2023年”和“2024年”割裂语义
import re import unicodedata def clean_text(text): # 删除 URL 并替换为占位符 text = re.sub(r'https?://\S+', '<URL>', text) # 全角转半角 text = unicodedata.normalize('NFKC', text) # 数字归一化 text = re.sub(r'\d+', '<NUM>', text) # 多空格转单空格 text = re.sub(r'\s+', ' ', text).strip() return text # 示例 raw = "最新!北京地铁惊现神秘生物!!!http://example.com/abc 2023年12月31日" print(clean_text(raw)) # 输出:最新!北京地铁惊现神秘生物!!!<URL><NUM>年<NUM>月<NUM>日提示:这步必须在
TfidfVectorizer之前做。如果把清洗逻辑塞进 vectorizer 的preprocessor参数,后续调试时无法单独 inspect 清洗效果——你得看到中间态文本才能判断是不是误删了关键符号。
2.2 TF-IDF 向量化:三个必调参数决定模型生死线
TfidfVectorizer不是开箱即用的黑盒。以下三个参数必须根据你的数据集手动实验,否则 MLP 输入全是噪声:
| 参数 | 默认值 | 推荐初值 | 为什么必须调 |
|---|---|---|---|
max_features | None(全量) | 5000 | 防止稀疏矩阵过大导致内存 OOM;对 1w 条新闻,5000 词已覆盖 95% 信息熵 |
ngram_range | (1,1) | (1,2) | 单词级("新冠")易被绕过,加入 bigram("新冠 疫苗")能捕获组合陷阱 |
min_df | 1 | 2 | 过滤只在 1 条新闻里出现的词(如错别字、ID、乱码),避免噪声维度 |
from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer( max_features=5000, ngram_range=(1, 2), # 启用 unigram + bigram min_df=2, # 至少在 2 篇新闻中出现才保留 stop_words=['的', '了', '在', '是', '我', '有', '和', '就', '不', '人', '都', '一', '一个', '上', '也', '很', '到', '说', '要', '去', '你', '会', '着', '没有', '看', '好', '自己', '这'], # 中文停用词表(需自行准备) lowercase=False, # 中文无需小写 token_pattern=r'(?u)\b\w+\b' # 匹配中文字符、英文字母、数字 ) # 拟合并转换训练集 X_train_tfidf = vectorizer.fit_transform(train_texts) # shape: (n_samples, 5000) print(f"Vocab size: {len(vectorizer.vocabulary_)}") # 应 ≈5000 print(f"Feature names sample: {list(vectorizer.vocabulary_.keys())[:5]}")逻辑说明:fit_transform一次性完成词典构建与向量化。vocabulary_字典键是 token,值是索引(0~4999),后续所有文本都按此映射。若len(vocabulary_)明显小于max_features(如只有 1200),说明min_df=2过严或文本太短——此时应降低min_df到 1 并检查是否有大量空行/纯 URL 样本。
2.3 特征维度校验:用.toarray()看懂稀疏矩阵在“呼吸”
TfidfVectorizer输出scipy.sparse.csr_matrix,直接 print 只显示形状和非零元素数,根本看不出特征质量。必须转成 dense array 抽样 inspect:
# 取第一条新闻的 TF-IDF 向量(转 dense) sample_vec = X_train_tfidf[0].toarray().flatten() # shape: (5000,) # 找出权重最高的 10 个词 top_indices = sample_vec.argsort()[-10:][::-1] feature_names = list(vectorizer.vocabulary_.keys()) for idx in top_indices: word = feature_names[idx] if idx < len(feature_names) else "OUT_OF_RANGE" print(f"{word}: {sample_vec[idx]:.4f}")参数说明:.toarray()会消耗内存,仅用于调试。生产环境必须保持 sparse matrix(MLP 训练时自动兼容)。若发现 top 词全是“ ”“ ”或停用词漏删,说明清洗或 vectorizer 参数有问题——这是后续 MLP 准确率上不去的根源,比调 learning_rate 重要十倍。
3. MLP 构建与训练:Keras 不是唯一选择,但tf.keras.Sequential最省心
用 PyTorch 写 MLP 对大作业来说是自找麻烦:需要手动定义forward()、管理 device、写 train loop。而tf.keras.Sequential一行model.compile()就搞定 optimizer、loss、metrics,且与 sklearn pipeline 无缝衔接(后续可直接用sklearn.model_selection.cross_val_score)。重点不是框架多先进,而是让代码在同学电脑上也能跑通——毕竟不是所有人都装了 CUDA。
3.1 模型结构设计:三层足够,第四层是过拟合开关
虚假新闻文本特征维度通常在 1000~5000,输入层大小即input_dim。经验公式:
- 隐藏层 1:
input_dim * 0.7(向下取整) - 隐藏层 2:
hidden1 * 0.5 - 输出层:1(sigmoid 二分类)
例如input_dim=5000→hidden1=3500→hidden2=1750。但实际中,hidden1=1024、hidden2=512更稳——层数越多,越容易在小数据集上 overfit。务必加 Dropout 和 BatchNormalization:
import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers def build_mlp(input_dim, dropout_rate=0.3, l2_reg=1e-4): model = keras.Sequential([ layers.Input(shape=(input_dim,)), # 输入层,shape 必须明确 layers.Dense(1024, activation='relu', kernel_regularizer=keras.regularizers.l2(l2_reg)), layers.BatchNormalization(), layers.Dropout(dropout_rate), layers.Dense(512, activation='relu', kernel_regularizer=keras.regularizers.l2(l2_reg)), layers.BatchNormalization(), layers.Dropout(dropout_rate), layers.Dense(1, activation='sigmoid') # 二分类输出 ]) return model # 实例化模型 mlp_model = build_mlp(input_dim=X_train_tfidf.shape[1]) mlp_model.compile( optimizer=keras.optimizers.Adam(learning_rate=0.001), loss='binary_crossentropy', metrics=['accuracy'] )逻辑说明:kernel_regularizer=l2(l2_reg)防止权重爆炸,Dropout(0.3)在训练时随机屏蔽 30% 神经元,BatchNormalization加速收敛并提升泛化。learning_rate=0.001是 Adam 的安全起点,若 loss 下降慢,可试0.002;若震荡剧烈,降为0.0005。
3.2 训练配置:早停不是可选,是防止“同学跑 200 epoch 还没停”的救命绳
虚假新闻数据集通常 ≤10k 样本,MLP 很快过拟合。必须用EarlyStopping监控验证集 loss:
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau callbacks = [ EarlyStopping( monitor='val_loss', # 监控验证集 loss patience=10, # 连续 10 epoch 无改善则停止 restore_best_weights=True # 恢复最优权重(不是最后权重!) ), ReduceLROnPlateau( monitor='val_loss', factor=0.5, # loss 不降时,lr 减半 patience=5, min_lr=1e-7 ) ] history = mlp_model.fit( X_train_tfidf, y_train, batch_size=64, epochs=100, # 设上限,靠 early stopping 实际终止 validation_split=0.2, # 自动划分 20% 验证集 callbacks=callbacks, verbose=1 # 显示进度条 )参数说明:validation_split=0.2比手动train_test_split更省事,且保证每次 fit 都用相同验证集。restore_best_weights=True关键——否则模型保存的是最后 epoch 的权重,可能已是过拟合状态。verbose=1便于观察 loss 是否稳定下降;若第 1 个 epochval_loss就比loss高很多,说明数据泄露或清洗有问题。
3.3 模型保存与加载:.h5格式最兼容,别碰.keras
虽然 Keras 新版推荐.keras格式,但大作业提交时,同学用旧版 TensorFlow(如 2.8)可能打不开。保险做法是存为 HDF5:
# 保存完整模型(含架构、权重、optimizer 状态) mlp_model.save('mlp_fakenews_detector.h5') # 加载模型(无需重新 build) loaded_model = keras.models.load_model('mlp_fakenews_detector.h5') # 验证加载后预测一致 pred_orig = mlp_model.predict(X_test_tfidf[:3]) pred_load = loaded_model.predict(X_test_tfidf[:3]) print(np.allclose(pred_orig, pred_load)) # 应输出 True注意:
.h5文件包含全部信息,直接load_model即可预测,无需compile()。但若要继续训练,需重新compile(因 optimizer 状态不保存在.h5中)。
4. 避坑指南:90% 的“模型不准”问题,其实卡在数据预处理和评估方式
学生交大作业最常翻车的不是模型写错,而是:
- 用训练集的
vectorizer去 transform 测试集,却忘了fit_transform只能用在训练集; - 评估时用 accuracy 当唯一指标,而虚假新闻数据天然不平衡(假新闻可能只占 20%);
- 把
predict()输出的概率直接当标签,却不设阈值。
以下是血泪经验总结的 4 条硬核避坑:
4.1 现象:测试集预测全是 0 或全是 1
原因:TfidfVectorizer未正确复用——在测试集上用了fit_transform而非transform,导致测试集词汇表与训练集不一致,向量全零。
解决:严格区分fit_transform(仅训练集)和transform(训练/测试/新文本均用):
# ✅ 正确:训练集拟合 + 转换,测试集仅转换 X_train_tfidf = vectorizer.fit_transform(train_texts) # fit + transform X_test_tfidf = vectorizer.transform(test_texts) # transform only! # ❌ 错误:测试集也 fit_transform → 词汇表错乱 X_test_tfidf = vectorizer.fit_transform(test_texts) # 绝对禁止!4.2 现象:验证集 loss 持续上升,但 accuracy 还在涨
原因:类别不平衡(如假新闻仅 15%),模型学会“全预测真”就能得 85% accuracy,但实际毫无判别力。
解决:改用f1-score或balanced_accuracy作为早停监控指标,并在model.compile中加class_weight:
from sklearn.utils.class_weight import compute_class_weight # 计算类别权重(假新闻样本少,权重更高) classes = np.unique(y_train) class_weights = compute_class_weight('balanced', classes=classes, y=y_train) class_weight_dict = dict(enumerate(class_weights)) # 训练时传入 history = mlp_model.fit( X_train_tfidf, y_train, class_weight=class_weight_dict, # 关键! ... )4.3 现象:predict()输出概率,但报告要求“真/假”标签
原因:model.predict()返回 [0.002, 0.998, 0.015...],直接np.argmax会出错(二分类输出是 1 维,不是 2 维)。
解决:用阈值0.5二值化,或更优地用predict_classes()(TF 2.10-)或np.where:
# ✅ 安全做法(兼容所有版本) y_pred_proba = mlp_model.predict(X_test_tfidf) y_pred = (y_pred_proba > 0.5).astype(int).flatten() # 得到 0/1 数组 # ✅ 更灵活:用 ROC 曲线选最优阈值(见第 5 章) from sklearn.metrics import roc_curve fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba) optimal_idx = np.argmax(tpr - fpr) # Youden's J statistic optimal_threshold = thresholds[optimal_idx] y_pred_opt = (y_pred_proba > optimal_threshold).astype(int).flatten()4.4 现象:本地跑通,但老师电脑报ModuleNotFoundError: No module named 'tensorflow'
原因:未提供requirements.txt,或用了tf-nightly等不稳定版本。
解决:固定版本 + 一键安装:
# 生成当前环境依赖(排除 dev 包) pip freeze --exclude-editable > requirements.txt # 要求文件内容示例(必须指定 patch 版本) numpy==1.23.5 pandas==1.5.3 scikit-learn==1.2.2 tensorflow==2.12.0 # 避免 2.13+ 的 CUDA 11.8 依赖提示:在文档说明中明确写出“请用
pip install -r requirements.txt安装,勿用 conda”。conda 安装 tensorflow 常因 channel 源不同导致版本错乱。
5. 部署级验证:用混淆矩阵 + ROC 曲线,把“准确率 85%”变成可信结论
交大作业不能只扔个accuracy_score就完事。老师会问:“假新闻漏检率多少?”“真新闻误杀率多少?”——这需要混淆矩阵(Confusion Matrix)和 ROC 曲线。它们不增加代码量,但能让报告瞬间专业 3 个档次。
5.1 混淆矩阵:四格表里藏着所有真相
sklearn.metrics.confusion_matrix输出 2x2 矩阵,但 raw 数字难读。必须用seaborn.heatmap可视化,并标注 recall/precision:
from sklearn.metrics import confusion_matrix, classification_report, roc_curve, auc import seaborn as sns import matplotlib.pyplot as plt # 获取预测标签(用最优阈值) y_pred = (mlp_model.predict(X_test_tfidf) > 0.5).astype(int).flatten() # 绘制混淆矩阵 cm = confusion_matrix(y_test, y_pred) plt.figure(figsize=(6, 5)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['Predicted Real', 'Predicted Fake'], yticklabels=['Actual Real', 'Actual Fake']) plt.title('Confusion Matrix') plt.ylabel('Actual') plt.xlabel('Predicted') plt.show() # 打印详细报告(含 precision/recall/f1) print(classification_report(y_test, y_pred, target_names=['Real News', 'Fake News']))关键解读:
- Recall(查全率):所有假新闻中,模型抓出了百分之几?(老师最关心“漏掉多少假新闻”)
- Precision(查准率):模型标为“假”的新闻里,真有百分之几是假的?(影响人工复核成本)
- 若 recall=0.6,precision=0.8 → 模型抓得准但漏得多;若 recall=0.9,precision=0.4 → 抓得多但误伤严重。二者需权衡。
5.2 ROC 曲线:找到属于你数据集的最优阈值
Accuracy 固定阈值 0.5,但虚假新闻场景中,我们往往愿牺牲部分 precision 换更高 recall(宁可多审几条,也不能放走一条谣言)。ROC 曲线就是干这个的:
# 计算 ROC 曲线 y_pred_proba = mlp_model.predict(X_test_tfidf).flatten() fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba) roc_auc = auc(fpr, tpr) # 绘制 plt.figure(figsize=(6, 6)) plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.3f})') plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--') plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.title('ROC Curve') plt.legend(loc="lower right") plt.grid(True) plt.show() # 找最优阈值(Youden's J = sensitivity + specificity - 1) j_scores = tpr - fpr optimal_idx = np.argmax(j_scores) optimal_threshold = thresholds[optimal_idx] print(f"Optimal threshold: {optimal_threshold:.3f}")参数说明:auc值越接近 1.0 越好(0.5 是随机猜测)。optimal_threshold是使tpr-fpr最大的点,平衡漏检与误杀。例如optimal_threshold=0.32,意味着把预测概率 >0.32 的都标为假新闻,比默认 0.5 更激进——这正是业务场景需要的灵活性。
5.3 文档说明的核心技巧:用“可复现命令”代替“请安装环境”
我在帮学弟改大作业时发现,90% 的文档失败源于“安装说明”写成散文。正确写法是:
运行前必做三件事
- 解压
大作业基于Python和MLP实现的互联网虚假新闻检测器源码+文档说明.zip- 进入解压目录,执行:
python -m venv env && source env/bin/activate # Linux/Mac # 或 Windows: python -m venv env && env\Scripts\activate.bat pip install -r requirements.txt
- 下载数据集(示例用 FakeNewsNet):
wget https://github.com/KaiDMML/FakeNewsNet/archive/refs/heads/master.zip unzip master.zip && mv FakeNewsNet-master/data/politifact/real/ ./data/real/ # (其他路径同理,文档中给出完整 mkdir + cp 命令)
最后一句我想说:当年我第一次交这个作业,因为没画 ROC 曲线被老师问“你确定这个 82% 准确率不是靠猜出来的?”,当场脸红。后来养成习惯——只要模型输出概率,必画 ROC;只要文本进模型,必打印vectorizer.vocabulary_前 10 个词。这些动作不花时间,但让每一次调试都有依据,而不是靠玄学调参。希望帮到你。
本文还有配套的精品资源,点击获取