大家好,我們是 AI . FREE Team - 人工智慧自由團隊,這一次的鐵人賽,自由團隊將從0到1 手把手教各位讀者學會 (1)Python基礎語法 (2)Python Web 網頁開發框架 – Django (3)Python網頁爬蟲 – 周易解夢網 (4)Tensorflow AI語言模型基礎與訓練 – LSTM (5)實際部屬AI解夢模型到Web框架上。
自由團隊的成立宗旨為開發AI/新科技的學習資源,提供各領域的學習者能夠跨域學習資料科學,並透過自主學習發展協槓職涯,結合智能應用到各式領域,無論是文、法、商、管、醫領域的朋友,都可以自由的學習AI技術。
AI . FREE Team 讀者專屬福利 → Python Basics 免費學習資源
接下來介紹LSTM的部分,繼上次介紹完RNN(Recurrent Neural Network),這次我們就來看一下LSTM的起源與原理
RNN再長篇文章的發揮中,經常會有前期資訊到後面影響的決策影響越小,也就是前面的權重再後期的影響會越來越小的梯度消失問題。
而為了解決這個問題,1997年 LSTM(Long Short-Trem Memory) 首次發表於論文上,其獨特的設計可以良好的處理間隔與時序較長的Task,而真正發揚光大是在2009年的ICDAR的手寫識別比賽,透過LSTM建立的模型取得冠軍。
LSTM雖然看起來很複雜,但是在計算上其實與RNN是有相似之處,都是會透過前一個的輸出加上這次計算的輸入,不過LSTM特別之處就在有額外3個門閘(gate),會決定input是否重要到能被記住及能不能被輸出output。
※ 沒有好的數據,演算法也做不出好的決策。
那是因為全世界每天有15億的人,在幫 google 做決策(整理、準備數據)。
因此,這次的數據,我們就從靈狐算命網爬取資料
text_sentence = []
with open("clean_all.txt","r") as f:
for i in f.readlines():
if i.strip() != '':
text_sentence.append(i.strip())
text = ''.join(text_sentence)
text[:50]
這裡的 with open("clean_all.txt","r") as f:
路徑因人而異
大家放檔案的位置可能都不同,我這裡使用的是相對路徑
n = len(text)
w = len(set(text))
print(f'這個資料集共有{n}個字')
print(f'這個資料集不重複的字有{w}個字')
當資料準備好也處理完之後,我們要將文本轉成數字,因為電腦看的是數字不是文字,這個步驟大家應該都能夠理解吧~
numpy
大家肯定最熟悉不過了,他提供了很好的矩陣功能API,不論是在運算或是高階函式庫,都是大家做深度學習時最好的朋友~tensorflow
,tensorflow是google實驗室開發的深度學習框架,提供的高階API可以加速我們完成這個任務tf.keras.preprocessing.text.Tokenizer
,來當作tokenizerimport tensorflow as tf
import numpy as np
# 初始化一個以字為單位的 Tokenizer
tokenizer = tf.keras.preprocessing.text.Tokenizer(
num_words=w,
char_level=True,
filters=''
)
# 將文字轉成int
tokenizer.fit_on_texts(text)
text_as_int = tokenizer.texts_to_sequences([text])[0]
print(text_as_int[:10])
len(text_as_int)
index_word
將數值轉回文字,並檢查start_idx = 10
end_idx = 20
partial_indices = text_as_int[start_idx:end_idx]
partial_texts = [
tokenizer.index_word[idx] for idx in partial_indices
]
print("原本的中文字序列:")
print()
print(partial_texts)
print()
print()
print("轉換後的索引序列:")
print()
print(partial_indices)
AI 可以幫我們解決問題,但是 AI 不會知道問題是什麼,問題由我們定義!
_type = type(text_as_int)
n = len(text_as_int)
print(f"text_as_int 是一個 {_type}\n")
print(f"文本的序列長度: {n}\n")
print("前 5 索引:", text_as_int[:5])
print("實際丟給模型的數字序列:")
print(partial_indices[:-1])
print()
print("方便我們理解的文本序列:")
print(partial_texts[:-1])
print("實際丟給模型的文本序列:")
print(partial_texts[:-1])
print()
print("模型預期輸出的文本序列:")
print(partial_texts[1:])
tensorflow
的API tf.data.Dataset.from_tensor_slices
,將轉換成數值的文本,轉成tensorflow
可以接受的tensor
# 將list轉換成tensor
characters = tf.data.Dataset.from_tensor_slices(text_as_int)
characters
batch
這個內建的function# tensorflow的dataset
SEQ_LENGTH = 10 # 數字序列長度
sequences = characters.batch(SEQ_LENGTH+1,drop_remainder=True)
for item in sequences.take(1):
d = tokenizer.index_word
print('dataset索引序列',[i.numpy() for i in item])
print('dataset文本序列',[d[i.numpy()] for i in item])
build_seq_pairs
來幫我們建立成對的句子(輸入/輸出),這也是為什麼剛剛要 SEQ_LENGTH+1
,因為我們丟入一個長度11的句子,他才能夠返回長度各為10的(輸入/輸出),並使用dataset的內建function map
,讓全部的文本都做過一輪def build_seq_pairs(chunk):
input_text = chunk[:-1]
target_text = chunk[1:]
return input_text, target_text
ds = sequences.map(build_seq_pairs)
for idx,item in enumerate(ds.take(2)):
d = tokenizer.index_word
print(f"第 {idx} 個輸入句子的索引序列")
print([i for i in item[0].numpy()])
print(f"第 {idx} 個輸入句子的文本序列:")
print([d[i] for i in item[0].numpy()])
print()
print(f"第 {idx} 個輸出句子的索引序列:")
print([i for i in item[1].numpy()])
print(f"第 {idx} 個輸出句子的文本序列:")
print([d[i] for i in item[1].numpy()])
print('-'*100)
BATCH_SIZE
以及設定緩衝區大小 BUFFER_SIZE
重新排列數據集BATCH_SIZE
決定的是模型一次可以看多少個的句子,並使用GPU平行運算(效率)
這也是為什麼我們要使用 tensor
tensorflow
被設定為可以處理無限的序列,它並不會在內存嘗試不同的組合,因此需要宣告一個
緩衝區也就是BUFFER_SIZE
,它維持一個緩衝區,在緩衝區重新排列元素
BATCH_SIZE = 64
BUFFER_SIZE = 10000
ds = ds.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)
keras
這個來自 tensorflow
的高級API來做實作EMBEDDING_DIM = 512
UNITS = 1024
LEARNING_RATE = 0.001
model = tf.keras.Sequential()
model.add(tf.keras.layers.Embedding(input_dim=w, output_dim=EMBEDDING_DIM,batch_input_shape=[BATCH_SIZE, None]))
model.add(tf.keras.layers.LSTM(units=RNN_UNITS, return_sequences=True, stateful=True, recurrent_initializer='glorot_uniform'))
model.add(tf.keras.layers.Dense(w))
model.summary()
model.summary()
可以檢視已經建完的模型Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding (Embedding) (128, None, 512) 2787840
_________________________________________________________________
lstm (LSTM) (128, None, 1024) 6295552
_________________________________________________________________
dense (Dense) (128, None, 5445) 5581125
=================================================================
Total params: 14,664,517
Trainable params: 14,664,517
Non-trainable params: 0
_________________________________________________________________
這裡的output指的是文本當中每個字的機率值,然後輸出賄選機率最大的那個字
首先檢查輸出的形狀:
for input_example_batch, target_example_batch in ds.take(1):
example_batch_predictions = model(input_example_batch)
print(example_batch_predictions.shape, "# (batch_size, sequence_length, w)")
宣告一個function來作為損失函數(loss_function),本文的問題可以被視為一個有 w
個分類(字)的問題。而要定義分類問題的損失相對簡單,使用 sparse_categorical_crossentropy
是個不錯的選擇
def loss(y_true, y_pred):
return tf.keras.losses.sparse_categorical_crossentropy(y_true, y_pred, from_logits=True)
而 優化器(optimizer) 使用不論在任何任務中,表現都不錯的 tf.keras.optimizers.Adam
,除了學習率(learning_rate)
之外,其他採用默認參數,並使用 tf.keras.Model.compile
方法配置訓練步驟,以及損失函數
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE), loss=loss
)
fit
來訓練,並且把資料 ds
、epochs
,作為fit的參數EPOCHS = 30
history = model.fit(
ds,
epochs=EPOCHS,
)
# 儲存訓練完成的模型
model.save('/content/gdrive/My Drive/LAB/IT_post/model_01.h5')
Epoch 1/10
2668/2668 [==============================] - 694s 260ms/step - loss: 3.3912
Epoch 2/10
2668/2668 [==============================] - 695s 260ms/step - loss: 2.5633
Epoch 3/10
2668/2668 [==============================] - 699s 262ms/step - loss: 2.3310
Epoch 4/10
2668/2668 [==============================] - 696s 261ms/step - loss: 2.2017
Epoch 5/10
2668/2668 [==============================] - 693s 260ms/step - loss: 2.1163
Epoch 6/10
2668/2668 [==============================] - 690s 259ms/step - loss: 2.0567
Epoch 7/10
2668/2668 [==============================] - 689s 258ms/step - loss: 2.0134
Epoch 8/10
2668/2668 [==============================] - 690s 259ms/step - loss: 1.9830
Epoch 9/10
2668/2668 [==============================] - 692s 259ms/step - loss: 1.9613
Epoch 10/10
73/2668 [..............................] - ETA: 11:07 - loss: 2.3482
EMBEDDING_DIM = 512
RNN_UNITS = 1024
BATCH_SIZE = 1
# 定義生成的模型
infer_model = tf.keras.Sequential()
infer_model.add(tf.keras.layers.Embedding(input_dim=w, output_dim=EMBEDDING_DIM, batch_input_shape=[BATCH_SIZE, None]))
infer_model.add(tf.keras.layers.LSTM(units=RNN_UNITS, return_sequences=True, stateful=True))
infer_model.add(tf.keras.layers.Dense(w))
# 載入已儲存模型之權重
infer_model.load_weights('/content/gdrive/My Drive/LAB/IT_post/model_01.h5')
infer_model.build(tf.TensorShape([1, None]))
:::success
batch_size會盡量設成2的n次方,因為電腦的記憶體配置是用二進制去做運算
此舉會稍微加速(可以實驗看看)
:::
load_weights
作為讀取權重build
會重建模型架構,而 tf.TensorShape
會將BATCH_SIZE
(維度)固定為1:::info
因為循環神經網路傳遞狀態的方式,一旦建好模型,BATCH_SIZE
就不能做變動了。但在實際生成文章時,我們需要讓 BATCH_SIZE
等於 1
:::
infer_model.summary()
text_generated = '夢見老鼠'
# 代表「喬」的索引
a = 1
for i in range(100):
dream = tokenizer.texts_to_sequences([text_generated])[0]
# 增加 batch 維度丟入模型取得預測結果後
# 再度降維,拿掉 batch 維度
input = tf.expand_dims(dream, axis=0)
predictions = infer_model(input)
predictions = tf.squeeze(predictions, 0)
temperature = 1.0
# 利用生成溫度影響抽樣結果
predictions /= temperature
# 從 4330 個分類值中做抽樣
# 取得這個時間點模型生成的中文字
predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy()
input_eval = tf.expand_dims([predicted_id], 0)
partial_texts = [ tokenizer.index_word[predicted_id] ]
text_generated += partial_texts[0]
# 成功生成 解夢文字檔 → text_generated
# 透過擷取重點解夢文字作呈現
print(text_generated.split('\n')[0].split('。')[0])
自由團隊 官方網站:https://aifreeblog.herokuapp.com/
自由團隊 Github:https://github.com/AI-FREE-Team/
自由團隊 粉絲專頁:https://www.facebook.com/AI.Free.Team/
自由團隊 IG:https://www.instagram.com/aifreeteam/
自由團隊 Youtube:https://www.youtube.com/channel/UCjw6Kuw3kwM_il39NTBJVTg/
文章同步發布於:自由團隊部落格
(想看更多文章?學習更多AI知識?敬請鎖定自由團隊的頻道!)