2019-CS224n-Assignment2

我的原文:2019-CS224n-Assignment2

这次复习cs224n主要是先熟悉python和pytorch,方便之后进行论文复现等工作,同时也回顾一下模型和数学公式推导,找找感觉。

解答:理解词向量(23分)

我们先快速回顾一下word2vec算法,它的核心思想是“一个词的含义取决于它周围的词”。具体来说,我们有一个中心词(center word) c,和这个词 c 周围上下文构成的窗口,这个窗口内的除了 c 之外的词叫做外围词(outside words)。比如下图中,中心词是“banking”,窗口大小为2,所以上下文窗口是:“turning”、”into“、”crises“和”as“。

mark

Skip-gram模型(word2vec的一种实现)目的是习得概率分布 $ P(O|C) $。这样一来,就能计算给定的一个词 o 和词 c 的概率 $ P(O=o|C=c) $(意为,在已知词 c 出现的情况下,词 o 出现的概率), c 是中心词,o 是外围词。

在word2vec中,这个条件概率分布是通过计算向量点积(dot-products),再应用naive-softmax函数得到的:

mark

这里, u o u_o uo 向量代表外围词, v c v_c vc 向量代表中心词。为了包含这些向量,我们有两个矩阵 U \boldsymbol{U} U V \boldsymbol{V} V U \boldsymbol{U} U 的列是外围词, V \boldsymbol{V} V 的列是中心词,这两矩阵都有所有词 $w \in Vocabulary $ 的表示 。

对于词 c 和词 o,损失函数为对数几率:

mark

可以从交叉熵的角度看这个损失函数。真实值为 y \boldsymbol{y} y ,是一个独热向量,预测值 y ^ \boldsymbol{\hat{y}} y^ 是由公式(1)计算得到。具体来说, y \boldsymbol{y} y 如果是第k个单词,那么它的第k维为1,其余维都是0,而 y ^ \boldsymbol{\hat{y}} y^ 的第k维表示这是第k个词的概率大小。

问题(a) (3分)

证明公式(2)给出的naive-softmax的损失函数,和 y \boldsymbol{y} y y ^ \boldsymbol{\hat{y}} y^ 的交叉熵损失函数是一样的,均如下所示(答案控制在一行)

mark

答:

因为除了 o o o 之外的词都不在窗口内,所以只有词 o o o 对损失函数有贡献

问题(b) (5分)

计算损失函数 J n a i v e − s o f t m a x ( v c , o , U ) \boldsymbol{J}_{naive-softmax}(v_c, o, \boldsymbol{U}) Jnaivesoftmax(vc,o,U) 对中心词 v c v_c vc 的偏导数,用 y \boldsymbol{y} y y ^ \boldsymbol{\hat{y}} y^ U \boldsymbol{U} U 来表示。

答:

为了方便表述,对于该外围词 o o o 我们设:

$ x_o = u_o^T v_c , , t_o =exp(x_o) , , s_o=\sum_{w \in Vocab} exp(x_w)$ , y o ^ = g o = t o s o \hat{y_o} = g _o=\frac{t_o}{s_o} yo^=go=soto ,下面对 x o x_o xo 求导

J = − l o g ( y o ^ ) = − l n ( g o ) J = -log(\hat{y_o})=-ln(g_o) J=log(yo^)=ln(go) (这里的log蕴含意思是ln)

t o ′ = t o = e x o t_o'= t_o = e^{x_o} to=to=exo s ′ = e x o s' = e^{x_o} s=exo

g o ′ = t o ′ s o − t o s o ′ s o 2 = g o ( 1 − g o ) g_o' =\frac{t_o's_o -t_os'_o}{s_o^2} = g_o (1-g_o) go=so2tosotoso=go(1go)

∴ $\frac{\partial{J}}{\partial{x_o}} = \frac{\partial{J}}{\partial{g_o}} · \frac{\partial{g_o}}{\partial{x_o}} = \hat{y_o} - y_o $

则对 v c v_c vc 的导数为:

∂ J ∂ v c = ∂ J ∂ x o ⋅ ∂ x o ∂ v c = ( y o ^ − y o ) ⋅ u T \frac{\partial{J}}{\partial{v_c}}=\frac{\partial{J}}{\partial{x_o}} · \frac{\partial{x_o}}{\partial{v_c}} = (\hat{y_o} - y_o) · u^T vcJ=xoJvcxo=(yo^yo)uT,引入矩阵得:

$\boldsymbol{J}_{native-softmax}=(v_c, o, \boldsymbol{U}) = (\hat{y} - y) · \boldsymbol{U} $

问题© (5分)

计算损失函数 J n a i v e − s o f t m a x ( v c , o , U ) \boldsymbol{J}_{naive-softmax}(v_c, o, \boldsymbol{U}) Jnaivesoftmax(vc,o,U) 对上下文窗口内的词 w w w 的偏导数,考虑两种情况,即 w 是外围词 o o o,和 w 不是 o o o,用 y \boldsymbol{y} y y ^ \boldsymbol{\hat{y}} y^ v c v_c vc 来表示。

答:

在问题(b)基础上,对 x w = u w T v c x_w=u_w^Tv_c xw=uwTvc 求导。

w ̸ = o w \not =o w̸=o 时:

t o ′ = 0 t_o'=0 to=0 s o ′ = e x w s_o'=e^{x_w} so=exw

g o ′ = 0 − t o s o ′ s o 2 = − e x o s o ⋅ e x w s o = − g o ⋅ g w g_o' = \frac{0-t_os'_o}{s_o^2}=- \frac{e^{x_o}}{s_o} · \frac{e^{x_w}}{s_o}=-g_o ·g_w go=so20toso=soexosoexw=gogw

w = o w = o w=o 时,由问题(b)得:

g o ′ = g o ( 1 − g o ) g_o'=g_o(1-g_o) go=go(1go)

综合上述两种情况,所以有:

∂ J ∂ x w = − ∑ i ∈ V o c a b y i ∂ l o g ( g i ) ∂ x w \frac{\partial{J}}{\partial{x_w}}=-\sum_{i \in Vocab}y_i\frac{\partial log(g_i)}{\partial{x_w}} xwJ=iVocabyixwlog(gi)

$ = -y_w(1-g_w) + \sum_{i \not=w}y_ig_w$

= − y w + g w ∑ i ∈ V o c a b y i = -y_w+g_w\sum_{i \in Vocab}y_i =yw+gwiVocabyi

$ = g_w-y_w$

∴ $\frac{\partial{J}}{\partial{u_w}} = (\hat{y}-y)·v_c $

问题(d) (3分)

sigmoid函数如公式(4)所示

mark

请计算出它对于 x \boldsymbol{x} x 的导数, x \boldsymbol{x} x 是一个向量

答:

σ ( x ) ′ = σ ( x ) ( 1 − σ ( x ) ) \sigma(x)'=\sigma(x)(1-\sigma(x)) σ(x)=σ(x)(1σ(x))

问题(e) (4分)

现在我们考虑负采样的损失函数。假设有K个负样本,表示为 w 1 , w 2 , . . . , w K w_1, w_2, ..., w_K w1,w2,...,wK,它们对应的向量为 u 1 , u 2 , . . . , u K u_1, u_2, ..., u_K u1,u2,...,uK,外围词 o ̸ ∈ { w 1 , w 2 , . . . , w K } o \not\in \{w_1, w_2, ..., w_K\} o̸{w1,w2,...,wK},则外围词 o o o 在中心词是 c c c 时产生的损失函数如公式(5)所示。

mark

根据该损失函数,重新计算问题(b)、问题©的偏导数,用 u o \boldsymbol{u}_o uo v c \boldsymbol{v}_c vc u k \boldsymbol{u}_k uk 来表示。

完成计算后,简要解释为什么这个损失函数比naive-softmax效率更高。

注意:你可以用问题(d)的答案来帮助你计算导数

答:

(略,详见代码)

提示:

  • 词库从 V o c a b Vocab Vocab 变成了这K+1个词

  • 在求内层导数的时候用了sigmoid函数

问题(f) (3分)

假设中心词是 c = w t c = w_t c=wt,上下文窗口是 [ w t − m , . . . , w t − 1 , w t , w t + 1 , . . . , w t + m ] [w_{t-m}, ..., w_{t-1}, w_t, w_{t+1}, ..., w_{t+m}] [wtm,...,wt1,wt,wt+1,...,wt+m] m m m 是窗口大小,回顾skip-gram的word2vec实现,在该窗口下的总损失函数是:

mark

这里, J ( v c , w t + j , U ) \boldsymbol{J}(\boldsymbol{v}_c, w_{t+j}, \boldsymbol{U}) J(vc,wt+j,U) 是外围词 w t + j w_{t+j} wt+j 在中心词 c = w t c=w_t c=wt 下产生的损失,损失函数可以是naive-softmax或者是neg-sample(负采样),这取决于具体实现。

计算:

(i) 损失函数对 U \boldsymbol{U} U 的偏导数

(ii) 损失函数对 v c \boldsymbol{v}_c vc 的偏导数

(iii) 损失函数对 u w \boldsymbol{u}_w uw w ̸ = c w \not= c w̸=c )的偏导数

答:

(略,详见代码)

提示:把上下文窗口所有词的损失加起来即可

代码:实现word2vec(20分)

点击 此处 下载代码,python版本 >= 3.5,需要安装numpy,你利用conda来配置环境:

conda env create -f env.yml
conda activate a2

写完代码后,运行:

conda deactivate

问题(a) (12分)

首先,实现 word2vec.py 里的 sigmoid函数,要支持向量输入。接着实现同一个文件里的 softmax 、负采样损失和导数。然后实现skip-gram的损失函数和导数。全部做完之后,运行python word2vec.py来检查是否正确。

答:

sigmoid

没什么好讲的,numpy会自己广播,最终得到向量输出

s = 1 / (1 + np.exp(-x))
naiveSoftmaxLossAndGradient

这个模型其实就是一个三层的前馈神经网络(详解),只需要注意维度即可,注释里已经标记出了维度。

需要注意的是,单词表示是在行,而不是列。

# forward
a, W, target = centerWordVec, outsideVectors, outsideWordIdx
a = a.reshape((a.shape[0], 1))
# assume N words, V dimentions, so
# a.shape == (V, 1) W.shape == (N, V)

z = np.dot(W, a) # (N, 1)
preds = softmax(z.reshape(-1)).reshape(-1, 1) # (N, 1)

loss = -np.log(preds[target])


# backprop
delta = preds.copy() # (N, 1)
delta[target] -= 1.0

gradCenterVec = np.dot(W.T, delta) # (V, 1)

gradOutsideVecs = np.dot(delta, a.T) # (N, V)

gradCenterVec = gradCenterVec.flatten() # (V, )
negSamplingLossAndGradient

与native-softmax不同的是:

  • 只选取K个非外围词(负样本,可能有重复词),外加1个正确的外围词,共K+1个输出
  • 最后一层使用sigmoid输出,而不是softmax

注意,反向传播得到的是这K+1个词的梯度,所以需要挨个更新到 梯度矩阵 中去

### Please use your implementation of sigmoid in here.

# indices might have same index
# extract W
W = np.zeros((len(indices), outsideVectors.shape[1]))
for i in range(len(indices)):
    W[i] = outsideVectors[indices[i]]

# forward
a = centerWordVec
a = a.reshape((a.shape[0], 1))

z = np.dot(W, a) # (K+1, 1)
preds = sigmoid(z)

# backprop
y = np.zeros((preds.shape[0], 1))
y[0] = 1 # index 0 is target

loss = -(y*np.log(preds) + (1 - y)*np.log(1 - preds)).sum()

delta = preds - y
gradCenterVec = np.dot(W.T, delta) # (V, 1)
gradW = np.dot(delta, a.T) # (K+1, V)
gradCenterVec = gradCenterVec.flatten()

# apply gradW into gradOutsideVecs
gradOutsideVecs = np.zeros_like(outsideVectors)
for i in range(len(indices)):
    oi = indices[i]
    gradOutsideVecs[oi] += gradW[i]
skipgram

遍历所有的外围词,求和损失函数

ci = word2Ind[currentCenterWord]
vc = centerWordVectors[ci]

for o in outsideWords:
    oi = word2Ind[o]
    loss_, gradVc, gradUo = word2vecLossAndGradient(vc, oi, outsideVectors, dataset)
    gradCenterVecs[ci] += gradVc
    gradOutsideVectors += gradUo
    loss += loss_

问题(b) (4分)

完成sgd.py文件的SGD优化器,运行python sgd.py来检查是否正确。

sgd

调用函数得到损失值和梯度,更新即可

loss, grad = f(x)
x = x - step*grad

问题© (4分)

至此所有的代码都写完了,接下来是下载数据集,这里我们使用Stanform Sentiment Treebank(SST)数据集,它可以用在简单的语义分析任务中去。通过运行 sh get_datasets.sh 可以获得该数据集,下载完成后运行 python run.py 即可。

注意:训练的时间取决于代码是否高效(即便是高效的实现,也要跑接近一个小时)

经过40,000次迭代后,最终结果会保存到 word_vectors.png 里。

答:

mark

  • 注意看,male->famale 和 king -> queen 这两条向量是平行的
  • (women, famale),(enjoyable,annoying) 这些词距离很近

参考

[1] CS224n: Natural Language Processing with Deep Learning, 2019-03-14. http://web.stanford.edu/class/cs224n.

[2] CS224n Assignment 1, 2019-03-14. http://www.hankcs.com/nlp/cs224n-assignment-1.html

  • 12
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值