赞
踩
首先定义一个配置文件类,类里边存放Bert和CNN的一些超参数
class Config(object): ''' 配置参数 ''' def __init__(self,dataset): # 模型名称 self.model_name='Bert CNN 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' # bert 分词器 self.tokenizer=BertTokenizer.from_pretrained(self.bert_path) #定义分词器 self.hidden_size=768 # Bert模型 token的embedding维度 = Bert模型后接自定义分类器(单隐层全连接网络)的输入维度 # 每个n-gram的卷积核数量 self.num_filters=256 # 卷积核在序列维度上的尺寸 = n-gram大小 卷积核总数量=filter_size*num_filters self.filter_size=(2,3,4) self.dropout=0.1
在这个配置文件中,分别定义了一下内容:
我们自定义的模型要继承自 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 # 使参数可更新 self.convs=nn.ModuleList( # 输入通道数,输出通道数(卷积核数),卷积核维度 [nn.Conv2d(1,config.num_filters,(k,config.hidden_size)) for k in config.filter_size] #(k,config.hidden_size) n-gram,embedding维度 ) self.dropout=nn.Dropout(config.dropout) self.fc=nn.Linear(config.num_filters*len(config.filter_size),config.num_classes ) #输入的最后一个维度,输出的最后一个维度 全连接层只改变数据的最后一个维度 由输入最后的一个维度转化为 类别数 def conv_and_pool(self,x,conv): x=conv(x) #[batch_size,channel_num,pad_size,embedding_size(1)] x=F.relu(x) x=x.squeeze(3) #[batch_size,channel_num,pad_size] x=F.max_pool1d(x,x.size(2)) #经过卷积之后,x x = x.squeeze(2) # [batch_size,channel_num] return x def forward(self,x): context=x[0] #128*32 batch_size*seq_length mask=x[2] #128*32 batch_size*seq_length # 第一个参数 是所有输入对应的输出 第二个参数 是 cls最后接的分类层的输出 encoder_out,pooled = self.bert(context,attention_mask=mask,output_all_encoded_layers=False) # output_all_encoded_layers 是否将bert中每层(12层)的都输出,false只输出最后一层【128*768】 out = encoder_out.unsqueeze(1) #增加一个维度,[batch_size,channel_num,pad_size,embedding_num] -> [batch_size,channel_num,pad_size,embedding_num] out = torch.cat([self.conv_and_pool(out,conv) for conv in self.convs],1) out=self.fc(out) # 128*10 return out
init(self,config)函数中主要进行了如下操作:
使用 nn.Conv2d() 函数定义卷积核,nn.Conv2d() 函数的主要参数如下:
参数名称 | 作用 |
---|---|
in_channels | 输入数据的通道数(这里可以理解为使用了多少 word embedding方法) |
out_channels | 输出通道数,表示使用了多少个卷积核 |
kernel_size | 卷积核尺寸,[n-gram大小,word_embedding大小(bert隐含层节点数)] |
自定义卷积核时,每种n-gram对应一组卷积核(每组卷积核数量相同),上述定义卷积核的代码中,首先遍历每个配置类中的 filter_size,得到每组局卷积核在0维(序列维度上的长度),然后将bert隐含层节点数作为每组局卷积核1维上的长度,从而定义每组卷积核的尺寸(每组卷积核内的卷积核尺寸相同)
conv_and_pool()函数内主要执行了以下过程:
关于PyTorch中一维卷积和二维卷积的区别请参考此文章:深入理解 PyTorch 的一维卷积和二维卷积,一维池化和二维池化
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_out,pooled = self.bert(context,attention_mask=mask,output_all_encoded_layers=False) # output_all_encoded_layers 是否将bert中每层(12层)的都输出,false只输出最后一层【128*768】
out = encoder_out.unsqueeze(1) #增加一个维度,[batch_size,channel_num,pad_size,embedding_num] -> [batch_size,channel_num,pad_size,embedding_num]
out = torch.cat([self.conv_and_pool(out,conv) for conv in self.convs],1)
out=self.fc(out) # 128*10 通道数=n-gram数*每种n-gram内的卷积数
return out
forward(self,x)函数函数内主要执行了以下过程:
关于bert模块两个返回值的深度解析请参考此文章 ->从源码层面,深入理解 Bert 框架
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。