储备知识
- 建议先了解一下关于nn.Conv1d的相关知识,附上链接https://blog.csdn.net/sunny_xsc1994/article/details/82969867
数据预处理
- 与循环神经网络使用的数据一样,都是[batch_size,max_len]格式。
模型建立
- 下面
class TextCNN(nn.Module):
def __init__(self, vocab, embed_size, kernel_sizes, num_channels):
super(TextCNN, self).__init__()
self.embedding = nn.Embedding(len(vocab), embed_size)
# 不参与训练的嵌入层
self.constant_embedding = nn.Embedding(len(vocab), embed_size)
self.dropout = nn.Dropout(0.5)
self.decoder = nn.Linear(sum(num_channels), 2)
# 时序最大池化层没有权重,所以可以共用一个实例
self.pool = GlobalMaxPool1d()
self.convs = nn.ModuleList() # 创建多个一维卷积层
for c, k in zip(num_channels, kernel_sizes):
self.convs.append(nn.Conv1d(in_channels = 2*embed_size,
out_channels = c,
kernel_size = k))
def forward(self, inputs):
# 将两个形状是(批量大小, 词数, 词向量维度)的嵌入层的输出按词向量连结
embeddings = torch.cat((
self.embedding(inputs),
self.constant_embedding(inputs)), dim=2) # (batch, seq_len, 2*embed_size)
# 根据Conv1D要求的输入格式,将词向量维,即一维卷积层的通道维(即词向量那一维),变换到前一维
embeddings = embeddings.permute(0, 2, 1)
# 对于每个一维卷积层,在时序最大池化后会得到一个形状为(批量大小, 通道大小, 1)的
# Tensor。使用flatten函数去掉最后一维,然后在通道维上连结
encoding = torch.cat([self.pool(F.relu(conv(embeddings))).squeeze(-1) for conv in self.convs], dim=1)
# 应用丢弃法后使用全连接层得到输出
outputs = self.decoder(self.dropout(encoding))
return outputs
首先老套路,先embedding一下,再联结,得到[batch,max_len,2*embed_size],联结的两个embedding层一个是可以学习的,另一个是钉死的。然后变换维度为[batch,2*embed_size,max_len,],max_len就是步长,即num_steps。变换维度是一维卷积的规矩,一维卷积就是把[batch,in_channels,max_len]转化为[batch,out_channels,max_len-kernels_size+1]。变换维度之后就进行卷积。
下面要进行池化就相当于将每一个out_channal的全部数据进行GlobalMaxPool1d(),得到一个元素,相当于对上图输出通道为4的左图每一行求最大值。所以池化后得到[batch,out_channels,1],然后squeeze(-1),得到[batch,out_channels],然后有好多个卷积层,执行第二维度的联结操作得到[batch,out_channels之和],然后正好对应Linear(),输出[batch,2]完事。
简单来说,获取一个batch的词向量,然后通过卷积提取特征,池化把一个卷积核提取到的整个句子的特征转化成了一个值,也就是说池化之后的[batch,out_channels之和],每个元素就代表一个卷积核对整个句子提取的特征。然后提取特征阶段完毕送入全连接层进行分类。