赞
踩
TextCNN实现文本分类
文本分类系列将会有几篇,所有的代码将都会放在我的github里。
我的github:https://github.com/422xiaomage/Textcnn_tensorflow
1、TextCNN原理
在这里我们首先先祭出下面这张原理图,然后在细细说出textcnn内部是如何进行计算的。
如果学过cnn的,看这个textcnn一定是小意思,由于对图像不太了解,所以只知道这里存在的两个区别,一是图像是三通道,而文本只是一通道的,所以肯定比计算图像的卷积的要简单啊。其次这里在做卷积计算时,由于这个向量通常是词向量,所以很明显,这里肯定不能左右n-gram啊。这样说可能还不太明白。下面将基于上面这幅原理图,细细的解释每一步的计算。
其实很简单,看图,这里我们将图中的英文句子换成中文句子(因为小编喜欢中文啦),假设我们向网络里面输入一个文本“小白的一篇深度学习博客,有问题还望大家指出。”,然后呢,我们可以分词,分词结果为“小白/ 的/ 一篇/ 深度学习/ 博客,/ 有问题/ 还/ 望/ 大家/ 指出”,再通过word2vec训练出的词向量来表示每一个词,假设是300维的吧,那么输入便是一个[11,300]的矩阵。输入层是一个[11,300]的矩阵,然后下一层就是卷积层,这里其实也很简单,卷积层一般是取[1,2,3,4,5]这几个尺寸的卷积核,当然你可以只用一个尺寸的,也可以用几个尺寸的然后最后将结果拼接,下面将详细解释这一层是怎么计算的。假设我们取了3、4两个大小的卷积核,这个3、4值得是上下滑动的窗口大小,左右窗口我们上面说过,一般左右不滑动,因为一行就是一个词向量,个人认为切断会破坏语义,所以对于我们取了3、4两个大小的卷积核来说,每一个卷积核实际上就是[3,词向量长度]的矩阵和[4,词向量长度]的矩阵,然后我们对输入进行依次取[3*词向量长度]大小的子矩阵,与卷积核进行计算,对应位置相乘并求和,得到的是一个数,然后对输入向下滑动一行,得到一个新的子矩阵,在进行卷积计算,然后依次滑动到结束,会得到一个[(7-3+1),1]的向量,那么如果这样的卷积核有128个,那么得到的就是一个[(7-3+1),128]的矩阵,然后到池化层,这里我们选择每一列最大的数作为特征,然后将这128列的所有最大的数据拼成一个128维的向量,当然这里池化层也可以选则最大的两位数,那相应的就是256维向量了,同理对[4,词向量长度]的卷积核也会得到一个128维的向量,最后将这两个128维的向量拼接成一个256维的向量,然后经过全连接层,便完成了这个模型的构建了啊。
小编上面虽然用了大量的文字,但其实原理很简单,只要看看就明白了,也不需要任何学习卷积神经网络的基础。到这里我们便可以根据这个思路利用tensorflow大模型了。当然如果你想要用keras搭,会更加容易。
2、Textcnn模型代码部分
如果想使用keras搭的话,github搜keras,看到那个星最多的项目,进去有一个 examples文件夹,里面有官方的源码。
当然小编这里是学习使用tensorflow搭textcnn,使用的是搜狐2019校园算法大赛的数据。下面闲扯一小段,由于这个数据属于工业数据,所以效果肯定很粗糙啊,本人当时比赛的时候,做了一些小处理,只用了这单一模型,跑的效果是0.6,用bert跑的效果比这个模型稍微好了那么一点点,但是用bert几乎没怎么对数据进行处理。这次也是几乎没对数据进行处理用textcnn跑了一下下,只有0.45左右的效果吧,确实很差。
可能模型上还有可以改进的地方,本人毕竟tensorflow小白一枚,这次也是打算通过这些一步步学习tensorflow。
不得不说中间遇到的一个问题,作为一个小白,必须记录一下,也希望能在大家也遇到这个问题的时候给大家提供一波帮助。训练的时候loss一直飙升,从4到了300000,这个loss我是真的服气了,查了很久,终于找到了一个问题,小白将三个类别one_hot时,将标签0编码one_hot成了[0,0,0],这个问题让我查了很多资料,都没找到,只能通过反复的看代码,才发现。果然还是要多看多写代码啊。由于今天时间不够,明天将会将完善后的代码上传至我的github里,力争做到能让大家开箱就用,下载下来就能跑自己的数据。
下面放出模型部分代码:
class TextCNN(object): def __init__(self, config, wordEmbedding): self.inputX = tf.placeholder(tf.int32, [None, config.sequenceLength], name="inputX") self.inputY = tf.placeholder(tf.float32, [None, 3], name="inputY") self.inputY_1 = tf.placeholder(tf.float32, [None, 1], name="inputY") self.dropoutKeepProb = tf.placeholder(tf.float32, name="dropoutKeepProb") self.T = [] # 定义l2损失 l2Loss = tf.constant(0.0) # 词嵌入层 with tf.name_scope("embedding"): # 利用预训练的词向量初始化词嵌入矩阵 self.W = tf.Variable(tf.cast(wordEmbedding, dtype=tf.float32, name="word2vec") ,name="W") # 利用词嵌入矩阵将输入的数据中的词转换成词向量,维度[batch_size, sequence_length, embedding_size] self.embeddedWords = tf.nn.embedding_lookup(self.W, self.inputX) # 卷积的输入是四维[batch_size, width, height, channel],因此需要增加维度,用tf.expand_dims来增大维度 self.embeddedWordsExpanded = tf.expand_dims(self.embeddedWords, -1) # 创建卷积和池化层 pooledOutputs = [] # 有三种size的filter,3, 4, 5,textCNN是个多通道单层卷积的模型,可以看作三个单层的卷积模型的融合 for i, filterSize in enumerate(config.model.filterSizes): with tf.name_scope("conv-maxpool-%s" % filterSize): # 卷积层,卷积核尺寸为filterSize * embeddingSize,卷积核的个数为numFilters # 初始化权重矩阵和偏置 #第三维1是通道数量,对于文本来说,通道数量固定是1 filterShape = [filterSize, config.model.embeddingSize, 1, config.model.numFilters] W = tf.Variable(tf.truncated_normal(filterShape, stddev=0.1), name="W") b = tf.Variable(tf.constant(0.1, shape=[config.model.numFilters]), name="b") conv = tf.nn.conv2d( self.embeddedWordsExpanded, W, strides=[1, 1, 1, 1], padding="VALID", name="conv") #tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None) #input见上面,是一个四维向量;filter见上面计算,也是一个四维计算;stride步长,分别表示 在batch_size上的步长,高度的步长, # 宽度的步长以及深度的步长,对应的是input的四个维度,对于文本来说可以改变的只有宽度的步长;padding 是填充,这里只有两个值 SAME 和 VALID, #SAME表示需要在句子前面进行填充,卷积后的尺寸与句子长度一致,VALID,卷积后尺寸长度是句子长度-filter尺寸+1 # relu函数的非线性映射 h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu") #tf.nn.bias_add(conv, b)一个叫bias的向量加到一个叫value的矩阵上,是向量与矩阵的每一行进行相加,得到的结果和value矩阵大小相同。 # 池化层,最大池化,池化是对卷积后的序列取一个最大值 pooled = tf.nn.max_pool( h, ksize=[1, config.sequenceLength - filterSize + 1, 1, 1], # ksize shape: [batch, height, width, channels] strides=[1, 1, 1, 1], padding='VALID', name="pool") pooledOutputs.append(pooled) # 将三种size的filter的输出一起加入到列表中 # 得到CNN网络的输出长度 numFiltersTotal = config.model.numFilters * len(config.model.filterSizes) # 池化后的维度不变,按照最后的维度channel来concat self.hPool = tf.concat(pooledOutputs, 3) # 摊平成二维的数据输入到全连接层 self.hPoolFlat = tf.reshape(self.hPool, [-1, numFiltersTotal]) # dropout with tf.name_scope("dropout"): self.hDrop = tf.nn.dropout(self.hPoolFlat, self.dropoutKeepProb) # 全连接层的输出 with tf.name_scope("output"): outputW = tf.get_variable( "outputW", shape=[numFiltersTotal, 3], initializer=tf.contrib.layers.xavier_initializer()) outputB= tf.Variable(tf.constant(0.1, shape=[3]), name="outputB") l2Loss += tf.nn.l2_loss(outputW) l2Loss += tf.nn.l2_loss(outputB) self.predictions = tf.nn.xw_plus_b(self.hDrop, outputW, outputB, name="predictions") #self.predictions = tf.nn.softmax(self.prediction, name="softmax") #少了一个softmax层 self.binaryPreds = tf.cast(tf.arg_max(self.predictions, 1), tf.float32, name="binaryPreds") # 计算三元元交叉熵损失 with tf.name_scope("loss"): losses = tf.nn.softmax_cross_entropy_with_logits(logits=self.predictions, labels=self.inputY) self.loss = tf.reduce_mean(losses) + config.model.l2RegLambda * l2Loss
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。