赞
踩
DPCNN(Deep Pyramid Convolutional Neural Networksfor Text Categorization),是RieJohnson和腾讯AI-Lab等提出的一种深度卷积神经网络,可以称之为"深度金字塔卷积神经网络"。相关论文链接Deep Pyramid Convolutional Neural Networks for Text Categorization。
文献中提出了一种基于word-level级别的网络——DPCNN。由于TextCNN不能通过卷积获得文本的长距离依赖关系,而论文中DPCNN通过不断加深网络,可以抽取长距离的文本依赖关系。实验证明在不增加太多计算成本的情况下,增加网络深度就可以获得最佳的准确率。
DPCNN的深度与padding后的最大句子长度有关系,例如 m a x _ l e n = 64 = 2 5 max\_len=64=2^5 max_len=64=25,即长度为64的句子只够5次pooling/2,因此DPCNN更加适合长文本。
Deep Pyramid Convolutional设计的目的,一是提高准确率;另一个则是在提高准确率的前提下,尽量的提升计算效率。
文本分类是一个十分重要的任务,其应用场景包括垃圾邮件检测,情感和主题分类等。研究显示:深度为32层字符级CNN显示出优于深度为9层字符级CNN;非常浅的1层单词级CNN被证明比非常深的字符级CNN更加准确且快得多。结合这两点,对深度的单词级CNN进行探究,文献中提出了一种深度但低复杂度的网络架构,名为DPCNN(深度金字塔卷积神经网络)。
DPCNN主要的特色是引入了残差结构,增加了多尺度信息,并且增加了用于文本分类CNN的网络深度,以提取文本中远程关系特征,并且并没有带来较高的复杂度。
DPCNN主要的组成结构包括:
1.模型输入:输入词维度为[batch_size, seq_len]。
2.Region Embeeding层
将TextCNN的包含多尺寸卷积滤波器的卷积层的卷积结果称之为Region embedding,意思就是对一个文本区域/片段(比如3-gram)进行一组卷积操作后生成的embedding。
此外为了进一步提高性能,还使用了tv-embedding (two-views embedding)进一步提高DPCNN的accuracy,也就是引入预训练的词向量.
经过embedding层,词向量维度为:[batch_size, seq_len, embedded_size]
3.等长卷积层
等长卷积(equal-width convolution):步长
s
=
1
s=1
s=1,两端补零
p
=
(
m
−
1
)
/
2
p=(m-1)/2
p=(m−1)/2,卷积后输出长度为
n
n
n。DPCNN模型中采用两层(+relu)每层均为250个尺寸为3的卷积核。
该层采用了pre-activation的做法,卷积运算是
W
σ
(
x
)
+
b
W\sigma(x)+b
Wσ(x)+b,而不是通常用的
σ
(
W
x
+
b
)
\sigma(Wx+b)
σ(Wx+b),这种“线性”简化了深度网络的训练。
经过两层的卷积之后,输出序列维度为:[batch_size, 250, seq_len - 3 + 1]
4.Repeat结构
等长卷积后,固定feature maps的数量(减少计算量等),再进行池化。模型中的池化是在每个卷积块后结束之后,对特征合集做一个池化,池化用的是最大池化(
p
o
o
l
_
s
i
z
e
=
3
,
s
t
r
i
d
e
=
2
pool\_size=3, stride=2
pool_size=3,stride=2),使每个卷积核的维度减半,形成一个金字塔结构。这种压缩式的Downsampling,可以拼接等实现文本远距离信息匹配于对应。
残差连接:为了使深度网络的训练成为可能,使用加法进行shortcut connections,接近等值连接,不需要做任何的维度匹配工作。 z + f ( z ) z+f(z) z+f(z),其中 f f f用的是两层的等长卷积,缓解了梯度消失问题。
该层的具体操作如下:
最终该结构的输出维度为:[batch_size, 250, 1]
5.全连接+softmax归一化:
将张量的维度从[batch_size, num_block]转化到[batch_size, 1]。
首先从文件中读取相应训练集和测试集的语料,利用jieba对文本进行分词,然后利用nltk去停用词。
def tokenizer(text):
sentence = jieba.lcut(text, cut_all=False)
stopwords = stopwords.words('chinese')
sentence = [_ for _ in sentence if _ not in stopwords]
return sentence
利用torchtext包处理预处理好的语料,将所提供的语料集转化为相应的词向量模型。由于每一个词均为一个向量,作为模型的输入。
train_set, validation_set = data.TabularDataset.splits(
path='corpus_data/',
skip_header=True,
train='corpus_train.csv',
validation='corpus_validation.csv',
format='csv',
fields=[('label', label), ('text', text)],
)
text.build_vocab(train_set, validation_set)
将处理好的词向量输入到DPCNN模型中进行处理。
self.conv_region = nn.Conv2d(1, args.filter_num, (3, embedding_dim), stride=1) self.conv = nn.Conv2d(args.filter_num, args.filter_num, (3, 1), stride=1) self.max_pool = nn.MaxPool2d(kernel_size=(3, 1), stride=2) 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(args.filter_num, label_num) def forward(self, x): # region embedding 层 # 输入x的维度为(batch_size, max_len) x = self.embedding(x) # [batch_size, seq_length, embedding_dim] x = x.view(x.size(0), 1, x.size(1), self.args.embedding_dim) # [(]batch_size, 1, seq_length, embedding_dim] x = self.conv_region(x) # x = [batch_size, num_filters, seq_len-3+1, 1] # 等长卷积层 x = self.padding1(x) # [batch_size, num_filters, seq_len, 1] x = self.relu(x) x = self.conv(x) # [batch_size, num_filters, seq_len-3+1, 1] x = self.padding1(x) # [batch_size, num_filters, seq_len, 1] x = self.relu(x) x = self.conv(x) # [batch_size, num_filters, seq_len-3+1, 1] # block结构 while x.size()[2] >= 2: x = self._block(x) # [batch_size, num_filters,1,1] x = x.squeeze() # [batch_size, num_filters] # 全连接+输出 loggits = self.fc(x) # [batch_size, 1] return loggits 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 # Short Cut return x
利用训练集对模型进行训练,同时评估训练效果,并利用测试集对模型的准确性进行评估。为了防止偶然性产生的不确定,每一轮迭代会产生100个模型,分别评估其效率,进行调优后再用测试集测试其效率。
在训练开始时,测试集的准确率在75%左右,然后逐步提高到90%。
多轮迭代之后,模型的准确率大概稳定在91%~92%之间。
从.csv文件中导入相关的训练集、评估集和测试集数据,并进行一定的处理,转化为DataSet。词向量采用globe.6B dim=300的已训练好的词向量。
ID = Field(sequential=False, use_vocab=False) LABEL = LabelField(sequential=False, use_vocab=True, is_target=True) INFO = Field(sequential=True, tokenize=jieba.lcut, include_lengths=True) fields = [ ('id', ID), (None, None), ('label', LABEL), ('info', INFO), ] # 加载数据 train_data = TabularDataset( os.path.join('data', 'train.csv'), format='csv', fields=fields, csv_reader_params={'delimiter': '\t'} ) ··· # 评估集和测试集数据加载省略 # 创建字典 INFO.build_vocab(train_data, vectors='glove.6B.300d') LABEL.build_vocab(train_data) return LABEL, INFO, train_data, valid_data, test_data
构建模型:通过nn所提供的函数,构造DPCNN的模型,以及其中的block结构。
class DPCNN(nn.Module): def __init__(self, vocab_size, label_size, embedding_dim, hidden_dim, layer_num, filter_num, dropout=float(0.1)): super().__init__() # embedding层 self.embedding = nn.Embedding(vocab_size, embedding_dim) self.conv_region = nn.Conv2d(1, filter_num, (3, embedding_dim), stride=1) self.conv = nn.Conv2d(filter_num, filter_num, (3, 1), stride=1) self.max_pool = nn.MaxPool2d(kernel_size=(3, 1), stride=2) 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(filter_num, label_size) # 丢弃概率 self.dropout = nn.Dropout(p=dropout) def forward(self, text): embedded = self.embedding(text) embedded = embedded.unsqueeze(1) # [batch_size, 1, seq_len, embedding_dim] conv_r = self.conv_region(embedded) # [batch_size, num_filters, seq_len-3+1, 1] conv_r_padding = self.padding1(conv_r) # [batch_size, num_filters, seq_len, 1] conv_r_padding = self.relu(conv_r_padding) conv = self.conv(conv_r_padding) # [batch_size, num_filters, seq_len-3+1, 1] conv_padding = self.padding1(conv) # [batch_size, num_filters, seq_len, 1] conv_padding = self.relu(conv_padding) conv_padding = self.conv(conv_padding) # [batch_size, num_filters, seq_len-3+1, 1] while conv_padding.size()[2] >= 2: conv_padding = self._block(conv_padding) # [batch_size, num_filters,1,1] conv_padding = conv_padding.squeeze() # [batch_size, num_filters] loggits = self.fc(conv_padding) # [batch_size, 1] return loggits 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) # Short Cut x = x + px return x
数据分批:将dataset中的数据进行分批,依次传入模型中运行。
train_iterator, valid_iterator, test_iterator = BucketIterator.splits(
(train_data, valid_data, test_data),
batch_size=args.batch_size,
sort_within_batch=True,
sort_key = lambda x: len(x.info),
device=torch.device('cpu')
)
使用TensorBoard可视化:
writer = SummaryWriter()
writer.add_scalar('训练集/损失率', loss.item(), step)
writer.add_scalar('训练集/学习率', scheduler.get_last_lr()[0], step)
···
writer.close()
tensorboard --logdir ./runs
TensorFlow installation not found - running with reduced feature set.
Serving TensorBoard on localhost; to expose to the network, use a proxy or pass --bind_all
TensorBoard 2.3.0 at http://localhost:6006/ (Press CTRL+C to quit)
多轮迭代之后,模型的准确率在82%左右。
查看模型在训练过程中的参数曲线,相关参数如下图所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。