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


这里我们一个是128 。注意.shape的输出,是[128,200,768] 。代表128句话,每句话200个词,每个词被构建成了768维度的Word (词向量) 。接下来,我们就可以把它们丢进LSTM层了 。
step4:生成层–和全连接层,测试
于4.13日修改 输出

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

文章插图
我们还在这部分选择了损失函数和优化器 。
首先说明两个易错的点:
问题1:使用CrossLoss到底需不需要在时经过层?
关于这个问题的讨论:
-I useaswhen using crossloss in ? - Stack
之前说了,CrossLoss本身已经实现了 。所以时最后只需要经过全连接层就可以输出,当然你还可以再加一个Relu激活,我这里没有加 。
问题2:最后时刻的输出应该怎么取?
先来看看的图 。我们需要注意的是,第一层由正向lstm第一时刻状态和反向lstm最后时刻状态拼接而成;而最后一层则由正向lstm最后时刻状态和反向lstm第一时刻状态拼接而成 。所以我们在时简单取[:,-1,:]并不是正确的 。
下面,我们通过一个简单的实验证明以上结论 。
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 optim# 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__()self.drop = dropself.hidden_dim = hidden_dimself.output_dim = output_dim# 加载bert中文模型,生成embedding层self.embedding = BertModel.from_pretrained('bert-base-chinese')# 冻结上游模型参数(不进行预训练模型参数学习)for param in self.embedding.parameters():param.requires_grad_(False)# 生成下游RNN层以及全连接层self.lstm = nn.LSTM(input_size=768, hidden_size=self.hidden_dim, num_layers=1, batch_first=True,bidirectional=True, dropout=self.drop)self.fc = nn.Linear(self.hidden_dim * 2, self.output_dim)# 使用CrossEntropyLoss作为损失函数时,不需要激活 。因为实际上CrossEntropyLoss将softmax-log-NLLLoss一并实现的 。但是使用其他损失函数时还是需要加入softmax层的 。def forward(self, input_ids, attention_mask, token_type_ids):embedded = self.embedding(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)embedded = embedded.last_hidden_state# 第0维才是我们需要的embedding,embedding.last_hidden_state = embedding[0]out, (h_n, c_n) = self.lstm(embedded)print(out.shape)# [128, 200 ,1536]因为是双向的,所以out是两个hidden_dim拼接而成 。768*2 = 1536# h_n[-2, :, :] 为正向lstm最后一个隐藏状态 。# h_n[-1, :, :] 为反向lstm最后一个隐藏状态print(out[:, -1, :768] == h_n[-2, :, :])# 正向lstm最后时刻的输出在output最后一层print(out[:, 0, 768:] == h_n[-1, :, :])# 反向lstm最后时刻的输出在output第一层
而我们现在同时需要正向lstm最后时刻的隐藏状态和反向lstm最后时刻的隐藏状态 。于是,我们应该将两个进行拼接作为输出 。
生成层,编写与测试
修改刚才代码中的部分:
def forward(self, input_ids, attention_mask, token_type_ids):embedded = self.embedding(input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)embedded = embedded.last_hidden_state# 第0维才是我们需要的embedding,embedding.last_hidden_state = embedding[0]out, (h_n, c_n) = self.lstm(embedded)print(out.shape)# [128, 200 ,1536]因为是双向的,所以out是两个hidden_dim拼接而成 。768*2 = 1536# h_n[-2, :, :] 为正向lstm最后一个隐藏状态 。# h_n[-1, :, :] 为反向lstm最后一个隐藏状态# print(out[:, -1, :768] == h_n[-2, :, :])# 正向lstm最后时刻的输出在output最后一层# print(out[:, 0, 768:] == h_n[-1, :, :])# 反向lstm最后时刻的输出在output第一层# print(out[:, -1, :].shape)# [128, 1536]128句话,每句话的最后一个状态output = torch.cat((h_n[-2, :, :], h_n[-1, :, :]), dim=1)print(output.shape)# 检查是否拼接成功print(out[:, -1, :768] == output[:, :768])print(out[:, 0, 768:] == output[:, 768:])output = self.fc(output)return output