上個禮拜做完了,今天做個總結,主要方法和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) 6分
長度爲n的句子,經過多少步後可以被解析完(用n表示)?簡要解析爲什麼
答:要使buffer長度爲0,則需要n步,使stack長度爲1,也需要n步,所以經過2n步後解析完畢。
問題© 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)
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.