文章目录
-
-
- 昇思MindSpore应用实践
-
-
- [1、基于 MindSpore 实现 BERT 对话情绪识别](#1、基于 MindSpore 实现 BERT 对话情绪识别)
-
- [BERT 模型简介](#BERT 模型简介)
- 数据集
- 数据加载和数据预处理
- 2、模型训练
- 3、模型推理
-
- Reference
-
昇思MindSpore应用实践
本系列文章主要用于记录昇思25天学习打卡营的学习心得。
1、基于 MindSpore 实现 BERT 对话情绪识别
BERT 模型简介
自2017年Google发表"Attention is ALL You Need"之后,基于自注意力机制结构的网络模型开始在各领域发展,特别是Transformer网络,针对序列的强大长距离关联处理能力在NLP领域取得了成效,BERT 的创新点在于它将双向 Transformer 用于语言模型,曾一度刷新各项NLP任务的SOTA记录,包括问答 Question Answering (SQuAD v1.1),推理 Natural Language Inference (MNLI) 等,也由此拉开了LLM的序幕。
在此之前,以往的NLP模型,如RNN网络,通常是从左向右输入一个文本序列,或者将 left-to-right 和 right-to-left 的训练结合起来(如Bi-LSTM)。实验结果表明,双向训练的语言模型对语境的理解会比单向的语言模型更深刻,
BERT 利用了 Transformer 的 Encoder 部分。Transformer 基于自注意力机制可以学习文本中单词之间的上下文关系。Transformer 的原型包括两个独立的机制,一个 Encoder 负责接收文本作为输入,一个 Decoder 负责预测任务的结果。BERT 的目标是生成语言模型,所以只需要 Encoder 机制。
Transformer 的 encoder 是一次性读取整个文本序列 ,而不是从左到右或从右到左地按顺序读取,这个特征使得模型能够基于单词的两侧学习,相当于是一个双向的功能。
下图是 Transformer 的 encoder 部分,输入是一个 token 序列,先对其进行 embedding 称为向量,然后输入给神经网络,输出是大小为 H 的向量序列,每个向量对应着具有相同索引的 token。

BERT模型的主要创新点都在pre-train方法上,即用了Masked Language Model和Next Sentence Prediction两种方法分别捕捉词语和句子级别的representation。
在用Masked Language Model方法训练BERT的时候,随机把语料库中15%的单词做Mask操作。对于这15%的单词做Mask操作分为三种情况:80%的单词直接用Mask替换、10%的单词直接替换成另一个新的单词、10%的单词保持不变。
因为涉及到Question Answering (QA) 和 Natural Language Inference (NLI)之类的任务,增加了Next Sentence Prediction预训练任务,目的是让模型理解两个句子之间的联系。与Masked Language Model任务相比,Next Sentence Prediction更简单些,训练的输入是句子A和B,B有一半的几率是A的下一句,输入这两个句子,BERT模型预测B是不是A的下一句。
BERT预训练之后,会保存它的Embedding table和12层Transformer权重(BERT-BASE)或24层Transformer权重(BERT-LARGE)。使用预训练好的BERT模型可以对下游任务进行Fine-tuning,比如:文本分类、相似度判断、阅读理解等。
对话情绪识别(Emotion Detection,简称EmoTect),专注于识别智能对话场景中用户的情绪,针对智能对话场景中的用户文本,自动判断该文本的情绪类别并给出相应的置信度,情绪类型分为积极、消极、中性。 对话情绪识别适用于聊天、客服等多个场景,能够帮助企业更好地把握对话质量、改善产品的用户交互体验,也能分析客服服务质量、降低人工质检成本。
下面以MindSpore给出的一个文本情感分类任务为例子来说明BERT模型的整个应用过程:
python
import os
import mindspore
from mindspore.dataset import text, GeneratorDataset, transforms
from mindspore import nn, context
from mindnlp._legacy.engine import Trainer, Evaluator
from mindnlp._legacy.engine.callbacks import CheckpointCallback, BestModelCallback
from mindnlp._legacy.metrics import Accuracy
# 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)
数据集
这里提供一份已标注的、经过分词预处理的机器人聊天数据集,来自于百度飞桨团队。数据由两列组成,以制表符('\t')分隔,第一列是情绪分类的类别(0表示消极;1表示中性;2表示积极),第二列是以空格分词的中文文本,如下示例,文件为 utf8 编码。
label--text_a
0--谁骂人了?我从来不骂人,我骂的都不是人,你是人吗 ?
1--我有事等会儿就回来和你聊
2--我见到你很高兴谢谢你帮我
这部分主要包括数据集读取,数据格式转换,数据 Tokenize 处理和 pad 操作。
数据加载和数据预处理
新建 process_dataset 函数用于数据加载和数据预处理:
python
import numpy as np
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处理:
python
from mindnlp.transformers import BertTokenizer
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()
'input_ids', 'attention_mask', 'labels'
python
print(next(dataset_train.create_tuple_iterator()))
Tensor(shape=\[32, 64, dtype=Int64, value=
\[ 101, 6443, 3221 ... 0, 0, 0,
101, 872, 1963 ... 0, 0, 0,
101, 6929, 872 ... 0, 0, 0,
...
101, 872, 4268 ... 0, 0, 0,
101, 671, 4991 ... 0, 0, 0,
101, 1376, 1480 ... 0, 0, 0]), Tensor(shape=32, 64, dtype=Int64, value=
\[1, 1, 1 ... 0, 0, 0,
1, 1, 1 ... 0, 0, 0,
1, 1, 1 ... 0, 0, 0,
...
1, 1, 1 ... 0, 0, 0,
1, 1, 1 ... 0, 0, 0,
1, 1, 1 ... 0, 0, 0]), Tensor(shape=32, dtype=Int32, value= [1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 2, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1,
1, 1, 2, 1, 1, 1, 1, 1])]
2、模型训练
通过 BertForSequenceClassification 构建用于情感分类的 BERT 模型,加载预训练权重,设置情感三分类的超参数自动构建模型。后面对模型采用自动混合精度操作,提高训练的速度,然后实例化优化器,紧接着实例化评价指标,设置模型训练的权重保存策略,最后就是构建训练器,模型开始训练。
python
from mindnlp.transformers import BertForSequenceClassification, BertModel
from mindnlp._legacy.amp import auto_mixed_precision
# set bert config and define parameters for training
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=3)
model = auto_mixed_precision(model, 'O1')
optimizer = nn.Adam(model.trainable_params(), learning_rate=2e-5)
metric = Accuracy()
# define callbacks to save checkpoints
ckpoint_cb = CheckpointCallback(save_path='checkpoint', ckpt_name='bert_emotect', epochs=1, keep_checkpoint_max=2)
best_model_cb = BestModelCallback(save_path='checkpoint', ckpt_name='bert_emotect_best', auto_load=True)
trainer = Trainer(network=model, train_dataset=dataset_train,
eval_dataset=dataset_val, metrics=metric,
epochs=5, optimizer=optimizer, callbacks=[ckpoint_cb, best_model_cb])
# start training
trainer.run(tgt_columns="labels")

模型验证
将验证数据集加再进训练好的模型,对数据集进行验证,查看模型在验证数据上面的效果,此处的评价指标为准确率。
python
evaluator = Evaluator(network=model, eval_dataset=dataset_test, metrics=metric)
evaluator.run(tgt_columns="labels")
% print_log:
Evaluate Score: {'Accuracy': 0.9083011583011583}
3、模型推理
遍历推理数据集,将结果与标签进行统一展示。
python
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)
from mindspore import Tensor
for label, text in dataset_infer:
predict(text, label)
# print_log:
inputs: '我 要 客观', predict: '中性' , label: '中性'
inputs: '靠 你 真是 说 废话 吗', predict: '消极' , label: '消极'
inputs: '口嗅 会', predict: '中性' , label: '中性'
inputs: '每次 是 表妹 带 窝 飞 因为 窝路痴', predict: '中性' , label: '中性'
inputs: '别说 废话 我 问 你 个 问题', predict: '消极' , label: '消极'
inputs: '4967 是 新加坡 那 家 银行', predict: '中性' , label: '中性'
inputs: '是 我 喜欢 兔子', predict: '积极' , label: '积极'
inputs: '你 写 过 黄山 奇石 吗', predict: '中性' , label: '中性'
inputs: '一个一个 慢慢来', predict: '中性' , label: '中性'
inputs: '我 玩 过 这个 一点 都 不 好玩', predict: '消极' , label: '消极'
inputs: '网上 开发 女孩 的 QQ', predict: '中性' , label: '中性'
inputs: '背 你 猜 对 了', predict: '中性' , label: '中性'
inputs: '我 讨厌 你 , 哼哼 哼 。 。', predict: '消极' , label: '消极'
