在Huggingface transformers平台上微调BERT-wwm-ext
今天是本系列的最后一期。
为了避免命名带来的混淆,我们首先来厘清一下:transformer 与 transformers。
在上一期里,我们已经做过介绍,transformer是一种具有多头自注意力机制的、可以取代RNN/LSTM的神经网络单元结构。
本质上它是一种深度学习技术。
今天提到的transformers,是Huggingface公司开发的一套python库包,它提供一个平台框架,使得transformer技术实现的各类模型能通过一致化的接口方式呈现和调用。在其主页上它是这么自我介绍的:
Transformers 提供 API 来轻松下载和训练最先进的预训练模型。使用预训练模型可以降低您的计算成本、碳足迹,并节省您从头开始训练模型的时间。这些模型可用于不同的模式,例如:
文本:超过 100 种语言的文本分类、信息提取、问答、摘要、翻译和文本生成。
️ 图像:图像分类、对象检测和分割。
️ 音频:语音识别和音频分类。
多模态:表格问答、光学字符识别、从扫描文档中提取信息、视频分类和视觉问答。
该库支持三个最流行的深度学习库之间的无缝集成:PyTorch、TensorFlow和JAX。在一个框架中用三行代码训练模型,然后加载它以与另一个框架进行推理。
每个 Transformers 架构都在独立的 Python 模块中定义,因此可以轻松定制它们以进行研究和实验。
本质上它是一种研发工具产品。
Google在发布公开论文『Attention Is All You Need』的同时,推出了开源的transformer架构BERT(Bidirectional Encoder Representation from Transformers)。可以说BERT开辟了NLP的新时代,产学研界参考BERT推出了很多类似的变种模型,如:RoBERTa、BART等。
在中文的类BERT研究中,除了Google原生的bert-base-chinese之外,Bert-wwm、MacBert、ChineseBert等模型创新了不同的机制进行优化,具有比较大的知名度和影响力。
我们今天将基于哈工大与科大讯飞联合实验室(HFL)研发的Bert-wwm-ext进行微调,实现我们的场景任务目标——辨别新闻标题A和B的关系分类。
与原始的bert-base-chinese中文采用单字掩码方式不同,Bert-wwm-ext提供了所谓全词掩码(Whole Word Masking)的预训练方式。如下图所示:
全词掩码有两个优点:
1、部分解决了MLM独立性假设,使得预测token之间拥有了一定的关联性
2、提高了MLM任务难度,使得模型需要更多依赖远距离的上下文来判断掩码部分的内容
接下来将看到,使用 Transformers 库将使我们的代码非常简化:虽然Bert-wwm-ext 的网络结构的复杂程度要远远超过前面的孪生LSTM网络,但实现的代码行数却会大大降低。
from huggingface_hub import snapshot_download
#从官方的huggingface_hub下载模型配置、参数、模型词库等信息
snapshot_download(repo_id="hfl/chinese-bert-wwm-ext", ignore_regex=["*.h5", "*.ot", "*.msgpack"])
c:\users\hp\appdata\local\programs\python\python37\lib\site-packages\huggingface_hub\utils\_deprecation.py:92: FutureWarning: Deprecated argument(s) used in 'snapshot_download': ignore_regex. Will not be supported from version '0.12'.
Please use `allow_patterns` and `ignore_patterns` instead.
warnings.warn(message, FutureWarning)
'C:\\Users\\HP/.cache\\huggingface\\hub\\models--hfl--chinese-bert-wwm-ext\\snapshots\\2a995a880017c60e4683869e817130d8af548486'
from transformers import AutoConfig,AutoTokenizer,AutoModel,AutoModelForSequenceClassification
model_name = 'C:\\Users\\HP/.cache\\huggingface\\hub\\models--hfl--chinese-Bert-wwm-ext\\snapshots\\2a995a880017c60e4683869e817130d8af548486'
config = AutoConfig.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=3) #新闻标题A和B的关系标签有3种类型
Some weights of the model checkpoint at C:\Users\HP/.cache\huggingface\hub\models--hfl--chinese-Bert-wwm-ext\snapshots\2a995a880017c60e4683869e817130d8af548486 were not used when initializing BertForSequenceClassification: ['cls.predictions.transform.LayerNorm.bias', 'cls.predictions.transform.dense.weight', 'cls.seq_relationship.weight', 'cls.predictions.decoder.weight', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.dense.bias', 'cls.predictions.bias', 'cls.seq_relationship.bias']
- This IS expected if you are initializing BertForSequenceClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing BertForSequenceClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Some weights of BertForSequenceClassification were not initialized from the model checkpoint at C:\Users\HP/.cache\huggingface\hub\models--hfl--chinese-Bert-wwm-ext\snapshots\2a995a880017c60e4683869e817130d8af548486 and are newly initialized: ['classifier.weight', 'classifier.bias']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
先通过一个小例子看看 Transformers的 tokenizer 是如何工作的:
list_ab = [
("我今天输液了", "输什么液?"),
("让我好好爱你行不?", "让我陪你一起过日子好不?")
]
encoded_input = tokenizer(list_ab, padding=True, truncation=True, max_length=20, return_tensors='pt')
encoded_input
{'input_ids': tensor([[ 101, 2769, 791, 1921, 6783, 3890, 749, 102, 6783, 784, 720, 3890,
136, 102, 0, 0, 0, 0, 0, 0],
[ 101, 6375, 2769, 1962, 1962, 4263, 872, 6121, 679, 102, 6375, 2769,
7373, 872, 671, 6629, 6814, 3189, 2094, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}
tokenizer.decode(encoded_input["input_ids"][1])
'[CLS] 让 我 好 好 爱 你 行 不 [SEP] 让 我 陪 你 一 起 过 日 子 [SEP]'
在此label 不需要人工做 One-hot 编码,后续模型训练中会自动化进行相关处理。
label_to_index = { 'unrelated' : 0 , 'agreed' : 1 , 'disagreed' : 2 }
label_pipeline = lambda x : label_to_index [ x ]
MAX_LEN = 60
TEST_SPLIT = 0.1
BATCH_SIZE = 64
LEARNING_RATE = 2e-5
EPOCHS=2
WEIGHT_DECAY=0.01
Huggningface transformers 与Huggingface 自己的数据集处理包datasets 集成较好。
Bert-wwm-ext 将直接处理中文文本语料,因此之前的结巴分词结果也不再需要,我们将使用pandas 的read_csv 函数 + datasets 包里的load_dataset 函数加载和处理原始的**.csv** 。
TRAIN_CSV_PATH = "./train.csv"
import pandas as pd
train = pd.read_csv(TRAIN_CSV_PATH, index_col = 0)
cols = ['title1_zh', 'title2_zh', 'label']
train = train.loc[:, cols].fillna('')
train.rename(columns={'label':'label_class'},inplace=True) # 重要!字段名称里需要空出transformers规定的「label」保留字
from datasets import Dataset
dataset = Dataset.from_pandas(train)
dataset
Dataset({
features: ['title1_zh', 'title2_zh', 'label_class', 'id'],
num_rows: 320552
})
对数据做些基本的处理,生成成对的新闻标题A和B的文本,以及数字化的关系分类标签。
def tokenize_function(examples):
#print(len(examples["label_class"]))
#print(examples)
labels = [label_pipeline(label) for label in examples["label_class"]]
#print(labels)
texts = [(examples["title1_zh"][i],examples["title2_zh"][i]) for i in range(len(examples["label_class"]))]
#print(texts)
tokenized = tokenizer(texts, padding='max_length', truncation=True, max_length=MAX_LEN)
tokenized['label'] = labels #transformers的模型通过「label」字段传递分类标签
return tokenized
tokenized_datasets = dataset.map(tokenize_function, batched=True)
0%| | 0/321 [00:00, ?ba/s]
手写一个简单的数据集分割功能,因为是cpu训练,为节省训练时间,我们仅使用跟测试验证集同样多的、占总体10%的样本进行模型微调训练。
dataset_size = len(tokenized_datasets['label'])
test_size = int(TEST_SPLIT * dataset_size)
train_size = dataset_size - test_size
train_dataset = tokenized_datasets.shuffle(seed=42).select(range(test_size)) #为节省训练时间,仅使用10%的样本训练
test_dataset = tokenized_datasets.shuffle(seed=42).select(range(test_size, 2* test_size))
test_dataset
Dataset({
features: ['title1_zh', 'title2_zh', 'label_class', 'id', 'input_ids', 'token_type_ids', 'attention_mask', 'label'],
num_rows: 32055
})
import torch
# 将模型和数据转移到cuda, 若无cuda,可更换为cpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
Trainer在训练期间不会自动评估模型性能。需要向Trainer传递一个函数来计算和报告指标。 Datasets 库提供了一个简单的函数 load_metric 加载。
定义一个compute_metric计算预测的准确性。
import numpy as np
from datasets import load_metric
metric = load_metric("accuracy")
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
from transformers import TrainingArguments, Trainer
此时,只剩下三个步骤:
在TrainingArguments 中定义训练超参数。
将训练参数连同模型、数据集、标记器和数据整理器一起传递给Trainer 。
调用train() 来微调模型。
training_args = TrainingArguments(
output_dir="./results",
evaluation_strategy="epoch",
learning_rate=LEARNING_RATE,
per_device_train_batch_size=BATCH_SIZE,
per_device_eval_batch_size=BATCH_SIZE,
num_train_epochs=EPOCHS,
weight_decay=WEIGHT_DECAY,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=test_dataset,
compute_metrics=compute_metrics,
)
trainer.train()
The following columns in the training set don't have a corresponding argument in `BertForSequenceClassification.forward` and have been ignored: title2_zh, title1_zh, label_class, id. If title2_zh, title1_zh, label_class, id are not expected by `BertForSequenceClassification.forward`, you can safely ignore this message.
c:\users\hp\appdata\local\programs\python\python37\lib\site-packages\transformers\optimization.py:310: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning
FutureWarning,
***** Running training *****
Num examples = 32055
Num Epochs = 2
Instantaneous batch size per device = 64
Total train batch size (w. parallel, distributed & accumulation) = 64
Gradient Accumulation steps = 1
Total optimization steps = 1002
结果摘要如下:
Epoch | Training Loss | Validation Loss | Accuracy |
1 | 0.331700 | 0.286503 | 0.875838 |
2 | 0.214100 | 0.286773 | 0.882546 |
经过2轮训练之后的准确率是88.3%,比之前最佳的孪生LSTM模型83.1%又提升了超过5个点,而这只是使用了10%的样本进行微调训练,而非LSTM模型使用90%的样本训练!
至此,本系列分享完毕。
相关代码已全部分享在我的github项目(moronism189/chinese-nlp-stepbystep)下,大家可以去那里下载。