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 也是不需要的,需要将其去掉。

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

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

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

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

数据处理

        text = self.all_text[index]

        # Tokenize the pair of sentences to get token ids, attention masks and token type ids
        encoded_pair = self.tokenizer(text,
                                      padding='max_length',  # Pad to max_length
                                      truncation=True,  # Truncate to max_length
                                      max_length=self.max_len,
                                      return_tensors='pt')  # Return torch.Tensor objects
        
        # shape [max_len]
        token_ids = encoded_pair['input_ids'].squeeze(0)  # tensor of token ids  torch.Size([max_len])
        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])
        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])

        if self.with_labels:  # True if the dataset has labels
            label = int(self.all_label[index])
            return token_ids, attn_masks, token_type_ids, label
        else:
            return token_ids, attn_masks, token_type_ids

模型准备

模型1:

class BertTextModel_encode_layer(nn.Module):
    def __init__(self):
        super(BertTextModel_encode_layer, self).__init__()
        self.bert = BertModel.from_pretrained(parsers().bert_pred)

        for param in self.bert.parameters():
            param.requires_grad = True

        self.linear = nn.Linear(parsers().hidden_size, parsers().class_num)
        self.textCnn = TextCnnModel()

    def forward(self, x):
        input_ids, attention_mask, token_type_ids = x[0], x[1], x[2]
        outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask,
                            token_type_ids=token_type_ids,
                            output_hidden_states=True  # 确保 hidden_states 的输出有值
                            )
        # 取每一层encode出来的向量
        hidden_states = outputs.hidden_states  # 13 * [batch_size, seq_len, hidden] 第一层是 embedding 层不需要
        cls_embeddings = hidden_states[1][:, 0, :].unsqueeze(1)  # [batch_size, 1, hidden]
        # 将每一层的第一个token(cls向量)提取出来,拼在一起当作textCnn的输入
        for i in range(2, 13):
            cls_embeddings = torch.cat((cls_embeddings, hidden_states[i][:, 0, :].unsqueeze(1)), dim=1)
        # cls_embeddings: [bs, 12, hidden]
        pred = self.textCnn(cls_embeddings)
        return pred

 模型2:

class BertTextModel_last_layer(nn.Module):
    def __init__(self):
        super(BertTextModel_last_layer, self).__init__()
        self.bert = BertModel.from_pretrained(parsers().bert_pred)
        for param in self.bert.parameters():
            param.requires_grad = True

        # TextCNN
        self.convs = nn.ModuleList(
            [nn.Conv2d(in_channels=1, out_channels=parsers().num_filters, kernel_size=(k, parsers().hidden_size),) for k in parsers().filter_sizes]
        )
        self.dropout = nn.Dropout(parsers().dropout)
        self.fc = nn.Linear(parsers().num_filters * len(parsers().filter_sizes), parsers().class_num)

    def conv_pool(self, x, conv):
        x = conv(x)  # shape [batch_size, out_channels, x.shape[1] - conv.kernel_size[0] + 1, 1]
        x = F.relu(x)
        x = x.squeeze(3)  # shape [batch_size, out_channels, x.shape[1] - conv.kernel_size[0] + 1]
        size = x.size(2)
        x = F.max_pool1d(x, size)   # shape[batch+size, out_channels, 1]
        x = x.squeeze(2)  # shape[batch+size, out_channels]
        return x

    def forward(self, x):
        input_ids, attention_mask, token_type_ids = x[0], x[1], x[2]  # shape [batch_size, max_len]
        hidden_out = self.bert(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids,
                               output_hidden_states=False)
        out = hidden_out.last_hidden_state.unsqueeze(1)   # shape [batch_size, 1, max_len, hidden_size]
        out = torch.cat([self.conv_pool(out, conv) for conv in self.convs], 1)  # shape  [batch_size, parsers().num_filters * len(parsers().filter_sizes]
        out = self.dropout(out)
        out = self.fc(out)
        return out

模型训练

def train(model, device, trainLoader, opt, epoch):
    model.train()
    loss_sum, count = 0, 0
    for batch_index, batch_con in enumerate(trainLoader):
        batch_con = tuple(p.to(device) for p in batch_con)
        pred = model(batch_con)

        opt.zero_grad()
        loss = loss_fn(pred, batch_con[-1])
        loss.backward()
        opt.step()
        loss_sum += loss
        count += 1

        if len(trainLoader) - batch_index <= len(trainLoader) % 1000 and count == len(trainLoader) % 1000:
            msg = "[{0}/{1:5d}]\tTrain_Loss:{2:.4f}"
            print(msg.format(epoch + 1, batch_index + 1, loss_sum / count))
            loss_sum, count = 0.0, 0

        if batch_index % 1000 == 999:
            msg = "[{0}/{1:5d}]\tTrain_Loss:{2:.4f}"
            print(msg.format(epoch + 1, batch_index + 1, loss_sum / count))
            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 文本分类使用 pytorch 框架来处理自然语言(文本分类、实体识别、三元组抽取). Contribute to mzc421/pytorch-nlp development by creating an account on GitHub.icon-default.png?t=N7T8https://github.com/mzc421/pytorch-nlp/tree/master/03-bert-TextCNN%20%E6%96%87%E6%9C%AC%E5%88%86%E7%B1%BB

 硬性的标准其实限制不了无限可能的我们,所以啊!少年们加油吧!

### BERTTextCNN结合用于文本分类 #### BERT简介 BERT(Bidirectional Encoder Representations from Transformers)是一种预训练的语言模型,能够捕捉上下文语境下的词语关系。通过双向Transformer编码器架构,BERT可以理解输入序列中单词的前后依赖关系[^2]。 #### TextCNN概述 TextCNN由Yoon Kim于2014年提出,旨在利用卷积神经网络(CNN)对短文本进行高效分类。该模型采用多尺寸内核滑动窗口扫描整个句子,并通过最大池化层汇总特征,最终连接全连接层完成预测任务[^3]。 #### 组合策略 为了融合两者优势,在实际应用中通常会先用已有的BERT模型生成高质量词嵌入作为新模型输入;接着这些向量会被送入设计好的TextCNN框架里进一步加工处理。具体实现方式如下: - **前馈过程**: - 输入一段待分析的文字给定到BERT模块得到相应维度的隐状态表示; - 将上述结果视作文档矩阵传递至后续搭建的标准TextCNN组件继续计算直至得出最后得分分布。 - **优化调整**:根据反馈情况微调整体结构内的可变系数直到满足预期效果为止。 ```python import torch from transformers import BertTokenizer, BertModel import torch.nn as nn class BERT_TextCNN(nn.Module): def __init__(self, config): super(BERT_TextCNN, self).__init__() # 加载预训练Bert模型 self.bert = BertModel.from_pretrained(&#39;bert-base-uncased&#39;) # 定义TextCNN相关配置 filter_sizes = (2, 3, 4) num_filters = 10 self.convs = nn.ModuleList([nn.Conv2d(1, num_filters, (k, 768)) for k in filter_sizes]) self.dropout = nn.Dropout(config[&#39;dropout&#39;]) self.fc = nn.Linear(len(filter_sizes)*num_filters, config[&#39;output_dim&#39;]) def forward(self, input_ids, attention_mask=None): encoded_layers, _ = self.bert(input_ids=input_ids, attention_mask=attention_mask) out = encoded_layers[-1].unsqueeze(1) # [batch_size, seq_len, hidden_size]->[batch_size, channel_num=1, seq_len, hidden_size] out = [F.relu(conv(out)).squeeze(3) for conv in self.convs] # [(N,filt_num,W), ...]*len(filter_sizes) out = [F.max_pool1d(i, i.size(2)).squeeze(2) for i in out] # [(N,filt_num),...]*len(filter_sizes) out = torch.cat(out, 1) out = self.dropout(out) logit = self.fc(out) return logit ```
评论 57
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牧锦程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值