145自然语言处理进阶手册--循环神经网络

循环神经网络

Vanilla RNN

循环神经网络是一个有向循环的过程,“有向”是因为朝着序列方依次输入各序列成分以及上一步的输出成分,“循环”是因为每个序列成分进行运算的参数是一致的,因为它对数据的每个输入执行相同的功能,而当前输入的输出取决于上一步的计算。
与前馈神经网络不同, 循环神经网络可以使用其内部状态(隐状态)来处理输入序列。这使它们适用于诸如语音、文本等序列数据。而在其他神经网络中,所有输入都是彼此独立的。但是在循环神经网络中,所有输入都是相互关联的,如下图所示为基本的循环神经网络模型结构: Vanilla RNN。


image.png

image.png

循环神经网络每一步的输出都包含了前面步骤的信息,因此具备记忆功能,而记忆功能是解读语境的关键。比如,对于“小梅很喜欢吃桔子,她不喜欢吃苹果”这句话,如果逐词输入输出但是缺少记忆性,我们只能解析出一个个独立词所表达的意思,反之,在具备记忆的情况下,当看到桔子时,可知其不仅仅指“水果的概念”,而是“一个人喜欢的食物对象”。因此循环神经网络很适合处理序列间存在联系的场景。
上图中展示了一个最基本最简单的单向循环神经网络,实际上根据需求可以在此基础上有所改进。如可以将单向序列行进的网络改为双向循环神经网络,因为很多时候,对于一个序列,元素之间的影响可以是双向的,即从前往后以及从后往前。还是以“小梅很喜欢吃桔子,她不喜欢吃苹果”为例,如果从后往前看,先看过“她不喜欢吃苹果”,再看到“桔子”,也能大概知道“桔子”可能和“一个人的喜好”相关。所以,双向循环神经网络能够提供更丰富的信息。
而在事实应用中,Vanilla RNN 并不常用,这是因为其在梯度下降过程中,存在累乘项及激活函数的值域导致的梯度消失和爆炸问题,也就是说,训练 Vanilla RNN 是一项非常困难的任务,无法处理很长的序列,获取不到远距离的信息。

LSTM 与 GRU

长短期记忆(LSTM)网络是 Vanilla RNN 的修改版,该网络由 Hochreiter & Schmidhuber (1997) 引入,并有许多人对其进行了改进和普及,可以更轻松地记住序列中的更长距离的过去数据,通过特制的门控结构改变了梯度更新的表达式,从而缓解了梯度消失问题(梯度爆炸可通过梯度裁剪解决)。LSTM 非常适合对序列数据进行分类,标注和预测。 LSTM 基本结构如下:


image.png

LSTM 的核心是细胞状态,用贯穿细胞的水平线表示。细胞状态像传送带一样,贯穿整个细胞却只有很少的分支,这样能保证信息稳定地流过整个网络,就好比人的记忆状态能够贯穿人的一生。


image.png

LSTM 网络能通过一种被称为门的结构对细胞状态进行删除或者添加信息。门能够有选择性地决定让哪些信息通过。其实门的结构很简单,即 Sigmoid 层和点乘操作的组合。因为 Sigmoid 层的输出是在 0-1 之间,这代表有多少概率的信息保留下来,0 表示都不能通过,1 表示都能通过。LSTM 里面包含三个门:忘记门、输入门和输出门。
image.png

image.png

image.png

image.png

以上便是 LSTM 的内部结构,通过门控状态来控制传输状态,记住对任务关键的信息,忘记不重要的信息;而不像普通的 RNN 那样只仅有一种记忆叠加的简单方式,可针对更长的文本。但同时也因为引入了很多内容,导致参数变多,也使得训练难度加大了很多。因此很多时候我们往往会使用效果和 LSTM 相当,但参数更少的 GRU 来构建大训练量的模型。


image.png

GRU 作为 LSTM 的一种变体,将忘记门和输入门合成了一个单一的更新门。同样还混合了细胞状态和隐藏状态,加诸其他一些改动。最终的模型比标准的 LSTM 模型要简单,也是非常流行的变体。

基于 PyTorch 搭建 LSTM

在 PyTorch 中直接调用 nn.LSTM() 便能获取已构建好的 LSTM 层结构,首先介绍其参数。
input_size: 表示的是输入的数据维数。
hidden_size: 表示的是输出维数。
num_layers: 表示堆叠几层的 LSTM,默认是 1。
bias: True 或者 False,决定是否使用 bias,默认为 True。
batch_first: 如果为 True, 接受的数据输入是 (batch_size,seq_len,input_size),如果为 False,则为 (seq_len,batch_size,input_size),默认为 False。
dropout: 表示除了最后一层之外都引入一个 dropout。
bidirectional: 表示双向 LSTM,默认为 False。

接下来介绍 LSTM 的输入与输出。

输入包括:
input: 表示输入数据,其维度为 (seq_len,batch_size,input_size)。
h_0: 初始隐状态,维度为 (num_layers*num_directions,batch_size,hidden_size),num_layers 表示 LSTM 的层数,num_directions 在 LSTM 为单向时为 1,双向时为 2,非必须输入,网络会提供默认初始状态。
c_0: 初始的细胞状态,维度与 h_0 相同,非必须,网络会提供默认初始状态。

输出包括:
output: 最后输出,维度为 (seq_len, batch_size, num_directions * hidden_size)。
h_n: 最后时刻的输出隐藏状态,维度为 (num_layers * num_directions, batch_size, hidden_size)。
c_n: 最后时刻的输出单元状态,维度与 h_n 相同。

定义 LSTM 层:

import torch.nn as nn
lstm = nn.LSTM(input_size=10, hidden_size=30)
lstm

接下来定义输入,输入数据大小应为 (seq_len,batch_size,input_size):

import torch
seq_len = 15
batch_size = 100
input_size = 10
# 模拟数据
x = torch.randn((seq_len, batch_size, input_size))

在 LSTM 层中输入 x,观察输出:

y, (h, c) = lstm(x)
print(y.shape)
print(h.shape)
print(c.shape)

由于 num_layers 和 num_directions 均为 1,因此 h, c 的第一维度为 num_layers*num_directions = 1。
在处理文本数据进行诸如文本分类等任务时,一般在循环神经网络的基础上加词向量层以及最后的输出层作为整体的神经网络模型,典型构架如下:

class LSTM(nn.Module):
    def __init__(self, embedding_dim, hidden_dim, vocab_size, output_size):
        super(LSTM, self).__init__()
        self.embed = nn.Embedding(vocab_size, embedding_dim)  # 词向量层
        self.lstm = nn.LSTM(embedding_dim, hidden_dim)  # LSTM 层
        # 输出分类层, output_size 为类别大小
        self.fc = nn.Linear(hidden_dim, output_size)

    def forward(self, x):
        # x: [seq_len, batch_size]
        embeds = self.embed(x)  # 经由词向量层
        # embeds: [seq_len, batch_size, embedding_dim]
        lstm_out, _ = self.lstm(embeds)  # 经同由 LSTM 层
        # lstm_out: [seq_len, batch_size, hidden_dim]
        y = self.fc(lstm_out[-1])  # 取最后一步的输出进入最后的分类层
        # y: [batch_size, output_size]
        return y

初始化 LSTM 模型:

EMBEDDING_DIM = 128  # 词向量的大小为 128
HIDDEN_DIM = 216  # 隐层大小为 216
VOCAB_DIM = 1000  # 词典大小为 1000
OUTPUT_SIZE = 3  # 输出类别为 3 类
my_lstm = LSTM(EMBEDDING_DIM, HIDDEN_DIM, VOCAB_DIM, OUTPUT_SIZE)
my_lstm

定义输入,查看输出:

# seq_len 为 3,batch_size 为 2,各数字表示某单词的 id
x = torch.tensor([[1, 4, 5, 5], [3, 4, 9, 5]])
# x 大小为 [batch_size, seq_len],需要转置
y = my_lstm(x.permute(1, 0))
y.shape

输出大小为 2*3,即 batch_size * output_size。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容