深度學習(三)之LSTM寫詩

段小輝發表於2022-04-04

  1. 根據前文生成詩:

    機器學習業,聖賢不可求。臨戎辭蜀計,忠信盡封疆。天子諮兩相,建章應四方。自疑非俗態,誰復念鷦鷯。

  2. 生成藏頭詩:

    步平生不願君,古人今在古人風。

    公既得忘機者,白首空山道姓名。

    道不應無散處,未曾進退卻還徵。

環境:

  • python:3.9.7
  • pytorch:1.11.0
  • numpy:1.21.2

程式碼地址:https://github.com/xiaohuiduan/deeplearning-study/tree/main/寫詩

資料預處理

資料集檔案由3部分組成:ix2wordword2ixdata

  • ix2word:id到word的對映,如{23:'姑'},一共有8293個word。
  • word2ix2:word到id的對映,如{'姑':23}
  • data:儲存了詩的資料,一共有57580首詩,每條資料由125個word構成;如果詩的長度大於125則截斷,如果詩的長度小於125,則使用""進行填充。

每條資料的構成規則為:</s></s></s>\(\dots\)<START>詩詞<EOP>

在訓練的時候,不考慮填充資料,因此,將資料中的填充資料</s>去除,去除後,部分資料顯示如下:

構建資料集

模型輸入輸出決定了資料集怎麼構建,下圖是模型的輸入輸出示意圖。詩詞生成實際上是一個語言模型,我們希望Model能夠根據當前輸入\(x_0,x_1,x_2\dots x_{n-1}\)去預測下一個狀態\(x_n\)。如圖中所示例子,則是希望在訓練的過程中,模型能夠根據輸入<START>床前明月光生成床前明月光,

因此根據“<START>床前明月光,凝是地上霜。舉頭望明月,低頭思故鄉<EOP>”,可以生成如下的X和Y(seq_len=6)。

X:<START>床前明月光,Y:床前明月光,

X:,凝是地上霜,Y:凝是地上霜。

X:。舉頭望明月,Y:舉頭望明月,

X:,低頭思故鄉,Y:低頭思故鄉。

程式碼示意圖如下所示,seq_len代表每條訓練資料的長度。

seq_len = 48
X = []
Y = []

poems_data = [j for i in poems for j in i] # 將所有詩的內容變成一個一維陣列

for i in range(0,len(poems_data) - seq_len -1,seq_len):
    X.append(poems_data[i:i+seq_len])
    Y.append(poems_data[i+1:i+seq_len+1])

模型結構

模型結構如下所示,模型一共由3部分構成,Embedding層,LSTM層和全連線層。輸入資料首先輸入Embedding層,進行word2vec,然後將Word2Vec後的資料輸入到LSTM中,最後將LSTM的輸出輸入到全連線層中得到預測結果。

模型構建程式碼如下,其中在本文中embedding_dim=200,hidden_dim=1024

import torch
import torch.nn.functional as F
import torch.nn as nn
class PoemNet(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim):
        """
            vocab_size:訓練集合字典大小(8293)
            embedding_dim:word2vec的維度
            hidden_dim:LSTM的hidden_dim
        """
        super(PoemNet, self).__init__()
        self.hidden_dim = hidden_dim
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.lstm = nn.LSTM(embedding_dim, self.hidden_dim,batch_first=True)

        self.fc = nn.Sequential(
            nn.Linear(self.hidden_dim,2048),
            nn.ReLU(),
            nn.Dropout(0.25),
            
            nn.Linear(2048,4096),
            nn.Dropout(0.2),
            nn.ReLU(),
            nn.Linear(4096,vocab_size),
        )

    def forward(self, input,hidden=None):
        """
            input:輸入的詩詞
            hidden:在生成詩詞的時候需要使用,在pytorch中,如果不指定初始狀態h_0和C_0,則其
            預設為0.
            pytorch的LSTM的輸出是(output,(h_n,c_n))。實際上,output就是h_1,h_2,……h_n
        """
        embeds = self.embeddings(input)
        batch_size, seq_len = input.size()
        if hidden is None:
            output, hidden = self.lstm(embeds)
        else:
            # h_0,c_0 = hidden
            output, hidden = self.lstm(embeds,hidden)
    
        output = self.fc(output)
        output = output.reshape(batch_size * seq_len, -1)
        output = F.log_softmax(output,dim=1)
        return output,hidden

優化器使用的是Adam優化器,lr=0.001,損失函式是CrossEntropyLoss。訓練次數為100個epcoh。

生成詩

因為在模型構建的過程中,使用了dropout,所以在模型使用的時候,需要將model設定為eval模式。

生成詩的邏輯圖:

根據上文生成詩

根據上圖的原理,寫出的程式碼如下所示:

def generate_poem(my_words,max_len=128):
    '''
        根據前文my_words生成一首詩。max_len表示生成詩的最大長度。
    '''

    def __generate_next(idx,hidden=None):
        """
            根據input和hidden輸出下一個預測
        """
        input = torch.Tensor([idx]).view(1,1).long().to(device)
        output,hidden = my_net(input,hidden)
        return output,hidden

    # 初始化hidden狀態
    output,hidden = __generate_next(word2ix["<START>"])
    my_words_len = len(my_words)
    result = []
    for word in my_words:
        result.append(word)
        # 積累hidden狀態(h & c)
        output,hidden = __generate_next(word2ix[word],hidden)
    
    _,top_index = torch.max(output,1)

    word = idx2word[top_index[0].item()]

    result.append(word)

    for i in range(max_len-my_words_len):
        output,hidden = __generate_next(top_index[0].item(),hidden)

        _,top_index = torch.max(output,1)
        if top_index[0].item() == word2ix['<EOP>']: # 如果詩詞已經預測到結尾
            break
        word = idx2word[top_index[0].item()]
        result.append(word)
    return "".join(result)

generate_poem("睡覺")

睡覺寒爐火,晨鐘坐中朝。爐煙沾煖露,池月靜清砧。自有傳心法,曾無住處傳。不知塵世隔,一覺一壺秋。皎潔垂銀液,浮航入綠醪。誰知舊鄰里,相對似相親。

生成藏頭詩

生成藏頭詩的方法與根據上文生成詩的方法大同小異。

def acrostic_poetry(my_words):
    def __generate_next(idx,hidden=None):
        """
            根據input和hidden輸出下一個預測詞
        """
        input = torch.Tensor([idx]).view(1,1).long().to(device)
        output,hidden = my_net(input,hidden)
        return output,hidden

    def __generate(word,hidden):
        """
            根據word生成一句詩(以“。”結尾的話) 如根據床生成“床前明月光,凝是地上霜。”
        """
        generate_word = word2ix[word]
        sentence = []
        sentence.append(word)
        while generate_word != word2ix["。"]: 
            output,hidden = __generate_next(generate_word,hidden)
            _,top_index = torch.max(output,1)
            generate_word = top_index[0].item()
            sentence.append(idx2word[generate_word])
        # 根據"。"生成下一個隱狀態。
        _,hidden = __generate_next(generate_word,hidden)
        return sentence,hidden

    _,hidden = __generate_next(word2ix["<START>"])
    result = []
    for word in my_words:
        sentence,hidden = __generate(word,hidden)
        result.append("".join(sentence))
    print("\n".join(result))

acrostic_poetry("滾去讀書")

滾發初生光,三乘如太白。 去去冥冥沒,冥茫寄天海。 讀書三十年,手把棼琴策。 書罷華省郎,憂人惜凋病。

參考

相關文章