A2 · Word2Vec and Dependency Parsing

年份 2026(W26)—— 往年版本可能不同,见作业说明
分值 16%
截止 1/22(周四)4:30pm
算力 CPU 即可(PyTorch,CPU 版够用)
配哪几讲 候选(机器按术语匹配,供参考):第2讲 §三、回顾 Word2vec:从随机向量到词义;第2讲 §五、Word2vec 的更多细节与变体;第3讲 §四、梯度下降与"手算梯度"的开端;第3讲 §三、激活函数:从阈值单元到 SwiGLU;第4讲 §一、课前热身:让神经网络真正能训练起来的几个要点;第4讲 §三、神经语言模型:从固定窗口到 RNN

做什么

题面来自官方材料(本地原件在 _local/,官方链接见「材料」)。 编号是原件的编号(notebook 是 cell 序号、PDF 是页码),方便跟官方题面对号。 本项目不提供答案(D53)。

第 1 页

CS 224N Winter 2026 Assignment 2

Word2Vec and Dependency Parsing

截止日期:1 月 22 日,星期四,下午 4:30 PST

在本作业中,你将复习 Word2Vec 背后的数学,并使用 PyTorch 构建一个神经 dependency parser。如需复习 PyTorch 基础,请查看 Canvas 上的 PyTorch review session。

在 Part 1 中,你将探索使用 naive softmax loss 训练 Word2vec 模型时涉及的偏导数。在 Part 2 中,你将学习两种通用的神经网络技术(Adam Optimization 和 Dropout)。在 Part 3 中,你将使用 Part 2 中的技术实现并训练一个 dependency parser,然后分析几个错误的 dependency parse。

如果你使用 LaTeX,可以使用 \ifans{} 输入你的解答。

请在 Gradescope 上正确标注题目;否则,如果你不标注题目,TAs 会扣分。

1. 理解 word2vec(20 分)

回顾一下,word2vec 背后的关键洞见是“一个词由它周围的词来定义”。具体来说,考虑一个“中心”词 $c$,其前后被一定长度的上下文包围。我们把该上下文窗口中的词称为“outside words”($O$)。例如,在 Figure 1 中,上下文窗口长度为 2,中心词 $c$ 是“banking”,outside words 是“turning”、“into”、“crises”和“as”:

Figure 1: 窗口大小为 2 的 word2vec skip-gram 预测模型

Skip-gram word2vec 旨在学习概率分布 $P(O|C)$。具体来说,给定一个特定的词 $o$ 和一个特定的词 $c$,我们希望预测 $P(O=o\mid C=c)$:即词 $o$ 是 $c$ 的“outside”词的概率(也就是说,它落在 $c$ 的上下文窗口内)。我们通过对一系列向量点积取 softmax 函数来建模这个概率:

$$P(O=o\mid C=c)=\frac{\exp(u_o^\top v_c)}{\sum_{w\in\text{Vocab}}\exp(u_w^\top v_c)} \tag{1}$$

对于每个词,我们学习向量 $u$ 和 $v$,其中 $u_o$ 是表示 outside 词 $o$ 的“outside”向量,$v_c$ 是表示中心词 $c$ 的“center”向量。我们将这些参数存储在两个矩阵 $U$ 和 $V$ 中。$U$ 的各列是所有“outside”向量 $u_w$;$V$ 的各列是所有“center”向量 $v_w$。$U$ 和 $V$ 都为每个 $w \in \text{Vocabulary}$ 包含一个向量。[注 1]

[注 1] 假设我们词汇表中的每个词都匹配到一个整数 $k$。粗体小写字母表示向量。$u_k$ 既是 $U$ 的第 $k$ 列,也是索引为 $k$ 的词的“outside”词向量。$v_k$ 既是 $V$ 的第 $k$ 列,也是索引为 $k$ 的词的“center”词向量。为简化记号,我们将交替使用 $k$ 来指代词 $k$ 和词 $k$ 的索引。

第 2 页

回顾课堂内容,对于一对词 $c$ 和 $o$,loss 由下式给出:

$$J_{\text{naive-softmax}}(v_c, o, U) = -\log P(O=o\mid C=c). \tag{2}$$

我们可以把这个 loss 看作在特定中心词 $c$ 和特定 outside 词 $o$ 下,真实分布 $y$ 与预测分布 $\hat{y}$ 之间的 cross-entropy [注 2]。这里,$y$ 和 $\hat{y}$ 都是长度等于词汇表词数的向量。此外,这些向量中的第 $k$ 个条目表示第 $k$ 个词在给定 $c$ 时成为“outside word”的条件概率。对于中心词 $c$ 和 outside 词 $o$ 的这个特定例子,真实经验分布 $y$ 是一个 one-hot 向量,在真实 outside 词 $o$ 处为 1,其他所有位置为 0。[注 3] 预测分布 $\hat{y}$ 是由式 (1) 中我们的模型给出的概率分布 $P(O\mid C=c)$。

Note: 在本作业中,计算导数时请使用课堂上复习的方法(即不要使用 Taylor Series Approximations)。

(a)(2 分)

证明 naive-softmax loss(式 (2))与 $y$ 和 $\hat{y}$ 之间的 cross-entropy loss 相同,即(注意 $y$(真实分布)、$\hat{y}$(预测分布)是向量,而 $\hat{y}_o$ 是标量):

$$-\sum_{w\in\text{Vocab}} y_w \log(\hat{y}_w) = -\log(\hat{y}_o). \tag{3}$$

你的答案应为一句话。你可以用文字描述答案。

(b)(6 分)

i. 计算 $J_{\text{naive-softmax}}(v_c, o, U)$ 对 $v_c$ 的偏导数。请用 $y$、$\hat{y}$、$U$ 表示你的答案,并展示你的工作以获得满分。

ii. 你计算出的梯度何时等于零?写一个数学方程。

Hint: 你可能需要复习并使用一些入门线性代数概念。

iii. 你求得的梯度是两个项之差。请解释当该梯度从词向量 $v_c$ 中减去时,每一项如何改进词向量。

(c)(1 分)

在许多使用 word embeddings 的下游应用中,会使用 L2 归一化向量(例如 $u/\|u\|_2$,其中 $\|u\|_2=\sqrt{\sum_i u_i^2}$)代替其原始形式(例如 $u$)。让我们考虑一个假想的下游任务:将短语二分类为正面或负面,其中你根据各词 embedding 之和决定符号。L2 归一化在什么时候会去掉对下游任务有用的信息?在什么时候不会?

Hint: 考虑某些词 $x \neq y$ 和某个标量 $\alpha$ 满足 $u_x = \alpha u_y$ 的情况。当 $\alpha$ 为正时,归一化后的 $u_x$ 和归一化后的 $u_y$ 的值会是什么?对于这样的归一化要影响或不影响最终分类,$u_x$ 和 $u_y$ 可能有什么关系?

[注 2] 真实(离散)概率分布 $p$ 与另一个分布 $q$ 之间的 cross-entropy loss 为 $-\sum_i p_i \log(q_i)$。

[注 3] 注意,对于整个训练数据集,上下文词的真实条件概率分布不会是 one-hot。

[注 4] 这使我们可以使用 gradient descent 高效地最小化函数,而无需担心 reshape 或维度不匹配。遵循 shape convention 时,可以保证 $\theta := \theta - \alpha \frac{\partial J(\theta)}{\partial \theta}$ 是一个定义良好的更新规则。

第 3 页

(d)(1 分)

写出 $J_{\text{naive-softmax}}(v_c, o, U)$ 对 $U$ 的偏导数。请用列向量 $\frac{\partial J(v_c,o,U)}{\partial u_1}, \frac{\partial J(v_c,o,U)}{\partial u_2}, \cdots, \frac{\partial J(v_c,o,U)}{\partial u_{|\text{Vocab}|}}$ 来分解你的答案(不要进一步展开这些项)。无需推导,只需给出矩阵形式的答案。

(e)(5 分)

计算 $J_{\text{naive-softmax}}(v_c, o, U)$ 对每个“outside”词向量 $u_w$ 的偏导数。将有两种情况:$w=o$ 时,即真实“outside”词向量;以及 $w \neq o$ 时,即所有其他词。请用 $y$、$\hat{y}$ 和 $v_c$ 表示你的答案。在本子题中,你也可以使用这些项内的具体元素(如 $y_1, y_2, \dots$)。注意 $u_w$ 是向量,而 $y_1, y_2, \dots$ 是标量。展示你的工作以获得满分。

(f)(2 分)

作为本题的附加练习,你将求一些常见 loss 函数的导数,这些函数可能用于 word2vec 的变体(例如 negative sampling 变体)。Leaky ReLU(Leaky Rectified Linear Unit)激活函数由式 (4) 和 Figure 2 给出:

$$f(x)=\max(\alpha x, x) \tag{4}$$

Figure 2: Leaky ReLU

其中 $x$ 是标量,且 $0<\alpha<1$,请计算 $f(x)$ 对 $x$ 的导数。你可以忽略导数在 $0$ 处未定义的情况。[注 5]

(g)(3 分)

sigmoid 函数由式 (5) 给出:

$$\sigma(x)=\frac{1}{1+e^{-x}}=\frac{e^x}{e^x+1} \tag{5}$$

请计算 $\sigma(x)$ 对 $x$ 的导数,其中 $x$ 是标量。请用 $\sigma(x)$ 表示你的答案。展示你的工作以获得满分。

[注 5] 如果你对如何处理该点的导数感兴趣,可以阅读更多关于 subderivatives 概念的内容。

第 4 页

2. 神经网络优化(Neural Networks Optimization,8 分)

(a)(4 分)Adam 优化器

回顾标准的 Stochastic Gradient Descent(SGD)更新规则:

$$\theta_{t+1} \leftarrow \theta_t - \alpha \nabla_{\theta_t} J_{\text{minibatch}}(\theta_t)$$

其中 $t+1$ 是当前时间步,$\theta$ 是一个包含模型全部参数的向量($\theta_t$ 是时间步 $t$ 的模型参数,$\theta_{t+1}$ 是时间步 $t+1$ 的模型参数),$J$ 是损失函数,$\nabla_\theta J_{\text{minibatch}}(\theta)$ 是损失函数在一个 minibatch 数据上对参数的梯度,$\alpha$ 是学习率。Adam 优化[注 6]采用了一个更精细的更新规则,包含两个额外的步骤[注 7]。

i.(2 分)

首先,Adam 使用了一个叫做 momentum(动量)的技巧,维护 $m$,即梯度的滑动平均:

$$\begin{aligned} m_{t+1} &\leftarrow \beta_1 m_t + (1-\beta_1)\nabla_{\theta_t} J_{\text{minibatch}}(\theta_t)\\ \theta_{t+1} &\leftarrow \theta_t - \alpha m_{t+1} \end{aligned}$$

其中 $\beta_1$ 是 0 到 1 之间的超参数(通常设为 0.9)。请用 2–4 句话简要解释(不需要数学证明,给出直觉即可)使用 $m$ 如何使更新波动变小,以及这种低方差总体上为什么可能有助于学习。

ii.(2 分)

Adam 用 adaptive learning rates(自适应学习率)的技巧扩展了 momentum 的思想,维护 $v$,即梯度幅度的滑动平均:

$$\begin{aligned} m_{t+1} &\leftarrow \beta_1 m_t + (1-\beta_1)\nabla_{\theta_t} J_{\text{minibatch}}(\theta_t)\\ v_{t+1} &\leftarrow \beta_2 v_t + (1-\beta_2)\left(\nabla_{\theta_t} J_{\text{minibatch}}(\theta_t) \odot \nabla_{\theta_t} J_{\text{minibatch}}(\theta_t)\right)\\ \theta_{t+1} &\leftarrow \theta_t - \alpha \frac{m_{t+1}}{\sqrt{v_{t+1}}} \end{aligned}$$

其中 $\odot$ 和 $/$ 表示逐元素乘法和逐元素除法(所以 $z \odot z$ 是逐元素平方),$\beta_2$ 是 0 到 1 之间的超参数(通常设为 0.99)。既然 Adam 把更新除以 $\sqrt{v}$,哪些模型参数会得到更大的更新?为什么这可能有助于学习?请用 2–4 句话简要解释。

(b)(4 分)

Dropout[注 8] 是一种正则化技术。训练时,dropout 以概率 $p_{\text{drop}}$ 随机把隐藏层 $h$ 中的单元置为零(每个 minibatch 丢弃的单元不同),然后把 $h$ 乘以一个常数 $\gamma$。我们可以将其写成:

$$h_{\text{drop}} = \gamma d \odot h$$

其中 $d \in \{0,1\}^{D_h}$($D_h$ 是 $h$ 的大小)是一个掩码向量,其每一项以概率 $p_{\text{drop}}$ 为 0、以概率 $1-p_{\text{drop}}$ 为 1。$\gamma$ 的选取使得 $h_{\text{drop}}$ 的期望值为 $h$:

$$\mathbb{E}_{p_{\text{drop}}}[h_{\text{drop}}]_i = h_i$$

对所有 $i \in \{1,\dots,D_h\}$ 成立。

i.(2 分)

$\gamma$ 必须等于什么(用 $p_{\text{drop}}$ 表示)?请简要说明理由,或用上面给出的等式展示你的数学推导。

ii.(2 分)

为什么训练时应该使用 dropout?为什么评估(evaluation)时不应使用 dropout?请用 2–4 句话简要解释。提示:查看链接的 dropout 论文可能会有帮助。


[注 6] Kingma and Ba, 2015, https://arxiv.org/pdf/1412.6980.pdf

[注 7] 实际的 Adam 更新还使用了几个不太重要的额外技巧,但这里我们不关心它们。如果你想了解更多,可以看:http://cs231n.github.io/neural-networks-3/#sgd

[注 8] Srivastava et al., 2014, https://www.cs.toronto.edu/~hinton/absps/JMLRdropout.pdf

第 5 页

3. 基于神经网络的转移式依存句法分析(Neural Transition-Based Dependency Parsing,40 分)

本节中,你将实现一个基于神经网络的依存句法分析器(dependency parser),目标是在 UAS(Unlabeled Attachment Score,无标签依存正确率)指标上取得最佳性能。

开始之前,请按照 README 安装本作业所需的全部依赖。我们将使用来自 https://pytorch.org/get-started/locally/ 的 PyTorch 2.1.2(CUDA 选项设为 None),以及 tqdm 包 —— 它会在你的整个训练过程中产生进度条可视化。PyTorch 官方网站是很好的资源,其中有理解 PyTorch 的 Tensor 库和神经网络的教程。

依存句法分析器分析句子的语法结构,建立 head words(中心词)与修饰这些 head 的词之间的关系。依存句法分析器有多种类型,包括 transition-based parser(转移式)、graph-based parser(基于图)和 feature-based parser(基于特征)。你的实现将是一个 transition-based parser,它一次一步地增量构建句法分析。每一步它维护一个 partial parse(部分分析),表示如下:

初始时,stack 只包含 ROOT,dependencies 列表为空,buffer 按顺序包含句子中的所有词。每一步,分析器对 partial parse 施加一个 transition,直到 buffer 为空且 stack 大小为 1。可以施加以下 transition:

每一步,你的分析器将使用一个神经网络分类器在这三种 transition 之间做出选择。

(a)(4 分)

走一遍解析句子 “I presented my findings at the NLP conference” 所需的 transition 序列。该句的依存树如下图所示。在每一步,给出 stack 和 buffer 的配置,以及这一步施加了哪个 transition、新增了哪条依存关系(如果有)。前三步作为示例给出如下。

Stack Buffer New dependency Transition
[ROOT] [I, presented, my, findings, at, the, NLP, conference] Initial Configuration
[ROOT, I] [presented, my, findings, at, the, NLP, conference] SHIFT
[ROOT, I, presented] [my, findings, at, the, NLP, conference] SHIFT
[ROOT, presented] [my, findings, at, the, NLP, conference] presented→I LEFT-ARC

第 6 页

(b)(2 分) 一个包含 $n$ 个词的句子,解析时需要进行多少步(用 $n$ 表示)?请用 1–2 句话简要说明原因。

(c)(6 分) 实现 parser_transitions.py 中 PartialParse 类的 __init__ 和 parse_step 函数。这一步实现的是你的 parser 将要使用的 transition 机制。你可以通过运行 python parser_transitions.py part_c 来跑一些基本的(非穷尽的)测试。

(d)(8 分) 我们的网络会预测:在当前这个 partial parse 上,接下来应该施加哪一个 transition。我们本可以用它来解析单个句子——不断施加预测出的 transition,直到解析完成。但神经网络在一次性对一批数据做预测时效率要高得多(也就是说,同时为各个不同的 partial parse 预测下一个 transition)。我们可以用下面的算法以 minibatch 的方式解析句子。

Algorithm 1 Minibatch Dependency Parsing

请在 parser_transitions.py 的 minibatch_parse 函数中实现这个算法。你可以通过运行 python parser_transitions.py part_d 来跑一些基本的(非穷尽的)测试。

Note:你需要正确实现 minibatch_parse,才能评估你在 (e) 部分要构建的模型。不过训练模型并不需要它,所以即使 minibatch_parse 还没实现,你也应该能完成 (e) 的大部分内容。

(e)(20 分) 现在我们要训练一个神经网络:给定 stack、buffer 和 dependencies 的状态,预测接下来应该施加哪一个 transition。

首先,模型会抽取出一个表示当前状态的特征向量。我们将使用原始神经依存分析论文中提出的特征集:A Fast and Accurate Dependency Parser using Neural Networks [注 9]。抽取这些特征的函数已经在 utils/parser_utils.py 中为你实现好了。这个特征向量由一个 token 列表构成(例如:stack 中的最后一个词、buffer 中的第一个词、stack 中倒数第二个词的 dependent(如果存在)等等)。它们可以表示成一个整数列表 $\mathbf{w} = [w_1, w_2, \dots, w_m]$,其中 $m$ 是特征个数,每个 $0 \leq w_i < |V|$ 是该 token 在词表中的索引($|V|$ 是词表大小)。接着,我们的网络为每个词查一个 embedding,并把它们拼接成单个输入向量:

$$\mathbf{x} = [E_{w_1}, \dots, E_{w_m}] \in \mathbb{R}^{dm}$$

其中 $E \in \mathbb{R}^{|V| \times d}$ 是 embedding 矩阵,每一行 $E_w$ 是某个词 $w$ 的向量。

[注 9] Chen and Manning, 2014, https://nlp.stanford.edu/pubs/emnlp2014-depparser.pdf

第 7 页

其维度为 $d$。然后我们这样计算预测:

$$\begin{aligned} \mathbf{h} &= \mathrm{ReLU}(\mathbf{x}W + \mathbf{b}_1) \\ \mathbf{l} &= \mathbf{h}U + \mathbf{b}_2 \\ \hat{\mathbf{y}} &= \mathrm{softmax}(\mathbf{l}) \end{aligned}$$

其中 $\mathbf{h}$ 称为 hidden layer,$\mathbf{l}$ 称为 logits,$\hat{\mathbf{y}}$ 称为 predictions,且 $\mathrm{ReLU}(z) = \max(z, 0)$。我们将训练模型来最小化 cross-entropy loss:

$$J(\theta) = \mathrm{CE}(\mathbf{y}, \hat{\mathbf{y}}) = -\sum_{j=1}^{3} y_j \log \hat{y}_j$$

其中 $y_j$ 表示 $\mathbf{y}$ 的第 $j$ 个元素。为了计算训练集上的 loss,我们对所有训练样本取这个 $J(\theta)$ 的平均。

i. 计算 $\mathbf{h} = \mathrm{ReLU}(\mathbf{x}W + \mathbf{b}_1)$ 对 $\mathbf{x}$ 的导数。为简单起见,你只需要给出某两个下标 $i$ 和 $j$ 的 $\frac{\partial h_i}{\partial x_j}$。可以忽略导数在 0 处无定义的情况。

ii. 回忆在 part 1b 中,我们计算了 $J_{\text{naive-softmax}}(v_c, o, U)$ 的偏导数。同样地,请计算 $J(\theta)$ 对 $\mathbf{l}$ 的第 $i$ 个元素(记作 $l_i$)的偏导数。具体来说,计算 $\frac{\partial \mathrm{CE}(\mathbf{y}, \hat{\mathbf{y}})}{\partial l_i}$,假设 $\mathbf{l} \in \mathbb{R}^3$、$\hat{\mathbf{y}} \in \mathbb{R}^3$、$\mathbf{y} \in \mathbb{R}^3$,且真实标签为 $c$(即当 $j = c$ 时 $y_j = 1$)。提示:使用链式法则:

$$\frac{\partial J}{\partial \mathbf{l}} = \frac{\partial J}{\partial \hat{\mathbf{y}}} \cdot \frac{\partial \hat{\mathbf{y}}}{\partial \mathbf{l}}$$

iii. 我们将使用 UAS 分数作为评估指标。UAS 指 Unlabeled Attachment Score(无标签依存准确率),它的计算方式是:正确预测的依存关系数除以总依存关系数,不考虑关系类型(我们的模型不预测关系类型)。

在 parser_model.py 中,你会找到用 PyTorch 实现这个简单神经网络的骨架代码。补全 __init__、embedding lookup 和 forward 函数来实现这个模型。然后补全 run.py 文件中的 train_for_epoch 和 train 函数。

最后运行 python run.py 来训练你的模型,并在来自 Penn Treebank(用 Universal Dependencies 标注)的测试数据上计算预测。

Note:

Hints:

第 8 页

Deliverables

Submission Instructions

你需要在 GradeScope 上以两次提交的形式提交本次作业 —— 一次提交 Assignment 2 [coding],另一次提交 Assignment 2 [written]:

  1. 运行 collect_submission.sh 脚本,生成你的 assignment2.zip 文件。
  2. 将你的 assignment2.zip 文件上传到 GradeScope 的 Assignment 2 [coding]。
  3. 将你的书面解答上传到 GradeScope 的 Assignment 2 [written]。

材料

官方地址(W26 ✓ 全部验证过可下载 ✓)。本地副本放 _local/(只服务本地、不外传)。

材料 链接
官方代码 a2.zip(37 MB)
题目说明 PDF a2.pdf(0.5 MB)
LaTeX 模板 a2_tex.zip(0.4 MB)

怎么自查

这份作业和书的关系

见上表「配哪几讲」。等题面中英对照做完,这里会换成章节标题原文,并加定位校验 (指引指向一个不存在的标题会被 nav_check 报出来)。