课程作业原地址:CS231n Assignment 3
作业及整理:@张礼俊 && @Molly && @寒小阳
时间:2018年2月。
出处:http://blog.csdn.net/han_xiaoyang/article/details/79316411

1. 问题背景

在问题1里,我们要训练一个循环神经网络(Recurrent neural networks)来生成一个图片的文字注释(captions)。问题2中,用以长短时记忆单元(Long-short term memory,LSTM)为基础的循环神经网络来完成同样的任务。
我们将用到的数据集是微软的COCO数据集,该数据集是测试为图片加文字注释算法的标准数据集。在该数据集中有大概80000张训练图片和40000张测试图片,每张图片在Amazon Mechanical Turk上征集了五个志愿者来手动写文字说明。
我们可以运行问题1的前几个单元来对我们将要应用的数据有个直观的概念,要注意一点的是之后的处理中我们不会再碰到原始图像,该问题已经为我们提取了图像特征。

2. 循环神经网络

问题1要求我们实现一个循环神经网络。这一部分讲述了问题1的前半部分代码。
循环神经网络是一类处理序列数据的神经网络。在本题用到的循环神经网络的架构如下图所示:
image

1)单步前向传播(RNN step forward)

我们要实现的第一段代码是单步前向传播。对于每一层神经网络,或每一个时刻,我们输入一个隐藏状态ht−1h_{t-1}ht1(上一层神经网络的输出),一个外部输入xtx_txt;之后得到下一个隐藏状态hth_{t}ht,以及该时刻的输出yty_{t}yt。对应的数学表达式为
KaTeX parse error: No such environment: equation at position 8: \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \begin{aligned…
bbb为偏差值。
作业中,我们要在rnn_layers.py文件下实现如下函数:

def rnn_step_forward(x, prev_h, Wx, Wh, b):"""输入:- x: 外部输入数据, 维度 (N, D).- prev_h: 上一个时刻的隐藏状态, 维度 (N, H)- Wx: x对应的权值矩阵, 维度 (D, H)- Wh: 隐藏状态h对应的权值矩阵, 维度 (H, H)- b: 偏差值 shape (H,)输出:- next_h: 下一个时态的隐藏状态, 维度 (N, H)- cache: 计算梯度反向传播时需要用到的变量."""temp1 = np.dot(x,Wx)temp2 = np.dot(prev_h,Wh)cache=(x,prev_h,Wx,Wh,temp1+temp2+b)next_h = np.tanh(temp1+temp2+b)return next_h, cache

单层的前向传播并不难,实现的时候只要熟练使用numpy底下的矩阵运算即可。一个要注意的点是这里的激活函数是作用于向量中的每一个元素(element wise)。

2)梯度单步反向传播(RNN step backward)

可以说几乎所有训练神经网络的算法都是基于梯度下降(gradient descent)的,所以如何获得每个节点,以及相应的参数对应的梯度至关重要。正如之前的作业中训练神经网络时做的,求梯度反向传播其实只是在不断的应用求导的链式法则。假设最终的损失函数为EEE,在进行反向传播时,我们得到了上一层传来的梯度dEdht\frac{dE}{dh_t}dhtdE,我们需要计算dEdht−1\frac{dE}{dh_{t-1}}dht1dE,dEdxt\frac{dE}{dx_t}dxtdE,dEdWxh\frac{dE}{dW_{xh}}dWxhdE,dEdWhh\frac{dE}{dW_{hh}}dWhhdEdEdb\frac{dE}{db}dbdE。还记得前向传播的公式为
$$
\begin{equation*}

\begin{aligned}h &=f_w(h_{t-1},x_t)\\&=\tanh(W_{hh}h_{t-1}+W_{xh}x_t+b)
\end{aligned}

\end{equation*}
KaTeX parse error: Can't use function '$' in math mode at position 4: 假设$̲a=W_{hh}h_{t-1}…
\begin{equation*}

\begin{aligned}\frac{dE}{dh_{t-1}}&=\frac{dE}{dh_{t}}\frac{dh_{t}}{dh_{t-1}}  \\&=\frac{dE}{dh_{t}}\frac{dh_t}{da}\frac{da}{dh_{t-1}}
\end{aligned}

\end{equation*}
$$

dhtda\frac{dh_t}{da}dadht就是在对激活函数求导。dadht−1\frac{da}{dh_{t-1}}dht1da就是权值矩阵WhhW_{hh}Whh
要注意一点的是,为了更易于阐明概念,上式做了一些简化,dEdht\frac{dE}{dh_{t}}dhtdEdEdht\frac{dE}{dh_{t}}dhtdE都是向量,他们的相乘是逐项(element wise)相乘。之后与dadht−1\frac{da}{dh_{t-1}}dht1da相乘,则是一般的向量乘矩阵,编程时注意对应的维度就好。
我们可以用相似的思路获得dEdxt\frac{dE}{dx_t}dxtdE,dEdWxh\frac{dE}{dW_{xh}}dWxhdE,dEdWhh\frac{dE}{dW_{hh}}dWhhdEdEdb\frac{dE}{db}dbdE的计算方式。
作业中,我们要实现如下函数:

def rnn_step_backward(dnext_h, cache):"""输入:- dnext_h: 下一层传来的梯度- cache: 前向传播存下来的值输出- dx: 输入x的梯度, 维度(N, D)- dprev_h: 上一层隐藏状态的梯度, 维度(N, H)- dWx: 权值矩阵Wxh的梯度, 维度(D, H)- dWh: 权值矩阵Whh的梯度, 维度(H, H)- db: 偏差值b的梯度,维度(H,)"""x=cache[0]h=cache[1]Wx=cache[2]Wh=cache[3]# cache[4]对应着公式中的acacheD=cache[4]N,H=h.shape# 计算激活函数的导数temp = np.ones((N,H))-np.square(np.tanh(cacheD))delta = np.multiply(temp,dnext_h)# 计算x的梯度tempx = np.dot(Wx,delta.T)dx=tempx.T# h的提督temph = np.dot(Wh,delta.T)dprev_h=temph.T# Wxh的梯度dWx = np.dot(x.T,delta)# WhhdWh = np.dot(h.T,delta)# b的梯度tempb = np.sum(delta,axis=0)db=tempb.Treturn dx, dprev_h, dWx, dWh, db

3)前向传播(RNN forward)

我们完成了单步前向传播之后,对于整个循环神经网络的前向传播过程就是单步前向传播的循环,而且在本题中,每一层神经网络都共享了参数WxhW_{xh}WxhWhhW_{hh}Whhbbb。对应的代码如下:

def rnn_forward(x, h0, Wx, Wh, b):"""输入:- x: 整个序列的输入数据, 维度 (N, T, D).- h0: 初始隐藏层, 维度 (N, H)- Wx: 权值矩阵Wxh, 维度 (D, H)- Wh: 权值矩阵Whh, 维度 (H, H)- b: 偏差值,维度 (H,)输出:- h: 整个序列的隐藏状态, 维度 (N, T, H).- cache: 反向传播时需要的变量"""N,T,D=x.shapeN,H=h0.shapeprev_h=h0# 之前公式中对应的ah1=np.empty([N,T,H])# 隐藏状态h的序列h2=np.empty([N,T,H])# 滞后h一个时间点h3=np.empty([N,T,H])for i in range(0, T):#单步前向传播temp_h, cache_temp = rnn_step_forward(x[:,i,:], prev_h, Wx, Wh, b)#记录下需要的变量h3[:,i,:]=prev_hprev_h=temp_hh2[:,i,:]=temp_hh1[:,i,:]=cache_temp[4]cache=(x,h3,Wx,Wh,h1)return h2, cache

4)梯度反向传播(RNN backward)

和前向传播一样,我们已经有了单步反向传播,编程时反向传播就是单步反向传播的循环。另外因为每一层神经网络都共享了参数WxhW_{xh}WxhWhhW_{hh}Whhbbb,最终的dEdWxh\frac{dE}{dW_{xh}}dWxhdE是每一层计算得到的dEdWxh\frac{dE}{dW_{xh}}dWxhdE的和。另外两个参数也一样。

def rnn_backward(dh, cache):"""输入:- dh: 损失函数关于每一个隐藏层的梯度, 维度 (N, T, H)- cache: 前向传播时存的变量输出- dx: 每一层输入x的梯度, 维度(N, T, D)- dh0: 初始隐藏状态的梯度, 维度(N, H)- dWx: 权值矩阵Wxh的梯度, 维度(D, H)- dWh: 权值矩阵Whh的梯度, 维度(H, H)- db: 偏差值b的梯度,维度(H,)"""x=cache[0]N,T,D=x.shapeN,T,H=dh.shape#初始化dWx=np.zeros((D,H))dWh=np.zeros((H,H))db=np.zeros(H)dout=dhdx=np.empty([N,T,D])dh=np.empty([N,T,H])#当前时刻隐藏状态对应的梯度hnow=np.zeros([N,H])for k in range(0, T):i=T-1-k#我们要注意,除了上一层传来的梯度,我们每一层都有输出,对应的误差函数也会传入梯度hnow=hnow+dout[:,i,:]cacheT=(cache[0][:,i,:],cache[1][:,i,:],cache[2],cache[3],cache[4][:,i,:])#单步反向传播dx_temp, dprev_h, dWx_temp, dWh_temp, db_temp = rnn_step_backward(hnow, cacheT)hnow=dprev_hdx[:,i,:]=dx_temp#将每一层共享的参数对应的梯度相加dWx=dWx+dWx_tempdWh=dWh+dWh_tempdb=db+db_tempdh0=hnowreturn dx, dh0, dWx, dWh, db

至此我们完成了图一所示的循环神经网络的训练。该训练算法也被称为Back-Propagation Through Time(BPTT),是最典型的训练循环神经网络的算法之一。
我们可以试着跑一下上面的代码,然后可以通过问题中每一小段之后的error测试。

3. 长短时记忆单元(Long-Short Term Memory, LSTM)

这一部分讲述问题2中关于长短时记忆单元的代码。
在训练上一部分里实现的循环神经网络时,我们往往会碰到随着反向传播层数的增加,梯度越来越小,或者梯度越来越大的问题。为了解决这个问题,就有了以LSTM为基础单元的神经。一个LSTM单元如下图所示:
image

1)单步前向传播(LSTM step forward)

和一般的循环神经网络相似,在LSTM中,每一步我们会得到一个输入xt∈RDx_t\in \mathbb{R}^DxtRD,之前的隐藏状态ht−1∈RHh_{t-1}\in\mathbb{R}^Hht1RH。除此之外,LSTM还保存每一个H维的单元状态,所以我们还会收到之前单元的状态ct−1∈RHc_{t-1}\in\mathbb{R}^Hct1RH。LSTM的参数则是输入到隐藏层权值矩阵Wx∈R4H×DW_x\in\mathbb{R}^{4H\times D}WxR4H×D,一个隐藏层到隐藏层的权值矩阵Wh∈R4H×HW_h\in\mathbb{R}^{4H\times H}WhR4H×H和偏差向量b∈R4Hb\in\mathbb{R}^{4H}bR4H

每一步,我们先计算一个激活向量a∈R4Ha\in\mathbb{R}^{4H}aR4Ha=Wxxt+Whht−1+ba=W_xx_t+W_hh_{t-1}+ba=Wxxt+Whht1+b。然后我们该向量分成aia_iai,afa_faf,aoa_oao,ag∈RHa_g\in\mathbb{R}^HagRH四个子向量。其中aia_iai是向量aaa的前HHH个元素,afa_fafaaa之后的HHH个元素,以此类推。然后我们如下计算计算输入门i∈RHi\in\mathbb{R}^HiRH,遗忘门f∈RHf\in\mathbb{R}^HfRH,输出门o∈RHo\in\mathbb{R}^HoRH和块输入g∈RHg\in\mathbb{R}^HgRH
$$
\begin{equation*}

i=\sigma(a_i)\quad f=\sigma(a_f)\quad o=\sigma(a_o)\quad g=\tanh(a_g)

\end{equation*}
KaTeX parse error: Can't use function '$' in math mode at position 4: 其中$̲\sigma$时Sigmoid…
\begin{equation*}

c_t=f\odot c_{t-1}+i\odot g \qquad h_t=o\odot \tanh{c_t}

\end{equation*}
$$
其中⊙\odot表示逐项相乘。
至此,我们得到了LSTM每一步前向传播的规则,在代码中只要按照公式编写即可。

def lstm_step_forward(x, prev_h, prev_c, Wx, Wh, b):"""输入:- x: 输入数据, 维度 (N, D)- prev_h: 上一层隐藏状态, 维度 (N, H)- prev_c: 上一层单元状态, 维度 (N, H)- Wx: 输入到隐藏层的权值矩阵, 维度 (D, 4H)- Wh: 隐藏层到隐藏层的权值矩阵, 维度 (H, 4H)- b: 偏差, 维度 (4H,)输出:- next_h: 下一个隐藏状态, 维度 (N, H)- next_c: 下一个单元状态, 维度 (N, H)- cache: 反向传播时需要的变量."""N,H=prev_h.shapeA= x.dot(Wx)+prev_h.dot(Wh)+bai=A[:,0:H]af=A[:,H:2*H]ao=A[:,2*H:3*H]ag=A[:,3*H:4*H]i=sigmoid(ai)f=sigmoid(af)o=sigmoid(ao)g=np.tanh(ag)next_c=np.multiply(f,prev_c)+np.multiply(i,g)next_h=np.multiply(o,np.tanh(next_c))cache=(x,prev_h,prev_c,i,f,o,g,Wx,Wh,next_c,A)return next_h, next_c, cache

2)梯度单步反向传播(LSTM step backward)

虽然同样是利用了求导的链式法则,但是由于多了一条关于ctc_tct传播的路径,LSTM的反向传播会比一般的循环神经网络复杂一些。假设传到当前单元的导数为dEdht\frac{dE}{dh_t}dhtdEdEdct\frac{dE}{dc_t}dctdE,我们先讨论如何计算dEdct−1\frac{dE}{dc_{t-1}}dct1dEdEdct−1\frac{dE}{dc_{t-1}}dct1dE可以由两条路径产生,一条是通过dEdct\frac{dE}{dc_{t}}dctdE,一条是通过dEdht\frac{dE}{dh_{t}}dhtdE.
$$
\begin{equation*}

\begin{aligned}
\frac{dE}{dc_{t-1}}&=\frac{dE}{dh_{t}}\frac{dh_t}{dc_{t-1}}+\frac{dE}{dc_{t}}\frac{dc_t}{dc_{t-1}}\\
&=\frac{dE}{dh_{t}}\frac{dh_t}{dc_{t}}\frac{dc_t}{dc_{t-1}}+\frac{dE}{dc_{t}}\frac{dc_t}{dc_{t-1}}
\end{aligned}

\end{equation*}
$$

这里dhtdct=odtanh⁡ctdct\frac{dh_t}{dc_{t}}=o \frac{d\tanh{c_t}}{dc_{t}}dctdht=odctdtanhctdctdct−1=f\frac{dc_t}{dc_{t-1}}=fdct1dct=f。这里的所有乘法都是逐项(element wise)相乘。为了计算关于dEdht−1\frac{dE}{dh_{t-1}}dht1dE等梯度,我们先要得到到i,f,g,oi,f,g,oi,f,g,o这些节点的梯度。到ooo的梯度易得:
$$
\begin{equation*}

\frac{dE}{do}=\frac{dE}{dh_{t}}\frac{dh}{do}

\end{equation*}
KaTeX parse error: Can't use function '$' in math mode at position 4: 其中$̲\frac{dh}{do}=\…
\begin{equation*}

\begin{aligned}
\frac{dE}{df}&=\frac{dE}{dh_t}\frac{dh_t}{df}+\frac{dc_t}{df}\\&=\frac{dE}{dh_t}\frac{dh_t}{dc_t}\frac{dc_t}{df}+\frac{dc_t}{df}
\end{aligned}

\end{equation*}
$$

其中dctdf=ct−1\frac{dc_t}{df}=c_{t-1}dfdct=ct1
iiiggg的梯度计算过程相似,下面以计算dEdi\frac{dE}{di}didE为例:

$$
\begin{equation*}

\begin{aligned}
\frac{dE}{di}&=\frac{dE}{dh_t}\frac{dh_t}{di}+\frac{dc_t}{di}\\&=\frac{dE}{dh_t}\frac{dh_t}{dc_t}\frac{dc_t}{di}+\frac{dc_t}{di}
\end{aligned}

\end{equation*}
$$

其中dctdi=g\frac{dc_t}{di}=gdidct=g
o,g,i,fo,g,i,fo,g,i,fao,ag,ai,aga_o,a_g,a_i,a_gao,ag,ai,ag通告激活函数而得,所以我们有:
$$
\begin{equation*}

\frac{dE}{da_o}=\frac{dE}{do}\frac{do}{da_o} \qquad \frac{dE}{da_g}=\frac{dE}{dg}\frac{dg}{da_g}

\end{equation*}
$$

$$
\begin{equation*}

\frac{dE}{da_i}=\frac{dE}{di}\frac{di}{da_i}\qquad \frac{dE}{da_f}=\frac{dE}{df}\frac{df}{da_f}

\end{equation*}
$$

dEdai,dEdai,dEdai,dEdai​\frac{dE}{da_i},\frac{dE}{da_i},\frac{dE}{da_i},\frac{dE}{da_i}​daidE,daidE,daidE,daidE相连,我们得到dEda​\frac{dE}{da}​dadE。至此,之后求dEdht−1​\frac{dE}{dh_{t-1}}​dht1dEdEdWhh​\frac{dE}{dW_{hh}}​dWhhdE等梯度的步骤和之前求一般循环神经网络反向传播公式时相同,这里略去。注意以上的乘法都为逐项相乘。
最后,逐一将上面求梯度的步骤用代码实现:

def lstm_step_backward(dnext_h, dnext_c, cache):"""输入:- dnext_h: 下一层传来关于h的梯度, 维度 (N, H)- dnext_c: 下一层传来关于c的梯度, 维度 (N, H)- cache: 前向传播存的变量输出- dx: 输入x的梯度, 维度(N, D)- dprev_h: 上一层隐藏状态的梯度, 维度(N, H)- dprev_c: 上一层单元状态的梯度, 维度(N, H)- dWx: 权值矩阵Wxh的梯度, 维度(D, 4H)- dWh: 权值矩阵Whh的梯度, 维度(H, 4H)- db: 偏差值b的梯度,维度(4H,)"""#提取cache中的变量N,H=dnext_h.shapef=cache[4]o=cache[5]i=cache[3]g=cache[6]nc=cache[9]prev_c=cache[2]prev_x=cache[0]prev_h=cache[1]A=cache[10]ai=A[:,0:H]af=A[:,H:2*H]ao=A[:,2*H:3*H]ag=A[:,3*H:4*H]Wx=cache[7]Wh=cache[8]#计算到c_t-1的梯度dc_c=np.multiply(dnext_c,f)dc_h_temp=np.multiply(dnext_h,o)temp = np.ones_like(nc)-np.square(np.tanh(nc))temp2=np.multiply(temp,f)dprev_c=np.multiply(temp2,dc_h_temp)+dc_c#计算(dE/dh)(dh/dc)dc_from_h=np.multiply(dc_h_temp,temp)dtotal_c=dc_from_h+dnext_c#计算到o,f,i,g的梯度tempo=np.multiply(np.tanh(nc),dnext_h)tempf=np.multiply(dtotal_c,prev_c)tempi=np.multiply(dtotal_c,g)tempg=np.multiply(dtotal_c,i)#计算到ao,ai,af,ag的梯度tempao=np.multiply(tempo,np.multiply(o,np.ones_like(o)-o))tempai=np.multiply(tempi,np.multiply(i,np.ones_like(o)-i))tempaf=np.multiply(tempf,np.multiply(f,np.ones_like(o)-f))dtanhg = np.ones_like(ag)-np.square(np.tanh(ag))tempag=np.multiply(tempg,dtanhg)#计算各参数的梯度TEMP=np.concatenate((tempai,tempaf,tempao,tempag),axis=1)dx=TEMP.dot(Wx.T)dprev_h=TEMP.dot(Wh.T)xt=prev_x.TdWx=xt.dot(TEMP)ht=prev_h.TdWh=ht.dot(TEMP)db=np.sum(TEMP,axis=0).Treturn dx, dprev_h, dprev_c, dWx, dWh, db

3)前向传播(LSTM forward)

我们已经得到了LSTM单步前向传播,和一般循环神经网络一样,这里只要写个循环。

def lstm_forward(x, h0, Wx, Wh, b):"""输入:- x: 整个序列的输入数据, 维度 (N, T, D).- h0: 初始隐藏层, 维度 (N, H)- Wx: 权值矩阵Wxh, 维度 (D, H)- Wh: 权值矩阵Whh, 维度 (H, H)- b: 偏差值,维度 (H,)输出:- h: 整个序列的隐藏状态, 维度 (N, T, H).- cache: 反向传播时需要的变量"""N,T,D=x.shapeN,H=h0.shapeprev_h=h0#以下的变量为反向传播时所需h3=np.empty([N,T,H])h4=np.empty([N,T,H])I=np.empty([N,T,H])F=np.empty([N,T,H])O=np.empty([N,T,H])G=np.empty([N,T,H])NC=np.empty([N,T,H])AT=np.empty([N,T,4*H])h2=np.empty([N,T,H])prev_c=np.zeros_like(prev_h)for i in range(0, T):h3[:,i,:]=prev_hh4[:,i,:]=prev_c#单步前向传播next_h, next_c, cache_temp = lstm_step_forward(x[:,i,:], prev_h, prev_c, Wx, Wh, b)prev_h=next_hprev_c=next_ch2[:,i,:]=prev_hI[:,i,:]=cache_temp[3]F[:,i,:]=cache_temp[4]O[:,i,:]=cache_temp[5]G[:,i,:]=cache_temp[6]NC[:,i,:]=cache_temp[9]AT[:,i,:]=cache_temp[10]cache=(x,h3,h4,I,F,O,G,Wx,Wh,NC,AT)    return h2, cache

4)梯度反向传播(LSTM backward)

和之前一样,关于单步反向传播的循环


def lstm_backward(dh, cache):"""输入:- dh: 损失函数关于每一个隐藏层的梯度, 维度 (N, T, H)- cache: 前向传播时存的变量输出- dx: 每一层输入x的梯度, 维度(N, T, D)- dh0: 初始隐藏状态的梯度, 维度(N, H)- dWx: 权值矩阵Wxh的梯度, 维度(D, 4H)- dWh: 权值矩阵Whh的梯度, 维度(H, 4H)- db: 偏差值b的梯度,维度(4H,)"""x=cache[0]N,T,D=x.shapeN,T,H=dh.shapedWx=np.zeros((D,4*H))dWh=np.zeros((H,4*H))db=np.zeros(4*H)dout=dhdx=np.empty([N,T,D])hnow=np.zeros([N,H])cnow=np.zeros([N,H])for k in range(0, T):i=T-1-khnow=hnow+dout[:,i,:]cacheT=(cache[0][:,i,:],cache[1][:,i,:],cache[2][:,i,:],cache[3][:,i,:],cache[4][:,i,:],cache[5][:,i,:],cache[6][:,i,:],cache[7],cache[8],cache[9][:,i,:],cache[10][:,i,:])dx_temp, dprev_h, dprev_c, dWx_temp, dWh_temp, db_temp = lstm_step_backward(hnow, cnow, cacheT)hnow=dprev_hcnow=dprev_cdx[:,i,:]=dx_tempdWx=dWx+dWx_tempdWh=dWh+dWh_tempdb=db+db_tempdh0=hnowreturn dx, dh0, dWx, dWh, db

4. 图片注释(captioning)生成

我们已经完成了一般循环神经网络和以LSTM为基础的循环神经网络的编写。下一步,我们将整合所写的内容,运用到图片注释生成之中。

1)词嵌入(Word embedding)

在深度学习系统中,我们通常将词用向量表示。词表中的每一个词都将和一个向量关联,这些向量则会和系统的其余部分一样进行训练。

def word_embedding_forward(x, W):"""Inputs:输入:- x: 维度为(N,T)的整数列,每一项是相应词汇对应的索引。- W: 维度为(V,D)权值矩阵,V是词表的大小,每一列对应着一个词的向量表示Returns a tuple of:输出- out: 维度为(N, T, D),由所有输入词的词向量所组成- cache: 反向传播时需要的变量"""#这里只要把x里的整数对应到词向量表中即可out = W[x, :]cache = x, Wreturn out, cache

2)词嵌入梯度反向传播

def word_embedding_backward(dout, cache):"""输入- dout: 梯度, 维度(N, T, D)- cache: 前向传播存的变量Returns:输出:- dW: 词嵌入矩阵的梯度,维度 (V, D)."""# 提示: 使用np.add.at函数x, W = cachedW=np.zeros_like(W)# 在x指定的位置将dout加到dW上np.add.at(dW, x, dout)return dW

3)图片注释生成系统

除了上面我们写的函数,本题已经为我们提供了线性运算层和softmax损失函数的计算代码,我们可以直接使用。至此,我们有了写一个图片注释生成系统所需的所有模块,下面这段代码我们来将这些小模块整合到一块。
整体架构是,图像特征对应于初始隐藏状态,在训练时真实的图片注释为每一时刻的输入,输出序列为RNN/LSTM对图片注释序列的预测。

    def loss(self, features, captions):"""计算训练时RNN/LSTM的损失函数。我们输入图像特征和正确的图片注释,使用RNN/LSTM计算损失函数和所有参数的梯度输入:- features: 输入图像特征,维度 (N, D)- captions: 正确的图像注释; 维度为(N, T)的整数列输出一个tuple:- loss: 标量损失函数值- grads: 所有参数的梯度""""""这里将captions分成了两个部分,captions_in是除了最后一个词外的所有词,是输入到RNN/LSTM的输入;captions_out是除了第一个词外的所有词,是RNN/LSTM期望得到的输出。"""captions_in = captions[:, :-1]captions_out = captions[:, 1:]# 之后会用到mask = (captions_out != self._null)# 从图像特征到初始隐藏状态的权值矩阵和偏差值W_proj, b_proj = self.params['W_proj'], self.params['b_proj']# 词嵌入矩阵W_embed = self.params['W_embed']# RNN/LSTM参数Wx, Wh, b = self.params['Wx'], self.params['Wh'], self.params['b']# 每一隐藏层到输出的权值矩阵和偏差W_vocab, b_vocab = self.params['W_vocab'], self.params['b_vocab']N,D=features.shape# 用线性变换从图像特征值得到初始隐藏状态,将产生维度为(N,H)的数列out, cache_affine = temporal_affine_forward(features.reshape(N,1,D), W_proj, b_proj)N,t,H=out.shapeh0=out.reshape(N,H)# 用词嵌入层将captions_in中词的索引转换成词响亮,得到一个维度为(N, T, W)的数列word_out,cache_word=word_embedding_forward(captions_in, W_embed)# 用RNN/LSTM处理输入的词向量,产生每一层的隐藏状态,维度为(N,T,H),这里演示的是LSTM的# RNN forward# hidden, cache_hidden = rnn_forward(word_out, h0, Wx, Wh, b)# LSTM forwardhidden, cache_hidden = lstm_forward(word_out, h0, Wx, Wh, b)# 用线性变换计算每一步隐藏层对应的输出(得分),维度(N, T, V)out_vo, cache_vo = temporal_affine_forward(hidden, W_vocab, b_vocab)# 用softmax函数计算损失,真实值为captions_out, 用mask忽视所有向量中<NULL>词汇loss, dx = temporal_softmax_loss(out_vo[:,:,:],captions_out, mask, verbose=False)#之后再逐步计算反向传播,得到对应的参数dx_affine,dW_vocab,db_vocab=temporal_affine_backward(dx, cache_vo)grads['W_vocab']=dW_vocabgrads['b_vocab']=db_vocab# RNN backward# dx_hidden, dh0, dWx, dWh, db = rnn_backward(dx_affine, cache_hidden)# LSTM bascwarddx_hidden, dh0, dWx, dWh, db = lstm_backward(dx_affine, cache_hidden)grads['Wx']=dWxgrads['Wh']=dWhgrads['b']=dbdW_embed = word_embedding_backward(dx_hidden, cache_word)grads['W_embed']=dW_embeddx_initial,dW_proj,db_proj=temporal_affine_backward(dh0.reshape(N,t,H), cache_affine)grads['W_proj']=dW_projgrads['b_proj']=db_projreturn loss, grads

至此,我们可以通过图片和真实的图片注释训练我们的RNN/LSTM系统。在下一步中我们将介绍如何采样一个图片注释。

4)图片注释采样

该问题的最后一步,我们要对得到的图片,用训练好的RNN/LSTM来生成一个图片注释。

    def sample(self, features, max_length=30):"""和上一段代码不同的地方是,这里我们没有了真实的图像注释。所以每一时刻的输入这样获得:计算隐藏层对应的输出,这些输出表示所有词汇表中词汇的得分,取得分最高的词汇,作为下一时刻的输入。其他与上一节里的代码相同。因为不能同时获得所有输入,我们必须循环应用RNN/LSTM step forward。对于LSTM,还需记录单元c的状态,初始值为0Inputs:- features: Array of input image features of shape (N, D).- max_length: Maximum length T of generated captions.输入:- captions: 输入图像特征,维度 (N, D)- max_length: 生成的注释的最长长度输出:- captions: 采样得到的注释,维度(N, max_length), 每个元素是词汇的索引"""N = features.shape[0]captions = self._null * np.ones((N, max_length), dtype=np.int32)# 参数W_proj, b_proj = self.params['W_proj'], self.params['b_proj']W_embed = self.params['W_embed']Wx, Wh, b = self.params['Wx'], self.params['Wh'], self.params['b']W_vocab, b_vocab = self.params['W_vocab'], self.params['b_vocab']N,D=features.shape# 用线性变换从图像特征值得到初始隐藏状态,将产生维度为(N,H)的数列out, cache_affine = temporal_affine_forward(features.reshape(N,1,D), W_proj, b_proj)N,t,H=out.shapeh0=out.reshape(N,H)h=h0# 初始输入x0=W_embed[[1,1],:]x_input=x0captions[:,0]=[1,1]# prev_c only for lstmprev_c=np.zeros_like(h)for i in range(0,max_length-1):# RNN step forward# next_h, _ = rnn_step_forward(x_input, h, Wx, Wh, b)# LSTM step forwardnext_h, next_c, cache = lstm_step_forward(x_input, h, prev_c, Wx, Wh, b)# only for lstmprev_c=next_c#计算每一层输出out_vo, cache_vo = temporal_affine_forward(next_h.reshape(N,1,H), W_vocab, b_vocab)#找到输出最大值的项作为下一时刻的输入index=np.argmax(out_vo,axis=2)x_input=np.squeeze(W_embed[index,:])h=next_h#记录其索引captions[:,i+1]=np.squeeze(index)return captions

全球名校课程作业分享系列(9)--斯坦福CS231n之RNN与计算机看图说话相关推荐

  1. 全球名校课程作业分享系列(11)--斯坦福CS231n之生成对抗网络

    课程作业原地址:CS231n Assignment 3 作业及整理:@邓姸蕾 && @Molly && @寒小阳 时间:2018年2月. 出处:http://blog. ...

  2. 全球名校课程作业分享系列(10)--斯坦福CS231n之Network visualization

    课程作业原地址:CS231n Assignment 3 作业及整理:@邓姸蕾 && @Molly && @寒小阳 时间:2018年2月. 出处:http://blog. ...

  3. 全球名校课程作业分享系列(7)--斯坦福计算机视觉与深度学习CS231n之基于cifar10的卷积神经网络实践

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/yaoqiang2011/article ...

  4. 全球名校课程作业分享系列(8)--斯坦福计算机视觉与深度学习CS231n之tensorflow实践

    课程作业原地址:CS231n Assignment 1 作业及整理:@邓妍蕾 && @郭承坤 && @寒小阳 时间:2018年2月. 出处:http://blog.cs ...

  5. 全球名校课程作业分享系列(5)--斯坦福计算机视觉与深度学习CS231n之特征抽取与图像分类提升

    课程作业原地址:CS231n Assignment 1 作业及整理:@邓妍蕾 && @Molly && @寒小阳 时间:2018年1月. 出处:http://blog. ...

  6. Hugging Face 每周速递: 扩散模型课程完成中文翻译,有个据说可以教 ChatGPT 看图的模型开源了...

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  7. 计算机课作业怎么向老师提交,计算机基础课程作业布置与批改方式

    <计算机基础课程作业布置与批改方式>由会员分享,可在线阅读,更多相关<计算机基础课程作业布置与批改方式(2页珍藏版)>请在人人文库网上搜索. 1.计算机基础课程作业布置与批改方 ...

  8. [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 1笔记

    前言:目前做深度学习也有一段时间了,系统的知识也学了很多,但是大多数都是自己在网上所获得的零散的知识,最近看了李飞飞的斯坦福网上公开课,觉得可以好好的巩固一下基础,对每个Lecture做一下笔记,De ...

  9. 斯坦福机器人学导论(视频+英文PPT讲义+课程作业+英文书籍)

    斯坦福机器人学导论(视频+英文PPT讲义+课程作业+英文书籍) 网址 文件内容 讲义内容 课后作业 英文书籍 课程视频 网址 https://download.csdn.net/download/we ...

最新文章

  1. 各代程序设计语言拓扑
  2. URL解析-URLComponents
  3. treeview递归
  4. Hadoop集群日常运维
  5. ulli*3 实现翻书动画效果
  6. LeetCode 1027. 最长等差数列(DP)
  7. #pragma mark 基本使用
  8. powderdesinger显示中英文表名
  9. 特斯拉再因致命Autopilot车祸被起诉 遭索赔逾1.5万美元
  10. sql2012 ssrs_您必须在SQL Server Reporting Services(SSRS)中记录的十件事
  11. Moses Staff攻陷以色列网络并加密数据,拒绝谈判
  12. django xadmin 集成DjangoUeditor富文本编辑器
  13. 首都师范 博弈论 9 5 3 负激励机制下的博弈模型
  14. 计算机网络(第七版)部分课后习题含答案第一章 概述1-02 试简述分组交换的要点。分组交换最主要的特点就是采用存储转发技术。我们把要发送的整块数据称为一个报文。在发送报文之前,先把较长的报
  15. ps抠图-基础篇(三)
  16. 【Meta Learning学习笔记】Meta Learning详解
  17. 提示此windows副本不是正版的win7系统7601解决方法
  18. 安卓获取指定目录内所有指定文件类型的文件路径和名字
  19. discuz_result
  20. SQL Server2008从入门到精通pdf

热门文章

  1. 弘辽科技:抖音小店差评如何补救?
  2. 【无标题】h5跳转微信公众号关注页面
  3. windows服务器双网卡修改默认路由,windows下双网卡双网关的设置
  4. 从0开始学习 GitHub 系列之「01.初识 GitHub」----转载自stormzhang 原创文章
  5. 2021届秋招腾讯前端一面面经
  6. Python账号密码登录
  7. vue下载大文件时浏览器不显示下载进度
  8. 漫谈Linux系统的二次定制
  9. iOS-记一些官网地址
  10. 使用Java统计英文文章的单词频率。