• 深度学习-nlp系列(3)文本分类(Bert+TextCNN)pytorch


    在前面两章讲解了 bert 和 TextCNN 模型,用这两个模型来进行文本分类。那我们就可以试一下将这两个模型进行融合来进行文本分类。

    模型介绍

    我们知道在进行模型融合时,要注意的时在第一个模型的输出要符合第二个模型的输入。

    Bert 模型的输出是有不同的情况;TextCNN模型的输入是一个四维的,[bacth_size, 1, max_len, bedding]。

    Bert 模型输出

     图1 bert 模型输出

     前三个输出:

      图2 bert 模型前三个输出解释

    last_hidden_state:模型最后一层输出的隐藏状态序列。(batch_size, sequence_length, hidden_size)
    pooler_output:通常后面直接接线性层用来文本分类,不添加其他的模型或层。
    hidden_states:每层输出的模型隐藏状态加上可选的初始嵌入输出。12*(batch_size, sequence_length, hidden_size)
    

    根据上面三个可知,如果我们要加上 TextCNN 模型,可以选择last_hidden_state和hidden_states,这两个不同的区别就是 last_hidden_state 是最后一层的输出,而hidden_states 是每一层的输出。因此对于 bert 模型的输出我们就有两种选择。

    模型选择1:

     

      图3 模型结构图1

    我们以最后一层的模型输出的隐藏状态作为 TextCNN 模型的输入,此时要想在TextCNN 模型能正常进行训练,需要修改 shape 。

    [batch_size, max_len, hidden_size] --》 [batch_size, 1, max_len, hidden_size]
    out = hidden_out.last_hidden_state.unsqueeze(1)   # shape [batch_size, 1, max_len, hidden_size]

     模型选择2:

     图4 模型结构图2

    我们以每一层的模型输出的隐藏状态作为 TextCNN 模型的输入,此时要想在TextCNN 模型能正常进行训练,需要修改隐藏状态。输出的第一层是我们不需要的(第一层是 embedding 层不需要),且 sequence_length 也是不需要的,需要将其去掉。

    1. hidden_states = outputs.hidden_states # 13 * [batch_size, seq_len, hidden] 第一层是 embedding 层不需要
    2. cls_embeddings = hidden_states[1][:, 0, :].unsqueeze(1) # [batch_size, 1, hidden]

     我们通过循环将剩余的层数给取出来,并将取出来的层数进行拼接作为 TextCNN 模型的输入。

    1. for i in range(2, 13):
    2. cls_embeddings = torch.cat((cls_embeddings, hidden_states[i][:, 0, :].unsqueeze(1)), dim=1)

     拼接后的输入的 shpe 为 [batch_size, 12, hidden],最后将该 shape 送入 TextCNN 模型进行训练。

    数据处理

    1. text = self.all_text[index]
    2. # Tokenize the pair of sentences to get token ids, attention masks and token type ids
    3. encoded_pair = self.tokenizer(text,
    4. padding='max_length', # Pad to max_length
    5. truncation=True, # Truncate to max_length
    6. max_length=self.max_len,
    7. return_tensors='pt') # Return torch.Tensor objects
    8. # shape [max_len]
    9. token_ids = encoded_pair['input_ids'].squeeze(0) # tensor of token ids torch.Size([max_len])
    10. attn_masks = encoded_pair['attention_mask'].squeeze(0) # binary tensor with "0" for padded values and "1" for the other values torch.Size([max_len])
    11. token_type_ids = encoded_pair['token_type_ids'].squeeze(0) # binary tensor with "0" for the 1st sentence tokens & "1" for the 2nd sentence tokens torch.Size([max_len])
    12. if self.with_labels: # True if the dataset has labels
    13. label = int(self.all_label[index])
    14. return token_ids, attn_masks, token_type_ids, label
    15. else:
    16. return token_ids, attn_masks, token_type_ids

    模型准备

    模型1:

    1. class BertTextModel_encode_layer(nn.Module):
    2. def __init__(self):
    3. super(BertTextModel_encode_layer, self).__init__()
    4. self.bert = BertModel.from_pretrained(parsers().bert_pred)
    5. for param in self.bert.parameters():
    6. param.requires_grad = True
    7. self.linear = nn.Linear(parsers().hidden_size, parsers().class_num)
    8. self.textCnn = TextCnnModel()
    9. def forward(self, x):
    10. input_ids, attention_mask, token_type_ids = x[0], x[1], x[2]
    11. outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask,
    12. token_type_ids=token_type_ids,
    13. output_hidden_states=True # 确保 hidden_states 的输出有值
    14. )
    15. # 取每一层encode出来的向量
    16. hidden_states = outputs.hidden_states # 13 * [batch_size, seq_len, hidden] 第一层是 embedding 层不需要
    17. cls_embeddings = hidden_states[1][:, 0, :].unsqueeze(1) # [batch_size, 1, hidden]
    18. # 将每一层的第一个token(cls向量)提取出来,拼在一起当作textCnn的输入
    19. for i in range(2, 13):
    20. cls_embeddings = torch.cat((cls_embeddings, hidden_states[i][:, 0, :].unsqueeze(1)), dim=1)
    21. # cls_embeddings: [bs, 12, hidden]
    22. pred = self.textCnn(cls_embeddings)
    23. return pred

     模型2:

    1. class BertTextModel_last_layer(nn.Module):
    2. def __init__(self):
    3. super(BertTextModel_last_layer, self).__init__()
    4. self.bert = BertModel.from_pretrained(parsers().bert_pred)
    5. for param in self.bert.parameters():
    6. param.requires_grad = True
    7. # TextCNN
    8. self.convs = nn.ModuleList(
    9. [nn.Conv2d(in_channels=1, out_channels=parsers().num_filters, kernel_size=(k, parsers().hidden_size),) for k in parsers().filter_sizes]
    10. )
    11. self.dropout = nn.Dropout(parsers().dropout)
    12. self.fc = nn.Linear(parsers().num_filters * len(parsers().filter_sizes), parsers().class_num)
    13. def conv_pool(self, x, conv):
    14. x = conv(x) # shape [batch_size, out_channels, x.shape[1] - conv.kernel_size[0] + 1, 1]
    15. x = F.relu(x)
    16. x = x.squeeze(3) # shape [batch_size, out_channels, x.shape[1] - conv.kernel_size[0] + 1]
    17. size = x.size(2)
    18. x = F.max_pool1d(x, size) # shape[batch+size, out_channels, 1]
    19. x = x.squeeze(2) # shape[batch+size, out_channels]
    20. return x
    21. def forward(self, x):
    22. input_ids, attention_mask, token_type_ids = x[0], x[1], x[2] # shape [batch_size, max_len]
    23. hidden_out = self.bert(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids,
    24. output_hidden_states=False)
    25. out = hidden_out.last_hidden_state.unsqueeze(1) # shape [batch_size, 1, max_len, hidden_size]
    26. out = torch.cat([self.conv_pool(out, conv) for conv in self.convs], 1) # shape [batch_size, parsers().num_filters * len(parsers().filter_sizes]
    27. out = self.dropout(out)
    28. out = self.fc(out)
    29. return out

    模型训练

    1. def train(model, device, trainLoader, opt, epoch):
    2. model.train()
    3. loss_sum, count = 0, 0
    4. for batch_index, batch_con in enumerate(trainLoader):
    5. batch_con = tuple(p.to(device) for p in batch_con)
    6. pred = model(batch_con)
    7. opt.zero_grad()
    8. loss = loss_fn(pred, batch_con[-1])
    9. loss.backward()
    10. opt.step()
    11. loss_sum += loss
    12. count += 1
    13. if len(trainLoader) - batch_index <= len(trainLoader) % 1000 and count == len(trainLoader) % 1000:
    14. msg = "[{0}/{1:5d}]\tTrain_Loss:{2:.4f}"
    15. print(msg.format(epoch + 1, batch_index + 1, loss_sum / count))
    16. loss_sum, count = 0.0, 0
    17. if batch_index % 1000 == 999:
    18. msg = "[{0}/{1:5d}]\tTrain_Loss:{2:.4f}"
    19. print(msg.format(epoch + 1, batch_index + 1, loss_sum / count))
    20. loss_sum, count = 0.0, 0

    训练结果

    由于训练时间长,我对这两个模型都只训练的一次,来看模型在验证集上的准确率。发现在只训练一轮时,模型1的准确率高于模型2的准确率。

    BertTextModel_encode_layer

      图5 模型2输出

    BertTextModel_last_layer
    

    图6 模型1输出1

      图7 模型1输出2

    模型预测

    选取 BertTextModel_last_layer 模型的模型文件来进行预测

     图8 模型1预测结果

    源码获取

    bert-TextCNN 文本分类

  • 相关阅读:
    Redis基本数据类型
    垃圾收集器
    nlp与知识图谱代码解读_词嵌入
    ACM实训冲刺第二十二天
    聊聊自动驾驶中的路径和轨迹
    MATLAB中isequal函数转化为C语言
    基于JavaSwing开发中国象棋对战游戏+实验报告 课程设计 大作业
    linux基础网络设置
    Linux本地WBO创作白板部署与远程访问
    2022年最新浙江建筑特种工(施工升降机)真题题库及答案
  • 原文地址:https://blog.csdn.net/qq_48764574/article/details/126323731