简介:面向计算机、数学、电子信息等专业课程设计、期末大作业和毕业设计场景的虚假新闻检测项目,基于中文微信消息数据集,完整提供机器学习、深度学习与BERT三类方法的解决方案,解决NLP文本真假分类问题。压缩包共76个文件,以45个Python源码文件为主,涵盖传统模型训练、深度网络及BERT微调脚本,另有配置、模型记录、Jupyter Notebook等辅助文件,包体仅163KB,轻量易用。已有1138人学习下载。项目代码结构清晰,包含数据加载、预处理、特征工程(词袋/TF-IDF)、模型训练与评估的完整流程,集成了随机森林、支持向量机、朴素贝叶斯、逻辑回归等常用算法,并给出准确率、召回率、F1与AUC对比结果,适合需要理解多类模型差异、开展对比实验或快速搭建新闻分类系统的学习者参考。
1. 看到这个标题来搜源码的开发者,多半是想最快跑通一条虚假新闻检测流程
看到这个标题来搜源码的开发者,多半不是想读论文,而是希望拿下一份能直接跑出结果的虚假新闻检测项目,把“数据→训练→预测”这条链路在最短时间内打通。这里先打破一个预期:标题里的机器学习、深度学习、BERT,在这个项目里通常不是三选一,而是三条递进路线——先跑机器学习基线确定数据有没有问题,再上深度模型看增益,最后用BERT微调冲刺指标。而真正拉开差距的往往在模型之外:数据切分有没有泄漏、标签编码是否连续、评估用的是准确率还是宏F1。这个方向适合做NLP毕设、文本二分类入门,或者打算给内容审核加一个检测模块的工程师。
2. 训练数据先把住:标签映射、文本清洗与切分口径决定结果可信度
2.1 任务定义先行:二分类还是多分类,决定整个标注管线
虚假新闻检测在公开研究里有好几种口径。最常见的是文本二分类,标签只有真实和虚假;也有像LIAR这种六分类数据集,把结论分成真、大体真、半真、大体假、假、矛盾;还有带立场标签的,比如“支持”“反对”“观察”。拿到这类项目源码后,第一步不是看模型结构,而是先看数据标注口径。
这里有个关键判断:多分类虽然看起来信息量更大,但标注噪声和类间混淆会显著拉低模型可用度。比如“大体真”和“半真”对普通标注者来说边界非常模糊,模型学出来的边界也很难稳定。我一般会先做二分类,把多分类标签合并成真假两个桶,优先保证任务定义清晰、指标可解释。
公开数据集方面,常见选择是LIAR、FakeNewsNet和Kaggle上的经典Fake News数据集。它们的差异很明显:LIAR是短句级标注,标签偏政治言论核实;FakeNewsNet带新闻正文和传播特征;Kaggle那类经典数据集通常就是标题+正文+标签的CSV,最适合快速跑通流程。这三个数据集的共同问题是时间跨度有限,模型会学到当时的语言风格,这一点到后面评估时要特别注意。
标签映射是第一个容易翻车的细节。很多源码里标签是字符串,必须映射成从0开始的连续整数,否则PyTorch的CrossEntropyLoss会直接报错或者训练出无效模型:
# 多分类标签合并成二分类的常见映射方式 label_map = { "true": 0, "mostly-true": 0, "half-true": 1, "barely-true": 1, "false": 1, "pants-fire": 1, # 美式俚语标签,表示完全胡说 "real": 0, "fake": 1 } df["label"] = df["label"].str.lower().map(label_map) # 映射后必须检查是否有NaN,出现NaN说明有未覆盖的标签 assert df["label"].notna().all(), "存在未映射的标签,先检查数据"这段代码的逻辑是把所有带“真”倾向的标签归为0,所有带“假”倾向的归为1。断言那一步很有必要,因为公开数据集的标签写法不统一,大小写、下划线、空格都会导致map匹配失败,等训练时报错再回头查太浪费时间。
2.2 文本清洗与字段拼接:标题和正文是否合并,先做对照组
新闻文本的清洗和通用文本清洗不太一样。常见做法是去掉HTML标签、URL、@提及,但有一个工程细节容易被忽略:数字不要轻易删。新闻里的金额、日期、人数、百分比往往是判别真假的重要线索,一条假新闻常常在具体数字上含糊其辞或者编造得过于精确。无脑把所有数字替换成占位符,等于主动丢掉这部分信号。
import re def clean_text(text: str) -> str: if not isinstance(text, str): return "" text = re.sub(r"<[^>]+>", "", text) # 去HTML标签 text = re.sub(r"https?://\S+", "[URL]", text) # URL归一化 text = re.sub(r"@\w+", "[USER]", text) # 提及归一化 # 数字和百分号保留,新闻领域的数字是强特征 text = re.sub(r"\s+", " ", text).strip() return text.lower() df["title_clean"] = df["title"].map(clean_text) df["body_clean"] = df["body"].map(clean_text)参数说明:URL归一化成[URL]而不是直接删除,是因为URL本身暗示信息来源渠道,保留占位符可以让模型区分“有链接”和“没链接”两种状态。同理[USER]保留提及行为特征。转小写是标准的英文预处理,中文数据不需要这步。
字段拼接是个值得做对照实验的点。我一般会建三个版本:只用标题、只用正文、标题加正文。原因在于标题的措辞风格(惊叹号、极端词、断言句式)本身有很强的信号,但正文能提供论据细节;两者拼接时,BERT的输入长度限制会强制截断,正文后半段的信息可能被丢掉。先跑一个简单的机器学习基线来对比三个字段版本,比直接上BERT快得多,也能提前发现哪个字段是主要信号来源。
2.3 切分与去重:随机切分是虚假新闻检测最常见的虚高来源
这是整个项目里最容易让分数虚高的一步。新闻事件有天然的聚集性:同一个事件的多篇报道,甚至同一篇通稿的转载,会同时出现在训练集和测试集里。如果做随机切分,模型在训练时已经“见过”这些高度相似的文章,测试时等于开卷考试。
正确的做法是优先按发布时间切分。比如用前80%时间段的新闻训练,后20%时间段的新闻测试。这才符合真实场景:用过去的数据判断未来的新闻。只有明确没有时间字段时,才退而求其次做分层随机切分。
from sklearn.model_selection import train_test_split # 推荐做法:按发布时间切分 if "publish_date" in df.columns: df = df.sort_values("publish_date").reset_index(drop=True) cut = int(len(df) * 0.8) train_df = df.iloc[:cut] test_df = df.iloc[cut:] else: # 无时间字段的退路:分层随机切分 train_df, test_df = train_test_split( df, test_size=0.2, stratify=df["label"], # 按标签比例分层,避免切分后类别分布漂移 random_state=42 )参数说明:stratify必须传,否则切分后可能出现测试集里某个类别样本极少的情况,尤其当数据量只有几千条时。random_state固定下来,方便后面复现对比实验。
时间切分做完后,还要做一步近似去重。新闻网站之间互相转载太常见,同一篇稿件可能以不同标题出现多次。我习惯用正文的第一个句子算哈希,然后删除重复项;更精细的做法是用difflib或者向量相似度,但第一个句子的哈希已经能挡掉90%的转载重复。
评估指标上,准确率在这类任务里常常骗人。当数据集里70%是真实新闻时,一个全预测“真实”的模型准确率也有70%。必须看宏F1、每个类别的precision和recall,以及混淆矩阵。我一般会直接输出一个分类报告:
from sklearn.metrics import classification_report print(classification_report( y_test, y_pred, target_names=["真实", "虚假"], digits=4 ))这段代码用来强制自己看每个类别的表现。虚假新闻的recall低,说明漏检严重;precision低,说明误伤太多。这两个值在不同业务场景下权重完全不同——内容风控更怕漏检,公开展示更怕误伤。
3. 机器学习、深度学习、BERT三套路线:各自解决什么问题、什么时候该换
3.1 先跑机器学习基线:TF-IDF加逻辑回归是“照妖镜”
很多初学者拿到源码会直接跳到BERT,这是最亏的做法。机器学习基线在这个任务里的价值不是最后分数,而是给后续所有模型划定一条及格线。如果TF-IDF加逻辑回归的宏F1只有0.6,后面BERT跑到0.65,这可能不是BERT厉害,而是数据里还有没清干净的问题;反过来,如果逻辑回归已经到0.8,BERT死活上不去0.82,说明数据信号已经接近饱和,该考虑换特征而不是换模型。
逻辑回归在这类短文本任务上的表现一直被低估。新闻文本里“据称”“消息人士”“知情人士透露”这类转述短语的分布,以及感叹号、问号、绝对化用词(永远、绝不、唯一)的频率,对真假判别有强相关性。TF-IDF配合bigram正好能捕获“据称”“绝无此事”这类相邻词组合。
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline pipe = Pipeline([ ("tfidf", TfidfVectorizer( ngram_range=(1, 2), # 单字特征加相邻词组合 max_features=50000, # 限制词表膨胀,保留高频特征 sublinear_tf=True, # 词频用log压缩,防止高频词主导 min_df=2 # 去掉只出现一次的稀疏词 )), ("clf", LogisticRegression( C=1.0, class_weight="balanced", # 按类别频率自动加权 max_iter=1000 # 默认100不够,容易爆收敛警告 )), ]) pipe.fit(train_text, train_label)参数说明里值得留意的是sublinear_tf=True。新闻语料里“的”“了”“在”这类高频词虽然会被IDF降权,但长文本中绝对词频仍然很高,用1 + log(tf)压缩后能显著减少高频虚词对决策边界的拉扯。class_weight="balanced"是处理类别不平衡最简单的方式,它按类别样本数的反比给损失加权,比手动过采样更稳。
如果这个基线的宏F1连0.7都不到,先别碰深度模型,回头检查数据问题。
3.2 TextCNN与BiLSTM:深度学习路线的性价比分析
深度学习路线在这类任务上常见的是TextCNN和BiLSTM加Attention两个方案。TextCNN的思路是用多个卷积核并行扫描文本,每个卷积核相当于一个n-gram探测器;BiLSTM加Attention则是对整个序列做双向编码,再让注意力机制去找关键片段。理论上BiLSTM的表达能力更强,但在虚假新闻检测上不一定更好。
原因在于:新闻真假的核心信号往往是局部措辞,而不是长距离依赖。一条标题是“某地发生爆炸,官方回应称无伤亡”,判断真假的关键是“官方回应称”这个短语和后文数字是否矛盾,这种信号在局部窗口内就能捕获。BiLSTM擅长的是“先提到A,隔了50个词又提到B,两者存在指代关系”这类长距离依赖,在新闻短文本里比较少见。
import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim=100, num_filters=128, kernel_sizes=(2, 3, 4), num_classes=2): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.convs = nn.ModuleList([ nn.Conv1d(embed_dim, num_filters, k, padding=k // 2) for k in kernel_sizes ]) self.fc = nn.Linear(len(kernel_sizes) * num_filters, num_classes) def forward(self, x): # x: (batch, seq_len) emb = self.embedding(x).transpose(1, 2) # 转成Conv1d需要的(batch, embed_dim, seq_len) pooled = [ torch.relu(conv(emb)).max(dim=2).values # 全局最大池化 for conv in self.convs ] return self.fc(torch.cat(pooled, dim=1))参数说明:kernel_sizes=(2, 3, 4)对应bigram、trigram、四gram三种窗口,这是短文本分类的标准配置。padding=k // 2保证卷积后序列长度不变,这样max(dim=2)取的是整个序列的全局最大值。num_filters=128意味着每个卷积核输出128个通道,三层拼接后是384维,这个维度对二分类足够了,再往上加对效果帮助不大只会拖慢训练。
如果数据量在万级以下,TextCNN配合预训练词向量基本就是深度学习的上限了。BiLSTM加Attention在效果上通常打不过TextCNN,训练速度却慢两倍以上,除非你的数据有明显的长距离依赖特征,否则我建议直接放弃BiLSTM。
3.3 BERT方法的关键:预训练权重加载与微调策略
标题里的“bert方法”在开源项目里基本等于Transformers库加预训练权重加微调。BERT的价值在于它在大规模语料上预训练过,对语言的语法、指代、常识有先验理解。在虚假新闻检测这种数据量通常只有几千到几万条的任务上,这种先验知识非常宝贵。
真正的门槛不是模型结构,而是预处理对齐。BERT自带WordPiece分词器,有固定的词表和特殊token,输入必须走它的tokenizer。直接拿自己清洗后的文本喂进去,和预训练时的分布不一致,效果会明显下降。
from transformers import AutoTokenizer, AutoModelForSequenceClassification model_name = "bert-base-uncased" # 英文数据;中文数据换 bert-base-chinese tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained( model_name, num_labels=2 ) def encode(texts, max_len=160): return tokenizer( texts, truncation=True, padding=True, max_length=max_len, return_tensors="pt" )这里需要注意:truncation=True默认是截掉超长部分的后半段,也就是只保留文本开头。对新闻标题来说没问题,但正文类样本就需要考虑截断策略,后面第4章会单独讲。
微调训练循环里,优化器和调度器有固定的搭配方式。BERT微调不用普通SGD或者Adam,而是用AdamW加线性预热调度,学习率通常是2e-5到5e-5。这个范围比深度学习常见的0.001小很多,因为BERT的预训练权重已经很接近好的解,学习率太大一步就冲出去了。
from transformers import AdamW, get_linear_schedule_with_warmup optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01) total_steps = len(train_loader) * epochs scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=int(0.1 * total_steps), # 前10%的步数预热 num_training_steps=total_steps ) for epoch in range(epochs): for batch in train_loader: outputs = model(**batch) # 返回loss和logits outputs.loss.backward() optimizer.step() scheduler.step() # 注意,调度器每个step都要更新 optimizer.zero_grad()weight_decay=0.01的作用是给大权重加惩罚防止过拟合。num_warmup_steps设置为总步数的10%到20%,让学习率从小到大爬升,这一步对BERT收敛的稳定性影响很大,省略掉warmup经常会导致训练初期loss波动明显。
3.4 三路线怎么选:一张参数表和一个对照实验建议
三套路线放在同一个评测框架里对比,才能看到完整的演进逻辑。下面这张表是这类项目里最常见的选型参照:
| 路线 | 数据量要求 | 训练耗时 | 可解释性 | 适用场景 |
|---|---|---|---|---|
| TF-IDF + 逻辑回归 | 千级即可 | 分钟级 | 高,权重直接可见 | 基线、快速上线、验证数据质量 |
| TextCNN / BiLSTM | 万级以上 | 小时级 | 中,可看卷积核分布 | 有领域词向量、数据量中等 |
| BERT微调 | 千级可用 | GPU小时级 | 低,注意力可辅助解释 | 追求指标、算力充足 |
选型逻辑上,我的习惯是:先跑机器学习基线,用它的宏F1作为基准线;如果基线和业务目标差距在3个点以内,不值得上BERT,直接优化数据更划算;如果差距超过5个点,先试TextCNN看深度模型的增益方向;BERT最后上,并且和最优结果做显著性对比,而不是只看单次训练的分数。
4. 把BERT跑稳并把模型用起来:微调参数、类别不平衡与最小推理接口
4.1 BERT微调的四个必调参数
BERT微调能跑通不难,跑稳很看参数。四个必调参数里,学习率是第优先级,然后是max_len、batch_size和训练轮数。
学习率推荐从2e-5起步,只调这一个参数反复试,通常能稳定在2e-5到5e-5之间。超过5e-5时训练loss容易剧烈震荡,低于1e-5则收敛太慢。batch_size在显存允许的情况下用16或32。更大的batch会让梯度估计更稳,但会显著降低收敛速度,需要配合更多训练步数。显存不够时不要直接调小batch,用梯度累积模拟大batch的效果更稳。
max_len则要看输入构成:只用标题时128够用;标题加正文时256起步;长文本正文建议512上限,同时配合截断策略。训练轮数上,BERT微调通常2到3轮就够,再多容易过拟合。判断标准很简单:每轮结束看验证集的宏F1,连续两轮不涨就停。
warmup_ratio也不用过于纠结,固定在0.1就行。
4.2 类别不平衡和截断策略:两个容易让线上失效的细节
真实新闻数据天然不平衡,有些数据集里虚假新闻只占20%到30%。BERT在类别不平衡下容易被多数类带走,损失函数里大部分梯度来自真实新闻,模型最终偏向把什么都预测成真实。除了一开始就在模型里用class_weight,还可以做阈值调整:训练完在验证集上遍历置信度阈值,选宏F1最高的那个作为线上判定阈值,而不是默认的0.5。
from sklearn.utils.class_weight import compute_class_weight weights = compute_class_weight( class_weight="balanced", classes=[0, 1], y=df["label"].values ) # 返回的weights按classes顺序排列,例如 [0.8, 1.4] 表示真实类权重0.8,虚假类权重1.4参数说明:classes=[0, 1]必须传,compute_class_weight靠这个参数确定权重与类别的对应关系。计算出的权重传给AutoModelForSequenceClassification的class_weight参数,或者自己在损失函数里乘上去。
截断策略是BERT路线最容易忽略的细节。truncation=True默认只保留开头,但新闻正文的关键信息往往在结尾——最后的结论、数据来源、署名都可能在倒数几十个字里。我在实际项目里更常用头部加尾部拼接的方式:
def head_tail_truncate(text, tokenizer, max_len=256): ids = tokenizer(text, add_special_tokens=False)["input_ids"] head = ids[:128] # 保留开头128个token tail = ids[-124:] # 保留末尾124个token # 减去2个位置留给[CLS]和[SEP] return [tokenizer.cls_token_id] + head + tail + [tokenizer.sep_token_id]这里分配比例是128比124,差值来自特殊token占位。这个策略的核心逻辑是:新闻的导语在开头,结论和出处往往在末尾,中间段落的信息密度相对低,截断时优先牺牲中间部分。如果每个样本的正文长度差异很大,滑动窗口加池化是更精细的方案,但工程复杂度会上升,大多数项目里head-tail已经够用。
4.3 最小推理服务:训练完怎么把模型暴露成HTTP接口
训练好的模型最终要对外提供预测能力,常见做法是封装一个最小HTTP接口。这里的关键不是模型代码,而是模型生命周期管理:模型在服务启动时加载一次,之后每个请求只走forward,不做任何训练相关的计算。
from fastapi import FastAPI from pydantic import BaseModel import torch app = FastAPI() model.eval() # 切到推理模式,关闭dropout和BN统计 class NewsItem(BaseModel): title: str body: str = "" @app.post("/predict") def predict(item: NewsItem): text = f"{item.title} {item.body}".strip() inputs = tokenizer( text, return_tensors="pt", truncation=True, max_length=160 ) with torch.no_grad(): logits = model(**inputs).logits prob = torch.softmax(logits, dim=-1).squeeze().tolist() return {"真实": prob[0], "虚假": prob[1]}两个参数细节:第一,model.eval()必须放在加载完成之后,否则模型里的Dropout层还在随机丢弃信息,同一个样本每次预测结果都会波动;第二,torch.no_grad()不能省,它会避免构建计算图,省显存且速度快一倍以上。返回概率值而不是直接返回预测类别,是为了让上层业务自己决定阈值,比如审核场景可能要求虚假概率超过0.6才标记,推荐场景可能0.4就拦截。
5. 避坑:跑虚假新闻检测源码最容易翻车的五个地方
5.1 训练和验证都很好,一换新数据就崩
现象:随机切分下训练集和验证集宏F1都在0.9以上,模型放到新采集的新闻上直接掉到0.6。
原因:这是典型的新闻数据泄漏。同一个事件的多篇报道在随机切分时同时进训练集和验证集,模型在训练阶段已经见过高度相似的文本。换一批新新闻后,训练时学到的事件特定措辞全部失效。
解决:切分必须按发布时间排序,前80%训练后20%验证。同时用首句哈希做近似去重,把跨站转载的重复稿件去掉。最后再按事件维度做一次分组验证,确保同一个事件的报道只出现在一个集合里。
5.2 BERT训练时loss是NaN或者长时间不下降
现象:训练到某个step后loss变成NaN,或者前几百步loss一直不降。
原因:最常见的是学习率过高,BERT预训练权重已经很接近最优解,用超过5e-5的学习率直接导致梯度爆炸。另一个隐蔽原因是标签没有从0开始连续编号,比如label是1和2,模型输出2维logits而标签是2,越界导致loss计算异常。
解决:学习率从2e-5起,加载预训练权重时确认num_labels=2,检查label值域是不是[0, 1]。如果数据是英文但误用了bert-base-chinese权重,也会出现loss不降的情况,先确认模型文件语言的匹配。
5.3 预训练权重加载失败或下载中断
现象:from_pretrained报连接超时,或者下载到一半断掉,反复重试浪费时间。
原因:BERT权重文件通常是几百MB到1GB级别,from_pretrained默认从Hugging Face Hub下载。网络波动或下载链路不稳定时容易中断,且默认没有断点续传。
解决:先设置镜像站点环境变量,再触发下载;或者直接用snapshot_download把权重文件落盘,之后用本地目录加载:
export HF_ENDPOINT=https://hf-mirror.com# 下载完成后保存到本地目录,后续从本地加载,避免重复下载 tokenizer = AutoTokenizer.from_pretrained("./models/bert-base-uncased") model = AutoModelForSequenceClassification.from_pretrained( "./models/bert-base-uncased", num_labels=2 )参数说明:HF_ENDPOINT环境变量只影响Hugging Face Hub的访问地址。镜像站会同步托管原始模型文件,只要模型名没改,本地代码不需要任何变动。权重文件落盘后,以后加载全部走本地路径,离线也能跑。
5.4 准确率很好看,宏F1却惨不忍睹
现象:准确率0.85,宏F1只有0.55。
原因:类别不平衡。如果数据里80%是真实新闻,模型只要把一切都预测为真实,准确率就有80%。虚假类别的recall是0,宏F1直接被打下去。
解决:训练时用class_weight="balanced"或者compute_class_weight加权。评估时把准确率打入冷宫,专注看两个类别的precision、recall和宏F1。如果虚假类别的recall低于某个业务阈值(比如0.8),在验证集上调低判定阈值到0.3或0.4,宁可多误报。
5.5 复现不出论文或别人repo里的分数
现象:同一份数据集,别人报的宏F1是0.82,自己跑出来只有0.75。
原因:差异基本不在模型结构,而在预处理和切分。差别来源依次是:数据版本不同(原始数据可能有更新)、清洗规则不同(是否去数字、是否归一化URL)、随机种子不同、以及最重要的——切分方式不同(随机切分还是时间切分)。
解决:代码里固定所有seed(Python、NumPy、PyTorch都要设),数据预处理参数集中放到一个配置里,并保留一份独立的测试集不参与任何调参迭代。我在项目里会把预处理规则、切分时间点、seed写进一个config.json,这样每次训练结果都可追溯。
6. 模型没在“作弊”:用对抗样本和最小鲁棒性测试做最后一道验收
6.1 三个低成本对抗样本:改数字、改实体、删否定词
模型能跑通只是第一步,还得验证模型真的学到了“虚假信号”而不是记住了数据集的表面特征。我现在的习惯是训练结束后立刻做一组最小鲁棒性测试,用三个低成本的文本扰动方法检查模型反应。
import re import random def perturb_date(text: str) -> str: # 把日期替换成固定日期,检验模型是否依赖具体时间 return re.sub(r"\d{4}年\d{1,2}月\d{1,2}日", "2020年1月1日", text) def perturb_number(text: str) -> str: # 把所有数字替换成随机数,检验模型是否靠数字猜结论 return re.sub(r"\d+", str(random.randint(1, 9999)), text) def drop_negation(text: str) -> str: # 去掉否定词,检验模型是否真正理解语义方向 return text.replace("不", "").replace("没有", "").replace("否认", "承认")具体做法是:从测试集里抽出50条预测置信度最高的样本,分别做三种扰动后重新预测,统计预测类别翻转的比例。如果改几个数字或日期就让一半样本翻车,说明模型在靠数字特征猜答案,没有真正理解语义;如果删掉否定词后预测结果没变化,说明模型根本没学会逻辑关系,只是记住了词共现。这两类情况都需要回炉调数据或换模型路线。
还可以顺手做注意力可视化,把BERT对“据称”“伪造”“证实”这类词的注意力权重拉出来看,如果模型盯着抓人的关键词,而不是当真在判断内容一致性,说明训练数据本身带了不该有的风格偏差。
这个鲁棒性测试不会改变模型分数,但它能暴露模型上线后的真实表现。我一般在交付源码前都会跑一遍这三项检查,确认模型在扰动下预测变化不超过20%。希望这个习惯能帮到你。
本文还有配套的精品资源,点击获取