RNN介绍
神经网络包含输入层、隐层、输出层,通过激活函数控制输出,层与层之间通过权值连接。激活函数是事先确定好的,那么神经网络模型通过训练“学“到的东西就蕴含在“权值“中。
RNN与普通神经网络最大的不同就是建立了时序和状态的概念,即某个时刻的输出依赖与前一个状态和当前的输入,所以RNN可以用于处理序列数据。
展开之后

箭头上的字母代表权重矩阵,也就是不同层之间的连接。x代表输入序列,h代表状态,也就是时刻,o代表输出。
可以看到,整个网络是共享W、U、V三个矩阵的,这样的设计主要有两个好处:
(1)收敛快。参数越少越容易收敛
(2)可以想要有多少层就有多少层。因为权重就是固定的这三个,所以每次训练或者测试的数据长度不需要相等,比较灵活。
这是多输出对多输出的,RNN还有很多其他灵活的变体,可以根据不同的场景选择:

前向传播:
对于t时刻:
$$h^{(t)}=f_{w}(W*h^{(t-1)}+U*x^{t}+b)$$
其中是$f_{w}$激活函数,一般来说会选择tanh函数,原因见下面激活函数介绍,b为偏置。
t时刻的输出:
$$o^{(t)}=V*h^{(t)}+c$$
最终模型的预测输出为:
$$y^{(t)}=\sigma(o^{(t)})$$
通常最后的单时刻任务还是分类,所以激活函数$\sigma$选用softmax函数。
所有的y按时刻顺序组织起来就是最终输出。
反向传播:
RNN的反向传播跟普通的神经网络反向传播从本质上来说都是链式求导,没有什么太大的区别。
需要注意的就是权重就共享的,所以梯度需要累加,最后传播到$h_{0}$的时候更新一次就行。
求导并不难,只是堆在一起之后比较复杂,看起来比较吓人,用链式法则一步一步求就好了,这里就不写公式了,编辑比较麻烦。
想看具体怎么反向传播的公式可以直接看代码。
激活函数:

sigmoid函数图像和导数图像

tanh函数的图像和导数图像
二者的函数图像很相似,都把输出压缩在了一个范围之内。他们的导数图像也非常相近,,sigmoid函数的导数范围是(0,0.25],tanh函数的导数范围是(0,1],他们的导数最大都不大于1。这就会导致一个问题,在上面式子累乘的过程中,是一堆小数相乘,会越乘越小,随着时间序列的不断深入,小数的累乘就会导致梯度越来越小直到接近于0,这就是“梯度消失“现象。在较为深层的神经网络中会导致反向传播时梯度消失,梯度消失就意味消失那一层的参数再也不更新,那么那一层隐层就变成了单纯的映射层,毫无意义。但是看起来tanh会比sigmoid消失的更加慢一些,所以这里选用tanh函数。
还有一个原因是sigmoid函数还有一个缺点,Sigmoid函数输出不是零中心对称。sigmoid的输出均大于0,这就使得输出不是0均值,称为偏移现象,这将导致后一层的神经元将上一层输出的非0均值的信号作为输入。关于原点对称的输入和中心对称的输出,网络会收敛地更好。
图像标注:
有了上面的基础,接下来就可以进入正题了。

首先对输入的图像用CNN进行处理,得到降维的特征(这里直接用VGG16的fc7的输出),然后把正确标注和特征送入RNN。
在实际应用中,对单词直接处理比较不方便,所以一般会把单词映射成一个整数,这样就变成了一个分类问题。编码过程由word_embedding_forward函数完成。
好了详细的内容直接上代码吧。
RNN.py就一个类,定义网络结构
1 import numpy as np 2 3 from cs231n.layers import * 4 from cs231n.rnn_layers import * 5 6 class CaptioningRNN(object): 7 """ 8 这个类只是构建RNN的网络结构和定义内部计算 9 具体的权值更新训练过程不在这里实现 10 """ 11 12 def __init__(self, word_to_idx,input_dim=512,wordvec_dim=128, 13 hidden_dim=128,cell_type='rnn',dtype=np.float32): 14 15 ''' 16 - word_to_idx: 字典,存储了单词到整数的映射关系. 17 - input_dim: 单个输入的维度 18 - wordvec_dim: 单词向量的维度. 19 - hidden_dim: 隐层的大小. 20 - cell_type: 'rnn' or 'lstm'. 21 ''' 22 if cell_type not in {'rnn','lstm'}: 23 raise ValueError('Invalid cell type "%s"' %cell_type) 24 25 self.cell_type=cell_type 26 self.dtype=dtype 27 self.word_to_idx=word_to_idx 28 self.idx_to_word={i:w for w,i in word_to_idx.items()} 29 self.params={} 30 31 vocab_size=len(word_to_idx) 32 self._null=word_to_idx['<NULL>'] 33 self._start=word_to_idx.get('<START>',None) 34 self._end=word_to_idx.get('<END>',None) 35 36 #初始化代表每个单词的向量 37 self.params['W_embed']=np.random.randn(vocab_size,wordvec_dim) 38 self.params['W_embed']/=100 39 40 #由CNN的输出向RNN的第一层转换 41 #注意:给的输入数据已经是经过VGG16提取的特征,所以这里没有CNN层 42 self.params['W_proj']=np.random.randn(input_dim,hidden_dim) 43 self.params['W_proj']/=np.sqrt(input_dim) 44 self.params['b_proj']=np.zeros(hidden_dim) 45 46 #初始化RNN的参数,每一层共享参数,所以参数只需要设置一次 47 48 ####################这里的4是什么意思???????????见下回lstm分解 49 dim_mul={'lstm':4,'rnn':1}[cell_type] 50 self.params['Wx']=np.random.randn(wordvec_dim,dim_mul*hidden_dim) 51 self.params['Wx']/=np.sqrt(wordvec_dim) 52 self.params['Wh']=np.random.randn(hidden_dim,dim_mul*hidden_dim) 53 self.params['Wh']/=np.sqrt(hidden_dim) 54 self.params['b']=np.zeros(dim_mul*hidden_dim) 55 56 #得到每个时间点的输出,输出是每个单词的得分 57 self.params['W_vocab']=np.random.randn(hidden_dim,vocab_size) 58 self.params['W_vocab']/=np.sqrt(hidden_dim) 59 self.params['b_vocab']=np.zeros(vocab_size) 60 61 #强制转换,保证每个权重矩阵都是我们设置的数据类型 62 for k,v in self.params.items(): 63 self.params[k]=v.astype(self.dtype) 64 65 def loss(self,features,captions): 66 """ 67 Inputs: 68 - features: 输入的特征,shape (N, D) 69 - captions: Ground-truth; 数组,shape (N, T),N张图片都用T个单词描述 70 71 Returns a tuple of: 72 - loss: Scalar loss 73 - grads: Dictionary of gradients parallel to self.params 74 75 返回loss和梯度(如果是训练过程) 76 77 """ 78 79 #输入的描述和输出的描述对完整的描述有点区别 80 #具体在于输入去掉最后一个单词'<end>' 输出去掉第一个单词'<start>' 81 #因为输入第一个单词的时候就已经需要预测下一个单词了 所以有一个单词的错位 82 captions_in=captions[:,:-1] 83 captions_out=captions[:,1:] 84 85 #每个描述长度有所不同,短的用NULL补齐到T长度,所以NULL不计入loss 86 mask=(captions_out!=self._null) 87 88 W_proj,b_proj=self.params['W_proj'],self.params['b_proj'] 89 W_embed=self.params['W_embed'] 90 Wx,Wh,b=self.params['Wx'],self.params['Wh'],self.params['b'] 91 W_vocab,b_vocab=self.params['W_vocab'],self.params['b_vocab'] 92 93 #前向计算过程: 94 #(1)使用仿射函数将从CNN提取的特征转换到第一个RNN的隐层状态(N,H) 95 #(2)将captions_in从单词转换成向量(N,T,W) 96 #(3)在RNN各个隐层中进行运算,(N,T,H) 97 #(4)在每个时间点计算各个单词的得分(N,T,V) 98 #(5)用softmax计算各时间点的loss 99 # W表示单词向量的维度 V表示单词词库的个数 100 loss,grads=0.0,{} 101 102 #begin 103 104 affine_out,affine_cache=affine_forward(features,W_proj,b_proj) 105 106 word_embedding_out,word_embedding_cache=word_embedding_forward(captions_in,W_embed) 107 108 if self.cell_type=='rnn': 109 rnn_or_lstm_out,rnn_cache=rnn_forward(word_embedding_out,affine_out,Wx,Wh,b) 110 elif self.cell_type=='lstm': 111 rnn_or_lstm_out,lstm_cache=lstm_foward(word_embedding_out,affine_out,Wx,Wh,b) 112 else: 113 raise ValueError('Invalid cell type "%s"' %self.cell_type) 114 115 temporal_affine_out,temporal_affine_cache=temporal_affine_forward(rnn_or_lstm_out,W_vocab,b_vocab) 116 117 ''' 分割线''' 118 loss,dtemporal_affine_out=temporal_softmax_loss(temporal_affine_out,captions_out,mask) 119 120 #反向传播 121 drnn_or_lstm_out,grads['W_vocab'],grads['b_vocab']=temporal_affine_backward(dtemporal_affine_out,temporal_affine_cache) 122 123 if self.cell_type=='rnn': 124 dword_embedding_out,daffine_out,grads['Wx'],grads['Wh'],grads['b']=rnn_backward(drnn_or_lstm_out,rnn_cache) 125 else: 126 dword_embedding_out,daffine_out,grads['Wx'],grads['Wh'],grads['b']=lstm_backward(drnn_or_lstm_out, lstm_cache) 127 128 grads['W_embed']=word_embedding_backward(dword_embedding_out,word_embedding_cache) 129 130 dfeatures,grads['W_proj'],grads['b_proj']=affine_backward(daffine_out,affine_cache) 131 132 #end 133 134 return loss,grads 135 136 137 def sample(self,features,max_len=30): 138 ''' 139 预测 140 RNN与CNN不同,CNN的预测和训练过程没有区别 141 但是RNN不同,训练过程每个时刻都有输入,但是预测的时候根本没有输出怎么办? 142 还好我们有个<start>,可以当作第一个时刻的输入,可以得到第一个时刻的输出,把第一个时刻的输出当作第二个时刻的输入 143 依次类推 144 ''' 145 N,D=features.shape 146 captions=self._null*np.ones((N,max_len),dtype=np.int32) 147 148 W_proj, b_proj = self.params['W_proj'], self.params['b_proj'] 149 W_embed = self.params['W_embed'] 150 Wx, Wh, b = self.params['Wx'], self.params['Wh'], self.params['b'] 151 W_vocab, b_vocab = self.params['W_vocab'], self.params['b_vocab'] 152 153 affine_out,affine_cache=affine_forward(features,W_proj,b_proj) 154 prev_word_idx=[self._start]*N 155 prev_h=affine_out 156 prev_c=np.zeros(prev_h.shape) 157 captions[:,0]=self._start 158 159 for i in range(1,max_len): 160 prev_word_embed=W_embed[prev_word_idx] 161 if self.cell_type=='rnn': 162 next_h,rnn_step_cache=rnn_step_forward(prev_word_embed,prev_h,Wx,Wh,b) 163 elif self.cell_type=='lstm': 164 pass 165 else: 166 raise ValueError('Invalid cell_type "%s"' % self.cell_type) 167 vocab_affine_out,vocab_affine_cache=affine_forward(next_h,W_vocab,b_vocab) 168 captions[:,i]=list(np.argmax(vocab_affine_out,axis=1)) 169 prev_word_idx=captions[:,i] 170 prev_h=next_h 171 172 return captions
更多文章请关注《万象专栏》
转载请注明出处:https://www.wanxiangsucai.com/read/cv15046