[译] RNN 循环神经网络系列 2:文本分类

本文讲的是[译] RNN 循环神经网络系列 2:文本分类,


本系列文章汇总

  1. RNN 循环神经网络系列 1:基本 RNN 与 CHAR-RNN
  2. RNN 循环神经网络系列 2:文本分类
  3. RNN 循环神经网络系列 3:编码、解码器
  4. RNN 循环神经网络系列 4:注意力机制
  5. RNN 循环神经网络系列 5:自定义单元

RNN 循环神经网络系列 2:文本分类

在第一篇文章中,我们看到了如何使用 TensorFlow 实现一个简单的 RNN 架构。现在我们将使用这些组件并将其应用到文本分类中去。主要的区别在于,我们不会像 CHAR-RNN 模型那样输入固定长度的序列,而是使用长度不同的序列。

文本分类

这个任务的数据集选用了来自 Cornell 大学的语句情绪极性数据集 v1.0,它包含了 5331 个正面和负面情绪的句子。这是一个非常小的数据集,但足够用来演示如何使用循环神经网络进行文本分类了。

我们需要进行一些预处理,主要包括标注输入、附加标记(填充等)。请参考完整代码了解更多。

预处理步骤

  1. 清洗句子并切分成一个个 token;
  2. 将句子转换为数值 token;
  3. 保存每个句子的序列长。

Screen Shot 2016-10-05 at 7.32.36 PM.png

如上图所示,我们希望在计算完成时立即对句子的情绪做出预测。引入额外的填充符会带来过多噪声,这样的话你模型的性能就会不太好。注意:我们填充序列的唯一原因是因为需要以固定大小的批量输入进 RNN。下面你会看到,使用动态 RNN 还能避免在序列完成后的不必要计算。

模型

代码:

class model(object):

    def __init__(self, FLAGS):

        # 占位符
        self.inputs_X = tf.placeholder(tf.int32,
            shape=[None, None], name='inputs_X')
        self.targets_y = tf.placeholder(tf.float32,
            shape=[None, None], name='targets_y')
        self.dropout = tf.placeholder(tf.float32)

        # RNN 单元
        stacked_cell = rnn_cell(FLAGS, self.dropout)

        # RNN 输入
        with tf.variable_scope('rnn_inputs'):
            W_input = tf.get_variable("W_input",
                [FLAGS.en_vocab_size, FLAGS.num_hidden_units])

        inputs = rnn_inputs(FLAGS, self.inputs_X)
        #initial_state = stacked_cell.zero_state(FLAGS.batch_size, tf.float32)

        # RNN 输出
        seq_lens = length(self.inputs_X)
        all_outputs, state = tf.nn.dynamic_rnn(cell=stacked_cell, inputs=inputs,
            sequence_length=seq_lens, dtype=tf.float32)

        # 由于使用了 seq_len[0],state 自动包含了上一次的对应输出
        # 因为 state 是一个带有张量的元组
        outputs = state[0]

        # 处理 RNN 输出
        with tf.variable_scope('rnn_softmax'):
            W_softmax = tf.get_variable("W_softmax",
                [FLAGS.num_hidden_units, FLAGS.num_classes])
            b_softmax = tf.get_variable("b_softmax", [FLAGS.num_classes])

        # Logits
        logits = rnn_softmax(FLAGS, outputs)
        probabilities = tf.nn.softmax(logits)
        self.accuracy = tf.equal(tf.argmax(
            self.targets_y,1), tf.argmax(logits,1))

        # 损失函数
        self.loss = tf.reduce_mean(
            tf.nn.sigmoid_cross_entropy_with_logits(logits, self.targets_y))

        # 优化
        self.lr = tf.Variable(0.0, trainable=False)
        trainable_vars = tf.trainable_variables()
        # 使用梯度截断来避免梯度消失和梯度爆炸
        grads, _ = tf.clip_by_global_norm(
            tf.gradients(self.loss, trainable_vars), FLAGS.max_gradient_norm)
        optimizer = tf.train.AdamOptimizer(self.lr)
        self.train_optimizer = optimizer.apply_gradients(
            zip(grads, trainable_vars))

        # 下面是用于采样的值
        # (在每个单词后生成情绪)

        # 取所有输出作为第一个输入序列
        # (由于采样,只需一个输入序列)
        sampling_outputs = all_outputs[0]

        # Logits
        sampling_logits = rnn_softmax(FLAGS, sampling_outputs)
        self.sampling_probabilities = tf.nn.softmax(sampling_logits)

        # 保存模型的组件
        self.global_step = tf.Variable(0, trainable=False)
        self.saver = tf.train.Saver(tf.all_variables())

    def step(self, sess, batch_X, batch_y=None, dropout=0.0,
        forward_only=True, sampling=False):

        input_feed = {self.inputs_X: batch_X,
                      self.targets_y: batch_y,
                      self.dropout: dropout}

        if forward_only:
            if not sampling:
                output_feed = [self.loss,
                               self.accuracy]
            elif sampling:
                input_feed = {self.inputs_X: batch_X,
                              self.dropout: dropout}
                output_feed = [self.sampling_probabilities]
        else: # 训练
            output_feed = [self.train_optimizer,
                           self.loss,
                           self.accuracy]

        outputs = sess.run(output_feed, input_feed)

        if forward_only:
            if not sampling:
                return outputs[0], outputs[1]
            elif sampling:
                return outputs[0]
        else: # 训练
            return outputs[0], outputs[1], outputs[2]

上面的代码就是我们的模型代码,它在训练的过程中使用了输入的文本。注意:为了清楚起见,我们决定将批量数据的大小保存在我们的输入和目标占位符中,但是我们应该让它们独立于一个特定的批量大小之外。由于这个特定的批量大小依赖于 batch_size,如果我们这么做,那么我们就还得输入一个 initial_state。我们通过嵌入他们来为每个数据序列来输入 token。实践策略表明,我们在输入文本上使用 skip-gram 模型预训练嵌入权重能够取得更好的性能。

在此模型中,我们再次使用 dynamic_rnn,但是这次我们提供了sequence_length 参数的值,它是一个包含每个序列长度的列表。这样,我们就可以避免在输入序列的最后一个词之后进行的不必要的计算。length 函数就用来获取这个列表的长度,如下所示。当然,我们也可以在外面计算seq_len,再通过占位符进行传递。

def length(data):
    relevant = tf.sign(tf.abs(data))
    length = tf.reduce_sum(relevant, reduction_indices=1)
    length = tf.cast(length, tf.int32)
    return length

由于我们填充符 token 为 0,因此可以使用每个 token 的 sign 性质来确定它是否是一个填充符 token。如果输入大于 0,则 tf.sign 为 1;如果输入为 0,则为 tf.sign 为 0。这样,我们可以逐步通过列索引来获得 sign 值为正的 token 数量。至此,我们可以将这个长度提供给dynamic_rnn 了。

注意:我们可以很容易地在外部计算 seq_lens,并将其作为占位符进行传参。这样我们就不用依赖于 PAD_ID = 0 这个性质了。

一旦我们从 RNN 拿到了所有的输出和最终状态,我们就会希望分离对应输出。对于每个输入来说,将具有不同的对应输出,因为每个输入长度不一定不相同。由于我们将 seq_len 传给了 dynamic_rnn,而 state 又是最后一个对应输出,我们可以通过查看 state 来找到对应输出。注意,我们必须取 state[0],因为返回的 state 是一个张量的元组。

其他需要注意的事情:我并没有使用 initial_state,而是直接给 dynamic_rnn 设置dtype。此外,dropout 将根据 forward_only 与否,作为参数传递给 step()

推断

总的来说,除了单个句子的预测外,我还想为具有一堆样本句子整体情绪进行预测。我希望看到的是,每个单词都被 RNN 读取后,将之前的单词分值保存在内存中,从而查看预测分值是怎样变化的。举例如下(值越接近 0 表明越靠近负面情绪):

Screen Shot 2016-10-05 at 8.34.51 PM.png

注意:这是一个非常简单的模型,其数据集非常有限。主要目的只是为了阐明它是如何搭建以及如何运行的。为了获得更好的性能,请尝试使用数据量更大的数据集,并考虑具体的网络架构,比如 Attention 模型、Concept-Aware 词嵌入以及隐喻(symbolization to name)等等。

损失屏蔽(这里不需要)

最后,我们来计算 cost。你可能会注意到我们没有做任何损失屏蔽(loss masking)处理,因为我们分离了对应输出,仅用于计算损失函数。然而,对于其他诸如机器翻译的任务来说,我们的输出很有可能还来自填充符 token。我们不想考虑这些输出,因为传递了 seq_lens 参数的 dynamic_rnn 将返回 0。下面这个例子比较简单,只用来说明这个实现大概是怎么回事;我们这里再一次使用了填充符 token 为 0 的性质:

# 向量化 logits 和目标
targets = tf.reshape(targets, [-1]) # 将张量 targets 转为向量
losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits, targets)
mask = tf.sign.(tf.to_float(targets)) # targets 为 0 则输出为 0, target < 0 则输出为 -1, 否则 为 1
masked_losses = mask*losses # 填充符所在位置的贡献为 0

首先我们要将 logits 和 targets 向量化。为了使 logits 向量化,一个比较好的办法是将dynamic_rnn 的输出向量化为 [-1,num_hidden_units] 的形状,然后乘以 softmax 权重[num_hidden_units,num_classes]。通过损失屏蔽操作,就可以消除填充符所在位置贡献的损失。

代码

GitHub 仓库 (正在更新,敬请期待!)

张量形状变化的参考

原始未处理过的文本 X 形状为 [N,] 而 y 的形状为 [N, C],其中 C 是输出类别的数量(这些是手动完成的,但我们需要使用独热编码来处理多类情况)。

然后 X 被转化为 token 并进行填充,变成了 [N, <max_len>]。我们还需要传递形状为 [N,]的 seq_len 参数,包含每个句子的长度。

现在 Xseq_len 和 y 通过这个模型首先嵌入为 [NXD],其中 D 是嵌入维度。X 便从[N, <max_len>] 转换为了 [N, <max_len>, D]。回想一下,X 在这里有一个中间表示,它被独热编码为了 [N, <max_len>, <num_words>]。但我们并不需要这么做,因为我们只需要使用对应词的索引,然后从词嵌入权重中取值就可以了。

我们需要将这个嵌入后的 X 传递给 dynamic_rnn 并返回 all_outputs ([N, <max_len>, D])以及 state[1, N, D])。由于我们输入了 seq_lens,对于我们而言它就是最后一个对应的状态。从维度的角度来说,你可以看到, all_outputs 就是来自 RNN 的对于每个句子中的每个词的全部输出结果。然而,state 仅仅只是每个句子的最后一个对应输出。

现在我们要输入 softmax 权重,但在此之前,我们需要通过取第一个索引(state[0])来把状态从 [1,N,D] 转换为[N,D]。如此便可以通过与 softmax 权重 [D,C] 的点积,来得到形状为 [N,C] 的输出。其中,我们做指数级 softmax 运算,然后进行正则化,最终结合形状为[N,C] 的 target_y 来计算损失函数。

注意:如果你使用了基本的 RNN 或者 GRU,从 dynamic_rnn 返回的 all_outputs 和 state的形状是一样的。但是如果使用 LSTM 的话,all_outputs 的形状就是 [N, <max_len>, D] 而state 的形状为 [1, 2, N, D]





原文发布时间为:2017年10月26日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-10-30 19:29:36

[译] RNN 循环神经网络系列 2:文本分类的相关文章

[译] RNN 循环神经网络系列 1:基本 RNN 与 CHAR-RNN

本文讲的是[译] RNN 循环神经网络系列 1:基本 RNN 与 CHAR-RNN, 原文地址:RECURRENT NEURAL NETWORKS (RNN) – PART 1: BASIC RNN / CHAR-RNN 原文作者:GokuMohandas 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:Changkun Ou 校对者:CACppuccino, TobiasLee 本系列文章汇总 RNN 循环神经网络系列 1:基本 RNN 与 CHAR

用深度学习(CNN RNN Attention)解决大规模文本分类问题 - 综述和实践

近来在同时做一个应用深度学习解决淘宝商品的类目预测问题的项目,恰好硕士毕业时论文题目便是文本分类问题,趁此机会总结下文本分类领域特别是应用深度学习解决文本分类的相关的思路.做法和部分实践的经验. 业务问题描述: 淘宝商品的一个典型的例子见下图,图中商品的标题是"夏装雪纺条纹短袖t恤女春半袖衣服夏天中长款大码胖mm显瘦上衣夏".淘宝网后台是通过树形的多层的类目体系管理商品的,覆盖叶子类目数量达上万个,商品量也是10亿量级,我们是任务是根据商品标题预测其所在叶子类目,示例中商品归属的类目为

手把手教你用 TensorFlow 实现文本分类(上)

由于需要学习语音识别,期间接触了深度学习的算法.利用空闲时间,想用神经网络做一个文本分类的应用, 目的是从头到尾完成一次机器学习的应用,学习模型的优化方法,同时学会使用主流的深度学习框架(这里选择tensorflow). 文章分为两部分,本文仅实现流程,用简单的softmax回归对文本进行分类,后面一篇文章再从流程的各个方面对模型进行优化,达到比较好的效果.   收集数据 该部分不是这里的重点,数据从各大新闻网站爬取新闻文本,分十类保存到本地,包括科技.生活.体育.娱乐等.文本分别保存到trai

300万知乎多标签文本分类任务经验分享(附源码)

七月,酷暑难耐,认识的几位同学参加知乎看山杯,均取得不错的排名.当时天池AI医疗大赛初赛结束,官方正在为复赛进行平台调试,复赛时间一拖再拖.看着几位同学在比赛中排名都还很不错,于是决定抽空试一试.结果一发不可收拾,又找了两个同学一起组队(队伍init)以至于整个暑假都投入到这个比赛之中,并最终以一定的优势夺得第一名. 比赛介绍 这是一个文本多分类的问题:目标是"参赛者根据知乎给出的问题及话题标签的绑定关系的训练数据,训练出对未标注数据自动标注的模型".通俗点讲就是:当用户在知乎上提问题

循环神经网络(RNN, Recurrent Neural Networks)介绍

循环神经网络(RNN, Recurrent Neural Networks)介绍    这篇文章很多内容是参考:http://www.wildml.com/2015/09/recurrent-neural-networks-tutorial-part-1-introduction-to-rnns/,在这篇文章中,加入了一些新的内容与一些自己的理解.   循环神经网络(Recurrent Neural Networks,RNNs)已经在众多自然语言处理(Natural Language Proce

简单入门循环神经网络RNN:时间序列数据的首选神经网络

更多深度文章,请关注:https://yq.aliyun.com/cloud 随着科学技术的发展以及硬件计算能力的大幅提升,人工智能已经从几十年的幕后工作一下子跃入人们眼帘.人工智能的背后源自于大数据.高性能的硬件与优秀的算法的支持.2016年,深度学习已成为Google搜索的热词,随着最近一两年的围棋人机大战中,阿法狗完胜世界冠军后,人们感觉到再也无法抵挡住AI的车轮的快速驶来.在2017年这一年中,AI已经突破天际,相关产品也出现在人们的生活中,比如智能机器人.无人驾驶以及语音搜索等.最近,

用神经网络进行文本分类

本文讲的是用神经网络进行文本分类, 理解聊天机器人如何工作是很重要的.聊天机器人内部一个基础的组成部分是文本分类器.让我们一起来探究一个用于文本分类的人工神经网络的内部结构. 多层人工神经网络 我们将会使用两层神经元(包括一个隐层)和词袋模型来组织(organizing 似乎有更好的选择,求建议)我们的训练数据.有三种聊天机器人文本分类的方法:模式匹配,算法,神经网络.尽管基于算法的方法使用的多项式朴素贝叶斯方法效率惊人,但它有三个根本性的缺陷: 该算法的输出是一个评分而非概率.我们想要的是一个

前 Google 科学家林德康详解:卷积神经网络如何应用于文本分类 | AI 研习社

提起卷积神经网络(Convolutional Neural Network, CNN),大部分人首先会想到图像识别.图像分类.图像处理等视觉应用场景.的确,CNN 在计算机视觉领域做出了巨大贡献,是当今绝大多数计算机视觉系统的技术核心,在谷歌 AlphaGo.ImageNet 图像分类和 Facebook 图像自动标记等场景得到了广泛应用. 但其实,把模型的输入元素从像素点换成文本集,CNN 还可以应用在自然语言处理(Natural Language Processing,NLP)场景中,例如文

从Facebook AI Research开源fastText谈起文本分类:词向量模性、深度表征和全连接

更多深度文章,请关注:https://yq.aliyun.com/cloud          文本分类(text classification)是机器学习的一个主要任务,通常用作垃圾邮件检测.新闻/文章主题生成.多义词正确词义选择等.之前,Statsbot团队已经分享了<如何检测垃圾邮件/信息/用户评论>.本文主要介绍少数几个广义上的文本分类算法及相关案例,同时也提供了一些有用的教程和工具. 文本分类基准(Benchmarks) 目前,搞文本挖掘的人通常会使用很多小技巧和工具,比如TF-ID