循环神经网络(三)(常用循环神经网络,LSTM、GRU,文本生成)

长短期记忆网络(LSTM)

模型定义

简单循环神经网络(S-RNN)的每一个位置的状态以递归方式计算:
h t = tanh ⁡ ( U ⋅ h t − 1 + W ⋅ x t + b ) , t = 1 , 2 , ⋯   , T \bm{h}_t = \tanh(\bm{U}\cdot \bm{h}_{t-1} +\bm{W}\cdot \bm{x}_t + \bm{b}),\quad t=1,2,\cdots,T ht=tanh(Uht1+Wxt+b),t=1,2,,T状态表示序列数据中的短距离和长距离依存关系。S-RNN 对短距离依存关系可以有效地表示和学习,而对长距离依存关系的处理能力有限,因为长距离依存关系在模型中会被逐渐“遗忘”。为了解决这个问题,LSTM 被提出,LSTM 也能解决学习中的梯度消失和梯度爆炸问题。

LSTM 的基本想法是记录并使用之前所有位置的状态,以便更好地描述短距离和长距离依存关系。它增加了一种携带信息跨越多个时间步的方法。假设有一条传送带,其运行方向平行于所处理的序列。序列中的信息可以在任意位置跳上传送带,然后被传送到更晚的时间步,并在需要时原封不动地跳回来。这实际上就是LSTM 的原理:它保存信息以便后面使用,从而防止较早期的信号在处理过程中逐渐消失。

为此导入两个机制,一个是记忆元,另一个是门控。记忆元用来记录之前位置的状态信息。门控是指用门函数来控制状态信息的使用,有三个门,包括遗忘门(forget gate)、输入门输出门

LSTM 和 GRU 又被称为门控循环神经网络。这里门是一个向量,每一维取值在 0 和 1 之间,与其他向量进行逐元素积计算,起到“软的”逻辑门电路的作用。当某一维取值是 1 的时候,门是开放的;取值是 0 的时候,门是关闭的。门依赖于所在位置,由所在位置的输入和之前位置的状态决定。

以下的循环神经网络称为长短期记忆网络。在循环网络的每一个位置上有状态和记忆元,以及输入门、遗忘门、输出门,构成一个单元。第 t t t 个位置上的单元是以当前位置的输入 x t \bm{x}_t xt、之前位置的记忆元 c t − 1 \bm{c}_{t-1} ct1、之前位置的状态 h t − 1 \bm{h}_{t-1} ht1 为输入,以当前位置的状态 h t \bm{h}_t ht 和当前位置的记忆元 c t \bm{c}_t ct 为输出的函数,由以下方式计算 i t = σ ( U i ⋅ h t − 1 + W i ⋅ x t + b i ) \bm{i}_t=\sigma(\bm{U}_i\cdot \bm{h}_{t-1} +\bm{W}_i\cdot \bm{x}_t + \bm{b}_i) it=σ(Uiht1+Wixt+bi) f t = σ ( U f ⋅ h t − 1 + W f ⋅ x t + b f ) \bm{f}_t=\sigma(\bm{U}_f\cdot \bm{h}_{t-1} +\bm{W}_f\cdot \bm{x}_t + \bm{b}_f) ft=σ(Ufht1+Wfxt+bf) o t = σ ( U o ⋅ h t − 1 + W o ⋅ x t + b o ) \bm{o}_t=\sigma(\bm{U}_o\cdot \bm{h}_{t-1} +\bm{W}_o\cdot \bm{x}_t + \bm{b}_o) ot=σ(Uoht1+Woxt+bo) c ~ t = tanh ⁡ ( U c ⋅ h t − 1 + W c ⋅ x t + b c ) \tilde{\bm{c}}_t =\tanh(\bm{U}_c\cdot \bm{h}_{t-1} +\bm{W}_c\cdot \bm{x}_t + \bm{b}_c) c~t=tanh(Ucht1+Wcxt+bc) c t = i t ⊙ c ~ t + f t ⊙ c t − 1 \bm{c}_t = \bm{i}_t \odot \tilde{\bm{c}}_t+\bm{f}_t \odot \bm{c}_{t-1} ct=itc~t+ftct1 h t = o t ⊙ tanh ⁡ ( c t ) \bm{h}_t=\bm{o}_t\odot \tanh(\bm{c}_t) ht=ottanh(ct) 这里 i t \bm{i}_t it 是输入门, f t \bm{f}_t ft 是遗忘门, o t \bm{o}_t ot 是输出门, c ~ t \tilde{\bm{c}}_t c~t 是中间结果。

LSTM 的单元结构如下图所示。内部有三个门和一个记忆元。遗忘门、输入门、输出门有相同的结构,都是以当前位置的输入 x t \bm{x}_t xt 和之前位置的状态 h t − 1 \bm{h}_{t-1} ht1 为输入的函数。遗忘门决定忘记之前位置的哪些信息,输入门决定从之前位置传入哪些信息,输出门决定向下一个位置传出哪些信息

在这里插入图片描述

模型特点

LSTM 能更好地表示和学习长距离依存关系。

当输入门和遗忘门满足 i t = 1 , f t = 0 \bm{i}_t=1,\bm{f}_t=0 it=1,ft=0 时,当前位置的记忆元只依赖于当前位置的输入和之前位置的状态,LSTM 是 S-RNN 的近似。

当输入门和遗忘门满足 i t = 0 , f t = 1 \bm{i}_t=0,\bm{f}_t=1 it=0,ft=1 时,当前位置的记忆元只依赖于之前位置的记忆元,LTSM 将之前位置的记忆元复制到当前位置。

记忆元 c t \bm{c}_t ct 是之前所有位置的中间结果 c ~ t \tilde{\bm{c}}_t c~t 的线性组合,而中间结果由所在位置的输入和之前位置的状态决定。所以,当前位置的记忆元以及状态由之前位置的状态综合决定。

学习中由于位置之间的梯度传播不是通过矩阵的连乘而是通过矩阵连乘的线性组合,所以可以避免梯度消失和梯度爆炸。

用 LSTM 生成文本

用深度学习生成序列数据的通用方法,就是使用前面的标记作为输入,训练一个网络来预测序列中接下来的一个或多个标记。例如,给定输入 the cat is on the ma,训练网络来预测目标t,即下一个字符。与处理文本数据时一样,标记(token)通常是单词或字符,给定前面的标记,能够对下一个标记的概率进行建模的任何网络都叫作语言模型(language model)。语言模型能够捕捉到语言的潜在空间(latent space),即语言的统计结构。

一旦训练好了这样一个语言模型,就可以从中采样(sample,即生成新序列)。向模型中输入一个初始文本字符串[即条件数据(conditioning data)],要求模型生成下一个字符或下一个单词(甚至可以同时生成多个标记),然后将生成的输出添加到输入数据中,并多次重复这一过程。这个循环可以生成任意长度的序列,这些序列反映了模型训练数据的结构,它们与人类书写的句子几乎相同。我们将会用到一个LSTM 层,向其输入从文本语料中提取的 N 个字符组成的字符串,然后训练模型来生成第 N+1 个字符。模型的输出是对所有可能的字符做 softmax,得到下一个字符的概率分布。这个LSTM 叫作字符级的神经语言模型(character-level neural language model)。

生成文本时,如何选择下一个字符至关重要。一种简单的方法是贪婪采样(greedy sampling),就是始终选择可能性最大的下一个字符(使得到这个位置为止的单词序列的联合概率最大)。但这种方法会得到重复的、可预测的字符串,看起来不像是连贯的语言。一种更有趣的方法是做出稍显意外的选择:在采样过程中引入随机性,即从下一个字符的概率分布中进行采样。这叫作随机采样(stochastic sampling,stochasticity )。在这种情况下,根据模型结果,如果下一个字符是 e 的概率为 0.3,那么会有30% 的概率选择它。贪婪采样也可以被看作从一个概率分布中进行采样,即某个字符的概率为1,其他所有字符的概率都是0。

更小的熵(随机性)可以让生成的序列具有更加可预测的结构(因此可能看起来更真实),而更大的熵(随机性)会得到更加出人意料且更有创造性的序列。从生成式模型中进行采样时,在生成过程中探索不同的随机性大小总是好的做法。

为了在采样过程中控制随机性的大小,引入一个叫作 softmax 温度(softmax temperature)的参数,用于表示采样概率分布的熵,即表示所选择的下一个字符会有多么出人意料或多么可预测。

给定一个 temperature 值,将按照下列方法对原始概率分布(即模型的 softmax 输出)进行重新加权,计算得到一个新的概率分布。

import numpy as np

def reweight_distribution(original_distribution, temperature=0.5):
    distribution = np.log(original_distribution) / temperature
    distribution = np.exp(distribution)
    return distribution / np.sum(distribution)

本例将使用的文本数据是尼采的一些作品,他是19 世纪末期的德国哲学家,这些作品已经被翻译成英文。因此,我们要学习的语言模型将是针对于尼采的写作风格和主题的模型,而不是关于英语的通用模型。

首先下载语料,并将其转换为小写。

import keras
import numpy as np

path = keras.utils.get_file(
    'nietzsche.txt',
    origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt')

text = open(path).read().lower()

print('Corpus length:', len(text))

接下来,我们要提取长度为 maxlen 的序列(这些序列之间存在部分重叠),对它们进行 one-hot 编码,然后将其打包成形状为 (sequences, maxlen, unique_characters) 的三维 Numpy 数组。与此同时,我们还需要准备一个数组 y,其中包含对应的目标,即在每一个所提取的序列之后出现的字符(已进行 one-hot 编码)。

maxlen = 60
step = 3          # 每3 个字符采样一个新序列
sentences = []    # 保存所提取的序列
next_chars = []   # 保存目标(即下一个字符)

for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_chars.append(text[i + maxlen])

print('Number of sequences:', len(sentences))

chars = sorted(list(set(text)))  # 语料中唯一字符组成的列表
print('Unique characters:', len(chars))
char_indices = dict((char, chars.index(char)) for char in chars)

# one-hot 编码
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence):
        x[i, t, char_indices[char]] = 1
        y[i, char_indices[next_chars[i]]] = 1

"""
Number of sequences: 200278
Unique characters: 57
"""

构建网络:

from keras import layers

model = keras.models.Sequential()
model.add(layers.LSTM(128, input_shape=(maxlen, len(chars))))
model.add(layers.Dense(len(chars), activation='softmax'))

optimizer = keras.optimizers.RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

给定模型预测,采样下一个字符的函数:

def sample(preds, temperature=1.0):
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / temperature
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)

最后,下面这个循环将反复训练并生成文本。在每轮过后都使用一系列不同的温度值来生成文本。这样我们可以看到,随着模型收敛,生成的文本如何变化,以及温度对采样策略的影响。

import random
import sys

for epoch in range(1, 60):
    print('epoch', epoch)
    model.fit(x, y, batch_size=128, epochs=1)
    start_index = random.randint(0, len(text) - maxlen - 1)
    generated_text = text[start_index: start_index + maxlen]
    print('--- Generating with seed: "' + generated_text + '"')
    
    for temperature in [0.2, 0.5, 1.0, 1.2]:
        print('\n------ temperature:', temperature)
        sys.stdout.write(generated_text)
        
        for i in range(400):
            sampled = np.zeros((1, maxlen, len(chars)))
            for t, char in enumerate(generated_text):
                sampled[0, t, char_indices[char]] = 1.
            
            preds = model.predict(sampled, verbose=0)[0]
            next_index = sample(preds, temperature)
            next_char = chars[next_index]
            
            generated_text += next_char
            generated_text = generated_text[1:]
            
            sys.stdout.write(next_char)

门控循环单元网络(GRU)

模型定义

门控循环单元是对 LSTM 进行简化得到的模型,效果相当,但计算效率更高。GRU 有两个门,更新门(update gate)和重置门(reset gate),不使用记忆门。

以下的循环神经网络称为门控循环单元网络。在循环网络的每一个位置上有状态及重置门、更新门,构成一个单元。第 t t t 个位置上的单元是以当前位置的输入 x t \bm{x}_t xt、之前位置的状态 h t − 1 \bm{h}_{t-1} ht1 为输入,以当前位置的状态 h t \bm{h}_t ht 为输出的函数,由以下方式计算 r t = σ ( U r ⋅ h t − 1 + W r ⋅ x t + b r ) \bm{r}_t=\sigma(\bm{U}_r\cdot \bm{h}_{t-1} +\bm{W}_r\cdot \bm{x}_t + \bm{b}_r) rt=σ(Urht1+Wrxt+br) z t = σ ( U z ⋅ h t − 1 + W z ⋅ x t + b z ) \bm{z}_t=\sigma(\bm{U}_z\cdot \bm{h}_{t-1} +\bm{W}_z\cdot \bm{x}_t + \bm{b}_z) zt=σ(Uzht1+Wzxt+bz) h ~ t = tanh ⁡ ( U h ⋅ r t ⊙ h t − 1 + W h ⋅ x t + b h ) \tilde{\bm{h}}_t =\tanh(\bm{U}_h\cdot \bm{r}_t \odot \bm{h}_{t-1}+\bm{W}_h\cdot \bm{x}_t + \bm{b}_h) h~t=tanh(Uhrtht1+Whxt+bh) h t = ( 1 − z t ) ⊙ h ~ t + z t ⊙ h t − 1 \bm{h}_t = (1-\bm{z}_t) \odot \tilde{\bm{h}}_t+\bm{z}_t \odot \bm{h}_{t-1} ht=(1zt)h~t+ztht1

模型特点

GRU 也能很好地表示和学习长距离依存关系。更新门和重置门起着重要作用。

当更新门和重置门满足 z t = 0 , r t = 1 \bm{z}_t=0,\bm{r}_t=1 zt=0,rt=1 时,当前位置的状态只依赖于当前位置的输入和之前位置的状态,GRU 回退到 S-RNN。

当更新门和重置门满足 z t = 0 , r t = 0 \bm{z}_t=0,\bm{r}_t=0 zt=0,rt=0 时,当前位置的状态只依赖于当前位置的输入,忽视之前位置的状态。

当更新门满足 z t = 1 \bm{z}_t=1 zt=1 时,GRU 网络将之前位置的状态复制到当前位置,忽视当前位置的输入。

状态 h t \bm{h}_t ht 是之前所有位置的中间结果 h ~ t \tilde{\bm{h}}_t h~t 的线性组合,而中间结果由所在位置的输入和之前位置的状态决定。所以,当前位置的状态由之前位置的状态综合决定。


References

[1] 《机器学习方法》,李航,清华大学出版社。
[2] 《Python 深度学习》,François Chollet.

猜你喜欢

转载自blog.csdn.net/myDarling_/article/details/129735576
今日推荐