文本分类(一) | (4) TextCNN

项目Github地址

本篇博客主要介绍基于TextCNN的文本分类算法的原理及实现细节。

目录

1. 分类原理

2. 实现细节


1. 分类原理

TextCNN可以从两个角度来解读,既可以把它看作但输入通道的2维卷积也可以把它看作多输入通道的1维卷积(其中词嵌入维度为通道维),二者其实是等价的。 

如果把它看作一个单输入通道的2维卷积的话,它的分类流程就如上图所示。

1)把输入文本中的词转换为其对应的词向量,那么每个输入文本就可以表示为一个n*d的矩阵(n是输入文本包含的词数,d为词向量的维数)。

2)对输入矩阵进行卷积操作。可以使用不同大小的卷积核,每种类型的卷积核可以有多个。假设卷积核的大小是(f,d),f可以是不同的取值(如f=2,3,4),而d是固定的,是词向量的维度,并且假设总共使用了k个卷积核,步长为1。经过卷积操作后我们会得到k个向量,每个向量的长度是n-f+1. 我们使用不同大小的卷积核,从输入文本中提取丰富的特征,这和n-gram特征有点相似(f=2,3,4分别对应于2-gram,3-gram-4-gram)。

3)对卷积操作的输出进行全局max-pooling操作。作用于k个长度为n-f+1的向量上,每个向量整体取最大值,得到k个标量。

4)把k个标量拼接起来,组成一个向量表示最后提取的特征。他的长度是固定的,取决于我们所使用的不同大小的卷积核的总数(k)。

5)最后在接一个全联接层作为输出层,如果是2分类的话使用sigmoid激活函数,多分类则使用softmax激活函数,得到模型的输出。

2. 实现细节

#自定义时序(全局)最大池化层
class GlobalMaxPool1d(nn.Module):
    def __init__(self):
        super(GlobalMaxPool1d, self).__init__()
    def forward(self, x):
         # x (batch_size, channel, seq_len)
        return F.max_pool1d(x, kernel_size=x.shape[2]) #  (batch_size, channel, 1)


# 多输入通道的一维卷积和单输入通道的2维卷积等价
# 这里按多输入通道的一维卷积来做 也可以用单输入通道的2维卷积来做
class TextCNN(BasicModule): #继承自BasicModule 其中封装了保存加载模型的接口,BasicModule继承自nn.Module

    def __init__(self, vocab_size, opt):#opt是config类的实例 里面包括所有模型超参数的配置

        super(TextCNN, self).__init__()


        # 嵌入层
        self.embedding = nn.Embedding(vocab_size,opt.embed_size)#词嵌入矩阵 每一行代表词典中一个词对应的词向量;
        # 词嵌入矩阵可以随机初始化连同分类任务一起训练,也可以用预训练词向量初始化(冻结或微调)

        # 创建多个一维卷积层
        self.convs = nn.ModuleList()
        for c, k in zip(opt.num_channels, opt.kernel_sizes): #num_channels定义了每种卷积核的个数 kernel_sizes定义了每种卷积核的大小
            self.convs.append(nn.Conv1d(in_channels=opt.embed_size, 
                                        out_channels=c,
                                        kernel_size=k))
        #定义dropout层
        self.dropout = nn.Dropout(opt.drop_prop)
        #定义输出层
        self.fc = nn.Linear(sum(opt.num_channels), opt.classes)
        # 时序最大池化层没有权重,所以可以共用一个实例
        self.pool = GlobalMaxPool1d()


    def forward(self, inputs):
        # inputs(batch_size,seq_len)
        embeddings = self.embedding(inputs) # (batch_size, seq_len, embed_size)

        # 根据conv1d的输入要求 把通道维提前(这里的通道维是词向量维度)
        # (batch_size,channel/embed_size,seq_len)
        embeddings = embeddings.permute(0, 2, 1)
        # 对于每个一维卷积层,会得到一个(batch_size,num_channel(卷积核的个数),seq_len-kernel_size+1)大小的tensor
        # 在时序最大池化后会得到一个形状为(batch_size, num_channel, 1)的 tensor
        # 使用squeeze去掉最后一维 并在通道维上连结 得到(batch_size,sum(num_channels))大小的tensor
        encoding = torch.cat([self.pool(F.relu(conv(embeddings))).squeeze(-1) for conv in self.convs], dim=1)

        # 应用丢弃法后使用全连接层得到输出 (batch_size,classes)
        outputs = self.fc(self.dropout(encoding))
        return outputs
发布了365 篇原创文章 · 获赞 714 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/sdu_hao/article/details/103596930
今日推荐