
HuggingFace 提供了巨大的模型库,虽然其中的很多模型性能表现出色,但这些模型往往是在广义的数据集上训练的,缺乏针对特定数据集的优化,所以在获得一个合适的模型之后,往往还要针对具体任务的特定数据集进行二次训练,这就是所谓的迁移学习。迁移学习的训练难度低,要求的数据集数量少,对计算资源的要求也低。
HuggingFace 提供了训练工具,统一了模型的再训练过程,使调用者无须了解具体模型的计算过程,只需针对具体的任务准备好数据集,便可以再训练模型。
本次将使用一个情感分类任务的例子来再训练一个模型,以此来讲解HuggingFace训练工具的使用方法
本文的代码和数据集已上传至GitHub
正片开始
首先,我们将导入所需的模块。 我们使用 `datasets` 模块来处理数据集,使用 `matplotlib` 来绘制结果,使用 `numpy` 来处理数值,使用 `torch` 来计算张量,使用 `torch.nn` 来处理神经网络,使用 `torch.optim` 来处理神经网络优化器,使用 `torchtext` 来处理文本,使用 `tqdm` 来衡量进度。
python
import collections
import datasets
import matplotlib.pyplot as plt
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torchtext
import tqdm
我们还将确保为"torch"和"numpy"设置随机种子。这是为了确保此笔记本可重现,即每次运行它时我们都会得到相同的结果。
通常,使用不同的随机种子多次运行实验是一种很好的做法------既可以测量模型的方差,也可以避免仅使用"好"或"坏"种子计算结果,即在训练过程中的随机性方面非常幸运或不幸。
python
seed = 1234
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.backends.cudnn.deterministic = True
使用 datasets.load_dataset 函数从 Hugging Face 的数据集仓库中加载 XiangPan/waimai_10k (https://huggingface.co/datasets/XiangPan/waimai_10k) 数据集的训练集部分。通过观察原始数据分布发现数据分布不均匀,使用 shuffle
方法对加载的训练集数据进行打乱,随机种子设置为 42,以确保结果的可重复性。使用 train_test_split
方法将打乱后的数据集按照 8:2 的比例划分为训练集和测试集。
python
train_data = datasets.load_dataset("XiangPan/waimai_10k", split="train")
train_data=train_data.shuffle(42)
train_data, test_data = train_data.train_test_split(test_size=0.2).values()
train_data, test_data
python
(Dataset({
features: ['label', 'text'],
num_rows: 9589
}),
Dataset({
features: ['label', 'text'],
num_rows: 2398
}))
接下来我们指定要使用的预训练模型名称为 "bert-base-chinese"。bert-base-chinese 是一个基于中文语料库预训练的 BERT 模型,它在许多中文自然语言处理任务中都有很好的表现。使用 transformers
库中的 AutoTokenizer
类来加载与指定预训练模型对应的分词器。
python
transformer_name = "bert-base-chinese"
tokenizer = transformers.AutoTokenizer.from_pretrained(transformer_name)
现在的数据集还是文本数据,使用编码工具将这些抽象的文字编码成计算机善于处理的数字。
python
def tokenize_and_numericalize_example(example, tokenizer):
"""
对输入的文本数据进行分词并将其转换为对应的词元 ID。
参数:
example (dict): 包含文本数据的字典,通常包含一个键 "text"。
tokenizer: 分词器对象,用于将文本分词并转换为词元 ID。
返回:
dict: 包含词元 ID 列表的字典,键为 "ids"。
"""
try:
# 从输入的字典中提取文本数据
text = example["text"]
# 使用分词器对文本进行处理,并截断过长的文本
tokenized_output = tokenizer(text, truncation=True)
# 从分词器的输出中提取词元 ID 列表
ids = tokenized_output["input_ids"]
except KeyError:
print("输入的字典中缺少 'text' 键。")
return {"ids": []}
except Exception as e:
print(f"处理文本时发生错误: {e}")
return {"ids": []}
return {"ids": ids}
python
train_data = train_data.map(
tokenize_and_numericalize_example, fn_kwargs={"tokenizer": tokenizer}
)
test_data = test_data.map(
tokenize_and_numericalize_example, fn_kwargs={"tokenizer": tokenizer}
)
下面我们将数据集格式转换为pytorch张量格式,并且只保留 ids 和 label 这两列数据。
python
train_data = train_data.with_format(type="torch", columns=["ids", "label"])
test_data = test_data.with_format(type="torch", columns=["ids", "label"])
python
def get_collate_fn(pad_index):
"""
该函数是一个高阶函数,用于生成一个适用于 DataLoader 的 collate_fn 函数。
:param pad_index: 填充 token 对应的索引,用于对序列进行填充
:return: 一个 collate_fn 函数,用于处理一批数据
"""
def collate_fn(batch):
"""
对输入的一批数据进行处理,将文本对应的 ids 序列进行填充对齐,同时将标签组合成张量。
:param batch: 一个包含多个样本的列表,每个样本是一个字典,包含 "ids" 和 "label" 字段
:return: 一个字典,包含填充对齐后的 "ids" 张量和组合后的 "label" 张量
"""
# 从 batch 中提取每个样本的 "ids" 字段,组成一个列表
batch_ids = [i["ids"] for i in batch]
# 使用 nn.utils.rnn.pad_sequence 对 ids 序列进行填充对齐,使其长度一致
# padding_value=pad_index 指定填充的值为 pad_index
# batch_first=True 表示输出的张量维度是 [batch_size, seq_len]
batch_ids = nn.utils.rnn.pad_sequence(
batch_ids, padding_value=pad_index, batch_first=True
)
# 从 batch 中提取每个样本的 "label" 字段,组成一个列表
batch_label = [i["label"] for i in batch]
# 使用 torch.stack 将列表中的标签组合成一个张量
batch_label = torch.stack(batch_label)
# 将填充对齐后的 ids 张量和组合后的标签张量封装成一个字典
batch = {"ids": batch_ids, "label": batch_label}
return batch
return collate_fn
python
def get_data_loader(dataset, batch_size, pad_index, shuffle=False):
"""
创建一个 PyTorch 的 DataLoader 对象,用于批量加载数据集。
参数:
dataset (torch.utils.data.Dataset): 要加载的数据集。
batch_size (int): 每个批次包含的样本数量。
pad_index (int): 填充 token 的索引,用于填充序列到相同长度。
shuffle (bool, 可选): 是否在每个 epoch 开始时打乱数据集,默认为 False。
返回:
torch.utils.data.DataLoader: 用于批量加载数据的 DataLoader 对象。
"""
# 获取 collate 函数,用于处理每个批次的数据
collate_fn = get_collate_fn(pad_index)
# 创建 DataLoader 对象
data_loader = torch.utils.data.DataLoader(
# 要加载的数据集
dataset=dataset,
# 每个批次的样本数量
batch_size=batch_size,
# 用于处理每个批次数据的函数
collate_fn=collate_fn,
# 是否在每个 epoch 开始时打乱数据集
shuffle=shuffle,
)
return data_loader
创建训练集和测试集的数据加载器,以便在训练和评估模型时批量加载数据
python
pad_index = tokenizer.pad_token_id
batch_size = 8
train_data_loader = get_data_loader(train_data, batch_size, pad_index, shuffle=True)
test_data_loader = get_data_loader(test_data, batch_size, pad_index)
定义一个名为 Transformer
的 PyTorch 神经网络模块,该模块基于预训练的 Transformer 模型构建,用于完成分类任务。
python
class Transformer(nn.Module):
def __init__(self, transformer, output_dim, freeze):
super().__init__()
self.transformer = transformer
hidden_dim = transformer.config.hidden_size
self.fc = nn.Linear(hidden_dim, output_dim)
if freeze:
for param in self.transformer.parameters():
param.requires_grad = False
def forward(self, ids):
output = self.transformer(ids, output_attentions=True)
hidden = output.last_hidden_state
attention = output.attentions[-1]
cls_hidden = hidden[:, 0, :]
prediction = self.fc(torch.tanh(cls_hidden))
return prediction
transformer = transformers.AutoModel.from_pretrained("bert-base-chinese")
model=Transformer(transformer, len(train_data["label"].unique()), freeze=False)
最后的训练效果图为


