当前,在BERT等预训练模型的基础上进行微调已经成了NLP任务的一个定式了。为了了解BERT怎么用,在这次实践中,我实现了一个最简单的NLP任务,即文本情感分类。

文章目录

  • 1.基于BERT进行情感分类的基本思路
  • 2.数据集准备
  • 3.数据预处理
  • 4.数据处理成dataSet
  • 5.模型的搭建
  • 6.模型的训练

1.基于BERT进行情感分类的基本思路

所谓情感分类就是指判断句子是积极情感还是消极情感,例如说“今天这顿饭太美味了”是积极的情感,“今天这顿饭简直吃不下去”是消极的情感。

基于BERT完成情感分类的基本思路如图所示。我们知道BERT是一个预训练模型,我们把句子扔给它的时候,它对应每个字都会输出一个向量。但是在把句子扔给BERT之前,我们会在句子最前面增加一个特殊符号[CLS]。对应这个[CLS],BERT也会输出一个向量,我们就是利用这个向量来进行情感分类。为什么可以直接利用这个向量呢?这是因为BERT内部采用的是自注意力机制,自注意力机制的特点是考虑全局又聚焦重点,实际上[CLS]对应的向量已经嵌入了整个句子的信息,而且重点词字嵌入的信息权重要大。所以,我们将这个向量扔给一个全连接层,就可以完成分类任务了。由于BERT已经是一个预训练模型了,我们在做情感分类时可以将BERT的参数固定住,不再调整,而只是调整全连接层的参数,我在这次实践中就是这么做的。当然也可以同时调整BERT和全连接层的参数,但是BERT模型较大,消耗的时间会长一些。

2.数据集准备

我在网上下了一个数据集(点击可下载,提取码为zfh3),csv格式的,包含两列,一列是句子,一列是标签,如下图所示。我才这个数据集应该是来自大众点评。。。,数据里面标签为0的时候表示是消极的情感,标签为1时表示的是积极的情感。这个数据集总共有11987行,由于我用的是CPU电脑,速度实在太慢,所以我只用了200条数据,其中150条数据用于训练,50条数据用于测试。

3.数据预处理

利用BERT实现情感分类的关键就是要把数据处理成BERT需要的输入形式。BERT的输入包括三个部分:第一个部分 是句子中每个字对应的id,我们用input_ids表示,这个id需要用到BERT的字库,字库里面每个字所排的次序就是id。第二个部分 是mask,我们用input_mask表示,假设我们设置BERT输入的句子最大长度是128,如果我们的句子长度是100,那么mask前100个填1,后面28个填0。第三部分 是句子标识符id,我们用segment_ids表示,如果第一句全为0,如果是第二句全为1,以此类推,由于情感分类只涉及到一个句子,所以该标识符都是0。

将一个句子处理成上面这样的输入,要经过两步,第一步 是对句子进行分词,在英文里面叫做“tokenize”,分词后的结果称为“tokens”。对于中文来说,分词后的结果很简单,就是一个一个的字。完成该项工作可以使用tokenizer.tokenize(text)。下图分词的一个示例。

完成分词后,第二步 要将tokens转换成id,对于中文来说,就是把一个一个的字转换成字对应的id。此外呢,还要获取input_mask和segment_ids。实现该步骤可以使用tokenizer.encode_plus()。下图是一个示例,第二个参数max_seq_length是指BERT输入句子的最大长度。

下面是数据预处理的代码,保存在dataProcessor.py文件中。

import pandas as pd
import os
import logginglogging.basicConfig(format='%(asctime)s - %(levelname)s - %(name)s -   %(message)s',datefmt='%m/%d/%Y %H:%M:%S',level=logging.INFO)
logger = logging.getLogger(__name__)class InputExample(object):"""A single training/test example for simple sequence classification."""def __init__(self, text, label=None):self.text = textself.label = labelclass InputFeatures(object):"""A single set of features of data."""def __init__(self, input_ids, input_mask, segment_ids, label_id):self.input_ids = input_idsself.input_mask = input_maskself.segment_ids = segment_idsself.label_id = label_idclass DataProcessor(object):"""Base class for data converters for sequence classification data sets."""def get_train_examples(self, data_dir):"""Gets a collection of `InputExample`s for the train set."""raise NotImplementedError()def get_dev_examples(self, data_dir):"""Gets a collection of `InputExample`s for the dev set."""raise NotImplementedError()def get_test_examples(self, data_dir):"""Gets a collection of `InputExample`s for the test set."""raise NotImplementedError()def get_labels(self):"""Gets the list of labels for this data set."""raise NotImplementedError()@classmethoddef _read_csv(cls, input_file, quotechar=None):"""Reads a tab separated value file."""# dicts = []data = pd.read_csv(input_file)return dataclass MyPro(DataProcessor):'''自定义数据读取方法,针对json文件Returns:examples: 数据集,包含index、中文文本、类别三个部分'''def get_train_examples(self, data_dir):return self._create_examples(self._read_csv(os.path.join(data_dir, 'train_data.csv')), 'train')def get_dev_examples(self, data_dir):return self._create_examples(self._read_csv(os.path.join(data_dir, 'dev_data.csv')), 'dev')def get_test_examples(self, data_dir):return self._create_examples(self._read_csv(os.path.join(data_dir, 'test_data.csv')), 'test')def get_labels(self):return [0, 1]def _create_examples(self, data, set_type):examples = []for index, row in data.iterrows():# guid = "%s-%s" % (set_type, i)text = row['review']label = row['label']examples.append(InputExample(text=text, label=label))return examplesdef convert_examples_to_features(examples, label_list, max_seq_length, tokenizer, show_exp=True):'''Loads a data file into a list of `InputBatch`s.Args:examples      : [List] 输入样本,句子和labellabel_list    : [List] 所有可能的类别,0和1max_seq_length: [int] 文本最大长度tokenizer     : [Method] 分词方法Returns:features:input_ids  : [ListOf] token的id,在chinese模式中就是每个分词的id,对应一个word vectorinput_mask : [ListOfInt] 真实字符对应1,补全字符对应0segment_ids: [ListOfInt] 句子标识符,第一句全为0,第二句全为1label_id   : [ListOfInt] 将Label_list转化为相应的id表示'''label_map = {}for (i, label) in enumerate(label_list):label_map[label] = ifeatures = []for (ex_index, example) in enumerate(examples):# 分词tokens = tokenizer.tokenize(example.text)# tokens进行编码encode_dict = tokenizer.encode_plus(text=tokens,max_length=max_seq_length,pad_to_max_length=True,is_pretokenized=True,return_token_type_ids=True,return_attention_mask=True)input_ids = encode_dict['input_ids']input_mask = encode_dict['attention_mask']segment_ids = encode_dict['token_type_ids']assert len(input_ids) == max_seq_lengthassert len(input_mask) == max_seq_lengthassert len(segment_ids) == max_seq_lengthlabel_id = label_map[example.label]if ex_index < 5 and show_exp:logger.info("*** Example ***")logger.info("tokens: %s" % " ".join([str(x) for x in tokens]))logger.info("input_ids: %s" % " ".join([str(x) for x in input_ids]))logger.info("input_mask: %s" % " ".join([str(x) for x in input_mask]))logger.info("segment_ids: %s" % " ".join([str(x) for x in segment_ids]))logger.info("label: %s (id = %d)" % (example.label, label_id))features.append(InputFeatures(input_ids=input_ids,input_mask=input_mask,segment_ids=segment_ids,label_id=label_id))return features

4.数据处理成dataSet

数据处理成dataSet的核心就是把BERT模型的输入处理成tensor格式,下面是代码,保存在dataset.py文件中

import torch
from torch.utils.data import Datasetclass MyDataset(Dataset):def __init__(self, features, mode):self.nums = len(features)self.input_ids = [torch.tensor(example.input_ids).long() for example in features]self.input_mask = [torch.tensor(example.input_mask).float() for example in features]self.segment_ids = [torch.tensor(example.segment_ids).long() for example in features]self.label_id = Noneif mode == 'train' or 'test':self.label_id = [torch.tensor(example.label_id) for example in features]def __getitem__(self, index):data = {'input_ids': self.input_ids[index],'input_mask': self.input_mask[index],'segment_ids': self.segment_ids[index]}if self.label_id is not None:data['label_id'] = self.label_id[index]return datadef __len__(self):return self.nums

5.模型的搭建

模型的搭建很简单,模型的第一层是BERT,将BERT输出的第一个向量,即[CLS]对应的向量,传递给一个线性层,该线性层作为一个分类器输出维度为2的向量。BERT模型会有两个输出:一个输出是序列输出,即句子的每一个字都输出一个向量,叫做seq_out,这个输出一般用于实体识别等句子标注任务;另一个输出是pooled_out,即[CLS]对应的向量,这个输出一般用于句子分类。我们用pooled_out这个输出。这里我把BERT的参数冻结住了,只调整线性层的参数,所以用x = pooled_out.detach()对反向传播进行截断。下面是模型搭建的代码,

from torch import nn
import os
from transformers import BertModelclass ClassifierModel(nn.Module):def __init__(self,bert_dir,dropout_prob=0.1):super(ClassifierModel, self).__init__()config_path = os.path.join(bert_dir, 'config.json')assert os.path.exists(bert_dir) and os.path.exists(config_path), \'pretrained bert file does not exist'self.bert_module = BertModel.from_pretrained(bert_dir)self.bert_config = self.bert_module.configself.dropout_layer = nn.Dropout(dropout_prob)out_dims = self.bert_config.hidden_sizeself.obj_classifier = nn.Linear(out_dims, 2)def forward(self,input_ids,input_mask,segment_ids,label_id=None):bert_outputs = self.bert_module(input_ids=input_ids,attention_mask=input_mask,token_type_ids=segment_ids)seq_out, pooled_out = bert_outputs[0], bert_outputs[1]#对反向传播及逆行截断x = pooled_out.detach()out = self.obj_classifier(x)return out

6.模型的训练

from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import *
from dataset import *
from dataProcessor import *
import matplotlib.pyplot as plt
import time
from transformers import BertTokenizer
from transformers import logginglogging.set_verbosity_warning()
# 加载训练数据
datadir = "data"
bert_dir = "bert\\bert-chinese"
my_processor = MyPro()
label_list = my_processor.get_labels()train_data = my_processor.get_train_examples(datadir)
test_data = my_processor.get_test_examples(datadir)tokenizer = BertTokenizer.from_pretrained(bert_dir)train_features = convert_examples_to_features(train_data, label_list, 128, tokenizer)
test_features = convert_examples_to_features(test_data, label_list, 128, tokenizer)
train_dataset = MyDataset(train_features, 'train')
test_dataset = MyDataset(test_features, 'test')
train_data_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_data_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=True)train_data_len = len(train_dataset)
test_data_len = len(test_dataset)
print(f"训练集长度:{train_data_len}")
print(f"测试集长度:{test_data_len}")# 创建网络模型
my_model = ClassifierModel(bert_dir)# 损失函数
loss_fn = nn.CrossEntropyLoss()# 优化器
learning_rate = 5e-3
#optimizer = torch.optim.SGD(my_model.parameters(), lr=learning_rate)
#  Adam 参数betas=(0.9, 0.99)
optimizer = torch.optim.Adam(my_model.parameters(), lr=learning_rate, betas=(0.9, 0.99))
# 总共的训练步数
total_train_step = 0
# 总共的测试步数
total_test_step = 0
step = 0
epoch = 50writer = SummaryWriter("logs")
# writer.add_graph(myModel, input_to_model=myTrainDataLoader[1], verbose=False)
# writer.add_graph(myModel)
train_loss_his = []
train_totalaccuracy_his = []
test_totalloss_his = []
test_totalaccuracy_his = []
start_time = time.time()
my_model.train()
for i in range(epoch):print(f"-------第{i}轮训练开始-------")train_total_accuracy = 0for step, batch_data in enumerate(train_data_loader):# writer.add_images("tarin_data", imgs, total_train_step)print(batch_data['input_ids'].shape)output = my_model(**batch_data)loss = loss_fn(output, batch_data['label_id'])train_accuracy = (output.argmax(1) == batch_data['label_id']).sum()train_total_accuracy = train_total_accuracy + train_accuracyoptimizer.zero_grad()loss.backward()optimizer.step()total_train_step = total_train_step + 1train_loss_his.append(loss)writer.add_scalar("train_loss", loss.item(), total_train_step)train_total_accuracy = train_total_accuracy / train_data_lenprint(f"训练集上的准确率:{train_total_accuracy}")train_totalaccuracy_his.append(train_total_accuracy)# 测试开始total_test_loss = 0my_model.eval()test_total_accuracy = 0with torch.no_grad():for batch_data in test_data_loader:output = my_model(**batch_data)loss = loss_fn(output, batch_data['label_id'])total_test_loss = total_test_loss + losstest_accuracy = (output.argmax(1) == batch_data['label_id']).sum()test_total_accuracy = test_total_accuracy + test_accuracytest_total_accuracy = test_total_accuracy / test_data_lenprint(f"测试集上的准确率:{test_total_accuracy}")print(f"测试集上的loss:{total_test_loss}")test_totalloss_his.append(total_test_loss)test_totalaccuracy_his.append(test_total_accuracy)writer.add_scalar("test_loss", total_test_loss.item(), i)
# for parameters in myModel.parameters():
#    print(parameters)
end_time = time.time()
total_train_time = end_time-start_time
print(f'训练时间: {total_train_time}秒')
writer.close()
plt.plot(train_loss_his, label='Train Loss')
plt.legend(loc='best')
plt.xlabel('Steps')
plt.show()
plt.plot(test_totalloss_his, label='Test Loss')
plt.legend(loc='best')
plt.xlabel('Steps')
plt.show()plt.plot(train_totalaccuracy_his, label='Train accuracy')
plt.plot(test_totalaccuracy_his, label='Test accuracy')
plt.legend(loc='best')
plt.xlabel('Steps')
plt.show()

尽管只用了150个数据,但是训练效果还是不错的,训练准确度达到了90%以上,测试准确度在85%以上。

我的实践:pytorch框架下基于BERT实现文本情感分类相关推荐

  1. 复盘:基于attention的多任务多模态情绪情感识别,基于BERT实现文本情感分类(pytorch实战)

    复盘:基于attention机制的多任务多模态情绪情感识别(pytorch实战),基于BERT实现文本情感分类 提示:系列被面试官问的问题,我自己当时不会,所以下来自己复盘一下,认真学习和总结,以应对 ...

  2. 基于Bert的文本情感分类

    详细代码已上传到github: click me 摘  要:     情感分类是对带有感情色彩的主观性文本进行分析.推理的过程,即分析说话人的态度,推断其所包含的情感类别.传统机器学习在处理情感分类问 ...

  3. NLP之基于TextCNN的文本情感分类

    TextCNN 文章目录 TextCNN 1.理论 1.1 基础概念 **最大汇聚(池化)层:** ![请添加图片描述](https://img-blog.csdnimg.cn/10e6e1ed6bf ...

  4. bert中文文本情感分类 微博评论挖掘之Bert实战应用案例-文本情感分类

    Bert模型全称Bidirectional Encoder Representations from Transformers,主要分为两个部分:1训练语言模型(language model)的预训练 ...

  5. 基于LSTM搭建文本情感分类的深度学习模型:准确率95%

    向AI转型的程序员都关注了这个号

  6. 【Kesci】【预选赛】2019中国高校计算机大赛——大数据挑战赛(基于FastText的文本情感分类)

    比赛链接:https://www.kesci.com/home/competition/5cb80fd312c371002b12355f 预选赛题--文本情感分类模型 本预选赛要求选手建立文本情感分类 ...

  7. BERT实战(1):使用DistilBERT作为词嵌入进行文本情感分类,与其它词向量(FastText,Word2vec,Glove)进行对比

    这次根据一篇教程Jay Alammar: A Visual Guide to Using BERT for the First Time学习下如何在Pytorch框架下使用BERT. 主要参考了中文翻 ...

  8. 基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)

    日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) Encoder编码器-Decoder解码器框架 + Atten ...

  9. PyTorch环境下对BERT进行Fine-tuning

    PyTorch环境下对BERT进行Fine-tuning 本文根据Chris McCormick的BERT微调教程进行优化并使其适应于数据集Quora Question Pairs里的判断问题对是否一 ...

最新文章

  1. 对比学习系列论文CPCforHAR(一):Contrastive Predictive Coding for Human Activity Recognition
  2. nyoj 791 Color the fence(贪心)
  3. Photon——Setup and Config 设置与配置
  4. 从C10K到C10M高性能网络的探索与实践
  5. 【Java类加载机制】深入类加载器(二)自定义加密、解密类加载器
  6. PHP易混淆函数的区分方法及意义
  7. 在local模式下的spark程序打包到集群上运行
  8. .NET Core中的验证组件FluentValidation的实战分享
  9. 分数诚可贵的飞鸽传书2012绿色版
  10. python mysql异常处理_python-处理PyMySql异常-最佳做法
  11. oracle11g分区表维护,Oracle11g维护分区(一)AddingPartitions
  12. python 菜鸟-python菜鸟教程
  13. win10系统计算机物理地址,Win10如何修改物理地址?Win10修改网卡物理地址(MAC)的两种方法...
  14. 完美解决苹果电脑mac终端无法输入大写T的问题
  15. 一些特殊字符(如:←↑→↓等箭头符号)的Unicode码值
  16. mysql分析问卷_问卷调查相关表
  17. MySQL主主从复制+TomCat高可用实践案例
  18. 当学术沾染名利,约翰伯努利对儿子的嫉恨,影响数学界几十年发展
  19. 网络营销中的动态定价策略
  20. 一步步教你搭建Android开发环境(有图有真相)--“自吹自擂:史上最详细、最啰嗦、最新的搭建教程”

热门文章

  1. 宅家36天咸鱼翻身入职腾讯,好文推荐
  2. [Network licensing error: license fill“e:\maxplus2\license\license.dat“is missing or corrupted]解决方法
  3. Unity人工智能编程精粹学习笔记 实现AI角色的自主移动——操控行为
  4. LAMP项目部署实战1
  5. 什么是 :kail LINUX
  6. js中国标准时间转化为年月日,时间戳
  7. 二、搜索蓝牙并连接(安卓蓝牙ble教程)
  8. tar解压包的时候出现错误 gzip: stdin: not in gzip format
  9. session会话中removeAttribute()和invalidate()的区别是什么
  10. LB集群——LVS负载均衡介绍(DR模式、TUN模式、NAT模式)