使用CNN进行网站文本分类

1,356 阅读6分钟
原文链接: zoeshaw101.github.io

本文主要讲述一些自然语言处理的基础知识,以及使用如何CNN模型对从网上爬取下来的网站网页文本进行分类。

Wod2Vec

词向量技术就是将词转化成向量形式,因为我们的分类器最后处理的都是向量,所以进行自然语言的预处理往往就是需要进行向量化。在sklearn库中使用svm对文本分类之前可以通过使用 CountVectorizer()TfidfTransformer() 来向量化。这两个函数所进行的操作就是最基本的利用词频和反文档词频特征进行向量化,但当我们的词量很大的时候,这样构造出来的矩阵就会非常大(也许它内部进行了矩阵压缩等优化),并且,这样最基本的词语特征并不能反映更高级的语义特征,所以,我们需要寻求更有效的向量方法。

谷歌推出的Word2Vec技术,就是很流行的方法。在给定一个中心词的情况下,Word2Vec 通过最大化上下文单词的对数概率并通过随机梯度下降(SGD)来修改向量,试图找到不同词的向量表征。因此它可以展现出不同词向量之间的线性关系,也就是通过神经网络的映射,可以扑捉到一些不同的语法和语义概念。

CNN文本分类

使用CNN进行文本分类,是因为CNN的卷积操作能够提取一些重要的文本特征。实现主要参考了 Convolutional Neural Networks for Sentence Classification.这篇文章。

数据预处理

对于从网页上爬下来的文本首相要进行正文提取、去除特殊字符、分词、词语连接等操作。然后要进行文本的padding,也就是对齐,将向量统一维度。再放入Word2Vec模型里进行训练,然后得到词典,也就得到了每个词语的向量映射。
函数data_helper()就是帮助前期处理数据的。包括数据的读入,我们需要分的类别是12类,所以将label表示为一个12维的向量,对应的data是哪个类就将这个类置1,其余为0。

CNN模型

这个模型只包含了一层卷积层和一个池化层。卷积有三种长度分别为[3, 4, 5]。
所以卷积核的大小应该是一个三维的向量:[卷积核大小, 嵌入的维度大小,卷积核的数量],但是tensorflow里面规定的特卷积核大小还有个通道数,由于这里不是图像而是文本,就将通道数置为1。
然后再通过一个非线性函数、池化操作得到输出。最后会有一个reshape来将所有的特征连接在一起并延展成一维的向量,用于之后的softmax计算概率。

模型核心的代码入如下:

ooled_outputs = []
for i, filter_size in enumerate(filter_sizes):
    with tf.name_scope("conv-maxpool-%s" % filter_size):
        # 卷积层
        filter_shape = [filter_size, embedding_size, 1, num_filters]
        W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")
        b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name="b")
        conv = tf.nn.conv2d(
            self.embedded_chars_expanded,
            W,
            strides=[1, 1, 1, 1],
            padding="VALID",
            name="conv")
        # 通过非线性函数
        h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
        # 池化操作
        pooled = tf.nn.max_pool(
            h,
            ksize=[1, sequence_length - filter_size + 1, 1, 1],
            strides=[1, 1, 1, 1],
            padding='VALID',
            name="pool")
        pooled_outputs.append(pooled)
# 连接所有pooled features
num_filters_total = num_filters * len(filter_sizes)
self.h_pool = tf.concat(3, pooled_outputs)
self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])

防止过拟合

为防止过拟合,可以加入正则化,和dropout等措施。dropout的概率默认值设为0.5,实际需要看训练过程的bias and variance trade-off来进行调整。正则化选择L2正则,因为模型本来并不太复杂,默认值为0,看实际情况进行调整。

实际训练中遇到的问题

Memory Error

开始时我将一共十多万条文本数据全部进行向量化后的向量转化成numpy形式的数组,执行:

x_test = np.array(all_vectors)

这一步会出现内存错误,st上说是因为 np.rarray 方法将数组整个一次性复制到内存,可能因为内存不够,但是我是放在服务器上跑的,有几十G的内存,x_test的大小应该是 文本条数 x 最大文本长度 x embedding_size 。检查之前的数据处理步骤,发现分词是直接单个字进行分词的,最大文本长度就会非常大,所以最后x_test向量会很大。之后我改成了基于词语的分词,然后还是发现内存错误。
st上说使用 np.asarray 方法,不会将数据一次性复制进内存。试了下就没有出现内存错误了。所以最后原因应该是因为文本长度太大了,embedding_size 这个值设置的是128,最后三个向量相乘的得到的矩阵维度会很大。
另外一个解决办法是在文本padding过程中需要将长度不够最大长度的文本补齐,所以万一短文本很多,最后的矩阵其实会有很多0值,是个很稀疏的矩阵,所以可以考虑使用稀疏矩阵存储的方式。

过拟合严重

训练时将训练数据进行1:0.8的划分,一部分训练,一部分验证。训练集模型正确率可以达到90%多,并且在验证集上表现和训练集曲线接近,相差不大。但是放到真实的测试数据(另一批没有标签的数据)上表现不是很好。最后原因可能是因为模型过拟合严重,因为训练数据是使用关键词匹配出来的(网页的类别和网页内容中的关键词),数据质量非常好。但是测试时拿的数据是另一批从网上爬去下来的数据,良莠不齐,模型表现并不理想。
尝试过提高dropout的概率,加入正则化等措施,效果不明显。应该是数据集的影响比较大。

在不同类别上表现差异较大

最后训练得到的模型在另一批数据上进行测试的时候发现12种类别,在不同类别上表现差异较大,并且并不是所有类别都能分出来。
最后发现原因是训练集里没有确保样本均衡,不同类别样本数相差较大。改进后在测试数据集上的不同类别上的变现更好。并且十二类都完全分的出来。

总结

本文记录了在实现CNN文本分类中遇到的一些问题,及解决方法。改进之处是还可以改进模型结构,提高泛化能力,比如可以进行数据增强(data augmentation)等措施,但是在自然语言领域data augmentation的方法比较困难,之后可以尝试一下。

相关代码已经方法github上,相比源来的代码改写了data_helper.py,增加了eval_helper.py,用于check 真实的测试数据的维度是否一致等。并且用于提取CNN中提取到的句子的特征,是一个 embedding_size x filer_num 的向量,将这个特征拿出来可以用于后续的任务。 代码戳这里 求star啊~

参考资料