从jieba分词到BERT-wwm——中文自然语言处理(NLP)基础分享系列(12)

在Huggingface transformers平台上微调BERT-wwm-ext

今天是本系列的最后一期。

transformer 与 transformers

为了避免命名带来的混淆,我们首先来厘清一下:transformer 与 transformers。

• transformer

在上一期里,我们已经做过介绍,transformer是一种具有多头自注意力机制的、可以取代RNN/LSTM的神经网络单元结构。

本质上它是一种深度学习技术。

• transformers

今天提到的transformers,是Huggingface公司开发的一套python库包,它提供一个平台框架,使得transformer技术实现的各类模型能通过一致化的接口方式呈现和调用。在其主页上它是这么自我介绍的:

Transformers 提供 API 来轻松下载和训练最先进的预训练模型。使用预训练模型可以降低您的计算成本、碳足迹,并节省您从头开始训练模型的时间。这些模型可用于不同的模式,例如:

文本:超过 100 种语言的文本分类、信息提取、问答、摘要、翻译和文本生成。

️ 图像:图像分类、对象检测和分割。

️ 音频:语音识别和音频分类。

多模态:表格问答、光学字符识别、从扫描文档中提取信息、视频分类和视觉问答。

该库支持三个最流行的深度学习库之间的无缝集成:PyTorch、TensorFlow和JAX。在一个框架中用三行代码训练模型,然后加载它以与另一个框架进行推理。

每个 Transformers 架构都在独立的 Python 模块中定义,因此可以轻松定制它们以进行研究和实验。

本质上它是一种研发工具产品。

BERT-wwm-ext

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)的预训练方式。如下图所示:
从jieba分词到BERT-wwm——中文自然语言处理(NLP)基础分享系列(12)_第1张图片

全词掩码有两个优点:

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 transformersHuggingface 自己的数据集处理包datasets 集成较好。
Bert-wwm-ext 将直接处理中文文本语料,因此之前的结巴分词结果也不再需要,我们将使用pandasread_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

手写一个简单的数据集分割功能,因为是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)下,大家可以去那里下载。

你可能感兴趣的:(自然语言处理,bert,深度学习,transformer,pytorch)