循环神经网络RNN,Bidirectional RNN,LSTM,GRU

背景

前馈网络、CNN对序列数据只能采用固定长度作为输入,但是,句子、音频等数据,输入固定长度(虽然可以通过输入填充到固定大小来克服)。这两种网络仍比序列模型RNN表现更差,因为传统模型不理解输入的上下文。

1. 循环神经网络Recurrent Neural Network

在这里插入图片描述

循环神经网络,时间步的每个节点从前前一个节点获取输入,并不断反馈循环。
在这里插入图片描述
表示形式:
a t = f ( h t − 1 , x t ) , g ( x ) = t a n h x , a t = g ( W h h ⋅ h t − 1 + W x h ⋅ x t ) h t = W h y ⋅ a t \mathbf a_t = f(\mathbf h_{t-1}, \mathbf x_t),\\ \mathbf g(\mathbf x) = tanh \mathbf x,\\ \mathbf a_t = g(\mathbf W_{hh} \cdot \mathbf h_{t-1} + \mathbf W_{xh} \cdot \mathbf x_t)\\ \mathbf h_t = \mathbf W_{hy} \cdot \mathbf a_t at=f(ht1,xt),g(x)=tanhx,at=g(Whhht1+Wxhxt)ht=Whyat

2. 长短时神经网络LSTM

在这里插入图片描述

2.1 遗忘门

在这里插入图片描述

f t = σ ( W f ⋅ [ h t − 1 , x t ] + b f ) f_t = \sigma (W_f \cdot [h_{t-1}, x_t] + b_f) ft=σ(Wf[ht1,xt]+bf)
输入的是,当前时刻的信息和历史隐状态的信息,经过sigmoid函数,值靠近0代表遗忘,值靠近1代表保持。

2.2 输入门及更新门

在这里插入图片描述

i t i_t it 是遗忘门类似,值接近0代表遗忘,值接近1代表保持;
C ^ t \hat{C}_{t} C^t为当前输入的细胞状态,经过tanh函数进行压缩.
由sigmoid的输出决定tanh的输出中哪部分信息是重要的。

在这里插入图片描述
值得注意的是,这里记忆状态是使用哈达玛积,不是矩阵相乘,而是相应元素相乘!
在这里插入图片描述

C t C_t Ct 为更新后当前时刻的细胞状态,一部分由之前细胞状态与遗忘门进行点乘操作,加上输入门的信息,这两部分都考虑到了上一时刻的隐层状态 h t − 1 h_{t-1} ht1

疑惑:这里为什么使用 tanh 激活函数? 更新门就是,tanh 激活函数和 sigmoid 激活函数后的矩阵逐位相乘。
在这里插入图片描述
LSTM管理一个内部状态向量,当我们添加某个函数的输出时,其值应该能够增加或减少。 Sigmoid输出总是非负的;该州的价值只会增加。 tanh的输出可以是正的或负的,允许状态的增加和减少。

2.3 输出门

在这里插入图片描述
同理,这里求隐层状态也是用了哈达玛积(element-wise),在这里插入图片描述

当前时刻输出为 o t o_t ot, 与前一时刻学习到的信息 h t − 1 h_{t-1} ht1和 当前时刻信息 x t x_t xt 有关。
输出 o t o_t ot和经过tanh变换的细胞状态 C t C_t Ct,作为 当前时刻的信息 h t h_t ht.

包含细胞状态的都用 t a n h tanh tanh激活函数。

3. 门控循环单元GRU

GRU比LSTM训练时间更加短,同时也可以记住长期依赖。
在这里插入图片描述
z t z_t zt代表更新门操作; r t r_t rt代表重置门操作, h t h_t ht代表 t 时刻隐层信息。

GRU只有更新门 z t z_t zt 和重置门 r t r_t rt,没有输出门和细胞状态。
更新门决定有多少过去信息用来利用,重置门决定多少过去信息丢弃。
思想就是充分考虑前一时刻隐层状态信息 h t − 1 h_{t-1} ht1,和当前时刻的信息 h ^ t \hat{h}_t h^t,这两部分通过权重 z t z_t zt来调节。

一般来说,GRU计算快速,计算精度略低于LSTM。

4. 双向RNN

在这里插入图片描述

双向RNN的出发点是,有时模型需要读取序列数据的上下文来消除歧义,特别是在文本领域。
双向RNN的重复模块是传统RNN,LSTM或者GRU单元,并且有一种在时间上前向学习过程和向后学习过程。

堆叠LSTM

在这里插入图片描述

5. 实践

简单模拟RNN实现,

import copy, numpy as np
np.random.seed(0)

# compute sigmoid nonlinearity
def sigmoid(x):
    output = 1/(1+np.exp(-x))
    return output

# convert output of sigmoid function to its derivative
def sigmoid_output_to_derivative(output):
    return output*(1-output)


# training dataset generation
int2binary = {}
binary_dim = 8

largest_number = pow(2,binary_dim)
binary = np.unpackbits(
    np.array([range(largest_number)],dtype=np.uint8).T,axis=1)
for i in range(largest_number):
    int2binary[i] = binary[i]


# input variables
alpha = 0.1
input_dim = 2
hidden_dim = 16
output_dim = 1


# initialize neural network weights
synapse_0 = 2*np.random.random((input_dim,hidden_dim)) - 1
synapse_1 = 2*np.random.random((hidden_dim,output_dim)) - 1
synapse_h = 2*np.random.random((hidden_dim,hidden_dim)) - 1

synapse_0_update = np.zeros_like(synapse_0)
synapse_1_update = np.zeros_like(synapse_1)
synapse_h_update = np.zeros_like(synapse_h)

# training logic
for j in range(10000):
    
    # generate a simple addition problem (a + b = c)
    a_int = np.random.randint(largest_number/2) # int version
    a = int2binary[a_int] # binary encoding

    b_int = np.random.randint(largest_number/2) # int version
    b = int2binary[b_int] # binary encoding

    # true answer
    c_int = a_int + b_int
    c = int2binary[c_int]
    
    # where we'll store our best guess (binary encoded)
    d = np.zeros_like(c)

    overallError = 0
    
    layer_2_deltas = list()
    layer_1_values = list()
    layer_1_values.append(np.zeros(hidden_dim))
    
    # moving along the positions in the binary encoding
    for position in range(binary_dim):
        
        # generate input and output
        X = np.array([[a[binary_dim - position - 1],b[binary_dim - position - 1]]])
        y = np.array([c[binary_dim - position - 1]]).T

        # hidden layer (input ~+ prev_hidden)
        layer_1 = sigmoid(np.dot(X,synapse_0) + np.dot(layer_1_values[-1],synapse_h))

        # output layer (new binary representation)
        layer_2 = sigmoid(np.dot(layer_1,synapse_1))

        # did we miss?... if so, by how much?
        layer_2_error = y - layer_2
        layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2))
        overallError += np.abs(layer_2_error[0])
    
        # decode estimate so we can print it out
        d[binary_dim - position - 1] = np.round(layer_2[0][0])
        
        # store hidden layer so we can use it in the next timestep
        layer_1_values.append(copy.deepcopy(layer_1))
    
    future_layer_1_delta = np.zeros(hidden_dim)
    
    for position in range(binary_dim):
        
        X = np.array([[a[position],b[position]]])
        layer_1 = layer_1_values[-position-1]
        prev_layer_1 = layer_1_values[-position-2]
        
        # error at output layer
        layer_2_delta = layer_2_deltas[-position-1]
        # error at hidden layer
        layer_1_delta = (future_layer_1_delta.dot(synapse_h.T) + layer_2_delta.dot(synapse_1.T)) * sigmoid_output_to_derivative(layer_1)

        # let's update all our weights so we can try again
        synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)
        synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)
        synapse_0_update += X.T.dot(layer_1_delta)
        
        future_layer_1_delta = layer_1_delta
    

    synapse_0 += synapse_0_update * alpha
    synapse_1 += synapse_1_update * alpha
    synapse_h += synapse_h_update * alpha    

    synapse_0_update *= 0
    synapse_1_update *= 0
    synapse_h_update *= 0
    
    # print out progress
    if(j % 1000 == 0):
        print("Error:" + str(overallError)) 
        print ("Pred:" + str(d))
        print ("True:" + str(c))
        out = 0
        for index,x in enumerate(reversed(d)):
            out += x*pow(2,index)
        print (str(a_int) + " + " + str(b_int) + " = " + str(out))
        print ("------------")

Example 2 搭建RNN:

# coding: utf-8

import numpy as np
import tensorflow as tf
from tensorflow.python import debug as tf_debug
from matplotlib import pyplot as plt

'''超参数'''
num_steps = 10
batch_size = 200
num_classes = 2
state_size = 16
learning_rate = 0.1

'''生成数据
就是按照文章中提到的规则,这里生成1000000个
'''
def gen_data(size=1000000):
    X = np.array(np.random.choice(2, size=(size,)))
    Y = []
    '''根据规则生成Y'''
    for i in range(size):   
        threshold = 0.5
        if X[i-3] == 1:
            threshold += 0.5
        if X[i-8] == 1:
            threshold -=0.25
        if np.random.rand() > threshold:
            Y.append(0)
        else:
            Y.append(1)
    return X, np.array(Y)


'''生成batch数据'''
def gen_batch(raw_data, batch_size, num_step):
    raw_x, raw_y = raw_data
    data_length = len(raw_x)
    batch_patition_length = data_length // batch_size                         # ->5000
    data_x = np.zeros([batch_size, batch_patition_length], dtype=np.int32)    # ->(200, 5000)
    data_y = np.zeros([batch_size, batch_patition_length], dtype=np.int32)    # ->(200, 5000)
    '''填到矩阵的对应位置'''
    for i in range(batch_size):
        data_x[i] = raw_x[batch_patition_length*i:batch_patition_length*(i+1)]# 每一行取batch_patition_length个数,即5000
        data_y[i] = raw_y[batch_patition_length*i:batch_patition_length*(i+1)]
    epoch_size = batch_patition_length // num_steps                           # ->5000/5=1000 就是每一轮的大小
    for i in range(epoch_size):   # 抽取 epoch_size 个数据
        x = data_x[:, i * num_steps:(i + 1) * num_steps]                      # ->(200, 5)
        y = data_y[:, i * num_steps:(i + 1) * num_steps]
        yield (x, y)    # yield 是生成器,生成器函数在生成值后会自动挂起并暂停他们的执行和状态(最后就是for循环结束后的结果,共有1000个(x, y))
def gen_epochs(n, num_steps):
    for i in range(n):
        yield gen_batch(gen_data(), batch_size, num_steps)

'''定义placeholder'''
x = tf.placeholder(tf.int32, [batch_size, num_steps], name="x")
y = tf.placeholder(tf.int32, [batch_size, num_steps], name='y')
init_state = tf.zeros([batch_size, state_size])
'''RNN输入'''
x_one_hot = tf.one_hot(x, num_classes)
rnn_inputs = tf.unstack(x_one_hot, axis=1)

'''定义RNN cell'''
with tf.variable_scope('rnn_cell'):
    W = tf.get_variable('W', [num_classes + state_size, state_size])
    b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
    
def rnn_cell(rnn_input, state):
    with tf.variable_scope('rnn_cell', reuse=True):
        W = tf.get_variable('W', [num_classes+state_size, state_size])
        b = tf.get_variable('b', [state_size], initializer=tf.constant_initializer(0.0))
    return tf.tanh(tf.matmul(tf.concat([rnn_input, state],1),W) + b)
'''将rnn cell添加到计算图中'''
state = init_state
rnn_outputs = []
for rnn_input in rnn_inputs:
    state = rnn_cell(rnn_input, state)  # state会重复使用,循环
    rnn_outputs.append(state)
final_state = rnn_outputs[-1]        # 得到最后的state

#cell = tf.contrib.rnn.BasicRNNCell(num_units=state_size)
#rnn_outputs, final_state = tf.contrib.rnn.static_rnn(cell=cell, inputs=rnn_inputs, 
                                                    #initial_state=init_state)
#rnn_outputs, final_state = tf.nn.dynamic_rnn(cell=cell, inputs=rnn_inputs, 
                                                    #initial_state=init_state)


'''预测,损失,优化'''
with tf.variable_scope('softmax'):
    W = tf.get_variable('W', [state_size, num_classes])
    b = tf.get_variable('b', [num_classes], initializer=tf.constant_initializer(0.0))
logits = [tf.matmul(rnn_output, W) + b for rnn_output in rnn_outputs]
predictions = [tf.nn.softmax(logit) for logit in logits]

y_as_list = tf.unstack(y, num=num_steps, axis=1)
losses = [tf.nn.sparse_softmax_cross_entropy_with_logits(labels=label,logits=logit) for logit, label in zip(logits, y_as_list)]
total_loss = tf.reduce_mean(losses)
train_step = tf.train.AdagradOptimizer(learning_rate).minimize(total_loss)


'''训练网络'''
def train_rnn(num_epochs, num_steps, state_size=4, verbose=True):
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        #sess = tf_debug.LocalCLIDebugWrapperSession(sess)
        training_losses = []
        for idx, epoch in enumerate(gen_epochs(num_epochs, num_steps)):
            training_loss = 0
            training_state = np.zeros((batch_size, state_size))   # ->(200, 4)
            if verbose:
                print('\nepoch', idx)
            for step, (X, Y) in enumerate(epoch):
                tr_losses, training_loss_, training_state, _ = \
                    sess.run([losses, total_loss, final_state, train_step], feed_dict={x:X, y:Y, init_state:training_state})
                training_loss += training_loss_
                if step % 100 == 0 and step > 0:
                    if verbose:
                        print('第 {0} 步的平均损失 {1}'.format(step, training_loss/100))
                    training_losses.append(training_loss/100)
                    training_loss = 0
    return training_losses

training_losses = train_rnn(num_epochs=5, num_steps=num_steps, state_size=state_size)
print(training_losses[0])
plt.plot(training_losses)
plt.show()

6. 总结

双向LSTM模型一般具有较高的性能。

Q: LSTM 参数如何计算?
A:设 LSTM 输入维度为 x_dim, 输出维度为 y_dim,那么参数个数 n 为:

n = 4 * ((x_dim + y_dim) * y_dim + y_dim)

Q:pytorch 中LSTM W, b 如何初始化?
均匀分布:
在这里插入图片描述
Q:LSTM如何处理处理变长序列?
以pytorch为例:压缩序列torch.nn.utils.rnn.pack_padded_sequence()

pack_padded_sequence(...)
-- 参数列表:
  -- input:<PAD> 的 batch 序列
  -- lengths: input 中每个序列的长度
  -- batch_first: 如果为Trueinput 必须是 [batch_size, seq_len, input_size]
  -- enforce_sorted: 如果为True, 那么 input 中的序列需要按照 长度递减排列

-- 返回值:
    一个 PackedSequence 对象

Q: 为什么LSTM解决了梯度消失的问题
A:
梯度消失的原因:在多层网络中,影响梯度大小的因素主要有两个:权重和激活函数的偏导。深层的梯度是多个激活函数偏导乘积的形式来计算,如果这些激活函数的偏导比较小(小于1)或者为0,那么梯度随时间很容易vanishing;相反,如果这些激活函数的偏导比较大(大于1),那么梯度很有可能就会exploding。因而,梯度的计算和更新非常困难。

解决方案:
使用一个合适激活函数,它的梯度在一个合理的范围。LSTM使用gate function,有选择的让一部分信息通过。gate是由一个sigmoid单元和一个逐点乘积操作组成,sigmoid单元输出1或0,用来判断通过还是阻止,然后训练这些gate的组合。所以,当gate是打开的(梯度接近于1),梯度就不会vanish。并且sigmoid不超过1,那么梯度也不会explode。
在这里插入图片描述

在这里插入图片描述
LSTM的效果:1、当gate是关闭的,那么就会阻止对当前信息的改变,这样以前的依赖信息就会被学到。2、当gate是打开的时候,并不是完全替换之前的信息,而是在之前信息和现在信息之间做加权平均。所以,无论网络的深度有多深,输入序列有多长,只要gate是打开的,网络都会记住这些信息。

(‘梯度消失’其实在DNN和RNN中意义不一样,DNN中梯度消失指的是误差无法传递回浅层,导致浅层的参数无法更新;而RNN中的梯度消失是指较早时间步所贡献的更新值,无法被较后面的时间步获取,导致后面时间步进行误差更新的时候,采用的只是附近时间步的数据。)
在这里插入图片描述
如果根据BPTT把梯度的结构展开,会包含连乘项:
在这里插入图片描述
在这里插入图片描述
该值范围在0~1之间,但是在实际参数更新中,可以通过控制bias比较大,使得该值接近于1;在这种情况下,即使通过很多次连乘的操作,梯度也不会消失,仍然可以保留"长距"连乘项的存在。即总可以通过选择合适的参数,在不发生梯度爆炸的情况下,找到合理的梯度方向来更新参数,而且这个方向可以充分地考虑远距离的隐含层信息的传播影响。

RNN:
在这里插入图片描述


最近开通了个公众号,主要分享深度学习相关内容,包含NLP,推荐系统,风控等算法相关的内容,感兴趣的伙伴可以关注下。
在这里插入图片描述
公众号相关的学习资料会上传到QQ群596506387,欢迎关注。


reference:

  1. towards 几种RNN讲解
  2. blog rnn讲解;
  3. 徒手写递归神经网络;
  4. RNN-LSTM循环神经网络-Tensorflow实现
  5. lstm 使用 tanh 激活函数原因
  6. Understanding LSTM from the code;
  7. LSTM的神经元个数
  8. pytorch中如何处理RNN输入变长序列padding;
  9. Illustrated Guide to LSTM’s and GRU’s: A step by step explanation;
  10. 作者:东东;
  11. LSTM如何来避免梯度弥散和梯度爆炸? - superbrother的回答 - 知乎
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rosefunR

你的赞赏是我创作的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值