2019-CS224n-Assignment3

上个礼拜做完了,今天做个总结,主要方法和2017年差不多。

机器学习和神经网络 (8分)

这一节没什么难度,认真看 a3.pdf 就行。

Adam的论文:ADAM: A METHOD FOR STOCHASTIC OPTIMIZATION

Dropout论文:Dropout: A Simple Way to Prevent Neural Networks from
Overfitting

基于神经Transition的依赖解析 (42分)

依赖解析,就是分析句子的句法结构,建立 head 词和修饰这些head的词之间的关系。这次构建的是 transition-based 解析器,它增量的,每一次只进行一步解析动作来生成依赖关系,每一步解析称为 partial parse,可表示为:

  • 一个 stack ,已被处理的词
  • 一个 buffer ,待处理的词

  • 一个 dependencies ,解析器生成的依赖

初始状态下,stack里有只 ROOT 一个词,在每一次解析中,运行 transition 操作,分为三个类型:

  • SHIFT:将buffer的左边(头部)第一个词取出,放到stack的右边(尾部)
  • LEFT-ARC:将stack的右第二个词作为依赖项,它依赖于右边第一个词,生成一个依赖关系,并删除右第二个词。
  • RIGHT-ARC:将stack的右第一个词作为依赖项,它依赖于右边第二个词,生成一个依赖关系,并删除右第一个词。

当buffer长度为0,stack长度为1(只有ROOT)时就算解析完毕了。

mark

上图是初始操作+三步解析动作的示意图。

若A依赖于B,则B为 head ,A为 dependent,记为 $B \rightarrow A​$

几个问题:

问题(b) 6分

长度为n的句子,经过多少步后可以被解析完(用n表示)?简要解析为什么

答:要使buffer长度为0,则需要n步,使stack长度为1,也需要n步,所以经过2n步后解析完毕。

问题(c) 6分

完成parser_trainsitions.py

init

初始化函数

1
2
3
self.stack = ['ROOT']
self.buffer = self.sentence.copy()
self.dependencies = []

parse_step

注意,stack的栈顶是list的右边,buffer队头是list的左边

1
2
3
4
5
6
7
8
9
10
11
if transition == 'S':
self.stack.append(self.buffer[0])
self.buffer = self.buffer[1:]
elif transition == 'LA':
self.dependencies.append((self.stack[-1], self.stack[-2]))
self.stack[-2:] = self.stack[-1:]
elif transition == 'RA':
self.dependencies.append((self.stack[-2], self.stack[-1]))
self.stack.pop()
else:
raise Exception('Unknown transition %s' % transition)

minibatch_parse

sentences含多个句子,每个句子都有一个partial parse对象。所以每一次取出一个batch的parse来进行一次transition操作,同时要过滤掉已经完成的parse。

1
2
3
4
5
6
7
8
9
10
11
12
partial_parses = [PartialParse(s) for s in sentences]

unfinished_parses = partial_parses.copy()
while len(unfinished_parses) > 0:
batch_parses = unfinished_parses[:batch_size].copy()
transition = model.predict(batch_parses)
for i, parse in enumerate(batch_parses):
parse.parse_step(transition[i])
if len(parse.stack) == 1 and len(parse.buffer) == 0:
unfinished_parses.remove(parse)

dependencies = [parse.dependencies for parse in partial_parses]

问题(e) 10分

完成 parser_model.py

实质上就是搭建一个三层的前馈神经网络,用ReLU做激活函数,最后一层用softmax输出,交叉熵做损失函数,同时还加了embedding层

init

初始化三个层,n_features 表示每一个词用几个特征来表示,每一个特征都要embed,所以输入层的大小是 n_features * embed_size

1
2
3
4
5
6
7
8
9
10
# Input Layer
self.embed_to_hidden = nn.Linear(self.n_features*self.embed_size, self.hidden_size)
nn.init.xavier_uniform_(self.embed_to_hidden.weight, gain=1)

# Dropout Layer
self.dropout = nn.Dropout(self.dropout_prob)

# Output Layer
self.hidden_to_logits = nn.Linear(self.hidden_size, self.n_classes)
nn.init.xavier_uniform_(self.hidden_to_logits.weight, gain=1)

embedding_lookup

使用的是预训练的embedding(Collobert at. 2011)

1
2
x = self.pretrained_embeddings(t)
x = x.view(x.shape[0], x.shape[1]*x.shape[2])

forward

提取特征、输入网络拿到节点,这里没用加softmax层是因为 torch.nn.CrossEntropyLoss 会内部帮我们加

1
2
3
4
5
a = self.embedding_lookup(t)
h = self.embed_to_hidden(a)
h = F.relu(h)
h = self.dropout(h)
logits = self.hidden_to_logits(h)

接着完成run.py

train

1
2
optimizer = optim.Adam(parser.model.parameters(), lr=lr)
loss_func = nn.CrossEntropyLoss()

train_for_epoch

1
2
3
4
5
logits = parser.model(train_x)
loss = loss_func(logits, train_y)

loss.backward()
optimizer.step()

参考资料

[1] CS224n: Natural Language Processing with Deep Learning, 2019-03-21.

[2] CS224n Assignment 2, 2019-03-21.

坚持原创文章分享,您的支持将鼓励我继续创作!