MindSpore学习实践——基于 MindSpore 实现 BERT 对话情绪识别

前言

BERT是Google于2018年末开发并发布的一种新型语言模型。与BERT模型相似的预训练语言模型例如问答、命名实体识别、自然语言推理、文本分类等在许多自然语言处理任务中发挥着重要作用。模型是基于Transformer中的Encoder并加上双向的结构,因此一定要熟练掌握Transformer的Encoder的结构。

BERT主要创新点都在pre-train方法上,即用了Masked Language Model和Next Sentence Prediction两种方法分别捕捉词语和句子级别的representation,这可以使得他的通用性得到增强;同时BERT的多层Transformer结构允许在不同任务之间共享参数。因此BERT只需通过在预训练模型的基础上微调部分参数,就可以较好的适应部分下游任务。

语言模型的演变:

请添加图片描述

BERT模型本质上是结合了ELMo模型与GPT模型的优势。 相比于ELMo,BERT仅需改动最后的输出层,而非模型架构,便可以在下游任务中达到很好的效果; 而相比于GPT,BERT在处理词元表示时考虑到了双向上下文的信息;

BERT的介绍

  1. Tokenization

使用分词器将输入的句子分词,然后首尾添加[CLS]与[SEP]特殊字符,后转换为数字id,(token序列转换成id序列,笔者正在学习transformer相关)

请添加图片描述

  1. Embedding(嵌入)

输入到BERT模型的信息由三部分内容组成:

  • 表示内容的token ids
  • 表示位置的position ids
  • 用于区分不同句子的token type ids

请添加图片描述

  1. BERT是由Transformer的Encoder层堆叠而成,BERT的模型大小有BERT BASE以及BERT LARGE两种。其中,前者与transformer参数量齐平(110兆参数);后者在此基础上扩大了参数量(340兆参数)。

  2. BERT会针对每一个位置输出大小为hidden size的向量,在下游任务中,会根据任务内容的不同,选取不同的向量放入输出层:

请添加图片描述

  1. BERT的输出层:
    • pooler output为[CLS]经过线性层+激活函数tanh的输出,其可以用于句子级别的分类/回归任务
    • sequence output为BERT输出的每个位置对应的vector

BERT的预训练

BERT预训练任务有两种:Masked Language Modelling(MLM) 和Next Sentence Prediction(NSP)。

  • MLM:随机遮盖输入句子中的一些词语,并预测被遮盖的词语是什么(完形填空)
  • NSP:预测两个句子是不是上下文的关系
  1. Masked Language Modelling(MLM)

    1. 在在输入中随机遮盖15%的token (即将token替换为[MASK])
    2. 将[MASK]位置对应的BERT输出 放入输出层中,预测被遮盖的 token,但这之后本质是在进行一个分类任务
    3. 为了使得预训练任务和推理任务尽可能接近,BERT在随机遮盖的15%的tokens中又进行了进一步的处理: •
      • 80%的概率替换为[MASK]
      • 10%的概率替换为文本中的随机词
      • 10%的概率不进行替换,保持原有的词
    4. MLM (完形填空)局限性是它们倾向于生成无意义或不恰当的文本。由于模型并不真正理解它生成的文本,它有时会生成语法正确但语义上无意义的输出。例如,它可能会生成类似“猫对狗吠叫”这样的句子,这在语法上是正确的,但在意义上是无效的。
  2. Next Sentence Prediction(NSP)

    Next Sentence Prediction (NSP) 捕捉句子级别信息,简单来说是一个针对句子对的分类问题,判断一组句子中,句子B是否为句子A的下一句(Is Next or NotNext)

BERT的微调

在下游任务中,我们使用少量的标注数据(labelled data)对预训练Transformer编码器的所有参数进行微调,额外的输出层将从头开始训练。

比如在预训练模型的基础上添加少量特定任务的层,并进行少量训练可以使得BERT适应各种下游任务,如文本分类、命名实体识别、问答系统等。

代码实训

  1. 环境配置
#安装mindnlp 0.4.0套件
!pip install mindnlp==0.4.0
!pip uninstall soundfile -y
!pip install https://ms-release.obs.cn-north-4.myhuaweicloud.com/2.3.1/MindSpore/unified/aarch64/mindspore-2.3.1-cp39-cp39-linux_aarch64.whl --trusted-host ms-release.obs.cn-north-4.myhuaweicloud.com -i https://pypi.tuna.tsinghua.edu.cn/simple
  1. 下载数据集
# download dataset
!wget https://baidu-nlp.bj.bcebos.com/emotion_detection-dataset-1.0.0.tar.gz -O emotion_detection.tar.gz
!tar xvf emotion_detection.tar.gz
  1. 准备数据集
import os
import mindspore
from mindspore.dataset import text, GeneratorDataset, transforms
from mindspore import nn, context
from mindnlp.engine import Trainer

# prepare dataset
class SentimentDataset:
    """Sentiment Dataset"""

    def __init__(self, path):
        self.path = path
        self._labels, self._text_a = [], []
        self._load()

    def _load(self):
        with open(self.path, "r", encoding="utf-8") as f:
            dataset = f.read()
        lines = dataset.split("\n")
        for line in lines[1:-1]:
            label, text_a = line.split("\t")
            self._labels.append(int(label))
            self._text_a.append(text_a)

    def __getitem__(self, index):
        return self._labels[index], self._text_a[index]

    def __len__(self):
        return len(self._labels)

  1. 数据预处理
import numpy as np
# 昇腾NPU环境下暂不支持动态Shape,数据预处理部分采用静态Shape处理:
from mindnlp.transformers import BertTokenizer

def process_dataset(source, tokenizer, max_seq_len=64, batch_size=32, shuffle=True):
    is_ascend = mindspore.get_context('device_target') == 'Ascend'

    column_names = ["label", "text_a"]
    
    dataset = GeneratorDataset(source, column_names=column_names, shuffle=shuffle)
    # transforms
    type_cast_op = transforms.TypeCast(mindspore.int32)
    def tokenize_and_pad(text):
        if is_ascend:
            tokenized = tokenizer(text, padding='max_length', truncation=True, max_length=max_seq_len)
        else:
            tokenized = tokenizer(text)
        return tokenized['input_ids'], tokenized['attention_mask']
    # map dataset
    dataset = dataset.map(operations=tokenize_and_pad, input_columns="text_a", output_columns=['input_ids', 'attention_mask'])
    dataset = dataset.map(operations=[type_cast_op], input_columns="label", output_columns='labels')
    # batch dataset
    if is_ascend:
        dataset = dataset.batch(batch_size)
    else:
        dataset = dataset.padded_batch(batch_size, pad_info={'input_ids': (None, tokenizer.pad_token_id),
                                                         'attention_mask': (None, 0)})

    return dataset
    
# 昇腾NPU环境下暂不支持动态Shape,数据预处理部分采用静态Shape处理:
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')

tokenizer.pad_token_id

dataset_train = process_dataset(SentimentDataset("data/train.tsv"), tokenizer)
dataset_val = process_dataset(SentimentDataset("data/dev.tsv"), tokenizer)
dataset_test = process_dataset(SentimentDataset("data/test.tsv"), tokenizer, shuffle=False)

dataset_train.get_col_names()

print(next(dataset_train.create_tuple_iterator()))
  1. 模型构建
from mindnlp.transformers import BertForSequenceClassification
from mindnlp.engine import TrainingArguments
from mindnlp import evaluate
import numpy as np

# set bert config and define parameters for training
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=3)

training_args = TrainingArguments(
    output_dir="bert_imdb_finetune",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    logging_strategy="epoch",
    load_best_model_at_end=True,
    num_train_epochs=3.0,
    learning_rate=2e-5
)

metric = evaluate.load("accuracy")

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset_train,
    eval_dataset=dataset_val,
    compute_metrics=compute_metrics
)
  1. 开始训练
# start training
trainer.train()
  1. 模型验证
trainer.evaluate(dataset_test)
  1. 模型推理
from mindspore import Tensor

dataset_infer = SentimentDataset("data/infer.tsv")

def predict(text, label=None):
    label_map = {0: "消极", 1: "中性", 2: "积极"}

    text_tokenized = Tensor([tokenizer(text).input_ids])
    logits = model(text_tokenized)
    predict_label = logits[0].asnumpy().argmax()
    info = f"inputs: '{text}', predict: '{label_map[predict_label]}'"
    if label is not None:
        info += f" , label: '{label_map[label]}'"
    print(info)
    

for label, text in dataset_infer:
    predict(text, label)
    
# 自定义一个推理数据集
predict("家人们咱就是说一整个无语住了 绝绝子叠buff")

推理结果:

请添加图片描述

总结心得

本次课学习了BERT模型,主要在于NLP中的预训练模型、BERT的结构、BERT的预训练以及BERT的微调上,笔者还有机会通过启智平台实践完成了一次BERT的微调与推理,收获很大,也对模型的推理有了进一步的认知。笔者还是对BERT模型在微调后可以适应多种任务的能力所惊叹,其快速易用性可以用于多种领域,虽然本次课程结束了,但笔者未来仍将继续学习BERT模型并尝试自己完成BERT模型的微调并实现既定的想法。

Logo

昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链

更多推荐