微调BERT检测假新闻
在当今信息过载的时代,区分真实和虚假新闻比以往任何时候都更加重要。在这篇指南中,我将带领你完成一个基于BERT的文本分类器的端到端实现,使用的是WELFake数据集,全部在Google Colab中进行,设置最少。这旨在帮助新手对Hugging Face生态系统和NLP管道感到自信。
1、挂载Google Drive
from google.colab import drive
drive.mount('/content/drive')
这让你可以直接将文件加载/保存到Google Drive。
2、安装所需库
由于你在使用Google Colab,许多重要的库如transformers
、datasets
和tqdm
已经预先安装好了。但如果它们版本过旧或缺失,你可能仍然需要安装或升级特定的库。
!pip install --upgrade tqdm
!pip install evaluate
tqdm
用于进度条,evaluate
用于计算准确率和AUC等指标。
3、导入必要的库
import numpy as np # 用于数值操作,如softmax或AUC
import pandas as pd # 用于读取和处理CSV或表格数据
import re # 用于使用正则表达式清理文本
import torch # 用于使用PyTorch并启用GPU支持
from datasets import DatasetDict, Dataset # 用于组织和分割数据集
from transformers import (
AutoTokenizer, # 加载适合模型的分词器
AutoModelForSequenceClassification, # 加载BERT用于分类任务
TrainingArguments, # 定义训练设置(批量大小、学习率等)
Trainer, # 处理训练、评估和保存
DataCollatorWithPadding, # 自动为批次添加填充
EarlyStoppingCallback # 如果模型停止改进,则停止训练
)
import evaluate # 用于计算准确率、AUC、F1和其他指标
4、加载和清理数据集
# 📥 从Google Drive加载数据集
DF = pd.read_csv("/content/drive/MyDrive/WELFake_Dataset.csv")
# 🧹 删除不必要的'Unnamed: 0'列(自动生成的索引)
DF.drop(["Unnamed: 0"], axis=1, inplace=True)
# 🧼 删除任何包含缺失值(空值)的行
DF.dropna(inplace=True)
# 🧩 将'title'和'text'列合并成单个'Content'列
DF["Content"] = DF["title"] + " " + DF["text"]
# 🗑️ 删除原始'title'和'text'列——我们只需要'Content'
DF.drop(["title", "text"], axis=1, inplace=True)
在这个阶段,我们通过删除不必要的列并将'title'和'text'合并到单个'Content'列中完成了初步的数据清理。但是,数据集尚未完全准备好。在接下来的步骤中,我们将删除重复项、检查类别平衡,并执行任何其他必要的清理工作以准备数据进行分词和微调。适当的预处理对于确保模型从高质量、无噪声的输入中学习至关重要。
5、清理和去重文本
# 🧼 定义一个简单的文本清理函数
def clean(text):
if not isinstance(text, str):
return "" # 如果输入不是字符串,则返回空字符串
text = text.lower() # 将所有文本转换为小写
text = re.sub(r'[^a-z0-9\s]', '', text) # 删除标点符号和特殊字符
text = re.sub(r'\s+', ' ', text).strip() # 替换多余的空格为单个空格并去除首尾空白
return text
# 🧹 将清理函数应用于'Content'列
DF["Content"] = DF["Content"].apply(clean)
# 🚫 删除清理过程中可能生成的任何空行
DF = DF[DF["Content"].str.strip() != ""]
# 🧭 基于'Content'列删除重复的文章
# 这有助于确保模型不会多次学习相同的信息
DF = DF.drop_duplicates(subset="Content").reset_index(drop=True)
为了进一步清理数据,我们创建了一个简单的函数来将文本转换为小写、删除特殊字符并减少多余空格。在将其应用于'Content'列后,我们删除了任何变成空的行,并删除了重复项以避免模型多次训练相同的新闻文章。这些步骤有助于确保我们的模型从干净、唯一且有意义的文本中学习。
6、转换为Hugging Face数据集
DF_dataset = Dataset.from_pandas(DF)
在清理完DataFrame后,我们将其转换为Hugging Face的Dataset
对象。这种格式针对Hugging Face的Trainer
、分词器和数据管道进行了优化。它使得应用转换、拆分数据和高效训练模型变得更加容易——特别是在处理大型数据集时。可以将其视为将数据转换为Hugging Face工具理解的最佳格式。
7、定义评估指标
# 📏 从Hugging Face加载内置的度量计算器
accuracy = evaluate.load("accuracy")
auc_score = evaluate.load("roc_auc")
# 📊 定义一个函数在评估期间计算指标
def compute_metrics(eval_pred):
predictions, labels = eval_pred # 解包模型预测和真实标签
# 🔁 使用softmax将logits转换为概率
probabilities = np.exp(predictions) / np.exp(predictions).sum(-1, keepdims=True)
positive_class_probs = probabilities[:, 1] # 保留正类的概率
# 🧮 计算AUC(模型分离类别的程度)
auc = auc_score.compute(prediction_scores=positive_class_probs, references=labels)['roc_auc']
# ✅ 计算准确率(预测与标签匹配的频率)
acc = accuracy.compute(predictions=np.argmax(predictions, axis=1), references=labels)['accuracy']
# 返回四舍五入后的分数以便易于阅读
return {"accuracy": round(acc, 3), "auc": round(auc, 3)}
为了评估模型的表现,我们定义了一个compute_metrics
函数。它计算两个关键指标:
准确率:预测正确的百分比。
AUC(接收者操作特征曲线下面积):一个分数,显示模型分离两类的能力。AUC越高,分离能力越强。
首先使用softmax函数将预测转换为概率,然后提取正类的概率。此函数被传递给Hugging Face的Trainer
,后者会在每次评估步骤后自动调用它以记录模型的性能。
8、加载BERT模型和分词器
model_path = "google-bert/bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForSequenceClassification.from_pretrained(
model_path, num_labels=2,
id2label={0: "Fake", 1: "Real"},
label2id={"Fake": 0, "Real": 1}
)
model_path = "google-bert/bert-base-uncased
是您要使用的预训练模型的名称。
"bert-base-uncased"
是一个标准的BERT模型,有1.1亿个参数。- 它被称为uncased,因为它忽略大小写(例如,“News”和“news”被视为相同)。
google-bert/
前缀表示它是Google上传到Hugging Face Hub的。
tokenizer = AutoTokenizer.from_pretrained(model_path)
这加载与所选BERT模型匹配的分词器。
为什么这很重要:
- 分词器将您的文本分成标记(如单词或子词)。
- 然后,它们将这些标记转换为数字(输入ID),模型可以理解。
- 使用
AutoTokenizer
确保它选择了正确的类型(BERT使用WordPiece)。
提示:始终使用与预训练模型匹配的分词器——否则,您可能会给模型提供它未受过训练的内容。
model = AutoModelForSequenceClassification.from_pretrained(...)
这加载预训练的BERT模型,并在顶部添加一个分类层——适合任务如假新闻检测。
num_labels=2:
告诉模型这是一个二元分类
{"Fake" : 0 , "Real" : 1}
id2label={0: "Fake", 1: "Real"}
这提供了从类ID(数字)到标签(文本)的映射。
它在打印预测时使模型输出更易读。
label2id={"Fake": 0, "Real": 1}
这是反向映射——它告诉模型如何在训练期间将文本标签转换回数字。
9、冻结大部分BERT
#冻结所有层
for param in model.bert.parameters():
param.requires_grad = False
#解冻最后两层
for param in model.bert.encoder.layer[-2:].parameters():
param.requires_grad = True
为什么要冻结BERT层?
当使用像BERT这样的大型预训练模型时,早期层已经从像维基百科这样的大型数据集中学习了通用语言模式。这些层已经擅长理解语法、意义和结构。
因此,与其从头开始重新训练整个模型(这既慢又需要更多数据),我们可以冻结这些层,只专注于微调最后几层以适应我们的特定任务——在这种情况下是假新闻检测。
这有助于:
- 加快训练速度
- 防止在小数据集上过拟合
- 使用更少的计算资源
为什么解冻最后两层?
BERT的较深层(接近末尾)捕获更多的任务特定信息。
通过仅解冻最后两层:
- 您让模型学习与您的数据集相关的特征
- 但仍然保持早期层稳定,以保留通用语言知识
这是一种常见的技术称为部分微调——它在性能和效率之间取得平衡。
什么时候解冻更多层?
如果您有更大的数据集,任务与BERT的预训练非常不同(例如法律/医学领域),或者您看不到仅使用1-2层的好验证准确性,则可以逐渐解冻更多层。
但要小心:过早解冻太多会导致训练时间更长和过拟合。
10、对数据进行分词
def preprocess_function(examples):
return tokenizer([str(x) for x in examples["Content"]], truncation=True, padding=True, max_length=512)
tokenized_data = DF_dataset.map(preprocess_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
在将文本馈送给BERT之前,需要对其进行分词——这意味着将原始文本转换为模型能够理解的数值输入。
preprocess_function()
接受每个示例的"Content"
字段,并使用BERT分词器应用:truncation=True
:将长文章截断以适应BERT的512令牌限制。padding=True
:为较短的示例添加填充,使其大小一致。max_length=512
:确保所有序列都被限制在512个令牌以内,这是BERT能处理的最大长度。
我们使用.map()
将此函数应用于整个数据集——这在批处理中高效运行。
然后,我们使用DataCollatorWithPadding
确保在形成训练批次时,它们被正确填充——节省内存并确保GPU性能顺畅。
11、训练/验证/测试分割
train_test = tokenized_data.train_test_split(test_size=0.2, seed=42)
val_test = train_test["test"].train_test_split(test_size=0.5, seed=42)
final_dataset = DatasetDict({
"train": train_test["train"], # 80%
"validation": val_test["train"], # 10%
"test": val_test["test"] # 10%
})
一旦数据被分词,我们需要将其分为三部分:
- 训练集:用于教模型
- 验证集:用于在训练期间检查性能
- 测试集:用于在训练结束后评估性能(最终得分)
12、定义训练参数
training_args = TrainingArguments(
output_dir="/content/drive/MyDrive/my_bert_model",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
per_device_train_batch_size=8,
per_device_eval_batch_size=8,
num_train_epochs=5,
weight_decay=0.01,
warmup_ratio=0.1,
load_best_model_at_end=True,
metric_for_best_model="eval_accuracy",
greater_is_better=True,
logging_steps=50,
fp16=True
)
想想TrainingArguments
就像模型的训练“设置面板”:
以下是完整分解:
13、训练模型
trainer = Trainer(
model=model,
args=training_args,
train_dataset=final_dataset["train"],
eval_dataset=final_dataset["validation"],
tokenizer=tokenizer,
data_collator=data_collator,
compute_metrics=compute_metrics,
callbacks=[EarlyStoppingCallback(early_stopping_patience=2)],
)
trainer.train()
“Trainer
类通过将所有组件——模型、数据、分词器、度量和配置——组合成单一、易于使用的对象,简化了训练过程。只需调用.train()
,它就会处理其余部分!”
trainer.train(resume_from_checkpoint=True)
这一行代码将在你的output_dir
中查找最新的保存检查点,并自动恢复训练。
14、在测试集上评估
eval_results = trainer.evaluate(eval_dataset=final_dataset["test"])
print(eval_results)
15、绘制AUC曲线和混淆矩阵
from sklearn.metrics import roc_curve, auc, confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt
y_true = final_dataset["test"]["label"]
predictions = trainer.predict(final_dataset["test"])
probs = np.exp(predictions.predictions) / np.exp(predictions.predictions).sum(-1, keepdims=True)
y_scores = probs[:, 1]
y_pred = np.argmax(predictions.predictions, axis=1)
# ROC曲线
fpr, tpr, _ = roc_curve(y_true, y_scores)
plt.plot(fpr, tpr, label=f"AUC = {auc(fpr, tpr):.2f}")
plt.plot([0, 1], [0, 1], linestyle='--')
plt.title("ROC Curve")
plt.xlabel("FPR")
plt.ylabel("TPR")
plt.legend()
plt.grid()
plt.show()
# 混淆矩阵
cm = confusion_matrix(y_true, y_pred)
ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=["Fake", "Real"]).plot(cmap="Blues")
plt.title("Confusion Matrix")
plt.grid(False)
plt.show()
16、学到的经验
- 仅微调BERT的最后两层可以在清洁、平衡良好的数据集上获得强大的结果
- 简单的文本清理(如小写化、删除特殊字符和去重)在提升模型性能方面大有裨益。
- 不要仅仅依赖准确率——使用AUC和混淆矩阵等指标可以给出更清晰、更可信的评估。
- Hugging Face的Trainer API非常初学者友好。它处理分词、训练、评估和保存时几乎没有样板代码。
- 然而,模型在未见过的不同来源数据上可能无法很好地泛化——即使经过彻底的预处理和类别平衡。
17、为什么在新数据集上准确率下降?
当我测试模型在一个完全不同来源的假新闻数据集上时,即使没有数据泄露、没有重复、类别平衡良好且预处理得当,准确率也明显下降。
这并不罕见。通常发生的原因是:
- 不同的写作风格或模型在训练期间未见过的来源
- 模型学会了特定于原始数据集的模式,而不是普遍真理
- 即使像BERT这样强大的模型也需要更多的数据多样性才能很好地泛化
18、最终想法
这个项目展示了,通过最小的设置,您可以从原始新闻文章到一个稳健的假新闻分类器,使用Hugging Face的强大工具和一些简单的预处理。
即使仅微调BERT的最后几层,我们在一个清理良好、平衡的数据集上也取得了令人印象深刻的结果——展示了迁移学习在NLP中的力量。
然而,在测试相同模型时,在一个完全不同来源的数据集上,准确率显著下降。这是一个常见且重要的观察:
这突显了一个关键教训:高验证准确率并不总是意味着强现实世界表现,尤其是在NLP任务中,数据在不同来源中差异很大。
你可以点击这里探索完整的笔记本并在Google Colab中自行运行它。
原文链接:Fake News Detection Using Fine-Tuned BERT : WELFake Dataset Analysis and Results
汇智网翻译整理,转载请标明出处