【深度學(xué)習(xí)】PyTorch:Bi-LSTM的文本生成
編譯 | VK?
來源 | Towards Data Science
?“寫作沒有規(guī)定。有時(shí)它來得容易而且完美;有時(shí)就像在巖石上鉆孔,然后用炸藥把它炸開一樣?!薄?dú)W內(nèi)斯特·海明威
?
本文的目的是解釋如何通過實(shí)現(xiàn)基于LSTMs的強(qiáng)大體系結(jié)構(gòu)來構(gòu)建文本生成的端到端模型。
博客分為以下幾個(gè)部分:
介紹
文本預(yù)處理
序列生成
模型體系結(jié)構(gòu)
訓(xùn)練階段
文本生成
完整代碼請?jiān)L問:https://github.com/FernandoLpz/Text-Generation-BiLSTM-PyTorch
介紹
多年來,人們提出了各種各樣的建議來建模自然語言,但這是怎么回事呢?“建模自然語言”指的是什么?我們可以認(rèn)為“建模自然語言”是指對構(gòu)成語言的語義和語法進(jìn)行推理,本質(zhì)上是這樣,但它更進(jìn)一步。
目前,自然語言處理(NLP)領(lǐng)域通過不同的方法和技術(shù)處理不同的任務(wù),即對語言進(jìn)行推理、理解和建模。
自然語言處理(NLP)領(lǐng)域在過去的十年里發(fā)展非常迅速。許多模型都從不同的角度提出了解決不同NLP任務(wù)的方法。同樣,最受歡迎的模型中的共同點(diǎn)是實(shí)施基于深度學(xué)習(xí)的模型。
如前所述,NLP領(lǐng)域解決了大量的問題,特別是在本博客中,我們將通過使用基于深度學(xué)習(xí)的模型來解決文本生成問題,例如循環(huán)神經(jīng)網(wǎng)絡(luò)LSTM和Bi-LSTM。同樣,我們將使用當(dāng)今最復(fù)雜的框架之一來開發(fā)深度學(xué)習(xí)模型,特別是我們將使用PyTorch的LSTMCell類來開發(fā)。
問題陳述
給定一個(gè)文本,神經(jīng)網(wǎng)絡(luò)將通過字符序列來學(xué)習(xí)給定文本的語義和句法。隨后,將隨機(jī)抽取一系列字符,并預(yù)測下一個(gè)字符。
文本預(yù)處理
首先,我們需要一個(gè)我們要處理的文本。有不同的資源可以在純文本中找到不同的文本,我建議你看看Gutenberg項(xiàng)目(https://www.gutenberg.org/).。
在這個(gè)例子中,我將使用George Bird Grinnell的《Jack Among the Indians》這本書,你可以在這里找到:https://www.gutenberg.org/cache/epub/46205/pg46205.txt。所以,第一章的第一行是:
The?train?rushed?down?the?hill,?with?a?long?shrieking?whistle,?and?then?began?to?go?more?and?more?slowly.?Thomas?had?brushed?Jack?off?and?thanked?him?for?the?coin?that?he?put?in?his?hand,?and?with?the?bag?in?one?hand?and?the?stool?in?the?other?now?went?out?onto?the?platform?and?down?the?steps,?Jack?closely?following.
如你所見,文本包含大寫、小寫、換行符、標(biāo)點(diǎn)符號等。建議你將文本調(diào)整為一種形式,使我們能夠以更好的方式處理它,這主要降低我們將要開發(fā)的模型的復(fù)雜性。
我們要把每個(gè)字符轉(zhuǎn)換成它的小寫形式。另外,建議將文本作為一個(gè)字符列表來處理,也就是說,我們將使用一個(gè)字符列表,而不是使用“字符串”。將文本作為字符序列的目的是為了更好地處理生成的序列,這些序列將提供給模型(我們將在下一節(jié)中詳細(xì)介紹)。
代碼段1-預(yù)處理
def?read_dataset(file):
????letters?=?['a','b','c','d','e','f','g','h','i','j','k','l','m',
???????????????'n','o','p','q','r','s','t','u','v','w','x','y','z','?']
????
????#?打開原始文件
????with?open(file,?'r')?as?f:
????????raw_text?=?f.readlines()
????????
????#?將每一行轉(zhuǎn)換為小寫
????raw_text?=?[line.lower()?for?line?in?raw_text]
????
????#?創(chuàng)建一個(gè)包含整個(gè)文本的字符串
????text_string?=?''
????for?line?in?raw_text:
????????text_string?+=?line.strip()
????????
?????#?。創(chuàng)建一個(gè)字符數(shù)組
????text?=?list()
????for?char?in?text_string:
????????text.append(char)
????????
?????#?去掉所有的符號,只保留字母
????text?=?[char?for?char?in?text?if?char?in?letters]
?
????return?text
如我們所見,在第2行我們定義了要使用的字符,所有其他符號都將被丟棄,我們只保留“空白”符號。
在第6行和第10行中,我們讀取原始文件并將其轉(zhuǎn)換為小寫形式。
在第14行和第19行的循環(huán)中,我們創(chuàng)建了一個(gè)代表整本書的字符串,并生成了一個(gè)字符列表。在第23行中,我們通過只保留第2行定義的字母來過濾文本列表。
因此,一旦文本被加載和預(yù)處理,例如:
text?=?"The?train?rushed?down?the?hill."
可以得到這樣的字符列表:
text?=?['t','h','e','?','t','r','a','i','n','?','r','u','s','h','e','d','?','d','o','w','n',
'?','t','h','e','?','h','i','l','l']
我們已經(jīng)有了全文作為字符列表。眾所周知,我們不能將原始字符直接引入神經(jīng)網(wǎng)絡(luò),我們需要一個(gè)數(shù)值表示,因此,我們需要將每個(gè)字符轉(zhuǎn)換成一個(gè)數(shù)值表示。為此,我們將創(chuàng)建一個(gè)字典來幫助我們保存等價(jià)的“字符索引”和“索引字符”。
代碼段2-字典創(chuàng)建
def?create_dictionary(text):
?
??char_to_idx?=?dict()
??idx_to_char?=?dict()
??
??idx?=?0
??for?char?in?text:
????if?char?not?in?char_to_idx.keys():
??????
??????#?構(gòu)建字典
??????char_to_idx[char]?=?idx
??????idx_to_char[idx]?=?char
??????idx?+=?1
????
?return?char_to_idx,?idx_to_char
我們可以注意到,在第11行和第12行創(chuàng)建了“char-index”和index-char”字典。
到目前為止,我們已經(jīng)演示了如何加載文本并以字符列表的形式保存它,我們還創(chuàng)建了兩個(gè)字典來幫助我們對每個(gè)字符進(jìn)行編碼和解碼。
序列生成
序列生成的方式完全取決于我們要實(shí)現(xiàn)的模型類型。如前所述,我們將使用LSTM類型的循環(huán)神經(jīng)網(wǎng)絡(luò),它按順序接收數(shù)據(jù)(時(shí)間步長)。
對于我們的模型,我們需要形成一個(gè)給定長度的序列,我們稱之為“窗口”,其中要預(yù)測的字符(目標(biāo))將是窗口旁邊的字符。每個(gè)序列將由窗口中包含的字符組成。要形成一個(gè)序列,窗口一次向右得到一個(gè)字符。要預(yù)測的字符始終是窗口后面的字符。我們可以在圖中清楚地看到這個(gè)過程。

在本例中,窗口的大小為4,這意味著它將包含4個(gè)字符。目標(biāo)是作者在窗口圖像右邊的第一個(gè)字符
到目前為止,我們已經(jīng)看到了如何以一種簡單的方式生成字符序列。現(xiàn)在我們需要將每個(gè)字符轉(zhuǎn)換為其各自的數(shù)字格式,為此,我們將使用預(yù)處理階段生成的字典。這個(gè)過程可以在下圖可視化。

很好,現(xiàn)在我們知道了如何使用一個(gè)一次滑動(dòng)一個(gè)字符的窗口來生成字符序列,以及如何將字符轉(zhuǎn)換為數(shù)字格式,下面的代碼片段顯示了所描述的過程。
代碼段3-序列生成
def?build_sequences(text,?char_to_idx,?window):
????x?=?list()
????y?=?list()
????
????for?i?in?range(len(text)):
????????try:
????????????#?從文本中獲取字符窗口
????????????#?將其轉(zhuǎn)換為其idx表示
????????????sequence?=?text[i:i+window]
????????????sequence?=?[char_to_idx[char]?for?char?in?sequence]
???
????????????#得到target
????????????#?轉(zhuǎn)換到它的idx表示
????????????target?=?text[i+window]
????????????target?=?char_to_idx[target]
????????????
????????????#?保存sequence和target
????????????x.append(sequence)
????????????y.append(target)
????????????
?????????except:
????????????pass
????????
????x?=?np.array(x)
????y?=?np.array(y)
????
????return?x,?y
太棒了,現(xiàn)在我們知道如何預(yù)處理原始文本,如何將其轉(zhuǎn)換為字符列表,以及如何以數(shù)字格式生成序列?,F(xiàn)在我們來看看最有趣的部分,模型架構(gòu)。
模型架構(gòu)
正如你已經(jīng)在這篇博客的標(biāo)題中讀到的,我們將使用Bi-LSTM循環(huán)神經(jīng)網(wǎng)絡(luò)和標(biāo)準(zhǔn)LSTM。本質(zhì)上,我們使用這種類型的神經(jīng)網(wǎng)絡(luò),因?yàn)樗谔幚眄樞驍?shù)據(jù)時(shí)具有巨大的潛力,例如文本類型的數(shù)據(jù)。同樣,也有大量的文章提到使用基于循環(huán)神經(jīng)網(wǎng)絡(luò)的體系結(jié)構(gòu)(例如RNN、LSTM、GRU、Bi-LSTM等)進(jìn)行文本建模,特別是文本生成[1,2]。
?所提出的神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)由一個(gè)嵌入層、一個(gè)雙LSTM層和一個(gè)LSTM層組成。緊接著,后一個(gè)LSTM連接到一個(gè)線性層。
?
方法
該方法包括將每個(gè)字符序列傳遞到嵌入層,這將為構(gòu)成序列的每個(gè)元素生成向量形式的表示,因此我們將形成一個(gè)嵌入字符序列。隨后,嵌入字符序列的每個(gè)元素將被傳遞到Bi-LSTM層。隨后,將生成構(gòu)成雙LSTM(前向LSTM和后向LSTM)的LSTM的每個(gè)輸出的串聯(lián)。緊接著,每個(gè)前向+后向串聯(lián)的向量將被傳遞到LSTM層,最后一個(gè)隱藏狀態(tài)將從該層傳遞給線性層。最后一個(gè)線性層將有一個(gè)Softmax函數(shù)作為激活函數(shù),以表示每個(gè)字符的概率。下圖顯示了所描述的方法。

到目前為止,我們已經(jīng)解釋了文本生成模型的體系結(jié)構(gòu)以及實(shí)現(xiàn)的方法。現(xiàn)在我們需要知道如何使用PyTorch框架來實(shí)現(xiàn)所有這些,但是首先,我想簡單地解釋一下bilstm和LSTM是如何協(xié)同工作的,以便稍后了解如何在代碼中實(shí)現(xiàn)這一點(diǎn),那么讓我們看看bilstm網(wǎng)絡(luò)是如何工作的。
Bi-LSTM和LSTM
標(biāo)準(zhǔn)LSTM和Bi-LSTM的關(guān)鍵區(qū)別在于Bi-LSTM由2個(gè)LSTM組成,通常稱為“正向LSTM”和“反向LSTM”。基本上,正向LSTM以原始順序接收序列,而反向LSTM接收序列。隨后,根據(jù)要執(zhí)行的操作,兩個(gè)LSTMs的每個(gè)時(shí)間步的每個(gè)隱藏狀態(tài)都可以連接起來,或者只對兩個(gè)LSTMs的最后一個(gè)狀態(tài)進(jìn)行操作。在所提出的模型中,我們建議在每個(gè)時(shí)間步加入兩個(gè)隱藏狀態(tài)。
很好,現(xiàn)在我們了解了Bi-LSTM和LSTM之間的關(guān)鍵區(qū)別?;氐轿覀冋陂_發(fā)的示例中,下圖表示每個(gè)字符序列在通過模型時(shí)的演變。

太好了,一旦Bi-LSTM和LSTM之間的交互都很清楚,讓我們看看我們是如何在代碼中僅使用PyTorch框架中的LSTMcell來實(shí)現(xiàn)的。
那么,首先讓我們了解一下如何構(gòu)造TextGenerator類的構(gòu)造函數(shù),讓我們看看下面的代碼片段:
代碼段4-文本生成器類的構(gòu)造函數(shù)
class?TextGenerator(nn.ModuleList):
?
????def?__init__(self,?args,?vocab_size):
????????super(TextGenerator,?self).__init__()
????
????????self.batch_size?=?args.batch_size
????????self.hidden_dim?=?args.hidden_dim
????????self.input_size?=?vocab_size
????????self.num_classes?=?vocab_size
?self.sequence_len?=?args.window
????
????????#?Dropout
????????self.dropout?=?nn.Dropout(0.25)
????
????????#?Embedding?層
????????self.embedding?=?nn.Embedding(self.input_size,?self.hidden_dim,?padding_idx=0)
????
????????#?Bi-LSTM
????????#?正向和反向
????????self.lstm_cell_forward?=?nn.LSTMCell(self.hidden_dim,?self.hidden_dim)
????????self.lstm_cell_backward?=?nn.LSTMCell(self.hidden_dim,?self.hidden_dim)
????
????????#?LSTM?層
????????self.lstm_cell?=?nn.LSTMCell(self.hidden_dim?*?2,?self.hidden_dim?*?2)
????
????????#?Linear?層
????????self.linear?=?nn.Linear(self.hidden_dim?*?2,?self.num_classes)
如我們所見,從第6行到第10行,我們定義了用于初始化神經(jīng)網(wǎng)絡(luò)每一層的參數(shù)。需要指出的是,input_size等于詞匯表的大?。ㄒ簿褪钦f,我們的字典在預(yù)處理過程中生成的元素的數(shù)量)。同樣,要預(yù)測的類的數(shù)量也與詞匯表的大小相同,序列長度表示窗口的大小。
另一方面,在第20行和第21行中,我們定義了組成Bi-LSTM的兩個(gè)「LSTMCells」 (向前和向后)。在第24行中,我們定義了LSTMCell,它將與「Bi-LSTM」的輸出一起饋送。值得一提的是,隱藏狀態(tài)的大小是Bi-LSTM的兩倍,這是因?yàn)锽i-LSTM的輸出是串聯(lián)的。稍后在第27行定義線性層,稍后將由softmax函數(shù)過濾。
一旦定義了構(gòu)造函數(shù),我們需要為每個(gè)LSTM創(chuàng)建包含單元狀態(tài)和隱藏狀態(tài)的張量。因此,我們按如下方式進(jìn)行:
代碼片段5-權(quán)重初始化
#?Bi-LSTM
#?hs?=?[batch_size?x?hidden_size]
#?cs?=?[batch_size?x?hidden_size]
hs_forward?=?torch.zeros(x.size(0),?self.hidden_dim)
cs_forward?=?torch.zeros(x.size(0),?self.hidden_dim)
hs_backward?=?torch.zeros(x.size(0),?self.hidden_dim)
cs_backward?=?torch.zeros(x.size(0),?self.hidden_dim)
#?LSTM
#?hs?=?[batch_size?x?(hidden_size?*?2)]
#?cs?=?[batch_size?x?(hidden_size?*?2)]
hs_lstm?=?torch.zeros(x.size(0),?self.hidden_dim?*?2)
cs_lstm?=?torch.zeros(x.size(0),?self.hidden_dim?*?2)
#?權(quán)重初始化
torch.nn.init.kaiming_normal_(hs_forward)
torch.nn.init.kaiming_normal_(cs_forward)
torch.nn.init.kaiming_normal_(hs_backward)
torch.nn.init.kaiming_normal_(cs_backward)
torch.nn.init.kaiming_normal_(hs_lstm)
torch.nn.init.kaiming_normal_(cs_lstm)
一旦定義了包含隱藏狀態(tài)和單元狀態(tài)的張量,是時(shí)候展示整個(gè)體系結(jié)構(gòu)的組裝是如何完成的.
首先,讓我們看一下下面的代碼片段:
代碼片段6-BiLSTM+LSTM+線性層
#?從?idx?到?embedding
out?=?self.embedding(x)
#?為LSTM準(zhǔn)備shape
out?=?out.view(self.sequence_len,?x.size(0),?-1)
forward?=?[]
backward?=?[]
#?解開Bi-LSTM
#?正向
for?i?in?range(self.sequence_len):
??hs_forward,?cs_forward?=?self.lstm_cell_forward(out[i],?(hs_forward,?cs_forward))
??hs_forward?=?self.dropout(hs_forward)
??cs_forward?=?self.dropout(cs_forward)
??forward.append(hs_forward)
??
?#?反向
for?i?in?reversed(range(self.sequence_len)):
??hs_backward,?cs_backward?=?self.lstm_cell_backward(out[i],?(hs_backward,?cs_backward))
??hs_backward?=?self.dropout(hs_backward)
??cs_backward?=?self.dropout(cs_backward)
??backward.append(hs_backward)
??
?#?LSTM
for?fwd,?bwd?in?zip(forward,?backward):
??input_tensor?=?torch.cat((fwd,?bwd),?1)
??hs_lstm,?cs_lstm?=?self.lstm_cell(input_tensor,?(hs_lstm,?cs_lstm))
#?最后一個(gè)隱藏狀態(tài)通過線性層
out?=?self.linear(hs_lstm)
為了更好地理解,我們將用一些定義的值來解釋程序,這樣我們就可以理解每個(gè)張量是如何從一個(gè)層傳遞到另一個(gè)層的。所以假設(shè)我們有:
batch_size?=?64
hidden_size?=?128
sequence_len?=?100
num_classes?=?27
所以x輸入張量將有一個(gè)形狀:
#?torch.Size([batch_size,?sequence_len])
x?:?torch.Size([64,?100])
然后,在第2行中,x張量通過嵌入層傳遞,因此輸出將具有一個(gè)大?。?/p>
#?torch.Size([batch_size,?sequence_len,?hidden_size])
x_embedded?:?torch.Size([64,?100,?128])
需要注意的是,在第5行中,我們正在reshape ?x_embedded ?張量。這是因?yàn)槲覀冃枰獙⑿蛄虚L度作為第一維,本質(zhì)上是因?yàn)樵贐i-LSTM中,我們將迭代每個(gè)序列,因此重塑后的張量將具有一個(gè)形狀:
#?torch.Size([sequence_len,?batch_size,?hidden_size])
x_embedded_reshaped?:?torch.Size([100,?64,?128])
緊接著,在第7行和第8行定義了forward 和backward 列表。在那里我們將存儲Bi-LSTM的隱藏狀態(tài)。
所以是時(shí)候給Bi-LSTM輸入數(shù)據(jù)了。首先,在第12行中,我們在向前LSTM上迭代,我們還保存每個(gè)時(shí)間步的隱藏狀態(tài)(hs_forward)。在第19行中,我們迭代向后的LSTM,同時(shí)保存每個(gè)時(shí)間步的隱藏狀態(tài)(hs_backward)。你可以注意到循環(huán)是以相同的順序執(zhí)行的,不同之處在于它是以相反的形式讀取的。每個(gè)隱藏狀態(tài)將具有以下形狀:
#?hs_forward?:?torch.Size([batch_size,?hidden_size])
hs_forward?:?torch.Size([64,?128])
#?hs_backward?:?torch.Size([batch_size,?hidden_size])
hs_backward:?torch.Size([64,?128])
很好,現(xiàn)在讓我們看看如何為最新的LSTM層提供數(shù)據(jù)。為此,我們使用forward 和backward 列表。在第26行中,我們遍歷與第27行級聯(lián)的forward 和backward 對應(yīng)的每個(gè)隱藏狀態(tài)。需要注意的是,通過連接兩個(gè)隱藏狀態(tài),張量的維數(shù)將增加2倍,即張量將具有以下形狀:
#?input_tesor?:?torch.Size([bathc_size,?hidden_size?*?2])
input_tensor?:?torch.Size([64,?256])
最后,LSTM將返回大小為的隱藏狀態(tài):
#?last_hidden_state:?torch.Size([batch_size,?num_classes])
last_hidden_state:?torch.Size([64,?27])
最后,LSTM的最后一個(gè)隱藏狀態(tài)將通過一個(gè)線性層,如第31行所示。因此,完整的forward函數(shù)顯示在下面的代碼片段中:
代碼片段7-正向函數(shù)
def?forward(self,?x):
????
????#?Bi-LSTM
????#?hs?=?[batch_size?x?hidden_size]
????#?cs?=?[batch_size?x?hidden_size]
????hs_forward?=?torch.zeros(x.size(0),?self.hidden_dim)
????cs_forward?=?torch.zeros(x.size(0),?self.hidden_dim)
????hs_backward?=?torch.zeros(x.size(0),?self.hidden_dim)
????cs_backward?=?torch.zeros(x.size(0),?self.hidden_dim)
????
????#?LSTM
????#?hs?=?[batch_size?x?(hidden_size?*?2)]
????#?cs?=?[batch_size?x?(hidden_size?*?2)]
????hs_lstm?=?torch.zeros(x.size(0),?self.hidden_dim?*?2)
????cs_lstm?=?torch.zeros(x.size(0),?self.hidden_dim?*?2)
????
????#?權(quán)重初始化
????torch.nn.init.kaiming_normal_(hs_forward)
????torch.nn.init.kaiming_normal_(cs_forward)
????torch.nn.init.kaiming_normal_(hs_backward)
????torch.nn.init.kaiming_normal_(cs_backward)
????torch.nn.init.kaiming_normal_(hs_lstm)
????torch.nn.init.kaiming_normal_(cs_lstm)
????
????#?從?idx?到?embedding
????out?=?self.embedding(x)
????
????#?為LSTM準(zhǔn)備shape
????out?=?out.view(self.sequence_len,?x.size(0),?-1)
????
????forward?=?[]
????backward?=?[]
????
????#?解開Bi-LSTM
????#?正向
????for?i?in?range(self.sequence_len):
????????hs_forward,?cs_forward?=?self.lstm_cell_forward(out[i],?(hs_forward,?cs_forward))
????????hs_forward?=?self.dropout(hs_forward)
????????cs_forward?=?self.dropout(cs_forward)
????????forward.append(hs_forward)
????????
?????#?反向
????for?i?in?reversed(range(self.sequence_len)):
????????hs_backward,?cs_backward?=?self.lstm_cell_backward(out[i],?(hs_backward,?cs_backward))
????????hs_backward?=?self.dropout(hs_backward)
????????cs_backward?=?self.dropout(cs_backward)
????????backward.append(hs_backward)
????????
?????#?LSTM
????for?fwd,?bwd?in?zip(forward,?backward):
????????input_tensor?=?torch.cat((fwd,?bwd),?1)
????????hs_lstm,?cs_lstm?=?self.lstm_cell(input_tensor,?(hs_lstm,?cs_lstm))
????????
?????#?最后一個(gè)隱藏狀態(tài)通過線性層
????out?=?self.linear(hs_lstm)
????
????return?out
到目前為止,我們已經(jīng)知道如何使用PyTorch中的LSTMCell來組裝神經(jīng)網(wǎng)絡(luò)。現(xiàn)在是時(shí)候看看我們?nèi)绾芜M(jìn)行訓(xùn)練階段了,所以讓我們繼續(xù)下一節(jié)。
訓(xùn)練階段
太好了,我們來訓(xùn)練了。為了執(zhí)行訓(xùn)練,我們需要初始化模型和優(yōu)化器,稍后我們需要為每個(gè)epoch 和每個(gè)mini-batch,所以讓我們開始吧!
代碼片段8-訓(xùn)練階段
def?train(self,?args):
??
??#?模型初始化
??model?=?TextGenerator(args,?self.vocab_size)
??
??#?優(yōu)化器初始化
??optimizer?=?optim.RMSprop(model.parameters(),?lr=self.learning_rate)
??
??#?定義batch數(shù)
??num_batches?=?int(len(self.sequences)?/?self.batch_size)
??
??#?訓(xùn)練模型
??model.train()
??
??#?訓(xùn)練階段
??for?epoch?in?range(self.num_epochs):
????
????#?Mini?batches
????for?i?in?range(num_batches):
??????
??????#?Batch?定義
??????try:
????????x_batch?=?self.sequences[i?*?self.batch_size?:?(i?+?1)?*?self.batch_size]
????????y_batch?=?self.targets[i?*?self.batch_size?:?(i?+?1)?*?self.batch_size]
??????except:
????????x_batch?=?self.sequences[i?*?self.batch_size?:]
????????y_batch?=?self.targets[i?*?self.batch_size?:]
????????
??????#?轉(zhuǎn)換?numpy?array?為?torch?tensors
??????x?=?torch.from_numpy(x_batch).type(torch.LongTensor)
??????y?=?torch.from_numpy(y_batch).type(torch.LongTensor)
??????
??????#?輸入數(shù)據(jù)
??????y_pred?=?model(x)
??????
??????#?loss計(jì)算
??????loss?=?F.cross_entropy(y_pred,?y.squeeze())
??????
??????#?清除梯度
??????optimizer.zero_grad()
??????
??????#?反向傳播
??????loss.backward()
??????
??????#?更新參數(shù)
??????optimizer.step()
??????
??????print("Epoch:?%d?,??loss:?%.5f?"?%?(epoch,?loss.item()))
一旦模型被訓(xùn)練,我們將需要保存神經(jīng)網(wǎng)絡(luò)的權(quán)重,以便以后使用它們來生成文本。為此我們有兩種選擇,第一種是定義一個(gè)固定的時(shí)間段,然后保存權(quán)重,第二個(gè)是確定一個(gè)停止函數(shù),以獲得模型的最佳版本。在這個(gè)特殊情況下,我們將選擇第一個(gè)選項(xiàng)。在對模型進(jìn)行一定次數(shù)的訓(xùn)練后,我們將權(quán)重保存如下:
代碼段9-權(quán)重保存
#?保存權(quán)重
torch.save(model.state_dict(),?'weights/textGenerator_model.pt')
到目前為止,我們已經(jīng)看到了如何訓(xùn)練文本生成器和如何保存權(quán)重,現(xiàn)在我們將進(jìn)入這個(gè)博客的最后一部分,文本生成!
文本生成
我們已經(jīng)到了博客的最后一部分,文本生成。為此,我們需要做兩件事:第一件事是加載訓(xùn)練好的權(quán)重,第二件事是從序列集合中隨機(jī)抽取一個(gè)樣本作為模式,開始生成下一個(gè)字符。下面我們來看看下面的代碼片段:
代碼片段10-文本生成器
def?generator(model,?sequences,?idx_to_char,?n_chars):
??
??#?評估模式
??model.eval()
??
??#?定義softmax函數(shù)
??softmax?=?nn.Softmax(dim=1)
??
??#?從序列集合中隨機(jī)選取索引
??start?=?np.random.randint(0,?len(sequences)-1)
??
??#?給定隨機(jī)的idx來定義模式
??pattern?=?sequences[start]
??
??#?利用字典,它輸出了Pattern
??print("\nPattern:?\n")
??print(''.join([idx_to_char[value]?for?value?in?pattern]),?"\"")
??
??#?在full_prediction中,我們將保存完整的預(yù)測
??full_prediction?=?pattern.copy()
??
??#?預(yù)測開始,它將被預(yù)測為一個(gè)給定的字符長度
??for?i?in?range(n_chars):
????
????#?轉(zhuǎn)換為tensor
????pattern?=?torch.from_numpy(pattern).type(torch.LongTensor)
????pattern?=?pattern.view(1,-1)
????
????#?預(yù)測
????prediction?=?model(pattern)
????#?將softmax函數(shù)應(yīng)用于預(yù)測張量
????prediction?=?softmax(prediction)
????
????#?預(yù)測張量被轉(zhuǎn)換成一個(gè)numpy數(shù)組
????prediction?=?prediction.squeeze().detach().numpy()
????#?取概率最大的idx
????arg_max?=?np.argmax(prediction)
????
????#?將當(dāng)前張量轉(zhuǎn)換為numpy數(shù)組
????pattern?=?pattern.squeeze().detach().numpy()
????#?窗口向右1個(gè)字符
????pattern?=?pattern[1:]
????#?新pattern是由“舊”pattern+預(yù)測的字符組成的
????pattern?=?np.append(pattern,?arg_max)
????
????#?保存完整的預(yù)測
????full_prediction?=?np.append(full_prediction,?arg_max)
????
??print("Prediction:?\n")
??print(''.join([idx_to_char[value]?for?value?in?full_prediction]),?"\"")
因此,通過在以下特征下訓(xùn)練模型:
window?:?100
epochs?:?50
hidden_dim?:?128
batch_size?:?128
learning_rate?:?0.001
我們可以生成以下內(nèi)容:
Seed:
one?of?the?prairie?swellswhich?gave?a?little?wider?view?than?most?of?them?jack?saw?quite?close?to?the
Prediction:
one?of?the?prairie?swellswhich?gave?a?little?wider?view?than?most?of?them?jack?saw?quite?close?to?the?wnd?banngessejang?boffff?we?outheaedd?we?band?r?hes?tller?a?reacarof?t?t?alethe?ngothered?uhe?th?wengaco?ack?fof?ace?ca??e?s?alee?bin??cacotee?tharss?th?band?fofoutod?we?we?ins?sange?trre?anca?y?w?farer?we?sewigalfetwher?d?e??we?n?s?shed?pack?wngaingh?tthe?we?the?we?javes?t?supun?f?the?har?man?bllle?s?ng?ou???y?anghe?ond?we?nd?ba?a??she?t?t?anthendwe?wn?me?anom?ly?tceaig?t?i?isesw?arawns?t?d?ks?wao?thalac?tharr?jad??d?anongive?where?the?awe?w?we?he?is?ma?mie?cack?seat?sesant?sns?t?imes?hethof?riges?we?he?d?ooushe?he?hang?out?f?t?thu?inong?bll?llveco?we?see?s?the?he?haa?is?s?igg?merin?ishe?d?t?san?wack?owhe?o?or?th?we?sbe?se?we?we?inange?t?ts?wan?br?seyomanthe?harntho?thengn??th?me?ny?we?ke?in?acor?offff??of?wan??s?arghe?we?t?angorro?the?wand?be?thing?a?sth?t?tha?alelllll?willllsse?of?s?wed?w?brstougof?bage?orore?he?anthesww?were?ofawe?ce?qur?the?he?sbaing?tthe?bytondece?nd?t?llllifsffo?acke?o?t?in?ir?me?hedlff?scewant?pi?t?bri?pi?owasem?the?awh?thorathas?th?we?hed?ofainginictoplid?we?me
正如我們看到的,生成的文本可能沒有任何意義,但是有一些單詞和短語似乎形成了一個(gè)想法,例如:
we,?band,?pack,?the,?man,?where,?he,?hang,?out,?be,?thing,?me,?were
恭喜,我們已經(jīng)到了博客的結(jié)尾!
結(jié)論
在本博客中,我們展示了如何使用PyTorch的LSTMCell建立一個(gè)用于文本生成的端到端模型,并實(shí)現(xiàn)了基于循環(huán)神經(jīng)網(wǎng)絡(luò)LSTM和Bi-LSTM的體系結(jié)構(gòu)。
值得注意的是,建議的文本生成模型可以通過不同的方式進(jìn)行改進(jìn)。一些建議的想法是增加要訓(xùn)練的文本語料庫的大小,增加epoch以及每個(gè)LSTM的隱藏層大小。另一方面,我們可以考慮一個(gè)基于卷積LSTM的有趣的架構(gòu)。
參考引用
[1] LSTM vs. GRU vs. Bidirectional RNN for script generation(https://arxiv.org/pdf/1908.04332.pdf)
[2] The survey: Text generation models in deep learning(https://www.sciencedirect.com/science/article/pii/S1319157820303360)
往期精彩回顧
獲取本站知識星球優(yōu)惠券,復(fù)制鏈接直接打開:
https://t.zsxq.com/qFiUFMV
本站qq群704220115。
加入微信群請掃碼:
