深度学习与自然语言处理(4)_斯坦福cs224d 大作业测验1与解答

作业内容翻译:@胡杨(superhy199148@hotmail.com) && @胥可(feitongxiaoke@gmail.com)
解答与编排:寒小阳 && 龙心尘
时间:2016年6月
出处:
http://blog.csdn.net/han_xiaoyang/article/details/51760923
http://blog.csdn.net/longxinchen_ml/article/details/51765418

说明:本文为斯坦福大学CS224d课程的中文版内容笔记,已得到斯坦福大学课程@Richard Socher教授的授权翻译与发表

0 前言

前面一个接一个的Lecture,看得老衲自己也是一脸懵逼,不过你以为你做一个安安静静的美男子(总感觉有勇气做deep learning的女生也是一条汉纸)就能在Stanford这样的学校顺利毕业啦?图样图森破,除掉极高的内容学习梯度,这种顶尖大学的作业和考试一样会让你突(tong)飞(bu)猛(yu)进(sheng)。

说起来,怎么也是堂堂斯坦福的课,这种最看重前言研究在实际工业应用的学校,一定是理论和应用并进,对动手能力要求极强的,于是乎,我们把作业和小测验(MD你这也敢叫小测验!!)也扒过来,整理整理,让大家都来体验体验。反正博主君自己每次折腾完这些大学的assignment之后,都会感慨一句,“还好不生在水生火热的万恶资本主义国家,才能让我大学和研究僧顺利毕业(什么?phd?呵呵…博主是渣渣,智商常年处于欠费状态,我就不参与你们高端人士的趴体了)”。

不能再BB了,直接开始做作业考试吧…

1 Softmax (10 分)

(part a) (5分)
证明针对任何输入向量 x \mathbf{x}和常数c,softmax函数的输出不会随着输入向量偏移(也就是常数c)而改变。即:

softmax(x)=softmax(x+c)

softmax(\mathbf{x}) = softmax(\mathbf{x} + c)

其中 x+c \mathbf{x} + c就是给 x \mathbf{x}每一个元素加上常数c。注意:

softmax(x)i=exi∑jexj

softmax \left( \mathbf{x} \right)_i = \frac{e^{\mathbf{x}_i}}{\sum_je^{\mathbf{x}_j}}

提示:在实际应用中,经常会用到这个性质。为了稳定地计算softmax概率,我们会选择 c=−maxixi c = − max_i x_i。(即将 x \mathbf{x}的每个元素减去最大的那个元素)。

博主:熬过了高中,居然又看见证明了,也是惊(ri)喜(le)万(gou)分(le),答案拿来!!!

解答:

证明,针对所有维度 1≤i≤dim(x) 1 \le i \le dim\left( \mathbf{x} \right):

(softmax(x+c))i=exp(xi+c)∑dim(x)j=1exp(xj+c)=exp(c)exp(xi)exp(c)∑dim(x)j=1exp(xj)=exp(xi)∑dim(x)j=1exp(xj)=(softmax(x))i

\left(softmax \left( \mathbf{x} + c \right)\right)_i = \frac{\exp \left(\mathbf{x}_i + c \right) }{\sum_{j=1}^{dim\left( \mathbf{x}\right)}{\exp\left(\mathbf{x}_j+c \right)}} = \frac{\exp\left(c \right)\exp \left( x_i\right)}{\exp \left( c \right)\sum_{j=1}^{dim\left(x \right)}\exp\left( x_j\right)} = \frac{\exp \left( x_i\right)}{\sum_{j=1}^{dim\left(x \right)}\exp\left( x_j\right)} = \left( softmax \left( \mathbf{x}\right) \right)_i

(part b) (5 分)
已知一个N行d列的输入矩阵,计算每一行的softmax概率。在q1_softmax.py中写出你的实现过程,并使用python q1_softmax.py执行。

要求:你所写的代码应该尽可能的有效并以向量化的形式来实现。非向量化的实现将不会得到满分。

博主:简直要哭晕在厕所了,当年毕业设计也是加论文一星期都可以写完的节奏,这里一个5分的作业,还这么多要求…社会主义好…答案拿来!!!

import numpy as npdef softmax(x):"""Softmax 函数"""assert len(x.shape) > 1, "Softmax的得分向量要求维度高于1"x -= np.max(x, axis=1, keepdims=True)x = np.exp(x) / np.sum(np.exp(x), axis=1, keepdims=True)return x

2 神经网络基础(30分)

(part a) (3 分)
推导sigmoid函数的导数,并且只以sigmoid函数值的形式写出来(导数的表达式里只包含 σ(x) \sigma \left(x\right),不包含x)。证明针对这个问题没必要单独考虑x。方便回忆:下面给出sigmoid函数形式:

σ(x)=11+e−x

\sigma \left( x\right) = \frac{1}{1+e^{-x}}

旁白:我年纪轻轻干嘛要走上深度学习这条不归路,真是生无所恋了。

答案: σ'(x)=σ(x)(1−σ(x)) \sigma′\left(x\right) = \sigma\left(x\right)(1 − \sigma\left(x\right))。

(part b) (3 分)
当使用交叉熵损失来作为评价标准时,推导出损失函数以softmax为预测结果的输入向量 θ \theta的梯度。注意,

CE(y,y^)=−∑iyilog(y^i)

CE\left(\mathbf{y}, \mathbf{\hat{y}}\right) = - \sum_i{y_i \log \left( \hat{y}_i\right)}

其中 y \mathbf{y}是一个one-hot向量, y^ \hat{\mathbf{y}}是所有类别的预测出的概率向量。(提示:你需要考虑 y \mathbf{y}的许多元素为0,并且假设 y \mathbf{y}仅有第k个类别是1)

答案: ∂CE(y,y^)∂θ=y^−y \frac{\partial CE\left(\mathbf{y}, \mathbf{\hat{y}}\right)}{\partial \mathbf{\theta}}=\mathbf{\hat{y}}-\mathbf{y}

或者等价于下面表达式,其中假设k是正确的类别

∂CE(y,y^)∂θ={y^i−1,i=ky^i,otherwise

\frac{\partial CE\left(\mathbf{y}, \mathbf{\hat{y}}\right)}{\partial \mathbf{\theta}}=\left\{\begin{matrix} \hat{y}_i-1,i=k &\\ \hat{y}_i , otherwise& \end{matrix}\right.

(part c) (6 分)
推导出单隐层神经网络关于输入 x \mathbf{x}的梯度(也就是推导出 ∂J∂x \frac{\partial J}{\partial x},其中J是神经网络的损失函数)。这个神经网络在隐层和输出层采用了sigmoid激活函数, y \mathbf{y}是one-hot编码向量,使用了交叉熵损失。(使用 σ'(x) \sigma′\left( x \right) 作为sigmoid梯度,并且你可以任意为推导过中的中间变量命名)

前向传播方程如下:

h=sigmoid(xW1+b1)y^=softmax(hW2+b2)

\begin{matrix} \mathbf{h}=sigmoid \left( \mathbf{xW}_1+\mathbf{b}_1\right) \\ \hat{\mathbf{y}}=softmax \left( \mathbf{hW}_2 + \mathbf{b}_2\right) & \end{matrix}

在编程问题中,我们假设输入向量(隐层变量和输出概率)始终是一个行向量。此处我们约定,当我们说要对向量使用sigmoid函数时,也就是说要对向量每一个元素使用sigmoid函数。 Wi \mathbf{W}_i和 bi \mathbf{b}_i(其中i=1,2)分别是两层的权重和偏移。

旁白:好好的100分总分,硬要被你这么5分6分地拆,人家5分6分是一道选择题,你特么是一整个毕业设计!!好吧,不哭,跪着也要把题目做完,代码写完。哎,博主还是太年轻,要多学习啊。

答案:令 z2=hW2+b2 \mathbf{z}_2 = \mathbf{hW_2}+\mathbf{b}_2, z1=xW1+b1x \mathbf{z}_1=\mathbf{xW}_1+\mathbf{b}_1x,于是可得:

δ1=∂CE∂z2=y^−yδ2=∂CE∂h=δ1∂z2∂h=δ1WT2δ3=∂CEz1=δ2∂h∂z1=δ2∘σ′(z1)∂CE∂x=δ3∂z1∂x=δ3WT

\begin{matrix} \delta_1=\frac{\partial CE}{\partial \mathbf{z}_2}=\hat{\mathbf{y}}-\mathbf{y}&\\ \delta_2=\frac{\partial CE}{\partial \mathbf{h}}=\delta_1\frac{\partial \mathbf{z}_2}{\partial \mathbf{h}}=\delta_1 \mathbf{W}_2^T&\\ \delta_3=\frac{\partial CE}{\mathbf{z}_1}=\delta_2 \frac{\partial \mathbf{h}}{\partial \mathbf{z}_1}=\delta_2\circ \sigma' \left( \mathbf{z}_1\right)&\\ \frac{\partial CE}{\partial \mathbf{x}}=\delta_3 \frac{\partial \mathbf{z}_1}{\partial \mathbf{x}}=\delta_3 \mathbf{W}^T& \end{matrix}

(part d) (2 分)
上面所说的这个神经网络有多少个参数?我们可以假设输入是 Dx D_x维,输出是 Dy D_y,隐层单元有H个。

旁白:还有part d!!!

答案: (Dx+1)⋅H+(H+1)⋅Dy (D_x +1)·H +(H +1)·D_y.

(part e) (4 分) 在q2_sigmoid.py中补充写出sigmoid激活函数的和求它的梯度的对应代码。并使用python q2_sigmoid.py进行测试,同样的,测试用例有可能不太详尽,因此尽量检查下自己的代码。

旁白:如果博主没有阵亡,就在走向阵亡的路上…

def sigmoid_grad(f):""" 计算Sigmoid的梯度 """#好在我有numpyf = f * ( 1 - f )return f

(part f) (4 分)
为了方便debugging,我们需要写一个梯度检查器。在q2_gradcheck.py中补充出来,使用python q2_gradcheck.py测试自己的代码。

旁白:做到昏天黑地,睡一觉起来又是一条好汉…

def gradcheck_naive(f, x):""" 对一个函数f求梯度的梯度检验 - f 输入x,然后输出loss和梯度的函数- x 就是输入咯""" rndstate = random.getstate()random.setstate(rndstate)  fx, grad = f(x)h = 1e-4# 遍历x的每一维it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])while not it.finished:ix = it.multi_indexold_val = x[ix]x[ix] = old_val - hrandom.setstate(rndstate)( fxh1, _ ) = f(x)x[ix] = old_val + hrandom.setstate(rndstate)( fxh2, _ ) = f(x)numgrad = (fxh2 - fxh1)/(2*h)x[ix] = old_val# 比对梯度reldiff = abs(numgrad - grad[ix]) / max(1, abs(numgrad), abs(grad[ix]))if reldiff > 1e-5:print "Gradient check failed."print "First gradient error found at index %s" % str(ix)print "Your gradient: %f \t Numerical gradient: %f" % (grad[ix], numgrad)returnit.iternext() # Step to next dimensionprint "Gradient check passed!"

(part g) (8 分)
现在,在q2 neural.py中,写出只有一个隐层且激活函数为sigmoid的神经网络前向和后向传播代码。使用python q2_neural.py测试自己的代码。

旁白:一入DL深似海…

def forward_backward_prop(data, labels, params, verbose = False):""" 2个隐层的神经网络的前向运算和反向传播"""if len(data.shape) >= 2:(N, _) = data.shape### 展开每一层神经网络的参数t = 0W1 = np.reshape(params[t:t+dimensions[0]*dimensions[1]], (dimensions[0], dimensions[1]))t += dimensions[0]*dimensions[1]b1 = np.reshape(params[t:t+dimensions[1]], (1, dimensions[1]))t += dimensions[1]W2 = np.reshape(params[t:t+dimensions[1]*dimensions[2]], (dimensions[1], dimensions[2]))t += dimensions[1]*dimensions[2]b2 = np.reshape(params[t:t+dimensions[2]], (1, dimensions[2]))### 前向运算# 第一个隐层做内积a1 = sigmoid(data.dot(W1) + b1)    # 第二个隐层做内积a2 = softmax(a1.dot(W2) + b2)cost = - np.sum(np.log(a2[labels == 1]))/N### 反向传播# Calculate analytic gradient for the cross entropy loss functiongrad_a2 = ( a2 - labels ) / N# Backpropagate through the second latent layergradW2 = np.dot( a1.T, grad_a2 )gradb2 = np.sum( grad_a2, axis=0, keepdims=True )# Backpropagate through the first latent layergrad_a1 = np.dot( grad_a2, W2.T ) * sigmoid_grad(a1)gradW1 = np.dot( data.T, grad_a1 )gradb1 = np.sum( grad_a1, axis=0, keepdims=True )if verbose: # Verbose mode for logging informationprint "W1 shape: {}".format( str(W1.shape) )print "W1 gradient shape: {}".format( str(gradW1.shape) )print "b1 shape: {}".format( str(b1.shape) )print "b1 gradient shape: {}".format( str(gradb1.shape) )### 梯度拼起来grad = np.concatenate((gradW1.flatten(), gradb1.flatten(), gradW2.flatten(), gradb2.flatten()))return cost, grad

3 word2vec(40分+5附加分)

(part a) (3分)
假设你得到一个关联到中心词 c c的预测词向量υc\upsilon_c,并且这个词向量使用skip-gram方法生成,预测词使用的是softmax预测函数,它能够在word2vec模型中被找到。

y^o=p(o|c)=exp(μ⊤oυc)∑∑Ww=1exp(μ⊤wυc)(4)

\hat{y}_o=p(o|c)=\frac{\exp(\mu_o^\top\upsilon_c)}{\sideset{}{_{w=1}^W}\sum\exp(\mu_w^\top\upsilon_c)}\tag{4}

式中, w w代表第w个词,μw(w=1,…,W)\mu_w(w=1,\ldots,W)是词库中全体词汇的输出词向量。假设为交叉熵损失函数,且词 o o是被预测的词汇(noe-hot/独热模型的标记向量中第oo个元素为1),求解预测词向量 υc \upsilon_c的所对应的梯度。
提示:问题2中的标记法将有助于此问题的解答。比如:设 y^ \hat{y}为各个词汇使用softmax函数预测得到的向量, y y为期望词向量,而损失函数可以表示为:

Jsoftmax−CE(o,υc,U)=CE(y,y^)(5)

J_{softmax-CE}(o,\upsilon_c,U)=CE(y,\hat{y})\tag{5}

其中, U=[μ1,μ2,…,μW] U=[\mu_1,\mu_2,\ldots,\mu_W]是全体输出向量形成的矩阵,确保你已经规定好你的向量和矩阵的方向。

旁边:是的,旁白我已经不知道写什么了,感谢党感谢祖国吧。

解答:设 y^ \hat{y}为词汇softmax预测结果的列向量, y y是同样形为列向量的独热标签,那么有:

∂J∂υc=UT(y^−y).

\frac{\partial{J}}{\partial{\upsilon_c}}=U^T(\hat{y}-y).

或者等同于:

∂J∂υc=−μi+∑w=1Wyw^μw

\frac{\partial{J}}{\partial{\upsilon_c}}=-\mu_i+\sum_{w=1}^{W}\hat{y_w}\mu_w

(part b) (3分)
条件仍然如前一题所描述,求解输出词向量 μw \mu_w的梯度(包括 μo \mu_o在内)

旁白:我还是安安静静在天朝搬砖吧

解答:

∂J∂U=υc(y^−y)⊤

\frac{\partial{J}}{\partial{U}}=\upsilon_c(\hat{y}-y)^\top

或者等同于:

∂J∂μw={(yw^−1)υc,yw^,w=ootherwise

\frac{\partial{J}}{\partial{\mu_w}}=\begin{cases}(\hat{y_w}-1)\upsilon_c, &w=o\\\hat{y_w}, &otherwise\end{cases}

(part c) (6分)
仍然延续(part a)和(part b),假设我们使用为预测的向量 υc \upsilon_c使用负采样损失的计算方式,并且设定期望输出词为 o o。假设获得了KK个负样例(词),并且被记为 1,…,K 1,\ldots,K,分别作为这些样例的标签 (o∉1,…,K) (o\notin{1,\ldots,K})。那么,对于一个给定的词 o o,将其输出向量记作μo\mu_o。这里,负采样损失函数如下:

Jneg−sample(o,υc,U)=−log(σ(μ⊤oυc))−∑k=1Klog(σ(−μ⊤kυc))(6)

J_{neg-sample}(o,\upsilon_c,U)=-\log(\sigma(\mu_o^\top\upsilon_c))-\sum_{k=1}^{K}\log(\sigma(-\mu_k^\top\upsilon_c))\tag{6}

其中, σ(⋅) \sigma(\cdot)为sigmoid激活函数。

当你完成上述操作之后,尝试简要描述这个损失函数比softmax-CE损失函数计算更为有效的原因(你可以给出递增式的学习率,即,给出softmax-CE损失函数的计算时间除以负采样损失函数的计算时间的结果)。

注释:由于我们打算计算目标函数的最小值而不是最大值,这里提到的损失函数与Mikolov等人最先在原版论文中描述的正好相反。

旁白:突然想起来,小时候好焦虑,长大后到底去清华还是去北大,后来发现多虑了。我想如果当初走了狗屎运进了贵T大贵P大,也一定完不成学业。

解答:

∂J∂υc∂J∂μo∂J∂μk=(σ(μ⊤oυc)−1)μo−∑k=1K(σ(−μ⊤kυc)−1)μk=(σ(μ⊤oυc)−1)υc=−(σ(−μ⊤kυc)−1)υc,for all k=1,2,…,K

\begin{align*} \frac{\partial{J}}{\partial{\upsilon_c}}&=(\sigma(\mu_o^\top\upsilon_c)-1)\mu_o-\sum_{k=1}^{K}(\sigma(-\mu_k^\top\upsilon_c)-1)\mu_k\\\frac{\partial{J}}{\partial{\mu_o}}&=(\sigma(\mu_o^\top\upsilon_c)-1)\upsilon_c\\\frac{\partial{J}}{\partial{\mu_k}}&=-(\sigma(-\mu_k^\top\upsilon_c)-1)\upsilon_c,\qquad for \ all \ k=1,2,\ldots,K \end{align*}

(part d) (8分)
试得到由skip-gram和CBOW算法分别算出的全部词向量的梯度,前提步骤和词内容集合[wordc-m,…,wordc-1,wordc,wordc+1,…,wordc+m]都已给出,其中, m m是窗口的大小。将词wordk{word}_k的输入和输出词向量分别记为 υk \upsilon_k和 μk \mu_k。
提示:可以随意使用函数 F(o,υc) F(o,\upsilon_c)(其中 o o代表词汇)作为这一部分中Jsoftmax−CE(o,υc,…)J_{softmax-CE}(o,\upsilon_c,\ldots)或 Jneg−sample(o,υc,…) J_{neg-sample}(o,\upsilon_c,\ldots)损失函数的占位符——你将在编程部分看到一个非常有用的抽象类,那意味着你的解决方法可以用这样的形式表达: ∂F(o,υc)∂… \frac{\partial{F(o,\upsilon_c)}}{\partial{\ldots}}
回忆skip-gram算法,以 c c为中心周边内容的损失值计算如下:

Jskip−gram(wordc−m…c+m)=∑−m≤j≤m,j≠0F(wc+j,υc)(7)

J_{skip-gram}({word}_{c-m \ldots c+m})=\sum_{-m \le j \le m,j \ne 0}F(w_{c+j},\upsilon_c)\tag{7}

其中, wc+j w_{c+j}代表距离中心词的第j个词。
CBOW略有不同,不同于使用 υc \upsilon_c作为预测向量,我们以 υ^ \hat{\upsilon}为底,在CBOW中(一个小小的变体),我们计算上下文输入词向量的和:

υ^=∑−m≤j≤m,j≠0υc+j(8)

\hat{\upsilon}=\sum_{-m \le j \le m,j \ne 0}\upsilon_{c+j}\tag{8}

于是,CBOW的损失函数定义为:

JCBOW(wordc−m…c+m)=F(wc,υ^)(9)

J_{CBOW}({word_{c-m \ldots c+m}})=F(w_c,\hat{\upsilon})\tag{9}

注释:为了符合 υ^ \hat{\upsilon}在诸如代码部分中的各种表达规范,在skip-gram方法中,令: υ^=υc \hat{\upsilon}=\upsilon_c。

旁白:我诚实一点,这个部分真的是翻了课件抄下来的。

解答:为了表达得更为清晰,我们将词库中全部词汇的全部输出向量集合记作 U U,给定一个损失函数FF,我们可以很容易获得以下引出结果:
∂F(wi,υ^)∂U \frac{\partial{F(w_i,\hat{\upsilon})}}{\partial{U}} 和 ∂F(wi,υ^)∂υ^ \frac{\partial{F(w_i,\hat{\upsilon})}}{\partial{\hat{\upsilon}}}
对于skip-gram方法,一个内容窗口的损失梯度为:

∂Jskip−gram(wordc−m,…,c+m)∂U∂Jskip−gram(wordc−m,…,c+m)∂υc∂Jskip−gram(wordc−m,…,c+m)∂υj=∑−m≤j≤m,j≠0∂F(wc+j,υc)∂U,=∑−m≤j≤m,j≠0∂F(wc+j,υc)∂υc=0, for all j≠c.

\begin{align*} \frac{\partial{J_{skip-gram}({word}_{c-m, \ldots ,c+m})}}{\partial{U}} &= \sum_{-m \le j \le m,j \ne 0}\frac{\partial{F(w_{c+j},\upsilon_c)}}{\partial{U}},\\\frac{\partial{J_{skip-gram}({word}_{c-m, \ldots ,c+m})}}{\partial{\upsilon_c}} &= \sum_{-m \le j \le m,j \ne 0}\frac{\partial{F(w_{c+j},\upsilon_c)}}{\partial{\upsilon_c}}\\\frac{\partial{J_{skip-gram}({word}_{c-m, \ldots ,c+m})}}{\partial{\upsilon_j}} &= 0, \ for \ all \ j \ne c. \end{align*}

同样地,对于CBOW则有:

∂JCBOW(wordc−m,…,c+m)∂U∂JCBOW(wordc−m,…,c+m)∂υj∂JCBOW(wordc−m,…,c+m)∂υj=∂F(wc,υ^)∂U,(using the definition of υ^ in the problem)=∂F(wc,υ^)∂υ^,for all j∈{c−m,…,c−1,c+1,…,c+m}=0,for all j∉{c−m,…,c−1,c+1,…,c+m}

\begin{align*} \frac{\partial{J_{CBOW}({word}_{c-m, \ldots ,c+m})}}{\partial{U}} &= \frac{\partial{F(w_c,\hat{\upsilon})}}{\partial{U}}, \quad (using \ the \ definition \ of \ \hat{\upsilon} \ in \ the \ problem)\\\frac{\partial{J_{CBOW}({word}_{c-m, \ldots ,c+m})}}{\partial{\upsilon_j}} &= \frac{\partial{F(w_c,\hat{\upsilon})}}{\partial{\hat{\upsilon}}}, \quad for \ all \ j \in\{c-m, \ldots , c-1,c+1, \ldots , c+m \}\\\frac{\partial{J_{CBOW}({word}_{c-m, \ldots ,c+m})}}{\partial{\upsilon_j}} &= 0, \quad for \ all \ j \notin\{c-m, \ldots , c-1,c+1, \ldots , c+m\} \end{align*}

(part e) (12分)
在这一部分,你将实现word2vec模型,并且使用随机梯度下降方法(SGD)训练属于你自己的词向量。首先,在代码q3_word2vec.py中编写一个辅助函数对矩阵中的每一行进行归一化。同样在这个文件中,完成对softmax、负采样损失函数以及梯度计算函数的实现。然后,完成面向skip-gram的梯度损失函数。当你完成这些的时候,使用命令:python q3_word2vec.py对编写的程序进行测试。
注释:如果你选择不去实现CBOW(h部分),只需简单地删除对NotImplementedError错误的捕获即可完成你的测试。

旁白:前方高能预警,代码量爆炸了!

import numpy as np
import randomfrom q1_softmax import softmax
from q2_gradcheck import gradcheck_naive
from q2_sigmoid import sigmoid, sigmoid_graddef normalizeRows(x):""" 行归一化函数 """N = x.shape[0]x /= np.sqrt(np.sum(x**2, axis=1)).reshape((N,1)) + 1e-30return xdef test_normalize_rows():print "Testing normalizeRows..."x = normalizeRows(np.array([[3.0,4.0],[1, 2]])) # 结果应该是 [[0.6, 0.8], [0.4472, 0.8944]]print xassert (np.amax(np.fabs(x - np.array([[0.6,0.8],[0.4472136,0.89442719]]))) <= 1e-6)print ""def softmaxCostAndGradient(predicted, target, outputVectors, dataset):""" word2vec的Softmax损失函数 """                                                   # 输入:                                                         # - predicted: 预测词向量的numpy数组# - target: 目标词的下标              # - outputVectors: 所有token的"output"向量(行形式) # - dataset: 用来做负例采样的,这里其实没用着         # 输出:                                                        # - cost: 输出的互熵损失    # - gradPred: the gradient with respect to the predicted word   #        vector                                                # - grad: the gradient with respect to all the other word        #        vectors                                               probabilities = softmax(predicted.dot(outputVectors.T))cost = -np.log(probabilities[target])delta = probabilitiesdelta[target] -= 1N = delta.shape[0]D = predicted.shape[0]grad = delta.reshape((N,1)) * predicted.reshape((1,D))gradPred = (delta.reshape((1,N)).dot(outputVectors)).flatten()return cost, gradPred, graddef negSamplingCostAndGradient(predicted, target, outputVectors, dataset, K=10):""" Word2vec模型负例采样后的损失函数和梯度"""grad = np.zeros(outputVectors.shape)gradPred = np.zeros(predicted.shape)indices = [target]for k in xrange(K):newidx = dataset.sampleTokenIdx()while newidx == target:newidx = dataset.sampleTokenIdx()indices += [newidx]labels = np.array([1] + [-1 for k in xrange(K)])vecs = outputVectors[indices,:]t = sigmoid(vecs.dot(predicted) * labels)cost = -np.sum(np.log(t))delta = labels * (t - 1)gradPred = delta.reshape((1,K+1)).dot(vecs).flatten()gradtemp = delta.reshape((K+1,1)).dot(predicted.reshape((1,predicted.shape[0])))for k in xrange(K+1):grad[indices[k]] += gradtemp[k,:]t = sigmoid(predicted.dot(outputVectors[target,:]))cost = -np.log(t)delta = t - 1gradPred += delta * outputVectors[target, :]grad[target, :] += delta * predictedfor k in xrange(K):idx = dataset.sampleTokenIdx()t = sigmoid(-predicted.dot(outputVectors[idx,:]))cost += -np.log(t)delta = 1 - tgradPred += delta * outputVectors[idx, :]grad[idx, :] += delta * predictedreturn cost, gradPred, graddef skipgram(currentWord, C, contextWords, tokens, inputVectors, outputVectors, dataset, word2vecCostAndGradient = softmaxCostAndGradient):""" Skip-gram model in word2vec """# skip-gram模型的实现# 输入:                                                         # - currrentWord: 当前中心词所对应的串           # - C: 上下文大小(词窗大小)                          # - contextWords: 最多2*C个词                             # - tokens: 对应词向量中词下标的字典                # - inputVectors: "input" word vectors (as rows) for all tokens           # - outputVectors: "output" word vectors (as rows) for all tokens         # - word2vecCostAndGradient: the cost and gradient function for a prediction vector given the target word vectors, could be one of the two cost functions you implemented above# 输出:                                                   # - cost: skip-gram模型算得的损失值   # - grad: 词向量对应的梯度 currentI = tokens[currentWord]predicted = inputVectors[currentI, :]cost = 0.0gradIn = np.zeros(inputVectors.shape)gradOut = np.zeros(outputVectors.shape)for cwd in contextWords:idx = tokens[cwd]cc, gp, gg = word2vecCostAndGradient(predicted, idx, outputVectors, dataset)cost += ccgradOut += gggradIn[currentI, :] += gpreturn cost, gradIn, gradOutdef word2vec_sgd_wrapper(word2vecModel, tokens, wordVectors, dataset, C, word2vecCostAndGradient = softmaxCostAndGradient):batchsize = 50cost = 0.0grad = np.zeros(wordVectors.shape)N = wordVectors.shape[0]inputVectors = wordVectors[:N/2,:]outputVectors = wordVectors[N/2:,:]for i in xrange(batchsize):C1 = random.randint(1,C)centerword, context = dataset.getRandomContext(C1)if word2vecModel == skipgram:denom = 1else:denom = 1c, gin, gout = word2vecModel(centerword, C1, context, tokens, inputVectors, outputVectors, dataset, word2vecCostAndGradient)cost += c / batchsize / denomgrad[:N/2, :] += gin / batchsize / denomgrad[N/2:, :] += gout / batchsize / denomreturn cost, graddef test_word2vec():# Interface to the dataset for negative samplingdataset = type('dummy', (), {})()def dummySampleTokenIdx():return random.randint(0, 4)def getRandomContext(C):tokens = ["a", "b", "c", "d", "e"]return tokens[random.randint(0,4)], [tokens[random.randint(0,4)] \for i in xrange(2*C)]dataset.sampleTokenIdx = dummySampleTokenIdxdataset.getRandomContext = getRandomContextrandom.seed(31415)np.random.seed(9265)dummy_vectors = normalizeRows(np.random.randn(10,3))dummy_tokens = dict([("a",0), ("b",1), ("c",2),("d",3),("e",4)])print "==== Gradient check for skip-gram ===="gradcheck_naive(lambda vec: word2vec_sgd_wrapper(skipgram, dummy_tokens, vec, dataset, 5), dummy_vectors)gradcheck_naive(lambda vec: word2vec_sgd_wrapper(skipgram, dummy_tokens, vec, dataset, 5, negSamplingCostAndGradient), dummy_vectors)print "\n==== Gradient check for CBOW      ===="gradcheck_naive(lambda vec: word2vec_sgd_wrapper(cbow, dummy_tokens, vec, dataset, 5), dummy_vectors)gradcheck_naive(lambda vec: word2vec_sgd_wrapper(cbow, dummy_tokens, vec, dataset, 5, negSamplingCostAndGradient), dummy_vectors)print "\n=== Results ==="print skipgram("c", 3, ["a", "b", "e", "d", "b", "c"], dummy_tokens, dummy_vectors[:5,:], dummy_vectors[5:,:], dataset)print skipgram("c", 1, ["a", "b"], dummy_tokens, dummy_vectors[:5,:], dummy_vectors[5:,:], dataset, negSamplingCostAndGradient)print cbow("a", 2, ["a", "b", "c", "a"], dummy_tokens, dummy_vectors[:5,:], dummy_vectors[5:,:], dataset)print cbow("a", 2, ["a", "b", "a", "c"], dummy_tokens, dummy_vectors[:5,:], dummy_vectors[5:,:], dataset, negSamplingCostAndGradient)if __name__ == "__main__":test_normalize_rows()test_word2vec()

(f) (4分) 在代码q3_sgd.py中完成对随即梯度下降优化函数的实现。并且在该代码中运行测试你的实现。

旁白:想到这篇文章有可能会被无数可以智商碾压我的大神看到,就脸一阵发烫。

# 实现随机梯度下降# 随机梯度下降每1000轮,就保存一下现在训练得到的参数
SAVE_PARAMS_EVERY = 1000import glob
import os.path as op
import cPickle as pickle
import sysdef load_saved_params():"""载入之前的参数以免从头开始训练"""st = 0for f in glob.glob("saved_params_*.npy"):iter = int(op.splitext(op.basename(f))[0].split("_")[2])if (iter > st):st = iterif st > 0:with open("saved_params_%d.npy" % st, "r") as f:params = pickle.load(f)state = pickle.load(f)return st, params, stateelse:return st, None, Nonedef save_params(iter, params):with open("saved_params_%d.npy" % iter, "w") as f:pickle.dump(params, f)pickle.dump(random.getstate(), f)def sgd(f, x0, step, iterations, postprocessing = None, useSaved = False, PRINT_EVERY=10, ANNEAL_EVERY = 20000):""" 随机梯度下降 """############################################################ 输入#   - f: 需要最优化的函数#   - x0: SGD的初始值#   - step: SGD的步长#   - iterations: 总得迭代次数#   - postprocessing: 参数后处理(比如word2vec里需要对词向量做归一化处理)#   - PRINT_EVERY: 指明多少次迭代以后输出一下状态# 输出: #   - x: SGD完成后的输出参数                   ############################################################if useSaved:start_iter, oldx, state = load_saved_params()if start_iter > 0:x0 = oldx;step *= 0.5 ** (start_iter / ANNEAL_EVERY)if state:random.setstate(state)else:start_iter = 0x = x0if not postprocessing:postprocessing = lambda x: xexpcost = Nonefor iter in xrange(start_iter + 1, iterations + 1):cost, grad = f(x)x = x - step * gradx = postprocessing(x)if iter % PRINT_EVERY == 0:print "Iter#{}, cost={}".format(iter, cost)sys.stdout.flush()if iter % SAVE_PARAMS_EVERY == 0 and useSaved:save_params(iter, x)if iter % ANNEAL_EVERY == 0:step *= 0.5return x

(part g) (4分)
开始秀啦!现在我们将要载入真实的数据并使用你已经实现的手段训练词向量!我们将使用Stanford Sentiment Treebank (SST)数据集来进行词向量的训练,之后将他们应用到情感分析任务中去。在这一部分中,无需再编写更多的代码;只需要运行命令python q3 run.py即可。
注释:训练过程所占用的时间可能会很长,这取决于你所实现的程序的效率(一个拥有优异效率的实现程序大约需要占用1个小时)。努力去接近这个目标!
当脚本编写完成,需要完成对词向量的可视化显示。相应的结果同样被保存下来,如项目目录中的图片q3 word_vectors.png所示。包括在你作业中绘制的坐标图。简明解释最多三个句子在你的坐标图中的显示状况。
解答:

(part h) 附加题(5分)

在代码q3_word2vec.py中完成对CBOW的实现。注释:这部分内容是可选的,但是在d部分中关于CBOW的梯度推导在这里并不适用!

def cbow(currentWord, C, contextWords, tokens, inputVectors, outputVectors, dataset, word2vecCostAndGradient = softmaxCostAndGradient):"""word2vec的CBOW模型"""cost = 0gradIn = np.zeros(inputVectors.shape)gradOut = np.zeros(outputVectors.shape)D = inputVectors.shape[1]predicted = np.zeros((D,))indices = [tokens[cwd] for cwd in contextWords]for idx in indices:predicted += inputVectors[idx, :]cost, gp, gradOut = word2vecCostAndGradient(predicted, tokens[currentWord], outputVectors, dataset)gradIn = np.zeros(inputVectors.shape)for idx in indices:gradIn[idx, :] += gpreturn cost, gradIn, gradOut

4 情感分析(20分)

现在,随着词向量的训练,我们准备展示一个简单的情感分析案例。随着词向量的训练,我们准备展示一个简单的情感分析。对于每条Stanford Sentiment Treebank数据集中的句子,将句子中全体词向量的平均值算作其特征值,并试图预测所提句子中的情感层次。短语的情感层次使用真实数值在原始数据集中表示,并被我们用以下5个类别来表示:

“超级消极”,“比较消极”,“中立”,“积极”,“非常积极”

对其分别进行从0到4的编码。在这一部分,你将学习用SGD来训练一个softmax回归机,并且通过不断地训练/调试验证来提高回归机的泛化能力。
(part a)(10分)
实现一个句子的特征生成器和softmax回归机。在代码q4_softmaxreg.py中完成对这个任务的实现,并运行命令python q4_ softmaxreg.py,对刚才完成的功能函数进行调试。

import numpy as np
import randomfrom cs224d.data_utils import *from q1_softmax import softmax
from q2_gradcheck import gradcheck_naive
from q3_sgd import load_saved_paramsdef getSentenceFeature(tokens, wordVectors, sentence):""" 简单粗暴的处理方式,直接对句子的所有词向量求平均做为情感分析的输入"""# 输入:                                                         # - tokens: a dictionary that maps words to their indices in the word vector list                                # - wordVectors: word vectors (each row) for all tokens # - sentence: a list of words in the sentence of interest # 输出:                                                         # - sentVector: feature vector for the sentence    sentVector = np.zeros((wordVectors.shape[1],))indices = [tokens[word] for word in sentence]sentVector = np.mean(wordVectors[indices, :], axis=0)return sentVectordef softmaxRegression(features, labels, weights, regularization = 0.0, nopredictions = False):""" Softmax Regression """# 完成加正则化的softmax回归        # 输入:                                                         # - features: feature vectors, each row is a feature vector # - labels: labels corresponding to the feature vectors     # - weights: weights of the regressor                       # - regularization: L2 regularization constant              # 输出:                                                         # - cost: cost of the regressor                             # - grad: gradient of the regressor cost with respect to its weights                                               # - pred: label predictions of the regressor (you might find np.argmax helpful)  prob = softmax(features.dot(weights))if len(features.shape) > 1:N = features.shape[0]else:N = 1# A vectorized implementation of    1/N * sum(cross_entropy(x_i, y_i)) + 1/2*|w|^2cost = np.sum(-np.log(prob[range(N), labels])) / N cost += 0.5 * regularization * np.sum(weights ** 2)grad = np.array(prob)grad[range(N), labels] -= 1.0grad = features.T.dot(grad) / Ngrad += regularization * weightsif N > 1:pred = np.argmax(prob, axis=1)else:pred = np.argmax(prob)if nopredictions:return cost, gradelse:return cost, grad, preddef accuracy(y, yhat):""" Precision for classifier """assert(y.shape == yhat.shape)return np.sum(y == yhat) * 100.0 / y.sizedef softmax_wrapper(features, labels, weights, regularization = 0.0):cost, grad, _ = softmaxRegression(features, labels, weights, regularization)return cost, graddef sanity_check():"""Run python q4_softmaxreg.py."""random.seed(314159)np.random.seed(265)dataset = StanfordSentiment()tokens = dataset.tokens()nWords = len(tokens)_, wordVectors0, _ = load_saved_params()wordVectors = (wordVectors0[:nWords,:] + wordVectors0[nWords:,:])dimVectors = wordVectors.shape[1]dummy_weights = 0.1 * np.random.randn(dimVectors, 5)dummy_features = np.zeros((10, dimVectors))dummy_labels = np.zeros((10,), dtype=np.int32)    for i in xrange(10):words, dummy_labels[i] = dataset.getRandomTrainSentence()dummy_features[i, :] = getSentenceFeature(tokens, wordVectors, words)print "==== Gradient check for softmax regression ===="gradcheck_naive(lambda weights: softmaxRegression(dummy_features,dummy_labels, weights, 1.0, nopredictions = True), dummy_weights)print "\n=== Results ==="print softmaxRegression(dummy_features, dummy_labels, dummy_weights, 1.0)if __name__ == "__main__":sanity_check()

(part b)(2分)
解释当分类语料少于三句时为什么要引入正则化(实际上在大多数机器学习任务都这样)。
解答:为了避免训练集的过拟合以及对未知数据集的适应力不佳现象。

(part c)(4分)
q4 sentiment.py中完成超参数的实现代码从而获取“最佳”的惩罚因子。你是如何选择的?报告你的训练、调试和测试精度,在最多一个句子中校正你的超参数选定方法。 注释:在开发中应该获取至少30%的准确率。
解答:参考值为1e-4,在调试、开发和测试过程中准确率分别为29.1%,31.4%和27.6%

import numpy as np
import matplotlib.pyplot as pltfrom cs224d.data_utils import *from q3_sgd import load_saved_params, sgd
from q4_softmaxreg import softmaxRegression, getSentenceFeature, accuracy, softmax_wrapper# 试试不同的正则化系数,选最好的
REGULARIZATION = [0.0, 0.00001, 0.00003, 0.0001, 0.0003, 0.001, 0.003, 0.01]# 载入数据集
dataset = StanfordSentiment()
tokens = dataset.tokens()
nWords = len(tokens)# 载入预训练好的词向量
_, wordVectors0, _ = load_saved_params()
wordVectors = (wordVectors0[:nWords,:] + wordVectors0[nWords:,:])
dimVectors = wordVectors.shape[1]# 载入训练集
trainset = dataset.getTrainSentences()
nTrain = len(trainset)
trainFeatures = np.zeros((nTrain, dimVectors))
trainLabels = np.zeros((nTrain,), dtype=np.int32)
for i in xrange(nTrain):words, trainLabels[i] = trainset[i]trainFeatures[i, :] = getSentenceFeature(tokens, wordVectors, words)# 准备好训练集的特征
devset = dataset.getDevSentences()
nDev = len(devset)
devFeatures = np.zeros((nDev, dimVectors))
devLabels = np.zeros((nDev,), dtype=np.int32)
for i in xrange(nDev):words, devLabels[i] = devset[i]devFeatures[i, :] = getSentenceFeature(tokens, wordVectors, words)# 尝试不同的正则化系数
results = []
for regularization in REGULARIZATION:random.seed(3141)np.random.seed(59265)weights = np.random.randn(dimVectors, 5)print "Training for reg=%f" % regularization # batch optimizationweights = sgd(lambda weights: softmax_wrapper(trainFeatures, trainLabels, weights, regularization), weights, 3.0, 10000, PRINT_EVERY=100)# 训练集上测效果_, _, pred = softmaxRegression(trainFeatures, trainLabels, weights)trainAccuracy = accuracy(trainLabels, pred)print "Train accuracy (%%): %f" % trainAccuracy# dev集合上看效果_, _, pred = softmaxRegression(devFeatures, devLabels, weights)devAccuracy = accuracy(devLabels, pred)print "Dev accuracy (%%): %f" % devAccuracy# 保存结果权重results.append({"reg" : regularization, "weights" : weights, "train" : trainAccuracy, "dev" : devAccuracy})# 输出准确率
print ""
print "=== Recap ==="
print "Reg\t\tTrain\t\tDev"
for result in results:print "%E\t%f\t%f" % (result["reg"], result["train"], result["dev"])
print ""# 选最好的正则化系数
BEST_REGULARIZATION = None
BEST_WEIGHTS = Nonebest_dev = 0
for result in results:if result["dev"] > best_dev:best_dev = result["dev"]BEST_REGULARIZATION = result["reg"]BEST_WEIGHTS = result["weights"]# Test your findings on the test set
testset = dataset.getTestSentences()
nTest = len(testset)
testFeatures = np.zeros((nTest, dimVectors))
testLabels = np.zeros((nTest,), dtype=np.int32)
for i in xrange(nTest):words, testLabels[i] = testset[i]testFeatures[i, :] = getSentenceFeature(tokens, wordVectors, words)_, _, pred = softmaxRegression(testFeatures, testLabels, BEST_WEIGHTS)
print "Best regularization value: %E" % BEST_REGULARIZATION
print "Test accuracy (%%): %f" % accuracy(testLabels, pred)# 画出正则化和准确率的关系
plt.plot(REGULARIZATION, [x["train"] for x in results])
plt.plot(REGULARIZATION, [x["dev"] for x in results])
plt.xscale('log')
plt.xlabel("regularization")
plt.ylabel("accuracy")
plt.legend(['train', 'dev'], loc='upper left')
plt.savefig("q4_reg_v_acc.png")
plt.show()

(d)(4分)绘出在训练和开发过程中的分类准确率,并在x轴使用对数刻度来对正则化值进行相关设置。这应该自动化的进行。包括在你作业中详细展示的坐标图q4_reg_acc.png简明解释最多三个句子在此坐标图中的显示情况。
解答:

深度学习与自然语言处理(4)_斯坦福cs224d 大作业测验1与解答相关推荐

  1. 深度学习与自然语言处理(5)_斯坦福cs224d 大作业测验2与解答

    作业内容翻译:@胡杨(superhy199148@hotmail.com) && @面包君 && Fantzy同学 校正与调整:寒小阳 && 龙心尘 时 ...

  2. 深度学习与自然语言处理(3)_斯坦福cs224d Lecture 3

    原文作者:Rohit Mundra, Richard Socher 原文翻译:@熊杰(jie.xiong.cs@gmail.com) && @王昱森(ethanwang92@outlo ...

  3. 深度学习与自然语言处理(2)_斯坦福cs224d Lecture 2

    原文作者:Rohit Mundra, Richard Socher 原文翻译:@熊杰(jie.xiong.cs@gmail.com) && @王昱森 内容调整与校对:寒小阳 & ...

  4. 深度学习与自然语言处理(8)_斯坦福cs224d RNN,MV-RNN与RNTN

    原文作者:Richard Socher 翻译:@胥可 && @熊杰 && @杨帆 && @陈沛 && @Molly 校对调整:寒小阳 & ...

  5. 斯坦福大学深度学习与自然语言处理第二讲:词向量

    斯坦福大学在三月份开设了一门"深度学习与自然语言处理"的课程:CS224d: Deep Learning for Natural Language Processing,授课老师是 ...

  6. 斯坦福大学深度学习与自然语言处理第四讲:词窗口分类和神经网络

    斯坦福大学在三月份开设了一门"深度学习与自然语言处理"的课程:CS224d: Deep Learning for Natural Language Processing,授课老师是 ...

  7. 斯坦福大学深度学习与自然语言处理第三讲:高级的词向量表示

    斯坦福大学在三月份开设了一门"深度学习与自然语言处理"的课程:CS224d: Deep Learning for Natural Language Processing,授课老师是 ...

  8. 斯坦福大学深度学习与自然语言处理第一讲:引言

    斯坦福大学在三月份开设了一门"深度学习与自然语言处理"的课程:CS224d: Deep Learning for Natural Language Processing,授课老师是 ...

  9. 斯坦福大学深度学习与自然语言处理第一讲引言

    http://www.52nlp.cn/%E6%96%AF%E5%9D%A6%E7%A6%8F%E5%A4%A7%E5%AD%A6%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A ...

最新文章

  1. 初等数学O 集合论基础 第一节 集合及其基本运算、de Moivre公式
  2. 基于ReentrantLock发生死锁的解决方案
  3. 2009年网页设计10大趋势
  4. 70行Python代码,获取中国数据库大会(DTCC)全部PPT
  5. 新系统如何测试软件,怎样检测电脑能否升级到最新的Windows11系统?官方检测工具帮你一招搞定!...
  6. python数据结构-如何统计序列中元素的频度
  7. java开发简历模板下载,技术详细介绍
  8. 数电5_3——边沿触发的触发器
  9. phpstudy安装配置教程
  10. python用户画像_研究用户应该从哪几个维度去构建用户画像模型?
  11. Syntax error, annotations are only available if source level is 1.5 or greater错误
  12. 数据结构实训——运动会分数统计
  13. C语言调用jni中JNIEnv指针使用和理解
  14. H5移动端 引入高德地图(获取经纬度与地址带搜索反选
  15. 近红外光谱预测苹果糖度
  16. 2017 年终总结 —— 在路上
  17. 蓝牙学习笔记之建立蓝牙连接的过程
  18. 2010年3月编程语言排行榜
  19. Flutter尽然还能有这种操作!隔壁都馋哭了
  20. linux设置mac地址命令,[转载]Linux下修改MAC地址

热门文章

  1. 2019银行校招网站汇总
  2. 冷战2017,腾讯金融VS蚂蚁金服过去的一年
  3. 无人机飞控半实物实验平台
  4. IDEA从零到精通(15)之IDEA中创建maven项目
  5. Python 采用Scrapy爬虫框架爬取豆瓣电影top250
  6. 基于SpringBoot+jsp的商务安全邮箱邮件收发系统设计与实现
  7. 呕心沥血一周,我交出了这篇 List 面试文章,细不细你们定
  8. Thread、ThreadLocal、ThreadLocalMap是什么?看这一篇
  9. 虚拟服务器安装流程,虚拟主机安装WordPress教程,小白虚拟主机WordPress安装教程,流程很详细...
  10. Nono从一开始数数,他每数一个数时会计算这个数中1的个数(如211中有两个1)并对1的个数进行累和,当1的个数之和不小于x时,Nono就要起床了。特别需要注意的是,当Nono数数达到10000时,N