300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 基于 BERT 实现的情感分析(文本分类)----概念与应用

基于 BERT 实现的情感分析(文本分类)----概念与应用

时间:2020-02-04 08:57:14

相关推荐

基于 BERT 实现的情感分析(文本分类)----概念与应用

文章目录

基于 BERT 的情感分析(文本分类)基本概念理解简便的编码方式: One-Hot 编码突破: Word2Vec编码方式新的开始: Attention 与 Transformer 模型四方来降: 超一流预处理模型 BERT 诞生BERT 实现情感分析数据预处理并创建数据集定义网络模型定义训练函数与评估函数设置损失函数、优化方法、BertTokenizer词嵌入训练模型并预测结果小结

/2/17更新,已上传数据集和对应的代码至Github。

基于 BERT 的情感分析(文本分类)

的10月11日,Google发布一篇论文《Pre-training of Deep Bidirectional Transformers for Language Understanding》,成功在 11 项 NLP 任务中取得 SOTA(最优) 结果,赢得自然语言处理学界的一片赞誉之声。

从此各种类BERT模型如雨后春笋般不断破土而出,创造了 NLP 领域一项又一项新的记录,本次我将带领大家在 18 多万条数据集上微调BERT实现情感分析(文本分类)。

基本概念理解

是什么导致了 BERT 的诞生?

BERT 有哪些优势?

简便的编码方式: One-Hot 编码

我们都知道,大家说的话都是人类语言,而计算机本质只认识数字信息,那计算机理解人类语言信息又是必须过程,那么我们该如何设计数字编码表示语言信息呢?

此时,最初的编码方式诞生了,即One-Hot编码,又称独热编码方式。假设现在有四个词 ['钢琴 ', ‘绘画’, '舞蹈 ', ‘篮球’] ,则编码方式如下:

钢琴 → [1, 0, 0, 0]

绘画 → [0, 1, 0, 0]

舞蹈 → [0, 0, 1, 0]

篮球 → [0, 0, 0, 1]

每个词在向量中都会有一个 1 与之对应,代表该词元的编码信息。最初很多文本信息都是这种编码方式,但是久而久之,这种编码方式的两种缺陷便显露出来:

1、可能产生维度爆炸的问题,即当词元的个数超级多的时候,每个词元的向量长度均为词元个数 n ,导致计算机无法存储,运算速度极慢。

2、无法有效表示词元的相似关系,每个词元只能独立表示。

突破: Word2Vec编码方式

为了改进 One-Hot 编码的缺陷,一种新的编码方式应运而生。,Google团队发布了论文《Efficient Estimation of Word Representations inVector Space》,Word2Vec编码方式由此诞生,很大程度上推动了 NLP 领域的发展。其编码方式有以下两种:

1、Skip-Gram模型,即跳元模型。其原理是根据中心词预测上下文词

2、CBOW模型,即连续词袋模型。其原理是根据上下文词预测中心词

通过计算中心词与上下文词的余弦相似度,来确定不同词元直接的关系。最终将所有词元都转化为长度为 embedding_dim(50~300)的向量。

这样不仅能够很大程度上缩减词元的存储空间,又能获得词元与词元之间的相似度。但是这种方法也有一些缺陷:

1、由于词和向量是一对一的关系,所以多义词的问题无法解决。

2、Word2vec 是一种静态的方式,虽然通用性强,但是无法针对特定任务做动态优化

关于Word2Vec的原理与代码实现,请看这几部分内容Word2Vec的详情原理。

新的开始: Attention 与 Transformer 模型

6月,Google团队再次发布论文《Attention Is All You Need》关于注意力机制的Transformer模型。

该论文主张使用注意力的机制,完全抛弃CNN,RNN等网络模型结构。起初主要应用在自然语言处理NLP中,后面也逐渐应用到了计算机视觉中。

仅仅通过注意力机制(self-attention)前馈神经网络(Feed Forward Neural Network),不需要使用序列对齐的循环架构就实现了较好的效果。

模型结构如下图:

其存在两方面优点:

1、摒弃了RNN的网络结构模式,其能够很好的并行运算

2、其注意力机制能够帮助当前词获取较好的上下文信息

由于Transformer部分的原理内容较多,所以暂时跳过,有兴趣的朋友可阅读此篇文章。

四方来降: 超一流预处理模型 BERT 诞生

Bert基于Transformer编码器块架构进行设计,同时采用了两种方法来提升 NLP 处理水平:

1、MLM(Maked Language Model)掩蔽语言模型,Bert通过MLM方法来随机掩蔽句子中的一个词元,设计模型根据上下文信息去预测该词元。通过此种方法,使模型能够很好地解决多义词匹配上下文语义理解问题。

2、NSP(Next Sentence Prediction)下一句预测,随机选取多组 两个连接或两个不连接的句子,设置它们是否连续(为上下文)的标记,从而进行训练,得到文本对之间的关系,能够解决文本对之间的关系问题,即上下文关系问题

另外,Bert将普遍模型处理的特定NLP任务转化为了在Bert模型下的不可知NLP任务,也就是说,Bert只是一个语料库的预处理模型,和One-Hot、Word2Vec一致,均是语言预训练模型。用户可以在Bert预训练模型下作微调(fine-tune)操作,使其能够处理多种NLP任务,如:

(1)单一文本分类(如情感分析)

(2)文本对分类(如自然语言推断)

(3)问答

(4)文本标记(如命名实体识别)。

比如文本分类,也就是我们这次的任务(情感分析),只需得到整个评论语句的上下文综合信息(‘<cls>’),再接上一层全连接层,便可实现分类任务。

其它不可知任务类似。

本模型的详情参考这篇文章。

BERT 实现情感分析

前提准备:

准备18多万条手机评论信息和对应的情感标签, 0, 1, 2 代表 差中好 评。因为个人计算机资源无法训练出Bert预训练模型,我们使用 transformer 库中的 BertTokenizer、 BertModel模型。BertTokenizer将输入的评论语句转化为输入Bert模型的向量信息,BertModel根据输入信息输出结果。

数据预处理并创建数据集

读取文件中的评论信息,并对数据进行去重。

import csvimport pandas as pdimport randomimport torchfrom transformers import BertTokenizer, BertModelfrom torch import nnfrom d2l import torch as d2lfrom tqdm import tqdm"""读取评论文件的评论信息"""def read_file(file_name):comments_data = None# 读取评论信息with open(file_name, 'r', encoding='UTF-8') as f:reader = csv.reader(f)# 读取评论数据和对应的标签信息comments_data = [[line[0], int(line[1])] for line in reader if len(line[0]) > 0] # 打乱数据集random.shuffle(comments_data)data = pd.DataFrame(comments_data)same_sentence_num = data.duplicated().sum() # 统计重复的评论内容个数if same_sentence_num > 0:data = data.drop_duplicates() # 删除重复的样本信息f.close()return data

读取数据集信息,并输出样本的长度

comments_data = read_file('./file/comments.csv')len(comments_data)

181945

查看所有样本信息

comments_data

181945 rows × 2 columns

以 6:4 的比例拆分训练集与测试集,设定切分线。

split = 0.6split_line = int(len(comments_data) * split)split_line

109167

划分训练集 train_comments, train_lables 与测试集 test_comments,test_lables 并输出它们的长度

# 划分训练集与测试集,并将pandas数据类型转化为列表类型train_comments, train_labels = list(comments_data[: split_line][0]), list(comments_data[: split_line][1])test_comments, test_labels = list(comments_data[split_line:][0]), list(comments_data[split_line:][1])len(train_comments),len(train_labels), len(test_comments), len(test_labels)

(109167, 109167, 72778, 72778)

定义网络模型

现在我们来微调Bert,使用Bert来实现情感分析(文本分类)的效果。

默认这里使用基本模型Bert_base(bert-base-chinese),使用12层Transformer编码器块,768个隐藏单元和12个自注意头。

只需要在Bert的输出信息中提取出综合上下文信息 ‘<cls>’,并外接一层全连接层,即可完成情感分析(文本分类)效果。如下图

仍不太懂的小伙伴可以参考transform_bert官方文档,对于Bert的参数和返回有详细说明。

"""定义BERTClassifier分类器模型"""class BERTClassifier(nn.Module):# 初始化加载 bert-base-chinese 原型,即Bert中的Bert-Base模型def __init__(self, output_dim, pretrained_name='bert-base-chinese'):super(BERTClassifier, self).__init__()# 定义 Bert 模型self.bert = BertModel.from_pretrained(pretrained_name)# 外接全连接层self.mlp = nn.Linear(768, output_dim)def forward(self, tokens_X):# 得到最后一层的 '<cls>' 信息, 其标志全部上下文信息res = self.bert(**tokens_X)# res[1]代表序列的上下文信息'<cls>',外接全连接层,进行情感分析 return self.mlp(res[1])

定义训练函数与评估函数

设计以下的评估函数和训练函数,用以对模型进行训练测试

"""评估函数,用以评估数据集在神经网络下的精确度"""def evaluate(net, comments_data, labels_data):sum_correct, i = 0, 0while i <= len(comments_data):comments = comments_data[i: min(i + 8, len(comments_data))]tokens_X = tokenizer(comments, padding=True, truncation=True, return_tensors='pt').to(device=device)res = net(tokens_X) # 获得到预测结果y = torch.tensor(labels_data[i: min(i + 8, len(comments_data))]).reshape(-1).to(device=device)sum_correct += (res.argmax(axis=1) == y).sum() # 累加预测正确的结果i += 8return sum_correct/len(comments_data) # 返回(总正确结果/所有样本),精确率"""训练bert_classifier分类器"""def train_bert_classifier(net, tokenizer, loss, optimizer, train_comments, train_labels, test_comments, test_labels,device, epochs):max_acc = 0.5 # 初始化模型最大精度为0.5# 先测试未训练前的模型精确度train_acc = evaluate(net, train_comments, train_labels)test_acc = evaluate(net, test_comments, test_labels)# 输出精度print('--epoch', 0, '\t--train_acc:', train_acc, '\t--test_acc', test_acc)# 累计训练18万条数据 epochs 次,优化模型for epoch in tqdm(range(epochs)):i, sum_loss = 0, 0 # 每次开始训练时, i 为 0 表示从第一条数据开始训练# 开始训练模型while i < len(train_comments):comments = train_comments[i: min(i + 8, len(train_comments))] # 批量训练,每次训练8条样本数据# 通过 tokenizer 数据化输入的评论语句信息,准备输入bert分类器tokens_X = tokenizer(comments, padding=True, truncation=True, return_tensors='pt').to(device=device)# 将数据输入到bert分类器模型中,获得结果res = net(tokens_X)# 批量获取实际结果信息y = torch.tensor(train_labels[i: min(i + 8, len(train_comments))]).reshape(-1).to(device=device)optimizer.zero_grad() # 清空梯度l = loss(res, y) # 计算损失l.backward() # 后向传播optimizer.step() # 更新梯度sum_loss += l.detach() # 累加损失i += 8 # 样本下标累加# 计算训练集与测试集的精度train_acc = evaluate(net, train_comments, train_labels)test_acc = evaluate(net, test_comments, test_labels)# 输出精度print('\n--epoch', epoch+1, '\t--loss:', sum_loss / (len(train_comments) / 8), '\t--train_acc:', train_acc,'\t--test_acc', test_acc)# 如果测试集精度 大于 之前保存的最大精度,保存模型参数,并重设最大值if test_acc > max_acc:# 更新历史最大精确度max_acc = test_acc# 保存模型torch.save(net.state_dict(), 'bert.parameters')

设置损失函数、优化方法、BertTokenizer词嵌入

本次实验中,我们这里使用交叉熵损失函数、小批量随机梯度下降,并定义 BertTokenizer 将输入的评论语句(次元序列)转化为输入Bert的数据。

device = d2l.try_gpu() # 获取GPUnet = BERTClassifier(output_dim=3) # BERTClassifier分类器,因为最终结果为3分类,所以输出维度为3,代表概率分布net = net.to(device)# 将模型存放到GPU中,加速计算# 定义tokenizer对象,用于将评论语句转化为BertModel的输入信息tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')loss = nn.CrossEntropyLoss() # 损失函数optimizer = torch.optim.SGD(net.parameters(), lr=1e-4)# 小批量随机梯度下降算法

训练模型并预测结果

train_bert_classifier(net, tokenizer, loss, optimizer, train_comments, train_labels, test_comments, test_labels, device, 20)

运行结果如下

经过5个小时左右的模型训练,在测试集上的精确度最高能够达到82.38%,相对于之前的RNN系列的结果(75%左右),提升了7个百分比左右,而且我们并没有作任何调整优化操作,所以Bert预训练模型还是很强大的。

现在,我们再次可视化测试一下模型预测的准确度(测试集上),如下

# 定义模型net = BERTClassifier(output_dim=3)net = net.to(device)# 加载训练好的模型参数net.load_state_dict(torch.load('./bert.parameters'))start = 0while start < 20:comment = test_comments[start]token_X = tokenizer(comment, padding=True, truncation=True, return_tensors='pt').to(device)label = test_labels[start]# 实际结果result = net(token_X).argmax(axis=1).item() # 得到预测结果# 打印评论语句print(comment)# 输出预测结果if result == 0:print('预测结果: ', 0, '----》差评', end='\t')elif result == 1:print('预测结果: ', 1, '----》中评', end='\t')else:print('预测结果: ', 2, '----》好评', end='\t')# 输出实际结果if label == 0:print('实际结果: ', 0, '----》差评', end='\t')elif label == 1:print('实际结果: ', 1, '----》中评', end='\t')else:print('实际结果: ', 2, '----》好评', end='\t')if result == label:print('预测正确')else:print('预测错误')start += 1

运行结果如下:

小结

本次使用Bert实现情感分类总体上得到了不错的效果,但是仍有值得改进的地方,如未对评论数据作预处理操作,也未改善优化Bert分类器的基本结构,仅仅是外接了一个全连接层,未采用更佳的优化算法、未支持动态学习率变化等。

另外,Bert模型也存在一些缺陷,Bert是基于字粒度对文本数据进行划分的,我们也可以采用Bert的改进模型RoBERTa模型,或者中文语义更强的ERNIE模型来构建预训练模型。

希望本次分享可以帮助到大家。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。