cnn-residual用于意图识别

此分类模型是来自序列模型Convolutional Sequence to Sequence Learning中的encoder部分,这里暂且叫它带残差的cnn model,如上图所示。
1.句子token和其对应的position经过embedding后,逐元素加和作为source embedding。
2.source embedding进入: 线性层 -> 卷积块后得到的特征 -> 线性层。
3.以上的输出和source embedding进行残差连接。
4.以上的输出,我这里加了一个平均池化后进入线性层,预测输出。

以上是模型中的卷积块,可以设置多个卷积块。
1.卷积块的初始输入是 source embedding加一个线性层,padding后输入卷积。
2.卷积后经过glu激活函数
3.激活后的输出和padding后的输入进行残差连接,进入下一个卷积块。
4.最终输出卷积特征。
以上的图片和原始代码是改自https://github.com/bentrevett/pytorch-seq2seq 在此非常感谢作者实现了这么通俗易懂的代码架构,可以让其它人在上面进行修改。
模型主程序:(完整项目见:https://github.com/jiangnanboy/intent_classification/tree/master/cnn_residual)
class CNNResidual(nn.Module):
def __init__(self, input_dim, emb_dim, intent_dim, hid_dim, n_layers, kernel_size, dropout, max_length=20):
super(CNNResidual, self).__init__()
assert kernel_size % 2 == 1,'kernel size must be odd!' # 卷积核size为奇数,方便序列两边pad处理
self.scale = torch.sqrt(torch.FloatTensor([0.5])).to(DEVICE) # 确保整个网络的方差不会发生显著变化
self.tok_embedding = nn.Embedding(input_dim, emb_dim) # token编码
self.pos_embedding = nn.Embedding(max_length, emb_dim) # token的位置编码
self.emb2hid = nn.Linear(emb_dim, hid_dim) # 线性层,从emb_dim转为hid_dim
self.hid2emb = nn.Linear(hid_dim, emb_dim) # 线性层,从hid_dim转为emb_dim
# 卷积块
self.convs = nn.ModuleList([nn.Conv1d(in_channels=hid_dim,
out_channels=2*hid_dim, # 卷积后输出的维度,这里2*hid_dim是为了后面的glu激活函数
kernel_size=kernel_size,
padding=(kernel_size - 1)//2) # 序列两边补0个数,保持维度不变
for _ in range(n_layers)])
self.dropout = nn.Dropout(dropout)
# 利用encoder的输出进行意图识别
self.intent_output = nn.Linear(emb_dim, intent_dim)
def forward(self, src):
# src: [batch_size, src_len]
batch_size = src.shape[0]
src_len = src.shape[1]
# 创建token位置信息
pos = torch.arange(src_len).unsqueeze(0).repeat(batch_size, 1).to(DEVICE) # [batch_size, src_len]
# 对token与其位置进行编码
tok_embedded = self.tok_embedding(src) # [batch_size, src_len, emb_dim]
pos_embedded = self.pos_embedding(pos.long()) # [batch_size, src_len, emb_dim]
# 对token embedded和pos_embedded逐元素加和
embedded = self.dropout(tok_embedded + pos_embedded) # [batch_size, src_len, emb_dim]
# embedded经过一线性层,将emb_dim转为hid_dim,作为卷积块的输入
conv_input = self.emb2hid(embedded) # [batch_size, src_len, hid_dim]
# 转变维度,卷积在输入数据的最后一维进行
conv_input = conv_input.permute(0, 2, 1) # [batch_size, hid_dim, src_len]
# 以下进行卷积块
for i, conv in enumerate(self.convs):
# 进行卷积
conved = conv(self.dropout(conv_input)) # [batch_size, 2*hid_dim, src_len]
# 进行激活glu
conved = F.glu(conved, dim=1) # [batch_size, hid_dim, src_len]
# 进行残差连接
conved = (conved + conv_input) * self.scale # [batch_size, hid_dim, src_len]
# 作为下一个卷积块的输入
conv_input = conved
# 经过一线性层,将hid_dim转为emb_dim,作为enocder的卷积输出的特征
conved = self.hid2emb(conved.permute(0, 2, 1)) # [batch_size, src_len, emb_dim]
# 又是一个残差连接,逐元素加和输出,作为encoder的联合输出特征
combined = (conved + embedded) * self.scale # [batch_size, src_len, emb_dim]
# 意图识别,加一个平均池化,池化后的维度是:[batch_size, emb_dim]
intent_output = self.intent_output(F.avg_pool1d(combined.permute(0, 2, 1), combined.shape[1]).squeeze()) # [batch_size, intent_dim]
return intent_output



浙公网安备 33010602011771号