文档分类DPCNN简介
- DPCNN简介
- 模型结构
- 区域嵌入
- 等长卷积
- 1/2池化
- DPCNN模型代码实现
DPCNN简介
论文中提出了一种基于 word-level 级别的网络-DPCNN,由于 TextCNN 不能通过卷积获得文本的长距离依赖关系,而论文中 DPCNN 通过不断加深网络,可以抽取长距离的文本依赖关系。
实验证明在不增加太多计算成本的情况下,增加网络深度就可以获得最佳的准确率。
前面我们提到过TextRCNN就是将CNN中的池化加入到RNN中,来解决RNN
是一个有的偏倚,现在DPCNN通过不断加深网络,来弥补自身短缺的长距离依赖问题,可见每一种模型都不是十全十美的,只有不断探索,不断创新,相互借鉴,才能够使性能进一步提升。
模型结构
区域嵌入
这里是将TextCNN的包含多尺寸卷积滤波器的卷积层的卷积结果称之为区域嵌入,即对一个文本区域文本片段(比如3-gram)进行一组卷积操作后生成的embedding。这里不同于textCNN的二维卷积,DPCNN采用的是一维卷积。以3-gram为例子回顾textCNN,设置了一个大小为3xD的二维卷积核进行卷积操作(其中D是词嵌入的维度),其实这是一种保留词序的做法。那么对于DPCNN,采用的是不保留词序的做法,即:首先对3-grm中的3个词的词向量取均值得到一个大小为1xD的向量,然后设置一组大小为1*D的一维卷积核对该3-grm进行卷积操作。
等长卷积
经过区域嵌入后,是两层卷积层,这里采用的是等长卷积,以此来提高词位embedding的表示的丰富性。首先先介绍一下三种卷积的概念:
假设输入的序列长度为 n,卷积核大小为 m,步长(stride)为 s,输入序列两端各填补 p 个零(zero padding),那么该卷积层的输出序列为 (n-m+2p)/s+1。
- 窄卷积:步长 s=1,两端不补零,即 p=0,卷积后输出长度为 n-m+1。
- 宽卷积:步长 s=1,两端补零 p=m-1,卷积后输出长度 n+m-1。
- 等长卷积:步长 s=1,两端补零 p=(m-1)/2,卷积后输出长度为 n。
输入输出序列的位置数一样多,即为等长卷积,该卷积的意义是:输出的词是由该位置输入的词以及其左右词的上下文信息提取得到的,也就是说,这个词包含被上下文信息修饰过的更高级别的语义。
1/2池化
本文使用一个 size=3,stride=2(大小为3,步长为2)的池化层进行最大池化,在此称为1/2池化层。每经过一个1/2池化层,序列的长度就被压缩成了原来的一半。因此,经过1/2池化后,同样一个size为3的卷积核,其能够感知到的文本片段就比之前长了一倍。
在堆叠多层卷积池化层之后,就得到了加深的可以抽取长距离的文本依赖关系的网络。最后的池化层把每段文本聚合为一个向量。
主要区别在于输入层由无监督词嵌入层作为输入,把文档的每个词的词向量作出二维数组作为输入;卷积层有两个卷积层组成,卷积层输入通过跳跃连接,恒等映射和卷积层输出相加作为卷积层输出;采样层以尺度大小为2进行下采样,达到尺度缩放的目的。堆叠几层卷积层和采样层,形成尺度缩放金字塔,达到维度缩放的目的。最终将卷积层输出拼接成向量通过隐藏层和softmax层作为输出分类。
DPCNN模型代码实现
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class Config(object):
"""配置参数"""
def __init__(self):
self.dropout = 0.5 # 随机失活
self.require_improvement = 1000 # 若超过1000batch效果还没提升,则提前结束训练
self.num_classes =10 # 类别数
self.n_vocab = 10000 # 词表大小,在运行时赋值
self.num_epochs = 20 # epoch数
self.batch_size = 128 # mini-batch大小
self.pad_size = 32 # 每句话处理成的长度(短填长切)
self.learning_rate = 1e-3 # 学习率
self.embed = 300 # 字向量维度
self.num_filters = 250 # 卷积核数量(channels数)
'''Deep Pyramid Convolutional Neural Networks for Text Categorization'''
class Model(nn.Module):
def __init__(self, config):
super(Model, self).__init__()
self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)
self.conv_region = nn.Conv2d(1, config.num_filters, (3, config.embed), stride=1)
self.conv = nn.Conv2d(config.num_filters, config.num_filters, (3, 1), stride=1)
self.max_pool = nn.MaxPool2d(kernel_size=(3, 1), stride=2)
# (pad_left, pad_right, pad_top, pad_bottom)填充
self.padding1 = nn.ZeroPad2d((0, 0, 1, 1)) # top bottom
self.padding2 = nn.ZeroPad2d((0, 0, 0, 1)) # bottom
self.relu = nn.ReLU()
self.fc = nn.Linear(config.num_filters, config.num_classes)
def forward(self, x):
x = x[0] # torch.Size([128, 32])
x = self.embedding(x) # torch.Size([128, 32,300])
x = x.unsqueeze(1) # torch.Size([128, 1, 32, 300])
x = self.conv_region(x) # torch.Size([128, 250, 30, 300])
x = self.padding1(x) # [128, 250, 32, 1]
x = self.relu(x) # [128, 250, 32, 1]
x = self.conv(x) # [125, 250, 30, 1]
x = self.padding1(x) # [128, 250, 32, 1]
x = self.relu(x) # [128, 250, 32, 1]
x = self.conv(x) # [128, 250, 30, 1]
while x.size()[2] > 2:
x = self._block(x)
# print("x10", x)#torch.Size([128, 250, 1, 1])
x = x.squeeze() # [128, 250]
x = self.fc(x) # [128, 10]
return x
def _block(self, x):
x = self.padding2(x)
px = self.max_pool(x)
x = self.padding1(px)
x = F.relu(x)
x = self.conv(x)
x = self.padding1(x)
x = F.relu(x)
x = self.conv(x)
x = x + px
return x
config=Config()
model=Model(config)
print(model)
输出:
Model(
(embedding): Embedding(10000, 300, padding_idx=9999)
(conv_region): Conv2d(1, 250, kernel_size=(3, 300), stride=(1, 1))
(conv): Conv2d(250, 250, kernel_size=(3, 1), stride=(1, 1))
(max_pool): MaxPool2d(kernel_size=(3, 1), stride=2, padding=0, dilation=1, ceil_mode=False)
(padding1): ZeroPad2d((0, 0, 1, 1))
(padding2): ZeroPad2d((0, 0, 0, 1))
(relu): ReLU()
(fc): Linear(in_features=250, out_features=10, bias=True)
)
参考:
https://blog.csdn.net/sikh_0529/article/details/126912490
https://blog.csdn.net/qq_43592352/article/details/122764889