高级教程: 作出动态决策和 Bi-LSTM CRF 重点
https://www.zhihu.com/question/35866596
條件隨機(jī)場(chǎng)
CRF(條件隨機(jī)場(chǎng))與Viterbi(維特比)算法原理詳解
https://blog.csdn.net/qq_42189083/article/details/89350890
?
https://zhuanlan.zhihu.com/p/97829287
LSTM 和CRF 關(guān)系
?
https://zhuanlan.zhihu.com/p/92672564
http://www.dataguru.cn/article-15211-1.html
Bilstm+LAN
動(dòng)態(tài) VS 靜態(tài)深度學(xué)習(xí)工具集
Pytorch 是一個(gè)?動(dòng)態(tài)?神經(jīng)網(wǎng)絡(luò)工具包. 另一個(gè)動(dòng)態(tài)工具包的例子是?Dynet?(我之所以提這個(gè)是因?yàn)槭褂?Pytorch 和 Dynet 是十分類似的. 如果你看過(guò) Dynet 中的例子, 那么它將有可能對(duì)你在 Pytorch 下實(shí)現(xiàn)它有幫助). 與動(dòng)態(tài)相反的是?靜態(tài)?工具包, 包括了 Theano, Keras, TensorFlow 等等. 下面是這兩者核心的一些區(qū)別:
- 在一個(gè)靜態(tài)工具包中, 你一次性定義好一個(gè)計(jì)算圖, 接著編譯它, 然后把數(shù)據(jù)流輸實(shí)例送進(jìn)去.
- 在一個(gè)動(dòng)態(tài)工具包中, 你?為每一個(gè)實(shí)例?定義一個(gè)計(jì)算圖, 它完全不需要被編譯并且是在運(yùn)行中實(shí)時(shí)執(zhí)行的.
若沒(méi)有豐富的經(jīng)驗(yàn), 你很難體會(huì)出其中的差別. 舉一個(gè)例子, 假設(shè)我們想要構(gòu)建一個(gè)深度句法分析器. 那么我們的模型需要下列的一些步驟:
- 我們從下往上構(gòu)建樹(shù)
- 標(biāo)注根節(jié)點(diǎn)(句子中的詞語(yǔ))
- 從那兒開(kāi)始, 使用一個(gè)神經(jīng)網(wǎng)絡(luò)和詞向量來(lái)找到組成句法的不同組合. 一旦當(dāng)你形成了一個(gè)新的句法, 使用某種方式得到句法的嵌入表示 (embedding). 在這個(gè)例子里, 我們的網(wǎng)絡(luò)架構(gòu)將會(huì) 完全的依賴于輸入的句子. 來(lái)看這個(gè)句子: “綠色貓抓了墻”, 在這個(gè)模型的某一節(jié)點(diǎn), 我們想要把范圍?轉(zhuǎn)存失敗重新上傳取消?合并起來(lái)(即, 一個(gè) NP 句法范圍跨越詞1到詞3, 在這個(gè)例子中是”綠色貓”).
然而, 另一個(gè)句子可能是”某處, 那個(gè)大肥貓抓了墻.” 在這個(gè)句子中, 我們想要在某點(diǎn)形成句法?轉(zhuǎn)存失敗重新上傳取消?. 我們想要形成的句法將會(huì)依賴于這個(gè)實(shí)例. 如果僅僅編譯這個(gè)計(jì)算圖一次, 就像在靜態(tài)工具包中那樣, 那么我們給這個(gè)邏輯編程將會(huì)變得十分困難或者根本不可能. 然而, 在一個(gè)動(dòng)態(tài)工具包中, 并不僅僅只有一個(gè)預(yù)定義的計(jì)算圖. 對(duì)于每一個(gè)實(shí)例, 都能夠有一個(gè)新的計(jì)算圖, 所以上面的問(wèn)題就不復(fù)存在了.
動(dòng)態(tài)工具包也具有更容易調(diào)試和更接近所使用的編程語(yǔ)言的特點(diǎn)(我的意思是 Pytorch 和 Dynet 看上去 比 Keras 和 Theano 更像 Python).
Bi-LSTM CRF (條件隨機(jī)場(chǎng)) 討論
在這一部分, 我們將會(huì)看到一個(gè)完整且復(fù)雜的 Bi-LSTM CRF (條件隨機(jī)場(chǎng))用來(lái)命名實(shí)體識(shí)別 (NER) 的例子. 上面的 LSTM 標(biāo)注工具通常情況下對(duì)詞性標(biāo)注已經(jīng)足夠用了, 但一個(gè)序列模型比如 CRF 對(duì)于在 NER 下取得 強(qiáng)勁的表現(xiàn)是至關(guān)重要的. 假設(shè)熟悉 CRF. 盡管這個(gè)名字聽(tīng)上去嚇人, 但所有的模型只是一個(gè)由 LSTM 提供 特征的 CRF. 但這是一個(gè)高級(jí)的模型, 遠(yuǎn)比這個(gè)教程中的其它早期的模型更加復(fù)雜. 如果你要跳過(guò)這一部分, 沒(méi)有關(guān)系. 想要確定你是否準(zhǔn)備好, 那看看你是不是能夠:
- 復(fù)現(xiàn)標(biāo)簽 k 的第 i 步維特比變量的算法.
- 修改上述循環(huán)來(lái)計(jì)算正向變量.
- 再一次修改上述復(fù)現(xiàn)來(lái)在對(duì)數(shù)空間中計(jì)算正向變量. (提示: 對(duì)數(shù)-求和-指數(shù))
如果你能夠完成以上三件事, 那么你就不難理解下面的代碼了. 回想一下, CRF 計(jì)算的是一個(gè)條件概率. 讓?存失敗重新上傳取消作為一個(gè)標(biāo)注序列,?轉(zhuǎn)存失敗重新上傳取消?作為某個(gè)詞的輸入序列. 接下來(lái)我們計(jì)算:
轉(zhuǎn)存失敗重新上傳取消
上面的分?jǐn)?shù) Score 是由定義一些對(duì)數(shù)勢(shì)能?轉(zhuǎn)存失敗重新上傳取消?而決定的. 進(jìn)而
轉(zhuǎn)存失敗重新上傳取消
要使分割函數(shù)易于掌控, 勢(shì)能必須只能集中于局部的特征.
在 Bi-LSTM CRF 中, 我們定義兩種勢(shì)能 (potential): 釋放 (emission) 和過(guò)渡 (transition). 索引?轉(zhuǎn)存失敗重新上傳取消?處字的釋放勢(shì)能來(lái)自于?轉(zhuǎn)存失敗重新上傳取消?時(shí)間處的 Bi-LSTM 的隱藏狀態(tài). 過(guò)渡勢(shì)能的分?jǐn)?shù)儲(chǔ)存在?轉(zhuǎn)存失敗重新上傳取消?矩陣?轉(zhuǎn)存失敗重新上傳取消?, 其中?轉(zhuǎn)存失敗重新上傳取消?是標(biāo)注集合. 在我的實(shí)現(xiàn)中,?轉(zhuǎn)存失敗重新上傳取消?是從標(biāo)注?轉(zhuǎn)存失敗重新上傳取消?過(guò)渡到 標(biāo)注?轉(zhuǎn)存失敗重新上傳取消?的得分. 因此:
轉(zhuǎn)存失敗重新上傳取消
轉(zhuǎn)存失敗重新上傳取消
在上面第二個(gè)表達(dá)式中, 我們認(rèn)為標(biāo)簽被分配了獨(dú)一無(wú)二的非負(fù)索引.
如果上面的討論太簡(jiǎn)短了, 你還可以看看?這個(gè)?由 Michael Collins 寫(xiě)的關(guān)于 CRFs 的文章.
具體實(shí)現(xiàn)筆記
下面的例子實(shí)現(xiàn)了在對(duì)數(shù)空間中的前向算法來(lái)計(jì)算出分割函數(shù)和維特比算法來(lái)進(jìn)行譯碼. 反向傳播將會(huì)為我們自動(dòng)計(jì)算出梯度. 我們不需要手動(dòng)去實(shí)現(xiàn)這個(gè).
這個(gè)代碼中的實(shí)現(xiàn)并沒(méi)有優(yōu)化過(guò). 如果你理解下面的過(guò)程, 也許你會(huì)覺(jué)得下面的代碼中, 前向算法中 的迭代下一次標(biāo)注可以在一次大的運(yùn)算中完成. 雖然有簡(jiǎn)化的余地, 但我想的是讓代碼可讀性更好. 如果你想進(jìn)行相關(guān)的修改, 也許你可以在一些真實(shí)的任務(wù)中使用這個(gè)標(biāo)注器.
# 作者: Robert Guthrieimport torch
import torch.autograd as autograd
import torch.nn as nn
import torch.optim as optimtorch.manual_seed(1)
一些幫助函數(shù), 使代碼可讀性更好
def to_scalar(var):# 返回 python 浮點(diǎn)數(shù) (float)return var.view(-1).data.tolist()[0]def argmax(vec):# 以 python 整數(shù)的形式返回 argmax_, idx = torch.max(vec, 1)return to_scalar(idx)def prepare_sequence(seq, to_ix):idxs = [to_ix[w] for w in seq]tensor = torch.LongTensor(idxs)return autograd.Variable(tensor)# 使用數(shù)值上穩(wěn)定的方法為前向算法計(jì)算指數(shù)和的對(duì)數(shù)
def log_sum_exp(vec):max_score = vec[0, argmax(vec)]max_score_broadcast = max_score.view(1, -1).expand(1, vec.size()[1])return max_score + \torch.log(torch.sum(torch.exp(vec - max_score_broadcast)))
創(chuàng)建模型
class BiLSTM_CRF(nn.Module):def __init__(self, vocab_size, tag_to_ix, embedding_dim, hidden_dim):super(BiLSTM_CRF, self).__init__()self.embedding_dim = embedding_dimself.hidden_dim = hidden_dimself.vocab_size = vocab_sizeself.tag_to_ix = tag_to_ixself.tagset_size = len(tag_to_ix)self.word_embeds = nn.Embedding(vocab_size, embedding_dim)self.lstm = nn.LSTM(embedding_dim, hidden_dim // 2,num_layers=1, bidirectional=True)# 將LSTM的輸出映射到標(biāo)記空間self.hidden2tag = nn.Linear(hidden_dim, self.tagset_size)# 過(guò)渡參數(shù)矩陣. 條目 i,j 是# *從* j *到* i 的過(guò)渡的分?jǐn)?shù)self.transitions = nn.Parameter(torch.randn(self.tagset_size, self.tagset_size))# 這兩句聲明強(qiáng)制約束了我們不能# 向開(kāi)始標(biāo)記標(biāo)注傳遞和從結(jié)束標(biāo)注傳遞self.transitions.data[tag_to_ix[START_TAG], :] = -10000self.transitions.data[:, tag_to_ix[STOP_TAG]] = -10000self.hidden = self.init_hidden()def init_hidden(self):return (autograd.Variable(torch.randn(2, 1, self.hidden_dim // 2)),autograd.Variable(torch.randn(2, 1, self.hidden_dim // 2)))def _forward_alg(self, feats):# 執(zhí)行前向算法來(lái)計(jì)算分割函數(shù)init_alphas = torch.Tensor(1, self.tagset_size).fill_(-10000.)# START_TAG 包含所有的分?jǐn)?shù)init_alphas[0][self.tag_to_ix[START_TAG]] = 0.# 將其包在一個(gè)變量類型中繼而得到自動(dòng)的反向傳播forward_var = autograd.Variable(init_alphas)# 在句子中迭代for feat in feats:alphas_t = [] # 在這個(gè)時(shí)間步的前向變量for next_tag in range(self.tagset_size):# 對(duì) emission 得分執(zhí)行廣播機(jī)制: 它總是相同的,# 不論前一個(gè)標(biāo)注如何emit_score = feat[next_tag].view(1, -1).expand(1, self.tagset_size)# trans_score 第 i 個(gè)條目是# 從i過(guò)渡到 next_tag 的分?jǐn)?shù)trans_score = self.transitions[next_tag].view(1, -1)# next_tag_var 第 i 個(gè)條目是在我們執(zhí)行 對(duì)數(shù)-求和-指數(shù) 前# 邊緣的值 (i -> next_tag)next_tag_var = forward_var + trans_score + emit_score# 這個(gè)標(biāo)注的前向變量是# 對(duì)所有的分?jǐn)?shù)執(zhí)行 對(duì)數(shù)-求和-指數(shù)alphas_t.append(log_sum_exp(next_tag_var))forward_var = torch.cat(alphas_t).view(1, -1)terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]]alpha = log_sum_exp(terminal_var)return alphadef _get_lstm_features(self, sentence):self.hidden = self.init_hidden()embeds = self.word_embeds(sentence).view(len(sentence), 1, -1)lstm_out, self.hidden = self.lstm(embeds, self.hidden)lstm_out = lstm_out.view(len(sentence), self.hidden_dim)lstm_feats = self.hidden2tag(lstm_out)return lstm_featsdef _score_sentence(self, feats, tags):# 給出標(biāo)記序列的分?jǐn)?shù)score = autograd.Variable(torch.Tensor([0]))tags = torch.cat([torch.LongTensor([self.tag_to_ix[START_TAG]]), tags])for i, feat in enumerate(feats):score = score + \self.transitions[tags[i + 1], tags[i]] + feat[tags[i + 1]]score = score + self.transitions[self.tag_to_ix[STOP_TAG], tags[-1]]return scoredef _viterbi_decode(self, feats):backpointers = []# 在對(duì)數(shù)空間中初始化維特比變量init_vvars = torch.Tensor(1, self.tagset_size).fill_(-10000.)init_vvars[0][self.tag_to_ix[START_TAG]] = 0# 在第 i 步的 forward_var 存放第 i-1 步的維特比變量forward_var = autograd.Variable(init_vvars)for feat in feats:bptrs_t = [] # 存放這一步的后指針viterbivars_t = [] # 存放這一步的維特比變量for next_tag in range(self.tagset_size):# next_tag_var[i] 存放先前一步標(biāo)注i的# 維特比變量, 加上了從標(biāo)注 i 到 next_tag 的過(guò)渡# 的分?jǐn)?shù)# 我們?cè)谶@里并沒(méi)有將 emission 分?jǐn)?shù)包含進(jìn)來(lái), 因?yàn)? 最大值并不依賴于它們(我們?cè)谙旅鎸?duì)它們進(jìn)行的是相加)next_tag_var = forward_var + self.transitions[next_tag]best_tag_id = argmax(next_tag_var)bptrs_t.append(best_tag_id)viterbivars_t.append(next_tag_var[0][best_tag_id])# 現(xiàn)在將所有 emission 得分相加, 將 forward_var# 賦值到我們剛剛計(jì)算出來(lái)的維特比變量集合forward_var = (torch.cat(viterbivars_t) + feat).view(1, -1)backpointers.append(bptrs_t)# 過(guò)渡到 STOP_TAGterminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]]best_tag_id = argmax(terminal_var)path_score = terminal_var[0][best_tag_id]# 跟著后指針去解碼最佳路徑best_path = [best_tag_id]for bptrs_t in reversed(backpointers):best_tag_id = bptrs_t[best_tag_id]best_path.append(best_tag_id)# 彈出開(kāi)始的標(biāo)簽 (我們并不希望把這個(gè)返回到調(diào)用函數(shù))start = best_path.pop()assert start == self.tag_to_ix[START_TAG] # 健全性檢查best_path.reverse()return path_score, best_pathdef neg_log_likelihood(self, sentence, tags):feats = self._get_lstm_features(sentence)forward_score = self._forward_alg(feats)gold_score = self._score_sentence(feats, tags)return forward_score - gold_scoredef forward(self, sentence): # 不要把這和上面的 _forward_alg 混淆# 得到 BiLSTM 輸出分?jǐn)?shù)lstm_feats = self._get_lstm_features(sentence)# 給定特征, 找到最好的路徑score, tag_seq = self._viterbi_decode(lstm_feats)return score, tag_seq
運(yùn)行訓(xùn)練
START_TAG = "<START>"
STOP_TAG = "<STOP>"
EMBEDDING_DIM = 5
HIDDEN_DIM = 4# 制造一些訓(xùn)練數(shù)據(jù)
training_data = [("the wall street journal reported today that apple corporation made money".split(),"B I I I O O O B I O O".split()
), ("georgia tech is a university in georgia".split(),"B I O O O O B".split()
)]word_to_ix = {}
for sentence, tags in training_data:for word in sentence:if word not in word_to_ix:word_to_ix[word] = len(word_to_ix)tag_to_ix = {"B": 0, "I": 1, "O": 2, START_TAG: 3, STOP_TAG: 4}model = BiLSTM_CRF(len(word_to_ix), tag_to_ix, EMBEDDING_DIM, HIDDEN_DIM)
optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-4)# 在訓(xùn)練之前檢查預(yù)測(cè)結(jié)果
precheck_sent = prepare_sequence(training_data[0][0], word_to_ix)
precheck_tags = torch.LongTensor([tag_to_ix[t] for t in training_data[0][1]])
print(model(precheck_sent))# 確認(rèn)從之前的 LSTM 部分的 prepare_sequence 被加載了
for epoch in range(300): # 又一次, 正常情況下你不會(huì)訓(xùn)練300個(gè) epoch, 這只是示例數(shù)據(jù)for sentence, tags in training_data:# 第一步: 需要記住的是Pytorch會(huì)累積梯度# 我們需要在每次實(shí)例之前把它們清除model.zero_grad()# 第二步: 為我們的網(wǎng)絡(luò)準(zhǔn)備好輸入, 即# 把它們轉(zhuǎn)變成單詞索引變量 (Variables)sentence_in = prepare_sequence(sentence, word_to_ix)targets = torch.LongTensor([tag_to_ix[t] for t in tags])# 第三步: 運(yùn)行前向傳遞.neg_log_likelihood = model.neg_log_likelihood(sentence_in, targets)# 第四步: 計(jì)算損失, 梯度以及# 使用 optimizer.step() 來(lái)更新參數(shù)neg_log_likelihood.backward()optimizer.step()# 在訓(xùn)練之后檢查預(yù)測(cè)結(jié)果
precheck_sent = prepare_sequence(training_data[0][0], word_to_ix)
print(model(precheck_sent))
# 我們完成了!
練習(xí): 為區(qū)別性標(biāo)注定義一個(gè)新的損失函數(shù)
在解碼的時(shí)候, 我們不一定需要?jiǎng)?chuàng)建一個(gè)計(jì)算圖, 因?yàn)槲覀儾⒉粡木S特比路徑分?jǐn)?shù)中做反向傳播. 不管怎樣, 既然我們有了它, 嘗試訓(xùn)練這個(gè)標(biāo)注器, 使其損失函數(shù)是維特比路徑分?jǐn)?shù)和黃金標(biāo)準(zhǔn)分?jǐn)?shù)之差. 需要弄清楚的是, 這個(gè)函數(shù)在預(yù)測(cè)標(biāo)注序列是正確的時(shí)候應(yīng)當(dāng)大于等于0. 這本質(zhì)上是?結(jié)構(gòu)化感知機(jī)?.
這個(gè)改動(dòng)應(yīng)當(dāng)是很簡(jiǎn)短的, 因?yàn)?Viterbi 和 scoresentence 是已經(jīng)實(shí)現(xiàn)好了的. 這是?依賴于訓(xùn)練實(shí)例的_ 計(jì)算圖的形狀的一個(gè)例子. 但我們并沒(méi)有嘗試過(guò)在一個(gè)靜態(tài)工具包上實(shí)現(xiàn)過(guò), 我想象中這是可行的但并不是很顯而易見(jiàn).
+?
找一些真實(shí)數(shù)據(jù)做一下比較吧!
總結(jié)
以上是生活随笔為你收集整理的高级教程: 作出动态决策和 Bi-LSTM CRF 重点的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: pytorch实现BiLSTM+CRF用
- 下一篇: PyTorch里面的torch.nn.P