#手写代码# 用Bert+LSTM解决文本分类问题

31 篇文章 3 订阅
23 篇文章 11 订阅

1 配置文件

首先定义一个配置文件类,类里边存放Bert和LSTM的一些超参数

class Config(object):
    '''
    配置参数
    '''
    def __init__(self,dataset):
        self.model_name='Bert RNN Model'
        # 训练集,测试集,检验集,类别,模型训练结果保存路径
        # self.train_path=dataset+'/data/dev.txt'
        # self.test_path=dataset+'/data/dev.txt'
        # self.dev_path=dataset+'/data/dev.txt'

        self.train_path=dataset+'/data/train.txt'
        self.test_path=dataset+'/data/test.txt'
        self.dev_path=dataset+'/data/dev.txt'

        # python 数据结构保存路径
        self.datasetpkl = dataset + '/data/dataset.pkl'

        self.class_list=[x.strip() for x in open(dataset+'/data/class.txt').readlines()]
        self.save_path=dataset+'/saved_dict/'+self.model_name+'.ckpt'

        # 配置使用检测GPU
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        # 若超过1000还没有提升就提前结束训练
        self.require_improvement=1000
        # 类别数
        self.num_classes = len(self.class_list)

        # 整体训练次数
        self.num_epoch=3
        # batch大小
        self.batch_size=128
        #每个序列最大token数
        self.pad_size=32
        #学习率
        self.learning_rate = 1e-5

        self.bert_path='bert_pretrain'
        self.tokenizer=BertTokenizer.from_pretrained(self.bert_path) #定义分词器
        self.hidden_size=768  # Bert模型 token的embedding维度 = Bert模型后接自定义分类器(单隐层全连接网络)的输入维度

        # RNN 隐含层数量
        self.rnn_hidden_size=256
        # RNN数量
        self.num_layers=256
        # dropout
        self.dropout=0.5

在这个配置文件中,分别定义了一下内容:

  1. 测试集,训练集,开发集的原始数据存放路径
  2. 测试集,训练集,开发集转化成python内的数据结构后的存放路径 类别列表
  3. 模型训练使用的硬件(CPU还是GPU)
  4. 损失函数超过多少次没有提升提前结束训练 epoch数(整个数据集循环训练多少轮)
  5. batch_size
  6. 序列最大token数
  7. 学习率 模型的保存路径(本质上是保存的模型参数)
  8. 分词器
  9. Bert模型的隐含层节点数(经过Bert模型后,词向量的维度)
  10. RNN网络中每层网络的RNN节点数量
  11. RNN网络的层数
  12. dropout比例

2 定义模型

我们自定义的模型要继承自 nn.Module

class Model(nn.Module):
    def __init__(self,config):
        super(Model,self).__init__()
        self.bert=BertModel.from_pretrained(config.bert_path)  #从路径加载预训练模型
        for param in self.bert.parameters():
            param.requires_grad = True # 使参数可更新

        # batch_first 参数设置batch维度在前还是序列维度在前(输入输出同步改变)
        # 在每个RNN神经元之间使用 dropout 在单个神经元内部的两个时间步长间不使用 dropout
        self.lstm=nn.LSTM(config.hidden_size,config.rnn_hidden_size,config.num_layers,batch_first=True,dropout=config.dropout,bias=True,bidirectional=True)
        self.dropout=nn.Dropout(config.dropout)

        # 双向LSTM要*2 分析LSTM节点数和网络层数时,看成神经元是LSTM全连接网络
        self.fc=nn.Linear(config.rnn_hidden_size*2,config.num_classes) # 自定义全连接层 ,输入数(输入的最后一个维度),输出数(多分类数量),bert模型输出的最后一个维度是768,这里的输入要和bert最后的输出统一

    def forward(self,x):
        context=x[0] #128*32 batch_size*seq_length
        mask=x[2]   #128*32 batch_size*seq_length

        # 第一个参数 是所有输入对应的输出  第二个参数 是 cls最后接的分类层的输出
        encoder,pooled = self.bert(context,attention_mask=mask,output_all_encoded_layers=False) # output_all_encoded_layers 是否将bert中每层(12层)的都输出,false只输出最后一层【128*768】
        out=self.lstm(encoder) # 128*10
        return out

2.1 init(self,config)函数

__ init __(self,config)函数中主要进行了如下操作:

  1. 加载预训练bert模型
  2. 将Bert模型中的参数设置为可更新
  3. 定义LSTM网络,参数的意义请参考下文
  4. 定义dropout层
  5. 定义全连接网络层

其中,nn.LSTM()函数中的几个关键参数的解释如下:

参数名称作用
input_size输入数据的形状,这里指word embedding的维度
hidden_sizeLSTM网络每层LSTM节点数量
num_layersLSTM网络的网络层数
biasLSTM网络是否包含bias
batch_first输入数据的第一个维度是否代表batch编号
dropoutdropout比例
bidirectional是否使用双向LSTM网络

注意:
如果 bidirectional 参数为True,则LSTM的的输出的最后一个维度为 input_size*2,因为双向LSTM会将两个LSTM网络的输入拼接在一起

2.2 forward(self,x)函数

forward(self,x)函数是Bert中一个特殊文章函数,forward(self,x)函数详细解析请看此文章
这里输入数据的结构为 [输入的token序列,序列真实长度,mask序列],输入数据的格式和数据预处理部分相关,在上一篇文章中已经讲解过数据预处理的代码,这里不做赘述

    def forward(self,x):
        context=x[0] #128*32 batch_size*seq_length
        mask=x[2]   #128*32 batch_size*seq_length

        # 第一个参数 是所有输入对应的输出  第二个参数 是 cls最后接的分类层的输出
        encoder,pooled = self.bert(context,attention_mask=mask,output_all_encoded_layers=False) # output_all_encoded_layers 是否将bert中每层(12层)的都输出,false只输出最后一层【128*768】
        out=self.lstm(encoder) # encoder维度[batch_size,pad_size,bert_hidden_size],out的维度为 [batch_size,pad_size,lstm_hidden_size]
        out =self.dropout(out)  
        out =out[:,-1,:]   #只要序列中最后一个token对应的输出,(因为lstm会记录前边token的信息)
        out =self.fc(out)
        return out

forward(self,x)函数中主要进行了一下操作:

  1. 输入数据并得到预训练Bert模型的输出
  2. 得到LSTM模型的输出
  3. 经过dropout层,取每个序列中最后一个token的输出
  4. 将dropout层输入全连接层网络,并得到全连接层网络的输出

关于bert模块两个返回值的深度解析请参考此文章 ->从源码层面,深入理解 Bert 框架

  • 8
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

energy_百分百

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

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

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

打赏作者

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

抵扣说明:

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

余额充值