【NLP实战】基于Bert和双向LSTM的情感分类【中篇】( 四 )


在主函数中检查输出的shape 。为了快速实验,此处把修改为10 。
if __name__ == '__main__':# todo:定义超参数batch_size = 10epochs = 30dropout = 0.4rnn_hidden = 768rnn_layer = 1class_num = 3lr = 0.001...out = model.forward(input_ids, attention_mask, token_type_ids)print(out.shape)
Step5:前置工作:将进行one-hot
这件事留到现在才来做,其实最开始就可以做这件事 。作为多分类模型的基础,我们需要把
c l a s s _ n u m s = 3 , l a b e l s = [ ? 1 , ? 1 , 0 , 0 , 1 , . . . ] \begin{split} &class\_nums = 3,\\ & = [-1,-1,0,0,1,...] \end{split} ?=3,=[?1,?1,0,0,1,...]?
改成:
c l a s s _ n u m s = 3 , l a b e l s = [ [ 1 , 0 , 0 ] , [ 1 , 0 , 0 ] , [ 0 , 1 , 0 ] , [ 0 , 1 , 0 ] , [ 0 , 0 , 1 ] . . . ] \begin{split} &class\_nums = 3,\\ & = [[1,0,0],[1,0,0],[0,1,0],[0,1,0],[0,0,1]...] \end{split} ?=3,=[[1,0,0],[1,0,0],[0,1,0],[0,1,0],[0,0,1]...]?
提供了torch.nn..用于将上述功能,但是注意,的input中不应该包含负数 。在上篇中,我们观察了类别为-1,0,1,只需要对每项加一,就可以变成我们需要的 。注意,对于更一般的情况,比如label为字符等,我们需要创建字典,将它们label to id得到对应的ids,再进行one-hot 。
# 测试labels2one-hotlabels = torch.LongTensor([0, -1,0,1])labels += 1one_hot_labels = one_hot(labels,num_classes = 3)print(one_hot_labels)
思考:我们需要一次性对所有进行还是对一个batch进行呢?
显然,对一个batch进行 to 和对所有进行效果是一样的 。你可以选择先对所有做一个预处理,或者在批处理时一批一批的 。
考虑到内存等,这里我选择分批次处理 。
Step5:测试
if __name__ == '__main__':...for i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_loader):one_hot_labels = one_hot(labels+1, num_classes=3)# 将one_hot_labels类型转换成floatone_hot_labels = one_hot_labels.to(dtype=torch.float)optimizer.zero_grad()# 清空梯度output = model.forward(input_ids, attention_mask, token_type_ids)# forward# output = output.squeeze()# 将[128, 1, 3]挤压为[128,3]loss = criterion(output, one_hot_labels)# 计算损失print(loss)loss.backward()# backward,计算gradoptimizer.step()# 更新参数
跑了一个批次,没报错,并且观察到loss在逐渐变小即可 。
loss变化:
【【NLP实战】基于Bert和双向LSTM的情感分类【中篇】】tensor(1.0854, grad_fn=)tensor(1.2250, grad_fn=)tensor(1.0756, grad_fn=)tensor(0.9926, grad_fn=)tensor(0.8705, grad_fn=)tensor(0.8499, grad_fn=)
ok,没问题 。
但是很容易发现,计算速度太慢 。于是下一步,我们需要将模型和数据放到GPU上运行 。
第二部分:转移至GPU
关于gpu环境的搭建等就不再赘述 。
检查gpu环境
if __name__ == '__main__':# 设置GPU环境device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')print(device)# ...
将cpu环境转换至gpu环境需要注意的地方转移模型与数据
import torchfrom datasets import load_dataset# hugging-face datasetfrom torch.utils.data import Datasetfrom torch.utils.data import DataLoaderimport torch.nn as nnimport matplotlib.pyplot as pltimport seaborn as snsfrom transformers import BertTokenizer, BertModelimport torch.optim as optimfrom torch.nn.functional import one_hot# todo:自定义数据集class MydataSet(Dataset):...# todo: 定义批处理函数def collate_fn(data):...# todo: 定义模型,上游使用bert预训练,下游任务选择双向LSTM模型,最后加一个全连接层class BiLSTM(nn.Module):def __init__(self, drop, hidden_dim, output_dim):super(BiLSTM, self).__init__()...# 加载bert中文模型,生成embedding层self.embedding = BertModel.from_pretrained('bert-base-chinese')# 预处理模型需要转移至gpuself.embedding.to(device)# 冻结上游模型参数(不进行预训练模型参数学习)...# 生成下游RNN层以及全连接层...if __name__ == '__main__':# 设置GPU环境device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')print('device=', device)# todo:定义超参数...# load train datatrain_dataset = MydataSet('./data/archive/train_clean.csv', 'train')# print(train_dataset.__len__())# print(train_dataset[0])# todo: 加载字典和分词工具token = BertTokenizer.from_pretrained('bert-base-chinese')# 装载训练集train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, collate_fn=collate_fn,shuffle=True,drop_last=True)# 创建模型model = BiLSTM(drop=dropout, hidden_dim=rnn_hidden, output_dim=class_num)# 模型转移至gpumodel.to(device)# 选择损失函数criterion = nn.CrossEntropyLoss()# 选择优化器optimizer = optim.AdamW(model.parameters(), lr=lr)# 需要将所有数据转移到gpufor i, (input_ids, attention_mask, token_type_ids, labels) in enumerate(train_loader):input_ids = input_ids.long().to(device)attention_mask = attention_mask.long().to(device)token_type_ids = token_type_ids.long().to(device)labels = labels.long().to(device)one_hot_labels = one_hot(labels+1, num_classes=3)# 将one_hot_labels类型转换成floatone_hot_labels = one_hot_labels.to(dtype=torch.float)# print(one_hot_labels)optimizer.zero_grad()# 清空梯度output = model.forward(input_ids, attention_mask, token_type_ids)# forward# output = output.squeeze()# 将[128, 1, 3]挤压为[128,3]loss = criterion(output, one_hot_labels)# 计算损失print(loss)loss.backward()# backward,计算gradoptimizer.step()# 更新参数