上个礼拜做完了,今天做个总结,主要方法和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)时就算解析完毕了。
上图是初始操作+三步解析动作的示意图。
若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
初始化函数
self.stack = ['ROOT']
self.buffer = self.sentence.copy()
self.dependencies = []
parse_step
注意,stack的栈顶是list的右边,buffer队头是list的左边
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。
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
。
# 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)
x = self.pretrained_embeddings(t)
x = x.view(x.shape[0], x.shape[1]*x.shape[2])
forward
提取特征、输入网络拿到节点,这里没用加softmax层是因为 torch.nn.CrossEntropyLoss 会内部帮我们加
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
optimizer = optim.Adam(parser.model.parameters(), lr=lr)
loss_func = nn.CrossEntropyLoss()
train_for_epoch
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.