1. 程式人生 > >用Python打造一個AI作家為你寫詩(附原始碼)

用Python打造一個AI作家為你寫詩(附原始碼)

從短篇故事到長達5萬詞的小說,機器正以不可思議的方式“把玩”文字。網上已經湧現很多例子,越來越多人讓機器創作文字作品。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

其實,由於自然語言處理(NLP)領域的重大進步,如今計算機的確能夠理解上下文並自行編寫故事。比如去年我們曾分享過美國一位程式設計師小哥,由於《冰與火之歌》遲遲不出第六部,於是自己忍不住讓AI創作了第六部,結果雖然有些無厘頭,但讀起來也滿滿的“冰火”範兒。

那我們自己能建立這樣一個會寫故事的 AI 嗎?

本文,我們將使用 Python 和文字生成的概念來構建一個機器學習模型,最後它可以用莎士比亞的風格寫出十四行詩。

我們開始吧!

本文內容概覽

  1. 什麼是文字生成器?

  2. 文字生成的不同步驟

  • 匯入環境依賴項

  • 載入資料

  • 建立字元/字對映

  • 資料預處理

  • 搭建模型

  • 生成文字

3. 試驗不同的模型

  • 更訓練有素的模型

  • 更深層的模型

  • 更寬泛的模型

  • 一個巨大的模型

什麼是文字生成器?

如今,有大量的資料可以歸類為序列資料,常見形式為音訊、視訊、文字、時間序列、感測器資料等。這種型別資料有種特殊情況,如果在特定時間幀內發生兩個事件,則在事件 B 之前發生事件 A 與在事件 B 之後發生事件 A 完全是兩種不同的情況。

但是,在傳統的機器學習問題中,一個特定的資料點是否先於另一個數據點被記錄下來並不重要。這種考慮讓我們有不同的方式解決序列預測問題。

文字,即一個接一個排列的一串串字元,其實是很難破解的。這是因為在處理文字時,用先前存在的序列訓練的模型或許能做出非常準確的預測,但一旦出現一處錯誤的預測,就有可能使整個句子變得毫無意義。然而,如果出現數值序列預測問題,即使預測完全失敗,仍然有可能被視為有效的預測(可能具有高偏差)。但是,這一點人們往往注意不到。

這就是文字生成器的一大棘手問題!

文字生成通常包含以下步驟:

1.匯入環境依賴項

2.載入資料

3.建立字元/字對映

4.資料預處理

5.建立模型

6.生成文字

我們來仔細看看每一個步驟。

匯入環境依賴項

這一步沒什麼說的,我們需要匯入我們研究所需的所有資料庫。

import numpy as npimport pandas as pdfrom keras.models import Sequentialfrom keras.layers import Densefrom keras.layers import Dropoutfrom keras.layers import LSTMfrom keras.utils import
np_utils

載入資料

text=(open("/Users/pranjal/Desktop/text_generator/sonnets.txt").read())text=text.lower()

這一步中,我們載入下載的所有莎士比亞十四行詩的合集

http://www.gutenberg.org/ebooks/1041?msg=welcome_stranger

我清洗了檔案,刪除了開始和結尾部分,你可以從我的 git 庫上下載。檔案格式為 text。然後將這些內容轉換為小寫字母,以儘可能減少單詞數量(稍後會詳細介紹)。

建立字元/字詞對映

對映是我們為文字中的字元/單詞分配任意數字的一個步驟。通過這種方式,會將每個字元/單詞對映到一個數字上。這很重要,因為機器理解數字比理解文字要容易的多,並且這也會讓訓練過程更容易。

characters = sorted(list(set(text)))n_to_char = {n:char for n, char in enumerate(characters)}char_to_n = {char:n for n, char in enumerate(characters)}

我建立了一個字典,其中包含分配給文字中每個唯一字元的數字。所有唯一字元首先存為字元型別,然後變為列舉型別。

這裡還必須指出,我使用了字元級對映,而不是單詞對映。但是,相比於基於字元的模型,基於單詞的模型顯示出更高的準確性。這是因為後一種模式需要一個更大的網路來學習長期相關關係,因為它不僅要記住單詞的順序,還必須學會去預測一個單詞在語法上的正確性。但是,基於單詞的模型已經可以滿足後者(即:預測一個單詞在語法上的正確性)。

但由於這是一個小資料集(有17,670個單詞),並且唯一單詞的數量(4,605個數字)構成了大約四分之一的資料,所以在這樣的對映上進行訓練並不是一個明智的決定。這是因為如果我們假設所有唯一單詞在數量上都是相同的(這不是真的),那麼我們會在整個訓練資料集中大約出現四次單詞,這不足以構建文字生成器。

資料預處理

這是搭建 LSTM 模型時最棘手的部分。將手頭的資料轉換為可相互轉換的格式是一項艱鉅的任務。

我把這個過程分解成更細小的部分,讓你更容易理解。

X = []Y = []length = len(text)seq_length = 100 for i in range(0, length-seq_length, 1):    sequence = text[i:i + seq_length]    label =text[i + seq_length]    X.append([char_to_n[char] for char in sequence])    Y.append(char_to_n[label])

這裡,X 是我們的訓練陣列,Y 是我們的目標陣列。

seq_length 是我們在預測特定字元之前要考慮的字元序列的長度。

該 for 迴圈被用於迭代文字的整個長度,並建立此類序列(儲存在X)和它們的真值(儲存在Y)。現在,很難在這裡看到真實值的概念。讓我們以一個例子來理解這一點:

對於長度為4的序列和文字“ hello india ”,我們可以使用我們的X和Y(不易編碼為易理解的數字),如下所示:

640?wx_fmt=jpeg

現在,LSTMs 以(number_of_sequences,length_of_sequence,number_of_features)形式接受輸入,該輸入不是陣列的當前格式。另外,我們需要將陣列 Y 轉換為 one-hot 編碼格式。

X_modified = np.reshape(X, (len(X), seq_length, 1))X_modified = X_modified / float(len(characters))Y_modified = np_utils.to_categorical(Y)

我們首先將陣列 X 重塑成我們所需的維度(用來定義變數)。然後,我們調整 X_modified 的值,這樣我們的神經網路可以訓練得更快,而且陷入區域性最小值的機會也更小。此外,我們的 Y_modified 是一種 one-hot 編碼,用於刪除可能在對映字元的過程中引入的任何序數關係。也就是說,與'z'相比,'a'可能被分配給較小的數字, 但這並不表明兩者之間的任何關係。

我們的最終陣列將如下所示:

640?wx_fmt=jpeg

搭建模型

model = Sequential()model.add(LSTM(400, input_shape=(X_modified.shape[1], X_modified.shape[2]), return_sequences=True))model.add(Dropout(0.2))model.add(LSTM(400))model.add(Dropout(0.2))model.add(Dense(Y_modified.shape[1], activation='softmax'))model.compile(loss='categorical_crossentropy', optimizer='adam')

我們需要搭建一個具有兩個 LSTM 層的序列模型,每層有 400 個單元。第一層需要輸入形狀。為了讓下一個 LSTM 層能夠處理相同的序列,我們輸入 return_sequences 引數為 True。

此外,還添加了 dropout 率為 20% 的 dropout 層以檢查其是否過擬合。最後一層輸出一個提供了字元輸出的獨熱編碼向量。

生成文字

string_mapped = X[99]# generating charactersfor i in range(seq_length):   x = np.reshape(string_mapped,(1,len(string_mapped), 1))   x = x / float(len(characters))   pred_index = np.argmax(model.predict(x, verbose=0))   seq = [n_to_char[value] for value in string_mapped]   string_mapped.append(pred_index)string_mapped = string_mapped[1:len(string_mapped)]

我們從 X 陣列中的隨機行開始,它是一個由 100 個字元組成的陣列。在此之後,我們的目標是預測 X 之後的另外 100 個字元。將輸入進行重塑,按先前的方式縮放,這樣就能預測出具有最大概率的下一個字元。

Seq 用來儲存迄今預測到的字串的解碼格式。接下來,會更新新的字串,這樣會移除第一個字元,並且被預測的新字元也包括在內。

你可以到我的 git 庫上檢視完整程式碼。我也提供了訓練檔案,筆記和訓練後的模型權重以供參考。(見文末)

試驗不同的模型

以批次大小100訓練1個週期後,基線模型輸出結果如下:

's the riper should by time decease,his tender heir might bear his memory:but thou, contracted toet she the the the the the the the thethi the the the the the the the the the the the the the the the the thethi the the the the the the the the the the the the the the the the thethi the the the the the the the the the the the the the the the the thethi the the the the the the the the the the the the the the the the thethi the the the the the the the the th'

這個...

640?wx_fmt=jpeg

這個輸出很明顯沒有多大意義。這只不過是重複同樣的預測,就好像它陷入迴圈一樣。這是因為和我們已經訓練過的微型模型相比,語言預測模型太複雜了。

我們試試再訓練這個模型,但訓練時間再加長一點。

更訓練有素的模型

這次我們以批次大小 50 將模型訓練了 100 個週期,我們至少獲得了一個不重複的字元序列,其中包含了很多合理的字詞。此外,模型還學會了生成類似於十四行詩的詞語結構。

'The riper should by time decease,
his tender heir might bear his memory:
but thou, contracted to thine own besire,
that in the breath ther doomownd wron to ray,
dorh part nit backn oy steresc douh dxcel;
for that i have beauty lekeng norirness,
for all the foowing of a former sight,
which in the remame douh a foure to his,
that in the very bumees of toue mart detenese;
how ap i am nnw love, he past doth fiamee.
to diserace but in the orsths of are orider,
waie agliemt would have me '

但是,這種模式還不足以製作出高質量的內容。所以接下來我們會和其他人一樣,當深度學習模式沒有產生好的結果時,搭建更深層次的模型架構。

更深層的模型

魯迅曾經說過:如果模型不好,增加層數!

640?wx_fmt=jpeg

我的模型也要這樣。我們再新增一個有 400 個單元的 LSTM 層,然後新增一個20% dropout率的 dropout 層,並檢視我們得到的結果。

"The riper should by time decease,
his tender heir might bear his memory:
but thou, contracted to the world's false sporoe,
with eyes so dond touls be thy domfornds,
which for memorion of the seasons new;
mike own self-love to shou art constant
how can i then be oy love doth give,
the rose looks fair, but fairer bomments age.
now with the seas that i have seen dychl
that mot to teed of you, if h ho bontent,
and all the ress of the heartai thee;
the world will was iis bound and farty "

結果很有趣。語法正確率大大增強,保持了十四行詩結構和標點符號的完整。但是,模型仍然需要很多改進。我們接著嘗試探索更寬泛的、擁有更多單元的網路。

更寬泛的模型

我在兩個 LSTM 層的每一層上又增加了700個單元。這樣調整以後,模型寫出了以下詩歌:

"The riper should by time decease,
his tender heir might bear his memory:
but thou, contracted to thine own bright eyes,
feed'st thy sigh aare so beuider poise,
oor maty dis surre that gairs mot me love thee;
when i braye the would and lays in the eesire.
than thmu disgrmed stand with my bootr still nsentente;
tell ia to thou art thou wilt woon'thy sook,
and touain then tor, give thy soue semping.
whose whod the better of your befaiss nu sante,
though i, suoll oas, and i lose bouh twa"

起初這有點令人失望,因為這些詞語已經失去了意義。但是,有趣的是,我們可以看到正漸漸出現十四行詩的韻律。也就是說模型正試圖理解詩歌!但是,我們不能用無意義的詞寫詩,對吧?我們接著把所有資訊放入一個巨大的模型中。

一個巨大的模型

我把模型的層數增加到了三層,每層有 700 個單元,然後將模型訓練了 100 個週期。最終模型生成了一首氣勢恢巨集的詩歌,瞧一瞧:

"The riper should by time decease,
his tender heir might bear his memory:
but thou, contracted to thine own bright eyes,
feed'st thy light's flame with self-substantial fuel,
my beept is she breat oe bath dasehr ill:
tirse do i pine and turfeit day by day,
or gluttoning on all, or all away.
Lxxvi
why is my verse so barren of new pride,
so far from variation or quick change?
why with the time do i not glance aside
to new-found methods, and to compounds strange?
why write i stil"
640?wx_fmt=jpeg

其中一些詩句不僅讀起來非常睿智,而且還學會了押韻。如果將輸入模型的資料正確清洗的話,我們會得到一個更合乎情理的詩歌。但畢竟剛開始嘛,模型的效能已經很讓人驚喜了。我們創造的這位 AI 詩人比我們很多人都有詩意!

結尾筆記

文字生成器是否有效,關鍵是它生成相關故事的能力。在輸出層面上,許多模型正慢慢實現,能夠生成真實的、與人類編寫的文字區難以區分的語言文字。

總之,從建立原創作品到重新生成遺失的內容,文字生成器可以得到很好的應用。這種文字生成器的一個革命性應用可能是我們可以訓練它們編寫和操作程式碼。想象一頁,假如計算機程式和演算法可以根據需要自行修改,這會是個怎樣的世界。

附本專案程式碼庫:https://github.com/pranjal52/text_generators

∞∞∞

640?wx_fmt=jpeg&wx_lazy=1

IT派 - {技術青年圈}持續關注網際網路、區塊鏈、人工智慧領域640?wx_fmt=jpeg&wx_lazy=1

公眾號回覆“Python”

邀你加入{ IT派Python技術群 }