RCNN文本分类与tensorflow实现

1. 引言

    前面介绍了LSTM_CNN文本分类模型,虽然在CNN前加上一层LSTM层,可以提取文本中的序列信息,但是当文本的长度比较长时,LSTM提取信息的能力是有限的,并且,在LSTM中,每个词汇对应的隐藏状态都只包含了前面序列的信息,而没有考虑到后续序列的信息(BiLSTM除外),因此,本文将介绍一个新的模型结构,该结构采用一种递归CNN的形式,即每个词汇的向量表示既包括了其本身的词向量,也包括其前面序列和后面序列的上下文向量,从而使得每个词汇的向量信息更加丰富。

2. RCNN模型介绍

2.1 RCNN模型的结构

    首先,对于每一个文本D,用一串词汇序列w _ { 1 } , w _ { 2 } \ldots w _ { n }表示,并且记该文本属于某一类别k的概率为p ( k | D , \theta ),其中,\theta表示模型中的参数。RCNN模型主要包含三部分,分别是递归CNN层、max-pooling层、输出层。

    在递归CNN层,对于每个词汇,RCNN会递归地计算其左侧上下文向量和右侧上下文向量,然后将这两部分向量与当前词汇的词向量进行拼接作为该词汇的向量表示,如图1所示。记c _ { l } \left( w _ { i } \right)c _ { r } \left( w _ { i } \right)分别为词汇w _ { i }的左侧上下文向量和右侧上下文向量,它们都是长度为| c |的实数向量,计算公式分别如下:

                                                      \begin{array} { l } { c _ { l } \left( w _ { i } \right) = f \left( W ^ { ( l ) } c _ { l } \left( w _ { i - 1 } \right) + W ^ { ( s l ) } e \left( w _ { i - 1 } \right) \right) } \\\\ { c _ { r } \left( w _ { i } \right) = f \left( W ^ { ( r ) } c _ { r } \left( w _ { i + 1 } \right) + W ^ { ( s r ) } e \left( w _ { i + 1 } \right) \right) } \end{array}

其中,e \left( w _ { i - 1 } \right)e \left( w _ { i +1 } \right)分别表示前一个词汇w_{i-1}和后一个词汇w_{i+1}的词向量,其向量长度为| e |c _ { l } \left( w _ { i -1} \right)c _ { r } \left( w _ { i +1} \right)分别表示前一个词汇w_{i-1}的左侧上下文向量和后一个词汇的右侧上下文向量,对于每个文本的第一个词汇的左侧上下文向量和最后一个词汇的右侧上下文向量,分别采用共享的参数向量c _ { l } \left( w _ { 1} \right)c _ { r } \left( w _ { n} \right)表示,W ^ { ( l ) }W ^ { ( s l ) }W ^ { ( r ) }W ^ { ( r l ) }为权重矩阵,f为一个非线性激活函数。接着,将这三个向量拼接起来作为当前词汇的向量表示,这样一来,每个词汇的向量就囊括了左侧和右侧的语义信息,使得词汇的向量表示更具有区分性,其表示如下:

                                                      \boldsymbol { x } _ { i } = \left[ c _ { l } \left( w _ { i } \right) ; \boldsymbol { e } \left( w _ { i } \right) ; c _ { r } \left( w _ { i } \right) \right]

    当获取到每个词汇的向量表示x_i后,RCNN会将每个向量传入一个带有tanh激活函数的全连接层,其计算公式如下:

                                                      y _ { i } ^ { ( 2 ) } = \tanh \left( W ^ { ( 2 ) } x _ { i } + b ^ { ( 2 ) } \right)

其中,y _ { i } ^ { ( 2 ) }为潜在的语义向量。

    在max-pooling层,RCNN将每个潜在语义向量y _ { i } ^ { ( 2 ) }传入一个max-pooling层,获取所有向量中最重要的元素,其计算公式如下:

                                                      \boldsymbol { y } ^ { ( 3 ) } = \max _ { i = 1 } ^ { n } \boldsymbol { y } _ { i } ^ { ( 2 ) }

得到的向量\boldsymbol { y } ^ { ( 3 ) }即为整个文本的向量表示。

    在输出层,RCNN与其他传统的文本分类模型相似,将得到的文本向量传入一个带有softmax的全连接层,得到当前文本的在各个类别的概率分布,其计算公式如下:

                                                     y ^ { ( 4 ) } = W ^ { ( 4 ) } y ^ { ( 3 ) } + b ^ { ( 4 ) }

                                                    p _ { i } = \frac { \exp \left( y _ { i } ^ { ( 4 ) } \right) } { \sum _ { k = 1 } ^ { n } \exp \left( y _ { k } ^ { ( 4 ) } \right) }

图1 RCNN模型结构

3. RCNN模型的tensorflow实现

    本文利用tensorflow对RCNN进行了复现,并将其在之前的情感分析数据集上进行训练,有关数据集的介绍请参见之前的文章《FastText文本分类与tensorflow实现》,RCNN的模型代码如下:

import os
import numpy as np
import tensorflow as tf
from eval.evaluate import accuracy
from tensorflow.contrib import slim
from loss.loss import cross_entropy_loss


class RCNN(object):
    def __init__(self,
                 num_classes,
                 seq_length,
                 vocab_size,
                 embedding_dim,
                 learning_rate,
                 learning_decay_rate,
                 learning_decay_steps,
                 epoch,
                 dropout_keep_prob,
                 context_dim,
                 hidden_dim):
        self.num_classes = num_classes
        self.seq_length = seq_length
        self.vocab_size = vocab_size
        self.embedding_dim = embedding_dim
        self.learning_rate = learning_rate
        self.learning_decay_rate = learning_decay_rate
        self.learning_decay_steps = learning_decay_steps
        self.epoch = epoch
        self.dropout_keep_prob = dropout_keep_prob
        self.context_dim = context_dim
        self.hidden_dim = hidden_dim
        self.input_x = tf.placeholder(tf.int32, [None, self.seq_length], name='input_x')
        self.input_y = tf.placeholder(tf.float32, [None, self.num_classes], name='input_y')
        self.model()

    def model(self):
        # 词向量映射
        with tf.name_scope("embedding"):
            embedding = tf.get_variable('embedding', [self.vocab_size, self.embedding_dim])
            embedding_inputs = tf.nn.embedding_lookup(embedding, self.input_x)

        # Recurrent Structure(CNN)
        with tf.name_scope("bi_rnn"):
            fw_cell = tf.nn.rnn_cell.BasicLSTMCell(self.context_dim)
            fw_cell = tf.nn.rnn_cell.DropoutWrapper(fw_cell, output_keep_prob=self.dropout_keep_prob)
            bw_cell = tf.nn.rnn_cell.BasicLSTMCell(self.context_dim)
            bw_cell = tf.nn.rnn_cell.DropoutWrapper(bw_cell, output_keep_prob=self.dropout_keep_prob)
            (output_fw, output_bw), states = tf.nn.bidirectional_dynamic_rnn(cell_fw=fw_cell,
                                                                             cell_bw=bw_cell,
                                                                             inputs=embedding_inputs,
                                                                             dtype=tf.float32)

        with tf.name_scope("context"):
            shape = [tf.shape(output_fw)[0], 1, tf.shape(output_fw)[2]]
            c_left = tf.concat([tf.zeros(shape), output_fw[:, :-1]], axis=1, name="context_left")
            c_right = tf.concat([output_bw[:, 1:], tf.zeros(shape)], axis=1, name="context_right")

        with tf.name_scope("word_representation"):
            y2 = tf.concat([c_left, embedding_inputs, c_right], axis=2, name="word_representation")
            embedding_size = 2 * self.context_dim + self.embedding_dim

        # max_pooling层
        with tf.name_scope("max_pooling"):
            fc = tf.layers.dense(y2, self.hidden_dim, activation=tf.nn.relu, name='fc1')
            fc_pool = tf.reduce_max(fc, axis=1)

        # output层
        with tf.name_scope("output"):
            self.logits = tf.layers.dense(fc_pool, self.num_classes, name='fc2')
            self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits), 1, name="pred")

        # 损失函数
        self.loss = cross_entropy_loss(logits=self.logits, labels=self.input_y)

        # 优化函数
        self.global_step = tf.train.get_or_create_global_step()
        learning_rate = tf.train.exponential_decay(self.learning_rate, self.global_step,
                                                   self.learning_decay_steps, self.learning_decay_rate,
                                                   staircase=True)

        optimizer = tf.train.AdamOptimizer(learning_rate)
        update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
        self.optim = slim.learning.create_train_op(total_loss=self.loss, optimizer=optimizer, update_ops=update_ops)

        # 准确率
        self.acc = accuracy(logits=self.logits, labels=self.input_y)

    def fit(self, train_x, train_y, val_x, val_y, batch_size):
        # 创建模型保存路径
        if not os.path.exists('./saves/rcnn'): os.makedirs('./saves/rcnn')
        if not os.path.exists('./train_logs/rcnn'): os.makedirs('./train_logs/rcnn')

        # 开始训练
        train_steps = 0
        best_val_acc = 0
        # summary
        tf.summary.scalar('val_loss', self.loss)
        tf.summary.scalar('val_acc', self.acc)
        merged = tf.summary.merge_all()

        # 初始化变量
        sess = tf.Session()
        writer = tf.summary.FileWriter('./train_logs/rcnn', sess.graph)
        saver = tf.train.Saver(max_to_keep=10)
        sess.run(tf.global_variables_initializer())

        for i in range(self.epoch):
            batch_train = self.batch_iter(train_x, train_y, batch_size)
            for batch_x, batch_y in batch_train:
                train_steps += 1
                feed_dict = {self.input_x: batch_x, self.input_y: batch_y}
                _, train_loss, train_acc = sess.run([self.optim, self.loss, self.acc], feed_dict=feed_dict)

                if train_steps % 1000 == 0:
                    feed_dict = {self.input_x: val_x, self.input_y: val_y}
                    val_loss, val_acc = sess.run([self.loss, self.acc], feed_dict=feed_dict)

                    summary = sess.run(merged, feed_dict=feed_dict)
                    writer.add_summary(summary, global_step=train_steps)

                    if val_acc >= best_val_acc:
                        best_val_acc = val_acc
                        saver.save(sess, "./saves/rcnn/", global_step=train_steps)

                    msg = 'epoch:%d/%d,train_steps:%d,train_loss:%.4f,train_acc:%.4f,val_loss:%.4f,val_acc:%.4f'
                    print(msg % (i, self.epoch, train_steps, train_loss, train_acc, val_loss, val_acc))

        sess.close()

    def batch_iter(self, x, y, batch_size=32, shuffle=True):
        """
        生成batch数据
        :param x: 训练集特征变量
        :param y: 训练集标签
        :param batch_size: 每个batch的大小
        :param shuffle: 是否在每个epoch时打乱数据
        :return:
        """
        data_len = len(x)
        num_batch = int((data_len - 1) / batch_size) + 1

        if shuffle:
            shuffle_indices = np.random.permutation(np.arange(data_len))
            x_shuffle = x[shuffle_indices]
            y_shuffle = y[shuffle_indices]
        else:
            x_shuffle = x
            y_shuffle = y
        for i in range(num_batch):
            start_index = i * batch_size
            end_index = min((i + 1) * batch_size, data_len)
            yield (x_shuffle[start_index:end_index], y_shuffle[start_index:end_index])

    def predict(self, x):
        sess = tf.Session()
        sess.run(tf.global_variables_initializer())
        saver = tf.train.Saver(tf.global_variables())
        ckpt = tf.train.get_checkpoint_state('./saves/rcnn/')
        saver.restore(sess, ckpt.model_checkpoint_path)

        feed_dict = {self.input_x: x}
        logits = sess.run(self.logits, feed_dict=feed_dict)
        y_pred = np.argmax(logits, 1)
        return y_pred

      在训练时,RCNN模型的LSTM隐藏层维度设置为200,前后文向量的维度也是设置为200,中间全连接层的隐藏层维度也是设置为200,其他的参数与之前FastText模型的一致,最终的模型在验证集上的效果如图2所示,其中,在经过762000次迭代后,模型在验证集上的准确率达到最高,为95.1%,在3000个测试集上的准确率是97.87%,比之前的LSTM_CNN大概高0.1%,可以发现RCNN在文本分类任务上的效果还是比较不错的。

图2 RCNN模型训练效果

 

4. 总结

    最终,对RCNN模型做个总结吧:

  • RCNN对词汇的向量表示进行拓展,加入了前、后上下文向量,可以捕捉更大范围的上下文信息。
  • 训练速度上还是没有FastText、TextCNN快
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
# 工程内容 这个程序是基于tensorflow的tflearn库实现部分RCNN功能。 # 开发环境 windows10 + python3.5 + tensorflow1.2 + tflearn + cv2 + scikit-learn # 数据集 采用17flowers据集, 官网下载:http://www.robots.ox.ac.uk/~vgg/data/flowers/17/ # 程序说明 1、setup.py---初始化路径 2、config.py---配置 3、tools.py---进度条和显示带框图像工具 4、train_alexnet.py---大数据集预训练Alexnet网络,140个epoch左右,bitch_size为64 5、preprocessing_RCNN.py---图像的处理(选择性搜索、数据存取等) 6、selectivesearch.py---选择性搜索源码 7、fine_tune_RCNN.py---小数据集微调Alexnet 8、RCNN_output.py---训练SVM并测试RCNN(测试的时候测试图片选择第7、16类中没有参与训练的,单朵的花效果好,因为训练用的都是单朵的) # 文件说明 1、train_list.txt---预训练数据,数据在17flowers文件夹中 2、fine_tune_list.txt---微调数据2flowers文件夹中 3、1.png---直接用选择性搜索的区域划分 4、2.png---通过RCNN后的区域划分 # 程序问题 1、由于数据集小的原因,在微调时候并没有像论文一样按一个bitch32个正样本,128个负样本输入,感觉正样本过少; 2、还没有懂最后是怎么给区域打分的,所有非极大值抑制集合canny算子没有进行,待续; 3、对选择的区域是直接进行缩放的; 4、由于数据集合论文采用不一样,但是微调和训练SVM时采用的IOU阈值一样,有待调参。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值