下面给你一个简要的**任务概览**,帮助你理清整个作业需要完成的内容及交付物。在实际完成作业时,你需要同时写一些"**理论部分解答**"(相当于 written 作业)和"**代码实现**"(coding 作业),并按照助教要求提交。 --- # Part 1: Machine Learning & Neural Networks(8 分) 这是一些**基础理论题**,主要围绕 Adam 优化器和 Dropout 这两种神经网络技术展开。 1. **Adam Optimizer (4 分)** - (a)(i) 解答关于动量 (momentum) 的直觉: - 为什么引入 `m`(动量)可以减少更新的波动? - 为什么这种"低方差"更新可以帮助训练更稳定地收敛? - (a)(ii) 解答关于自适应学习率 (adaptive learning rate) 的直觉: - 为什么用 v\sqrt{v} 来缩放更新? - 哪些参数会得到更大或更小的更新? - 这样做对学习有什么好处? 2. **Dropout (4 分)** - (b)(i) 求出 dropout 中的系数 γ\gamma 跟 pdropp_{drop}(丢弃概率)的关系,并简要说明原因。 - (b)(ii) 为什么在**训练**时要使用 dropout,而在**推断/预测**(evaluation)时则不使用? 这部分你需要**写在书面报告(Assignment 3 [written])里**,回答上述问题(只需简要解释直觉即可)。 --- # Part 2: Neural Transition-Based Dependency Parsing(44 分) 这一部分是整个作业的核心:**实现一个基于神经网络的依存句法解析器**,并分析一些错误解析案例。你需要完成以下工作: ## 2(a) 理解并列出完整的转移序列 (4 分) 给定示例句 "I parsed this sentence correctly",以及一张目标依存树,你需要: - **一步步** 写出 parser 的状态转移(stack、buffer、所用的 transition 以及新增的依存关系)。 - 作业里已经示例了前面三步,你需要把后续步骤完整列出来,直到解析结束。 ## 2(b) 解析一个 n 词句子,需要多少步?(2 分) - 用简洁的理由说明,对长度为 n 的句子,为什么需要特定数量的 SHIFT / LEFT-ARC / RIGHT-ARC 步骤才能完成依存解析。 ## 2(c) 实现 `PartialParse` 的两个函数 (6 分) - 你需要在 `parser_transitions.py` 里完成类 `PartialParse` 的 `init` 与 `parse_step` 方法,来实现"单步"转移的逻辑。 - 可以用 `python parser_transitions.py part_c` 进行基本测试(但并非完整测试)。 ## 2(d) 实现 `minibatch_parse` 函数 (8 分) - 同样在 `parser_transitions.py` 里,按照给定算法"**小批量解析**"多句话。 - 你要用 `model`(提供的一个示例模型)去预测下一步应该做哪种转移,然后对这一小批的 partial parses 同步执行转移。 - 测试命令:`python parser_transitions.py part_d`。 ## 2(e) 训练并实现神经网络依存解析器 (12 分) - 核心是 `parser_model.py` 和 `run.py` 文件中的若干 TODO: 1. **`parser_model.py`** - `__init__`: 初始化网络,包括手写一个 **Embedding 层**、一个 **线性映射**层(hidden 层),再加一个 **输出层**(logits),以及相应的激活函数 ReLU 等。 > 注意作业要求**不允许**直接用 `torch.nn.Embedding`、`torch.nn.Linear`,而是需要手动写矩阵乘法和参数初始化。 - `embedding_lookup`: 给定词索引列表,把它们映射成对应的 embedding 向量,并拼接(concatenate)。 - `forward`: 前向传播,输出 logits,再用 softmax 得到预测。 2. **`run.py`** - `train_for_epoch` 和 `train`: 完成训练流程,包括把数据分批、前向/反向传播、更新参数,循环多 epoch。 - 训练完成后,脚本会在 dev/test 数据集上计算 **UAS (Unlabeled Attachment Score)**,并输出结果。 - 你需要写在报告里: - 你训练后在 dev set 上的最好 UAS 分数 - 以及在 test set 上的 UAS 分数 - 参考提示: - 你可以先 `python run.py -d` (debug 模式) 进行快速训练,看看是否能到 ~65+ 的 UAS; - 如果要跑完整训练,大约需要 **1 小时**,最好在 debug 确认没大问题后再跑全量。 ## 2(f) 错误解析分析 (12 分) 作业给出了四个句子的**错误**依存树,每个句子只有一个错误,对应四种常见错误类型: 1. **Prepositional Phrase Attachment Error (介词短语附着错误)** 2. **Verb Phrase Attachment Error (动词短语附着错误)** 3. **Modifier Attachment Error (修饰语附着错误)** 4. **Coordination Attachment Error (并列结构附着错误)** 对每个句子,你需要: - 指出**错误类型**是哪一种 - 给出**错误的依存关系**(即解析器产出的错误) - 给出**正确的依存关系**(应该正确附着到哪个词) 例子里也演示了如果句子里有个介词短语,attach 到了错误的 head,需要改成 attach 到正确的 head。 这部分也写在**书面报告**中提交。 --- # 最终提交 ## 1. 代码提交(Assignment 3 [coding]) 1. 你在 `parser_transitions.py`、`parser_model.py` 和 `run.py` 里完成的所有实现。 2. 作业官方提供了 `collect_submission.sh` 脚本来打包你的代码到 `assignment3.zip`。 3. 把 `assignment3.zip` 上传到 Gradescope 的 "**Assignment 3[coding]**"。 ## 2. 书面报告提交(Assignment 3 [written]) 你对 Part 1 (Adam + Dropout)、Part 2 (a)(b)(f) 等**问答题**、**错误解析分析**、**模型性能汇报**等写在一个 PDF(或学校要求的格式)里,上传到 "**Assignment 3 [written]**"。 --- # 总结 - **Part 1 (written)**:回答关于 Adam 和 Dropout 的理论题。 - **Part 2 (written + coding)**: 1. (a)~(b):手动写依存解析转移序列 & 解析步数; 2. (c)~(d):在 `parser_transitions.py` 完成 "单步 + 小批量" 解析逻辑(coding); 3. (e):实现并训练神经网络解析器(在 `parser_model.py` + `run.py` 里 coding),并汇报 UAS 分数(written); 4. (f):分析并给出 4 个句子的错误附着 (attachment) 类型与纠正(written)。 只要你**按作业要求实现并回答上述所有部分**,就完成了 Assignment 3 的全部要求。祝顺利完成! 以下是CS 224n Assignment #3 的主要任务分解和实现要点,帮助你高效完成作业: --- ## **1. 理论部分(8分)** ### **(a) Adam优化器** - **问题 i**:解释动量的作用 **答案要点**: - **动量(Momentum)** 通过维护梯度的移动平均(`m`),使更新方向更稳定,减少随机梯度下降中的震荡。 - **低方差的好处**:参数更新方向更一致,加速收敛,尤其在损失函数曲率复杂(如峡谷地形)时避免震荡。 - **问题 ii**:自适应学习率的影响 **答案要点**: - **参数更新幅度**:梯度较小的参数会获得更大的更新(因为 `v` 较小,分母 `sqrt(v)` 较小)。 - **帮助学习的原因**:自适应调整不同参数的学习率,避免梯度较小的参数被忽略,适合稀疏数据。 ### **(b) Dropout** - **问题 i**:计算 `γ` **答案要点**: - `γ = 1 / (1 - p_drop)` - **推导**:`E[h_drop]= γ * (1 - p_drop) * h = h` ⇒ `γ = 1 / (1 - p_drop)` - **问题 ii**:训练与评估的区别 **答案要点**: - **训练时应用**:增强模型鲁棒性,防止过拟合(通过随机失活模拟集成学习)。 - **评估时不应用**:保持确定性输出,避免随机性干扰预测结果。 --- ## **2. 依赖解析实现(44分)** ### **(a) 解析步骤分析(4分)** - **句子**:"I parsed this sentence correctly" **解析步骤示例**(需补充完整): | 堆栈 (Stack) | 缓冲区 (Buffer) | 新增依赖 (New Dependency) | 操作 (Transition) | |--------------|------------------|---------------------------|-------------------| |[ROOT]|[I, parsed,...]| - | 初始状态 | |[ROOT, I]|[parsed,...]| - | SHIFT | |[ROOT, parsed]|[this,...]| parsed → I | LEFT-ARC | ### **(b) 解析步骤数(2分)** - **答案**:`2n - 1` 步(n为句子词数) **解释**:每个词需一次SHIFT进入堆栈,每个依赖需一次ARC操作(共n-1次)。 ### **(c) 实现 `PartialParse` 类(6分)** - **关键函数**: ```python class PartialParse: def __init__(self, sentence): self.stack = ["ROOT"] self.buffer = list(sentence) self.dependencies = [] def parse_step(self, transition): if transition == "SHIFT": self.stack.append(self.buffer.pop(0)) elif transition == "LEFT-ARC": dependent = self.stack.pop(-2) head = self.stack[-1] self.dependencies.append((head, dependent)) elif transition == "RIGHT-ARC": dependent = self.stack.pop() head = self.stack[-1] self.dependencies.append((head, dependent)) ``` ### **(d) 实现 `minibatch_parse`(8分)** - **算法核心**:分批处理未完成的解析状态,动态移除已完成解析。 **代码框架**: ```python def minibatch_parse(sentences, model, batch_size): partial_parses = [PartialParse(s) for s in sentences] unfinished_parses = partial_parses.copy() while unfinished_parses: batch = unfinished_parses[:batch_size] transitions = model.predict(batch) for parse, trans in zip(batch, transitions): parse.parse_step(trans) unfinished_parses = [p for p in unfinished_parses if not p.is_complete()] return [p.dependencies for p in partial_parses] ``` ### **(e) 实现神经网络模型(12分)** - **关键组件**: - **嵌入层(Embedding)**:手动实现(禁止使用 `torch.nn.Embedding`)。 - **线性层(Linear)**:手动实现矩阵乘法(禁止使用 `torch.nn.Linear`)。 **前向传播代码示例**: ```python class ParserModel(nn.Module): def __init__(self, embeddings, n_features, hidden_size, n_classes): super().__init__() self.embed_size = embeddings.shape[1] self.embeddings = nn.Parameter(torch.tensor(embeddings)) self.W = nn.Parameter(torch.randn(n_features * self.embed_size, hidden_size)) self.b1 = nn.Parameter(torch.zeros(hidden_size)) self.U = nn.Parameter(torch.randn(hidden_size, n_classes)) self.b2 = nn.Parameter(torch.zeros(n_classes)) def embedding_lookup(self, w): return self.embeddings[w].view(-1, self.embed_size * w.shape[1]) def forward(self, w): x = self.embedding_lookup(w) h = torch.relu(torch.matmul(x, self.W) + self.b1) logits = torch.matmul(h, self.U) + self.b2 return logits ``` - **训练与评估**: - **目标**:在开发集(dev set)上达到UAS > 87,测试集(test set)接近92.5。 - **调参技巧**:调整隐藏层维度(`hidden_size`)、Adam超参数(`β1`, `β2`)、学习率(`α`)。 ### **(f) 错误分析(12分)** - **错误类型与修正示例**: **i. 句子**: ```text I disembarked and was heading to a wedding fearing my death . ``` - **错误类型**:Verb Phrase Attachment Error - **错误依赖**:`fearing → wedding` - **正确依赖**:`fearing → heading` **ii. 句子**: ```text It makes me want to rush out and rescue people from dilemmas of their own making . ``` - **错误类型**:Coordination Attachment Error - **错误依赖**:`rescue → people` - **正确依赖**:`rescue → rush` --- ## **3. 提交要求** - **代码**:实现 `parser_transitions.py` 和 `parser_model.py`,确保通过测试。 - **报告**:记录开发集和测试集的UAS分数(目标:开发集 > 87,测试集接近论文的92.5)。 - **错误分析**:对每个句子明确错误类型、错误依赖和正确依赖。 --- ## **关键调试技巧** 1. **梯度检查**:使用 `torch.autograd.gradcheck` 验证手动实现的梯度。 2. **维度对齐**:确保所有张量操作维度匹配(如嵌入向量拼接后的输入维度)。 3. **超参数调整**:若训练不稳定,尝试降低学习率(如从 `0.001` 开始)。 通过以上步骤,你将系统完成依赖解析器的实现与理论分析。遇到具体问题时,可进一步讨论代码实现细节或理论推导!