全文共5949字,預計學習時長15分鐘
還在等待霍格沃茨的錄取通知書?想在大禮堂裡享用晚宴?想探索通往霍格莫德的秘密通道?想在奧利凡德魔杖店買第一根魔杖?嘆氣,你不是一個人。這麼久了,我還是沉迷於哈利波特的魔法世界。
最近我開始學習神經網絡,著迷於深度學習的強大創造力。靈光乍現,我為什麼不把它們融合在一起呢?因此,我使用TensorFlow執行了一個簡單的文本生成模型來創作我自己的《哈利·波特》短篇小說。
本文將介紹了我為實現它而編寫的完整代碼。各位巫師可以在這裡直接找到github代碼並自己運行它:https://github.com/amisha-jodhani/text-generator-harry-potter
當你閒來無事時,它可以向無聊施一個驅逐咒。
背景
RNN是什麼?
循環神經網絡(RNN)與其他神經網絡不同,它是有記憶的神經網絡,可以存儲它處理過的所有層的信息,並在記憶的基礎上計算下一層。
GRU vs LSTM
二者在文本生成上都表現出色,GRU(門控循環單元)是比較新的概念,實際上並沒有一種方法可以確定二者哪個更好。優化超參數比選擇一個好的架構更能提高模型性能。
如果數據量不成問題,那麼則是LSTM(長短期記憶網絡)性能更優。如果數據量較少,那麼GRU參數更少,因此訓練更快,並能很好地泛化。
為什麼是基於字符的?
在處理這樣的大型數據集時,一個語料庫中不重複單詞的總量遠遠高於唯一字符的數量。一個大型數據集有許多不重複的單詞,當為如此大的矩陣分配獨熱編碼時,很可能會遇到內存問題。光是標籤本身就可以佔據Tb級的內存。用來預測單詞的原則也可以應用在這裡,但是現在要處理的詞彙量要小得多。
代碼
首先導入所需的庫
import tensorflow as tf
import numpy as np
import os
import time
現在,讀取數據
可以從 一個Kaggle數據集找到並下載所有《哈利波特》的文稿(https://www.kaggle.com/alex44jzy/harrypotter)。我將這七本書合併到一個名為「harrypotter.txt」的文本文件中,你也可以挑選你喜歡的任何一本來訓練模型。
files= [『1SorcerersStone.txt』, 『2ChamberofSecrets.txt』, 『3ThePrisonerOfAzkaban.txt』,『4TheGobletOfFire.txt』, 『5OrderofthePhoenix.txt』, 『6TheHalfBloodPrince.txt』,『7DeathlyHollows.txt』]
with open(『harrypotter.txt』, 『w』) as outfile:
for file in files:
with open(file) as infile:
outfile.write(infile.read())
text = open(『harrypotter.txt』).read()
查看數據
print(text[:300])
處理數據
通過建立兩個查找表將 vocab 中所有唯一的字符串映射到數字:
· 將字符映射到數字(char2index)
· 將數字映射回字符(index2char)
然後把文本轉換成數字:
vocab = sorted(set(text))
char2index = {u:i for i, u in enumerate(vocab)}
index2char = np.array(vocab)
text_as_int = np.array([char2index[c] for c in text])#how it looks:
print ('{} -- characters mapped to int -- > {}'.format(repr(text[:13]),text_as_int[:13]))
「Harry Potter」——字符映射到數字→ [39 64 81 81 88 3 47 78 83 83 68 81 3]
每個輸入模型的序列包含文本中seq_length 數量的字符,其對應的目標序列長度相同,所有字符都向右移動了一個位置,因此將文本分為 seq_length+1塊。
tf.data.Dataset.from_tensor_slices 將文本向量轉換為字符索引流, batch方法(分批處理法)將這些字符按所需的長度分批。
通過使用 map 方法對每個批處理應用一個簡單的函數,可以創建輸入和目標:
seq_length = 100
examples_per_epoch = len(text)//(seq_length+1)
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)
sequences = char_dataset.batch(seq_length+1, drop_remainder=True)defsplit_input_target(data):
input_text = data[:-1]
target_text = data[1:]
return input_text, target_textdataset =sequences.map(split_input_target)
在將這些數據輸入到模型之前對數據進行隨機排列,並將其劃分批次。tf.data維護一個緩衝,在緩衝區中它會隨機排列元素。
BATCH_SIZE = 64
BUFFER_SIZE = 10000dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE,drop_remainder=True)
搭建模型
有了目前算出來的所有字符,下一個字符會是什麼?這就要訓練RNN模型來預測了。使用 tf.keras.Sequential定義模型,因為其中的所有層都只有一個輸入並產生一個輸出。使用的不同層是:
· tf.keras.layers.Embedding:這是輸入層。嵌入用於將所有唯一字符映射到具有 embedding_dim維數的多維空間中的向量。
· tf.keras.layers.GRU:有rnn_units個單元數的RNN。(也可以在此處使用LSTM來看看哪種最適合已有數據。)
· tf.keras.layers.Dense:輸出層,有vocab_size 大小的輸出。
單獨定義所有的超參數也很有用,這樣以後無需編輯模型定義就可以更很容易地修改它們。
vocab_size = len(vocab)
embedding_dim = 300
# Number of RNN units
rnn_units1 = 512
rnn_units2 = 256
rnn_units= [rnn_units1, rnn_units2]def build_model(vocab_size, embedding_dim,rnn_units, batch_size):
model = tf.keras.Sequential([
tf.keras.layers.Embedding(vocab_size,embedding_dim,
batch_input_shape=[batch_size,None]), tf.keras.layers.GRU(rnn_units1, return_sequences=True,
stateful=True,recurrent_initializer='glorot_uniform'), tf.keras.layers.GRU(rnn_units2,return_sequences=True,
stateful=True,recurrent_initializer='glorot_uniform'), tf.keras.layers.Dense(vocab_size) ])
return modelmodel = build_model(
vocab_size = vocab_size,
embedding_dim=embedding_dim,
rnn_units=rnn_units,
batch_size=BATCH_SIZE)
訓練模型
標準的tf.keras.losses.sparse_categorical_crossentropy 損失函數與這個模型一起使用時效果最佳,因為它應用於預測的最後一層。將 from_logits設置為True,因為該模型返回logits。然後選擇adam優化器並編譯模型。
def loss(labels, logits):
returntf.keras.losses.sparse_categorical_crossentropy(labels,
logits,from_logits=True)model.compile(optimizer='adam', loss=loss,metrics=['accuracy'])
可以像這樣配置檢查點,以確保在訓練期間保存了檢查點。
# Directory where the checkpoints will be saved
checkpoint_dir = 『./training_checkpoints』
# Name of the checkpoint files
checkpoint_prefix = os.path.join(checkpoint_dir, 「ckpt_{epoch}」)
checkpoint_callback=tf.keras.callbacks.ModelCheckpoint(
filepath=checkpoint_prefix,save_weights_only=True)
每個期(epoch)的訓練時間取決於模型層和超參數的使用。我將期設置為50,以觀察準確性和損失如何隨時間變化,但可能不需要訓練所有50期。當你看到損失開始增加或在幾個期內保持不變時,一定要停止訓練。訓練的最後一個期將存儲在latest_check中。如果使用Google Colab,則將runtime設置為GPU以減少訓練時間。
EPOCHS= 50
history = model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])
latest_check = tf.train.latest_checkpoint(checkpoint_dir)
文本生成
如果希望使用不同的批處理大小,則需要在運行之前重新構建模型並重新加載檢查點。為了保持簡單,我設置batch_size 為1。可以運行model.summary() 來了解模型的各個層以及每個層運行之後的輸出形狀。
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)
model.load_weights(latest_check)
model.build(tf.TensorShape([1, None]))
model.summary()
下面的函數就在生成文本:
· 接收start_string,初始化RNN的狀態並將輸出字符數賦給 num_generate。
· 使用 start_string 和RNN狀態獲取下一個字符的預測分布。然後它會計算預測字符的索引,作為模型的下個輸入。
· 模型返回的輸出狀態反饋到模型中,這樣它就有了更多的上下文(如下所示)。在預測下一個字符之後,循環繼續。通過這種方式,RNN可以從之前的輸出中建立記憶。
· 較低的scaling會產生更可預測的文本,而較高的scaling會產生更令人驚訝的文本。
def generate_text(model, start_string): num_generate = 1000 #can beanything you like input_eval =[char2index[s] for s in start_string]
input_eval = tf.expand_dims(input_eval,0) text_generated = [] scaling = 0.5 #kept at a lower valuehere # Here batch size == 1
model.reset_states()
for i in range(num_generate):
predictions = model(input_eval)
# remove the batch dimension
predictions = tf.squeeze(predictions,0)
predictions = predictions / scaling
predicted_id =tf.random.categorical(predictions,
num_samples=1)[1,0].numpy()
input_eval =tf.expand_dims([predicted_id], 0)
text_generated.append(idx2char[predicted_id])return(start_string + 『』.join(text_generated))
然後就完成了!
輸出
可以嘗試不同的初始字符串以獲得不同的輸出。
這是用我最喜歡的人物名輸出的一部分:
print(generate_text(model, start_string=u」Severus Snape「))
也可以嘗試不同的句子:
如果你只使用第一本《哈利波特與魔法石》來訓練模型,可以得到下面結果:
你會發現模型知道什麼時候大寫,什麼時候另起一段,它還模仿了魔法風格的寫作詞彙!
為了使句子更連貫,你可以通過以下方法改進模型:
· 改變不同參數,如 seq_length , rnn_units , embedding_dims , scaling的值來找出最佳設置。
· 訓練更多期
· 為GRU / LSTM添加更多層
這個模型可以訓練你喜歡的任何系列書籍。好啦,惡作劇完畢~
留言點讚關注
我們一起分享AI學習與發展的乾貨
如轉載,請後臺留言,遵守轉載規範