Deep Learning for NLP with Pytorch-序列模型和LSTM網絡(長短記憶網絡)

原文鏈接:https://github.com/apachecn/pytorch-doc-zh/blob/master/docs/1.0/nlp_sequence_models_tutorial.md

之前我們已經學過了許多的前饋網絡. 所謂前饋網絡, 就是網絡中不會保存狀態. 然而有時 這並不是我們想要的效果. 在自然語言處理 (NLP, Natural Language Processing) 中, 序列模型是一個核心的概念. 所謂序列模型, 即輸入依賴於時間信息的模型. 一個典型的序列模型是隱馬爾科夫模型 (HMM, Hidden Markov Model). 另一個序列模型的例子是條件隨機場 (CRF, Conditional Random Field).

循環神經網絡是指可以保存某種狀態的神經網絡. 比如說, 神經網絡中上個時刻的輸出可以作爲下個 時刻的輸入的一部分, 以此信息就可以通過序列在網絡中一直往後傳遞. 對於LSTM (Long-Short Term Memory) 來說, 序列中的每個元素都有一個相應的隱狀態 hth_t, 該隱狀態 原則上可以包含序列當前結點之前的任一節點的信息. 我們可以使用隱藏狀態來預測語言模型 中的單詞, 詞性標籤以及其他各種各樣的東西.

Pytorch中的LSTM

在正式學習之前,有幾個點要說明一下,Pytorch中LSTM的輸入形式是一個3D的Tensor,每一個維度都有重要的意義,第一個維度就是序列本身,第二個維度是mini-batch中實例的索引,第三個維度是輸入元素的索引,我們之前沒有接觸過mini-batch,所以我們就先忽略它並假設第二維的維度是1。

如果要用"The cow jumped"這個句子來運行一個序列模型,那麼就應該把它整理成如下的形式:

$$\begin{split}\begin{bmatrix} \overbrace{q_\text{The}}^\text{row vector} \ q_\text{cow} \ q_\text{jumped} \end{bmatrix}\end{split}$$

除了有一個額外的大小爲1的第二維度.

此外, 你還可以向網絡逐個輸入序列, 在這種情況下, 第一個軸的大小也是1.

來看一個簡單的例子:

# 作者: Robert Guthrie

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)
lstm = nn.LSTM(3, 3)  # 輸入維度爲3維,輸出維度爲3維
inputs = [torch.randn(1, 3) for _ in range(5)]  # 生成一個長度爲5的序列

# 初始化隱藏狀態.
hidden = (torch.randn(1, 1, 3),
          torch.randn(1, 1, 3))
for i in inputs:
    # 將序列中的元素逐個輸入到LSTM.
    # 經過每步操作,hidden 的值包含了隱藏狀態的信息.
    out, hidden = lstm(i.view(1, 1, -1), hidden)

# 另外我們可以對一整個序列進行訓練.
# LSTM第一個返回的第一個值是所有時刻的隱藏狀態
# 第二個返回值是最後一個時刻的隱藏狀態
#(所以"out"的最後一個和"hidden"是一樣的)
# 之所以這樣設計:
# 通過"out"你能取得任何一個時刻的隱藏狀態,而"hidden"的值是用來進行序列的反向傳播運算, 具體方式就是將它作爲參數傳入後面的 LSTM 網絡.

# 增加額外的第二個維度.
inputs = torch.cat(inputs).view(len(inputs), 1, -1)
hidden = (torch.randn(1, 1, 3), torch.randn(1, 1, 3))  # 清空隱藏狀態. 
out, hidden = lstm(inputs, hidden)
print(out)
print(hidden)

輸出:

tensor([[[-0.0187,  0.1713, -0.2944]],

        [[-0.3521,  0.1026, -0.2971]],

        [[-0.3191,  0.0781, -0.1957]],

        [[-0.1634,  0.0941, -0.1637]],

        [[-0.3368,  0.0959, -0.0538]]], grad_fn=<StackBackward>)
(tensor([[[-0.3368,  0.0959, -0.0538]]], grad_fn=<StackBackward>), tensor([[[-0.9825,  0.4715, -0.0633]]], grad_fn=<StackBackward>))

例子:用LSTM來進行詞性標註

在這部分, 我們將會使用一個 LSTM 網絡來進行詞性標註. 在這裏我們不會用到維特比算法, 前向-後向算法或者任何類似的算法,而是將這部分內容作爲一個 (有挑戰) 的練習留給讀者, 希望讀者在瞭解了這部分的內容後能夠實現如何將維特比算法應用到 LSTM 網絡中來.

該模型如下:輸入的句子是$$w1,...,wM​$$對應的詞性爲$$y_1, ...,y_M​$$ ,用$$\hat{y}_i​$$表示對單詞$$w_i​$$詞性的預測,標籤的集合定義爲$$T$$。

這是一個結構預測模型, 我們的輸出是一個序列$$\hat{y}_1,...,\hat{y}_M$$, 其中$$\hat{y}_i\in T$$.

在進行預測時, 需將句子每個詞輸入到一個 LSTM 網絡中. 將時刻$$i​$$的隱藏狀態標記爲$$h_i​$$,同樣地, 對每個標籤賦一個獨一無二的索引 (類似 word embeddings 部分 word_to_ix 的設置). 然後就得到了$$\hat{y}_i​$$的預測規則。

$$\hat{y}^i=argmaxj (logSoftmax(Ahi+b))j​$$

即先對隱狀態進行一個仿射變換, 然後計算一個對數 softmax, 最後得到的預測標籤即爲對數 softmax 中最大的值對應的標籤. 注意, 這也意味着 $$A$$ 空間的維度是 $$|T|​$$.
準備數據:
def prepare_sequence(seq, to_ix):
    idxs = [to_ix[w] for w in seq]
    return torch.tensor(idxs, dtype=torch.long)

training_data = [
    ("The dog ate the apple".split(), ["DET", "NN", "V", "DET", "NN"]),
    ("Everybody read that book".split(), ["NN", "V", "DET", "NN"])
]
word_to_ix = {}
for sent, tags in training_data:
    for word in sent:
        if word not in word_to_ix:
            word_to_ix[word] = len(word_to_ix)
print(word_to_ix)
tag_to_ix = {"DET": 0, "NN": 1, "V": 2}

# 實際中通常使用更大的維度如32維, 64維.
# 這裏我們使用小的維度, 爲了方便查看訓練過程中權重的變化.
EMBEDDING_DIM = 6
HIDDEN_DIM = 6

輸出:

{'The': 0, 'dog': 1, 'ate': 2, 'the': 3, 'apple': 4, 'Everybody': 5, 'read': 6, 'that': 7, 'book': 8}

創建模型:

class LSTMTagger(nn.Module):

    def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size):
        super(LSTMTagger, self).__init__()
        self.hidden_dim = hidden_dim

        self.word_embeddings = nn.Embedding(vocab_size, embedding_dim)

        # LSTM以word_embeddings作爲輸入, 輸出維度爲 hidden_dim 的隱藏狀態值
        self.lstm = nn.LSTM(embedding_dim, hidden_dim)

        # 線性層將隱藏狀態空間映射到標註空間
        self.hidden2tag = nn.Linear(hidden_dim, tagset_size)
        self.hidden = self.init_hidden()

    def init_hidden(self):
        # 一開始並沒有隱藏狀態所以我們要先初始化一個
        # 關於維度爲什麼這麼設計請參考Pytoch相關文檔
        # 各個維度的含義是 (num_layers, minibatch_size, hidden_dim)
        return (torch.zeros(1, 1, self.hidden_dim),
                torch.zeros(1, 1, self.hidden_dim))

    def forward(self, sentence):
        embeds = self.word_embeddings(sentence)
        lstm_out, self.hidden = self.lstm(
            embeds.view(len(sentence), 1, -1), self.hidden)
        tag_space = self.hidden2tag(lstm_out.view(len(sentence), -1))
        tag_scores = F.log_softmax(tag_space, dim=1)
        return tag_scores

訓練模型:

model = LSTMTagger(EMBEDDING_DIM, HIDDEN_DIM, len(word_to_ix), len(tag_to_ix))
loss_function = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 查看訓練前的分數
# 注意: 輸出的 i,j 元素的值表示單詞 i 的 j 標籤的得分
# 這裏我們不需要訓練不需要求導,所以使用torch.no_grad()
with torch.no_grad():
    inputs = prepare_sequence(training_data[0][0], word_to_ix)
    tag_scores = model(inputs)
    print(tag_scores)

for epoch in range(300):  # 實際情況下你不會訓練300個週期, 此例中我們只是隨便設了一個值
    for sentence, tags in training_data:
        # 第一步: 請記住Pytorch會累加梯度.
        # 我們需要在訓練每個實例前清空梯度
        model.zero_grad()

        # 此外還需要清空 LSTM 的隱狀態,
        # 將其從上個實例的歷史中分離出來.
        model.hidden = model.init_hidden()

        # 準備網絡輸入, 將其變爲詞索引的 Tensor 類型數據
        sentence_in = prepare_sequence(sentence, word_to_ix)
        targets = prepare_sequence(tags, tag_to_ix)

        # 第三步: 前向傳播.
        tag_scores = model(sentence_in)

        # 第四步: 計算損失和梯度值, 通過調用 optimizer.step() 來更新梯度
        loss = loss_function(tag_scores, targets)
        loss.backward()
        optimizer.step()

# 查看訓練後的得分
with torch.no_grad():
    inputs = prepare_sequence(training_data[0][0], word_to_ix)
    tag_scores = model(inputs)

    # 句子是 "the dog ate the apple", i,j 表示對於單詞 i, 標籤 j 的得分.
    # 我們採用得分最高的標籤作爲預測的標籤. 從下面的輸出我們可以看到, 預測得
    # 到的結果是0 1 2 0 1. 因爲 索引是從0開始的, 因此第一個值0表示第一行的
    # 最大值, 第二個值1表示第二行的最大值, 以此類推. 所以最後的結果是 DET
    # NOUN VERB DET NOUN, 整個序列都是正確的!
    print(tag_scores)

輸出:

tensor([[-1.1389, -1.2024, -0.9693],
        [-1.1065, -1.2200, -0.9834],
        [-1.1286, -1.2093, -0.9726],
        [-1.1190, -1.1960, -0.9916],
        [-1.0137, -1.2642, -1.0366]])
tensor([[-0.0858, -2.9355, -3.5374],
        [-5.2313, -0.0234, -4.0314],
        [-3.9098, -4.1279, -0.0368],
        [-0.0187, -4.7809, -4.5960],
        [-5.8170, -0.0183, -4.1879]])
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章