iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
0
自我挑戰組

零基礎成為 AI 解夢大師秘笈系列 第 29

【零基礎成為 AI 解夢大師秘笈】Day29 - 周易解夢之人工智慧(10)

  • 分享至 

  • xImage
  •  

LSTM

前言

系列文章簡介

大家好,我們是 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。

1.忘記階段 :

  • 這個階段主要是對上一個節點傳進來的輸入進行選擇性忘記。簡單來說就是會“忘記不重要的,記住重要的”。

2.選擇記憶階段 :

3.輸出階段 :

4.最後

利用LSTM進行解夢模型

  • 準備數據、前處理

※ 沒有好的數據,演算法也做不出好的決策。

為什麼Google可以發展出這麼多好的預訓練模型,還有讓大家廣為使用的youtube、gmail等等...

那是因為全世界每天有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}個字')    
  • 將文字轉成數字

當資料準備好也處理完之後,我們要將文本轉成數字,因為電腦看的是數字不是文字,這個步驟大家應該都能夠理解吧~

我們使用tensorflow.keras提供的tokenizer,來tokenize我們的文本,將文本中的每一個文字賦予一個唯一的數值,進而建立起一個字典

Minion

當然實際上不會是這樣排的,要看python的記憶體位址怎麼給予~
這裡我們import兩個套件,作為python的使用者numpy大家肯定最熟悉不過了,他提供了很好的矩陣功能API,不論是在運算或是高階函式庫,都是大家做深度學習時最好的朋友~
另一個則是tensorflow,tensorflow是google實驗室開發的深度學習框架,提供的高階API可以加速我們完成這個任務
這裡,我們宣告一個tf.keras.preprocessing.text.Tokenizer,來當作tokenizer
import tensorflow as tf
import numpy as np

# 初始化一個以字為單位的 Tokenizer
tokenizer = tf.keras.preprocessing.text.Tokenizer(
        num_words=w,
        char_level=True,
        filters=''
)
將我們的整份文本丟進去,它會將整份文本看過,第二行code則是將每個字賦值(int)
# 將文字轉成int
tokenizer.fit_on_texts(text)
text_as_int = tokenizer.texts_to_sequences([text])[0]
print(text_as_int[:10])
len(text_as_int)
這裡,我們簡單看一下我們的文本與字典之間的關聯,並使用tokenizer的function 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])
給定一個字符或者一個字符序列,下一個最可能出現的字符是什麼?這就是我們訓練模型要執行的任務。輸入進模型的是一個字符序列,我們訓練這個模型來預測輸出 -- 每個步驟(time step)預測下一個字符是什麼。
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
這裡我們設定dataset裡的每筆資料長度,使用 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])
宣告一個function 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來做實作
  • 使用 tf.keras.Sequential 定義模型。在這個簡單的例子中,我們使用了三個層來定義模型:
    • tf.keras.layers.Embedding:輸入層。一個可訓練的對照表,它會將每個字符的數字映射到一個 embedding_dim 維度的向量。
    • tf.keras.layers.GRU:一種 RNN 類型,其大小由 units=rnn_units 指定(這裏你也可以使用一個 LSTM 層)。
    • tf.keras.layers.Dense:輸出層,帶有 vocab_size 個輸出。
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)")
看起來模型沒問題了,接下來定義優化器(optimizer)和損失函數(loss_function)

宣告一個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 來訓練,並且把資料 dsepochs,作為fit的參數
EPOCHS = 30 
history = model.fit(
    ds,
    epochs=EPOCHS, 
)
配置檢查點
# 儲存訓練完成的模型
model.save('/content/gdrive/My Drive/LAB/IT_post/model_01.h5')
keras的model再訓練時,會有像tqdm的進度條,看到模型一點一滴的訓練,有沒有覺得很有成就感阿~
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

建構生成的模型

跟訓練時一樣的超參數,只差在 BATCH_SIZE 為 1
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]))

這邊可以看到,我們的模型batch_size設成1,因為我們一次只能讀一個段落,而模型再訓練的時候因為要平行運算,因此batch_size才會設大於1

:::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])

想更深入認識 AI . FREE Team ?

自由團隊 官方網站: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知識?敬請鎖定自由團隊的頻道!)


上一篇
【零基礎成為 AI 解夢大師秘笈】Day28 - 周易解夢之人工智慧(9)
下一篇
【零基礎成為 AI 解夢大師秘笈】Day30 - Django 整合部署 AI model
系列文
零基礎成為 AI 解夢大師秘笈30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言