深度學習(三)之LSTM寫詩
-
根據前文生成詩:
機器學習業,聖賢不可求。臨戎辭蜀計,忠信盡封疆。天子諮兩相,建章應四方。自疑非俗態,誰復念鷦鷯。
-
生成藏頭詩:
國步平生不願君,古人今在古人風。
科公既得忘機者,白首空山道姓名。
大道不應無散處,未曾進退卻還徵。
環境:
- python:3.9.7
- pytorch:1.11.0
- numpy:1.21.2
程式碼地址:https://github.com/xiaohuiduan/deeplearning-study/tree/main/寫詩
資料預處理
資料集檔案由3部分組成:ix2word
word2ix
,data
:
- 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("滾去讀書")
滾發初生光,三乘如太白。 去去冥冥沒,冥茫寄天海。 讀書三十年,手把棼琴策。 書罷華省郎,憂人惜凋病。