深度学习之循环神经网络(RNN) — 理论与代码相结合

RNN基本概念

如何才能让神经网络记住整个句子来正确预测下一个单词呢?这正是RNN发挥作用的时候。

RNN的输出不仅是基于当前的输入,还会基于先前的隐态。此时可能会好奇为什么不能根据当前输入和之前的输入来预测输出,而必须是当前的输入和先前的隐态。这是因为先前的输入只保存了前一个单词的信息,而先前的隐态捕获了整个句子的信息,即先前的隐态捕获了上下文。因此,基于当前的输入和先前的隐态能够预测输出,而不是根据当前输入和先前输入。

RNN广泛用于诸如机器翻译,情感分析等各种自然语言处理(NLP)任务。另外,还适用于股票市场数据等时序数据。
在这里插入图片描述
RNN与普通神经网络的不同之处在于隐态中有一个循环,这表明是如何利用先前的隐态来计算输出的。

如下图所示,输出 y 1 y_1 y1 是根据当前输入 x 1 x_1 x1、当前隐态 h 1 h_1 h1,和先前隐态 h 0 h_0 h0预测的, y 2 y_2 y2同理。这就是RNN的工作原理,是采用当前输入和先前隐态来预测输出的。由于这些稳态可以保存所有已观察过的信息,因此可称为记忆单元。

在这里插入图片描述
接下来,从数学角度分析:

  • U表示输入到隐态的权重矩阵
  • W表示隐态到隐态的权重矩阵
  • V表示隐态到输出的权重矩阵

在前向传递中,可计算如下:
             h t = ϕ ( U x t + W h t − 1 ) h_{t}=\phi\left(U x_{t}+W h_{t-1}\right) ht=ϕ(Uxt+Wht1)
即,时刻 t 的隐态=tanh([输入 - 隐态权重矩阵 x 输入] + [ 隐态 - 隐态权重矩阵 x 时刻 t-1 的前一隐态]):
               y ^ t = σ ( v h t ) \hat{y}_{t}=\sigma\left(v h_{t}\right) y^t=σ(vht)
即,时刻 t 的输出=Sigmoid(隐态 - 输出权重矩阵 x 时刻 t 的隐态)
另外,还可以定义损失函数为交叉熵损失,如下:
              损失 = − y t log ⁡ y ^ t =-y_{t} \log \hat{y}_{t} =ytlogy^t
             总损失 = − ∑ t y t log ⁡ y t ^ =-\sum_{t} y_{t} \log \hat{y_{t}} =tytlogyt^

y t y_t yt 为时刻 t 的实际单词, y t ^ \hat{y_{t}} yt^为 t 时刻的预测单词,由于是取整个句子为训练样本,因此总的损失是每个时间步的损失之和。

基于时间的反向传播

可以利用反向传播训练RNN。但在RNN中,由于与所有时间步都相关,因此每个输出的梯度不仅与当前的时间步有关,而且还取决于先前的时间步。将上述过程称为基于时间的反向传播(BPTT) 这基本与反向传播相同,只是应用了RNN。
在这里插入图片描述
上图中, L 1 L_1 L1 L 2 L_2 L2 L 3 L_3 L3是在每个时间步的损失。现在,需要在每个时间步计算这些损失相对于权重矩阵U、V和W的梯度。与之前通过在每个时间步的损失之和来计算总的损失一样,在此利用每个时间步的梯度之和来更新权重矩阵。
             ∂ L ∂ V = ∑ t ∂ L t ∂ V \frac{\partial L}{\partial V}=\sum_{t} \frac{\partial L_{t}}{\partial V} VL=tVLt

但是,该方法有一个问题。梯度计算涉及计算相对于激活函数的梯度。在计算相对于Sigmoid/tanh函数的梯度时,梯度值会非常小。而当经过多个时间步反向传播网络并乘以梯度时,梯度值会变得越来越小。称为梯度消失问题。那么就不能学习长期相关信息,即RNN不能长时间保存信息。

梯度消失不仅发生在RNN中,而且也会在采用Sigmoid/tanh函数且具有多个隐层的其他深度神经网络中出现。如果梯度值大于1,当乘以这些梯度时,会产生一个非常大的值,这称为梯度爆炸问题。

一种解决方法是采用ReLU作为激活函数。另外,使用RNN的改进网络LSTM来解决梯度消失的问题。

LSTM RNN

LSTM是RNN的一种变型,主要用于解决梯度消失问题。只要需要,LSTM就会在记忆中保存信息。实质上是由LSTM替换了RNN单元。

利用LSTM RNN来生成歌词

首先,导入库

import warnings
warnings.filterwarnings('ignore')
import tensorflow as tf
import numpy as np

接着,读取包含歌词的文件

with open("data/ZaynLyrics.txt","r") as f:
    data=f.read()
    data=data.replace('\n','')
    data = data.lower()

可以观察数据内容:

print(data[:50])

然后,在all_chars变量中保存所有字符:

all_chars = list(set(data))

将唯一字符的个数保存在unique_chars中:

unique_chars = len(all_chars)

现在,创建每个字符与其编号之间的对应关系。char_to_ix具有字符到编号的映射,而ix_to_char是编号到字符的映射:

char_to_ix = { ch:i for i,ch in enumerate(all_chars) }
ix_to_char = { i:ch for i,ch in enumerate(all_chars) }

例如:

char_to_ix ['e']
9
ix_to_char[9]
e

接下来,定义一个generate_batch函数来生成输入和目标值。目标值就是移位 i i i次后的输入值

例如:如果 input = [12,13,24] 且移位值为1,则目标值为 [13,24]

def generate_batch(seq_length,i):
    inputs = [char_to_ix[ch] for ch in data[i:i+seq_length]]
    targets = [char_to_ix[ch] for ch in data[i+1:i+seq_length+1]] 
    inputs = np.array(inputs).reshape(seq_length, 1)
    targets = np.array(targets).reshape(seq_length, 1)
    return inputs, targets

接着,定义句子长度、学习率和节点个数,即神经元个数:

seq_length = 25 
learning_rate = 0.1
num_nodes = 300

现在开始构建LSTM RNN。 Tensorflow提供了一种构建LSTM单元的BasicLSTMCell() 函数,但需要指定LSTM单元中的单元个数和所采用的激活函数类型。
在此,创建一个LSTM单元,并利用tf.nn.dynamic_rnn函数来构建包含这一单元的RNN,该函数可返回输出值和状态值:

def build_rnn(x):
        cell= tf.contrib.rnn.BasicLSTMCell(num_units=num_nodes, activation=tf.nn.relu)
        outputs, states = tf.nn.dynamic_rnn(cell, x, dtype=tf.float32)
        return outputs,states

接着,创建输入X和目标Y的占位符

X=tf.placeholder(tf.float32,[None,1])
Y=tf.placeholder(tf.float32,[None,1])

将X和Y转换为int型

X=tf.cast(X,tf.int32)
Y=tf.cast(Y,tf.int32)

另外,还需创建对于X和Y的onehot表示,具体如下:

X_onehot=tf.one_hot(X,unique_chars)
Y_onehot=tf.one_hot(Y,unique_chars)

调用build_rnn函数,可由RNN得到输出和状态:

outputs,states=build_rnn(X_onehot)

输出转置:

outputs=tf.transpose(outputs,perm=[1,0,2])

初始化权重和偏置:

W=tf.Variable(tf.random_normal((num_nodes,unique_chars),stddev=0.001))
B=tf.Variable(tf.zeros((1,unique_chars)))

将输出乘以权重并加上偏置来计算最终的输出:

Ys=tf.matmul(outputs[0],W)+B

接着,应用Softmax激活函数,得到概率:

prediction = tf.nn.softmax(Ys)

计算cross_entropy

cross_entropy=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y_onehot,logits=Ys))

目标是使得损失最小化,因此,需反向传播网络并执行梯度下降:

optimiser = tf.train.GradientDescentOptimizer(learning_rate=learning_rate).minimize(cross_entropy)

在此,定义一个称为predict的帮助函数,用于根据RNN模型得到下一个预测字符的编号:

def predict(seed,i):
    x=np.zeros((1,1))
    x[0][0]= seed
    indices=[]
    for t in range(i):
        p=sess.run(prediction,{X:x})
        index = np.random.choice(range(unique_chars), p=p.ravel())
        x[0][0]=index
        indices.append(index)
    return indices

设置batch_size、批次数和epochs个数以及shift值,来生成一个批次:

batch_size=100
total_batch=int(total_chars//batch_size)
epoch=1000
shift=0

最后,启动Tensorflow模型,来构建损失:

init=tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)
    for epoch in range(epoch):
        print("Epoch {}:".format(epoch))
        if shift + batch_size+1 >= len(data): 
            shift =0
            
         # 通过generate_batch函数获得每个批量的输入目标
         # 该函数是通过 shifts 值将输入移位并形成目标值的
        for i in range(total_batch):
            inputs,targets=generate_batch(batch_size,shift)
            shift += batch_size
            
            # 计算损失
            if(i%100==0):
                loss=sess.run(cross_entropy,feed_dict={X:inputs, Y:targets}) 
                
                # 获得predict函数得到下一预测字符的符号
                index =predict(inputs[0],200)
                
                # 将该序号传给ix_to_char字典,并得到char
                txt = ''.join(ix_to_char[ix] for ix in index)
                print('Iteration %i: '%(i))
                print ('\n %s \n' % (txt, ))
                
            sess.run(optimiser,feed_dict={X:inputs,Y:targets})

7.10 Generating Song Lyrics Using LSTM RNN.ipynb

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值