从无到有RNN

xiaoxiao2021-02-28  142

这段时间看了一些关于循环神经网络的资料,也了解了一些框架中对RNN的封装。觉得还是要在非深度学习框架下用最直接的方式过一遍RNN的例子才能更好地理解RNN和其中用到的一系列算法(bptt,sgd,adam等)这篇博客主要结合一个简单的例子理解RNN内部的结构以及bptt算法的过程。还有个小心思,最近CNN相当的火爆,我不信RNN在序列问题上干不过CNN?(开玩笑的。。。其实想有些创新,就要了解原理)

参考资料

http://www.wildml.com/2015/09/recurrent-neural-networks-tutorial-part-1-introduction-to-rnns/ http://cs231n.github.io/neural-networks-case-study/ https://gist.github.com/karpathy/d4dee566867f8291f086 http://cs231n.github.io/optimization-2/#staged https://www.youtube.com/watch?v=cO0a0QYmFm8&feature=youtu.be&list=PLlJy-eBtNFt6EuMxFYRiNRS07MCWN5UIA&t=836 http://blog.csdn.net/linmingan/article/details/50958304 http://www.wildml.com/2015/10/recurrent-neural-networks-tutorial-part-3-backpropagation-through-time-and-vanishing-gradients/ http://www.wildml.com/2015/10/recurrent-neural-network-tutorial-part-4-implementing-a-grulstm-rnn-with-python-and-theano/

循环神经网络

循环神经网络的思想是利用序列信息。在传统神经网络中,输入之间相互独立。但是对于很多任务,这不是一个好的想法。如果要预测一个句子中下一个出现的单词,我们希望知道哪些词语已经出现过。循环神经网络被叫做“循环”,是因为网络对序列中的每个元素采取同样地操作,这些操作要用到前一步计算的结果。另一种对于循环神经网络的理解是网络有个“记忆单元”,可以捕捉到目前为止的信息。理论上循环神经网络可以发挥任意长序列的信息,但是在实践中,网络往往只能得到较短序列的信息。

从图中可以看到循环神经网络展开后的状态。例如,如果一个句子包含5个单词,那这个循环神经网络展开后就会有5层,每层的输入对应一个单词。图中,Xt代表着t时刻的输入。在自然语言处理相关任务中输入往往是单词的词向量表示。

St是t时刻隐藏层的状态。他是网络的记忆单元。他的计算需要当前时刻的输入以及前一时刻隐藏层的状态,可表示为St = F(U*Xt + W*St-1)。F函数通常为tanh或者ReLU。St-1在第一时刻通常被初始化为0。想想也是这个道理,对于一个句子,开始的时候我们一无所知,记忆为0,随着一个个单词的读入,我们逐渐掌握很多信息。

Ot是t时刻的输出。通常有 Ot = softmax(V*St)。其实,并不是每个时刻都需要有输出,需要看具体任务。比如,情感分析只要输出最后一个Ot。机器翻译就需要有不同时刻的输出。

我们需要注意的是: 1)St作为网络t时刻的记忆单元,捕捉之前时刻中的信息。Ot的计算只是依靠t时刻的记忆。 2)U,V,W作为网络的参数,需要经过训练得到。并且,U,V,W在时间维度上共享权值。对!没错图中t-1时刻,t时刻,t+1时刻。。。的UVW是同样一组参数。所以,网络的计算过程中,只有每一步的输入是不同的。这样,减少了需要训练的参数的个数。 3) 这篇文章Gradients for an RNN介绍了RNN中梯度的推导过程,我看到过的最值得推荐的资料!!

训练循环神经网络

训练循环神经网络和训练普通的神经网络相似,但是需要用到bptt算法。因为循环神经网络的参数是随时间维度共享的,所以每次输出的梯度不仅需要当前的步骤还需要之前的步骤。例如为了求4时刻的梯度,我们需要计算之前3个时刻的梯度。下面语言模型的例子中展示了bptt算法的过程,再详细介绍。

一个用numpy写的rnn语言模型例子

1)这个例子的功能是在语料上训练,预测下一个出现的字符。 2)通过这个例子可以了解bptt算法实现过程。

代码:

#coding:utf-8 import numpy as np data = open('input.txt','r').read() #得到data中的所有字符 chars = list(set(data)) data_size, vocab_size = len(data), len(chars) print('data has %d characters, %d unique.' % (data_size, vocab_size)) #字典,字符和数字一一对应,输入时用数字代表字符 char_to_ix = {ch:i for i,ch in enumerate(chars)} ix_to_char = {i:ch for i,ch in enumerate(chars)} #超参数 #隐层大小 hidden_size = 128 #句子长度 seq_length = 25 #学习率 learning_rate = 1e-1 #模型参数 #输入到隐层矩阵,形状为(hidden_size, vocab_size)为了方便计算,下同 Wxh = np.random.randn(hidden_size, vocab_size)*0.01 #隐层到隐层矩阵 Whh = np.random.randn(hidden_size, hidden_size)*0.01 #隐层到输出矩阵 Why = np.random.randn(vocab_size, hidden_size)*0.01 #隐层及输出层偏置 bh = np.zeros((hidden_size, 1)) by = np.zeros((vocab_size, 1)) #最重要部分,损失函数,包括前向传播,后向计算误差,bptt算法 def lossFun(inputs, targets, hprev): xs, hs, ys, ps = {}, {}, {}, {} hs[-1] = np.copy(hprev) loss = 0 #前向计算预测值 for t in range(len(inputs)): xs[t] = np.zeros((vocab_size, 1)) xs[t][inputs[t]] = 1 ''' 公式 h[t] = tanh(Wxh * x[t] + Whh * h[t-1] + bh) y[t] = Why * h[t] + by p[t] = sigmoid(y[t]) loss = -targets*log(p) ''' hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh, hs[t-1]) + bh) ys[t] = np.dot(Why, hs[t]) + by ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t])) loss += -np.log(ps[t][targets[t],0]) #后向进行梯度下降 #d开头变量用于存储各自变量的梯度 dWxh, dWhh, dWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why) dbh, dby = np.zeros_like(bh), np.zeros_like(by) dhnext = np.zeros_like(hs[0]) for t in reversed(range(len(inputs))): dy = np.copy(ps[t]) dy[targets[t]] -= 1 dWhy += np.dot(dy, hs[t].T) dby += dy dh = np.dot(Why.T, dy) + dhnext dhraw = (1 - hs[t]*hs[t])*dh dbh += dhraw dWxh += np.dot(dhraw, xs[t].T) dWhh += np.dot(dhraw, hs[t-1].T) dhnext = np.dot(Whh.T, dhraw) #防止梯度爆炸,控制梯度在-5到5之间 for dparam in [dWxh, dWhh, dWhy, dbh, dby]: np.clip(dparam, -5, 5, out=dparam) return loss, dWxh, dWhh, dWhy, dbh, dby, hs[len(inputs)-1] #用于测试 #根据当前输入字符,产生后面的200个字符 def sample(h, seed_ix, n): x = np.zeros((vocab_size), 1)) x[seed_ix] = 1 ixes = [] for t in xrange(n): h = np.tanh(np.dot(Wxh, x) + np.dot(Whh, h) + bh) y = np.dot(Why, h) + by p = np.exp(y) / np.sum(np.exp(y)) ix = np.random.choice(range(vocab_size), p=p.ravel()) x = np.zeros((vocab_size, 1)) x[ix] = 1 ixes.append(ix) return ixes n, p = 0, 0 #用于Adagrad算法的变量 mWxh, mWhh, mWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why) mbh, mby = np.zeros_like(bh), np.zeros_like(by) smooth_loss = -np.log(1.0/vocab_size)*seq_length while True: if p+seq_length+1 >= len(data) or n == 0: hprev = np.zeros((hidden_size,1)) p = 0 inputs = [char_to_ix[ch] for ch in data[p:p+seq_length]] targets = [char_to_ix[ch] for ch in data[p+1:p+seq_length+1]] if n%100 == 0: sample_ix = sample(hprev, inputs[0], 200) txt = ''.join(ix_to_char[ix] for ix in sample_ix) print('----\n %s \n----' % (txt, )) loss, dWxh, dWhh, dWhy, dbh, dby, hprev = lossFun(inputs, targets, hprev) smooth_loss = smooth_loss * 0.999 + loss * 0.001 if n%100 == 0: print('iter %d, loss: %f'%(n,smooth_loss)) #Adagrad算法,随机梯度下降法的改进 for param, dparam, mem in zip([Wxh, Whh, Why, bh, by], [dWxh, dWhh, dWhy, dbh, dby], [mWxh, mWhh, mWhy, mbh, mby]): mem += dparam * dparam param += -learning_rate * dparam / np.sqrt(mem + 1e-8) p += seq_length n += 1

实验结果

例子中bptt算法公式过程

整个公式一气呵成!

总结

这篇文章简单介绍了循环神经网络的基本结构和算法。通过一个小例子,看懂了RNN中的很多细节,然后这只是基础的部分。我们都知道原始的RNN梯度消失问题比较严重,他的变种lstm,gru能够稍微减轻这个问题。还有很多要研究的问题。最重要的应当是RNN的应用问题。后面博主会继续关注~

转载请注明原文地址: https://www.6miu.com/read-24119.html

最新回复(0)