从RNN到LSTM代码实战

本文从RNN的基础介绍开始,详细解释RNN的结构、作用及内部计算过程,然后过渡到LSTM,分析LSTM的结构优势,如何克服梯度消失问题。文中还提供了RNN和LSTM的实战代码,通过Python和TensorFlow实现,适合初学者理解这两种神经网络的工作原理。此外,文章还探讨了Keras库在RNN和LSTM中的应用,以及相关的资源推荐。
摘要由CSDN通过智能技术生成

RNN

对RNN的简单介绍

RNN的英文全称是Recurrent Neural Network,中文名称是循环神经网络。RNN适用于处理“序列问题”,所谓“序列问题”是指一个样本的特征与特征之间存在明显的先后顺序关系,如对于一张图片,组成该图片的特征是像素,像素与像素之间是有着十分明显的位置排列的关系,因此一个图片的像素组成了一个序列。当我们需要将图片作为输入到模型当中去判断该图片是狗还是猫的时候,这个问题就是“序列问题”。但更为人所熟知的“序列问题”则是与文本相关的问题,单词是文本组成的基本元素,单词与单词之间有明显的先后位置关系。因此,文本分类、文本预测等问题都可以归类为“序列问题”。RNN在处理“序列问题”时有明显的优势,因为它将序列当中元素之间的关系以信息的方式提取出来,并反作用于序列当中元素的处理过程,我们的文本在经过RNN的处理所提炼出的信息自然是更加合理。(白话:我讲的的确有些抽象,我常常用看书的例子来比喻RNN、LSTM的工作机制。RNN在看书的时候,当他看到一个单词,他会用前面所看到的内容来综合理解这个单词的含义,这样去看书,自然能够获得更好的效果。)

那么,在RNN诞生之前,我们是如何来处理序列问题的呢?对于一些简单的机器学习的方法,像逻辑回归、SVM等,它们把序列当中元素的相互关系全部给丢弃了,样本的特征是什么就是什么,它们不会去根据特征之间的关系来对特征进行延申。(白话:我讲得还是挺抽象的,如果你学习过这两种基本的方法的话,也许还能理解得比较到位。举个不太恰当的例子,当你喜欢的女生和你讲no,像逻辑回归、SVM这种傻瓜,会直接认为自己被拒绝掉了。但是RNN、LSTM就很聪明,它会把女生和它讲的话结合起来去看no这个单词,no也需并不代表着拒绝。)。对于MLP(多层感知机)而言,则是过度地考虑了一个元素所代表的涵义,它会结合所有的元素,一起来对这个元素进行分析,从而得到一个新的元素。这种方式太过于笨重,一个序列可能有很多元素,我们的参数实在是太多了,训练该模型会十分缓慢。而RNN、LSTM则是非常轻便,参数量非常少,所以RNN、LSTM是处理“序列问题”时的最佳人选。

带你了解RNN的结构

RNN的宏观作用

在这里插入图片描述

如图1所示,我没有像很多教程当中那样,不关注RNN在一个完整的系统当中所处的位置,直接去分析RNN内部的结构,这对于新手而言是很不友好的。我是将RNN在一个完整系统当中所处的位置给大家先展示出来,希望以一种由宏观到微观的过程来给大家介绍RNN。如图1所示,这是一个完整的文本二分类的模型架构(我把文本预处理的部分去除了),我们在代码实战当中,使用的就是这样一个模型架构。RNN自身并不是一个完整的模型,它自己什么也干不了,它的作用是对我们输入的文本进行加工处理,我们使用RNN加工处理之后的结果来代替原本的文本数据进行分类。(白话:对于RNN,你必须要建立起一个宏观的概念,它只是一个完整模型当中的一个组件而已。就像是汽车的发动机一样,它只是汽车的一个重要的组成部分。单靠发动机,什么也做不了。)

我来给大家描述一下图1的大致过程,我们希望输入一个文本到模型当中之后,可以得到关于该文本是正向情感文本还是负向情感文本的判断结果。输入的文本数据包含有n个单词,我们就以这n个单词作为该文本的各个维度的特征。我们将该n*d的矩阵T输入到RNN当中,d为词向量的维度,我们先不去关心RNN内部是如何处理T的。RNN输出的是一个m维的向量R,我们使用该m维的向量R输入到全连接隐藏层当中,得到了k维的隐藏层向量H。最后,我们使用仅包含一个单元的输出层,使用sigmoid来作为激活函数,输出值靠近0为负样本,靠近1为正样本。

RNN内部的结构

在这里插入图片描述

如图2所示,就是RNN在处理文本时的具体过程。会有一些数学上的计算,做好准备,不过不会太难。我们首先看一下图2的整体结构,它是一个从左到右的基本结构。对于文本当中的n个单词,我们进行了从左到右一共n步计算,最后得到了RNN处理的输出结果hn。

让我们来细致地描述一遍图2吧,h这个向量实际上代表着我们的记忆,让我们先把目光聚焦到第一个小方块单元上。此时我们的记忆h0是空白的(0向量),因为我们还没有处理这个文本当中的任何一个单词。word1是文本的第一个单词向量,它与记忆h0一同输入到第一个小方块当中,完成了一个运算去获取关于第一个单词的记忆h1。之后,我们又将带着这份记忆h1去处理第二个单词向量word2。到了最后一步,我所获得的hn,它代表着我们对于这个文本整体的记忆。因此,我们将hn作为RNN处理的输出结果。

那么,对于小方块当中的计算是如何进行的呢?
h 1 = t a n h ( h 0 ∗ W h + w o r d 1 ∗ W x + b ) h1=tanh(h_0*W_h+word_1*W_x+b) h1=tanh(h0Wh+word1Wx+b)
在这个式子当中,h的维度是RNN的一个超参数,我们可以人为去指定。我们就假定h的维度为m维,而对于 W h W_h Wh,它是一个矩阵,矩阵的维度为 m ∗ m m*m mm,它是RNN的一项参数,通过反向传播来进行调节优化, W h W_h Wh的初值我们通常使用正态分布来生成。还记得线性代数的一点内容的话,我们应该很容易能够知道 h 0 ∗ W h h_0*W_h h0Wh的结果仍旧是一个m维的向量。而 w o r d 1 word_1 word1是一个d维的词向量, w o r d 1 ∗ W x word_1*W_x word1Wx的结果也一定是一个m维向量,因为它要和 h 0 ∗ W h h_0*W_h h0Wh相加,向量之间相加必须要尺寸一致。所以, W x W_x Wx是一个尺寸为 d ∗ m d*m dm的矩阵,它同样是RNN的参数,要进行反向传播调节优化。 b b b是我们常常所说得偏置项,它是维度为m的向量,初值一般设为全0。

至于RNN所使用的激活函数 t a n h tanh tanh,以我的经验来理解 t a n h tanh tanh的话,它常常用于提取信息的时候。相比于sigmoid函数,它不仅能起到 s i g m o i d sigmoid sigmoid所具备的归一化的效果,还能够保持数据的正负号,在进行信息表示时, t a n h tanh tanh函数的表达能力更强。
t a n h ( x ) = e x − e − x e x + e − x s i g m o i d ( x ) = 1 1 + e − x tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}} \\sigmoid(x)=\frac{1}{1+e^{-x}} tanh(x)=ex+exexexsigmoid(x)=1+ex1
在这里插入图片描述
我们从图3进行观察的话,最为直观的感受就是 t a n h tanh tanh s i g m o i d sigmoid sigmoid的值域不一样。值域不同到底会有什么样的影响?没事儿躺在床上可以想一想。

RNN代码实战

任务介绍

做RNN的实战任务,使用文本分类是作为合适不过了。我所选用的数据集是IMDB(豆瓣)影评数据集,数据集当中包含有24500份数据,它们一部分是正向情感的影评数据,另一部分是负向情感的影评数据。我将这24500份数据中的17000份作为训练数据,7500份作为测试数据。我们的目标就是希望尽可能地去将这7500份数据都去预测准确。
数据集下载链接

编程环境设置

  • Python3.5
  • Tensorflow1.8.0

因为我是使用tensorflow1.8.0这个框架来搭建RNN的,python的版本下调至3.5。(很多同学做研究使用的是pytorch,基本原理是差不多的。)

代码详细解析

数据读取
import math
import re
import keras
import numpy as np
import pandas as pd
import tensorflow as tf
from keras.preprocessing.text import Tokenizer
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
#读取数据
train = pd.read_csv('train_17000.tsv', sep='\t')#训练集数据
test = pd.read_csv('test_7500.tsv', sep='\t')#测试集数据
train_data = train.loc[:, 'data']#训练集数据的'data'字段
train_labels = train.loc[:, 'labels']#训练集数据的'label'字段
test_data = test.loc[:, 'data']#测试集的'data'字段
test_labels = test.loc[:, 'labels']#测试集'label'字段

IMDB数据是以tsv文件格式保存的,tsv这种格式特别适合于机器学习数据的保存,我们可以通过属性名称去访问到一条样本数据的某个属性值。我们通过读取数据,就将训练集数据保存在了变量train当中,将测试集数据保存在变量test当中。我之所以要将数据和标签(label)分成两部分,是为了对数据部分进行预处理时会比较方便。

文本数据预处理
print(train_data.loc[0])

打印结果:
在这里插入图片描述
我们来打印一下训练数据来观察一下,我们当前的数据还是文本内容。但是,如果我们要让机器学习算法来处理的话,我们必须要将文本转化成维度一致的“数字向量”才可以。

# RNN 需要的参数
def get_default_params():
    return tf.contrib.training.HParams(
        num_embedding_size=16,  # 词向量的维度
        num_timesteps=50,  # 时间步的长度
        max_len=50,  # 文本的单词数目,注意它要与num_timesteps的值保持相同
        vocab_size=6000,  # 单词->数值表示时的词典大小。单词种数超过6000将转化成0进行表示。
        num_rnn_nodes=32,  # Rnn模块的维度
        num_fc_nodes=32,  # Rnn处理完之后,使用全连接层来进行后续处理
        batch_size=500,  # 一个batch所处理的样本数目
        clip_lstm_grads=1.0,  # 当梯度计算超过1时,令其为1
        learning_rate=0.001,  # 学习速率
        num_word_threshold=10,  # 过滤掉词频太少的单词,我们不会将这类单词转化为数值表示,而是直接化为0.10是在整个训练集当中统计的结果
    )
hps = get_default_params()  # 我们通过hps来调用相应的参数来进行使用

# 数据的预处理
def clear_review(text):
    texts = []
    for item in text:
        item = item.replace("<br /><br />", "")
        item = re.sub("[^a-zA-Z]", " ", item.lower())
        texts.append(" ".join(item.split()))
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值