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
在这个配置文件中,分别定义了一下内容:
- 测试集,训练集,开发集的原始数据存放路径
- 测试集,训练集,开发集转化成python内的数据结构后的存放路径 类别列表
- 模型训练使用的硬件(CPU还是GPU)
- 损失函数超过多少次没有提升提前结束训练 epoch数(整个数据集循环训练多少轮)
- batch_size
- 序列最大token数
- 学习率 模型的保存路径(本质上是保存的模型参数)
- 分词器
- Bert模型的隐含层节点数(经过Bert模型后,词向量的维度)
- RNN网络中每层网络的RNN节点数量
- RNN网络的层数
- 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)函数中主要进行了如下操作:
- 加载预训练bert模型
- 将Bert模型中的参数设置为可更新
- 定义LSTM网络,参数的意义请参考下文
- 定义dropout层
- 定义全连接网络层
其中,nn.LSTM()函数中的几个关键参数的解释如下:
参数名称 | 作用 |
---|---|
input_size | 输入数据的形状,这里指word embedding的维度 |
hidden_size | LSTM网络每层LSTM节点数量 |
num_layers | LSTM网络的网络层数 |
bias | LSTM网络是否包含bias |
batch_first | 输入数据的第一个维度是否代表batch编号 |
dropout | dropout比例 |
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)函数中主要进行了一下操作:
- 输入数据并得到预训练Bert模型的输出
- 得到LSTM模型的输出
- 经过dropout层,取每个序列中最后一个token的输出
- 将dropout层输入全连接层网络,并得到全连接层网络的输出
关于bert模块两个返回值的深度解析请参考此文章 ->从源码层面,深入理解 Bert 框架