# LSTM簡易問答機器人 紀錄使用 LSTM 製作一個簡單的問答系統,可以根據問題的輸入輸出預定的答案。 **特點** - 文本預處理: 輸入和輸出文本通過分詞器轉換成序列,並填充字數讓所有長度一致 - Embedding 層: 將詞彙索引映射到密集向量中,這有助於捕捉詞彙之間的語義關係和上下文信息。 - 雙向 LSTM 層: 兩個 LSTM 層被包裹在 Bidirectional 層中,能讓模型能夠在每個時間步中同時考慮過去和未來的序列信息。 - Dense 層: 用於預測詞彙的機率。 ## Step 1: 數據準備 在建立一個簡單的問答系統時,第一步是準備數據。這個數據集包含了問題和答案,讓模型可以學習如何根據問題生成正確的答案。 ```python= data = [ ("你好", "你好!"), ("你叫什麼名字?", "我是機器人"), ("你平常喜歡做什麼?", "不知道"), ("早安", "早安!") ] ``` ### 解釋: - `data` 是一個包含問題和對應答案的列表,這些問題和答案將成為我們模型的訓練數據。 - 每個元素包含兩個字串:問題(`input_texts`)和對應的答案(`output_texts`)。 ## Step 2: 文本預處理 在建立模型之前,需要對文本進行預處理。要將文本轉換為數字序列,並確保它們的長度一致。 ```python= input_texts = [x[0] for x in data] output_texts = [x[1] for x in data] tokenizers = Tokenizer() tokenizers.fit_on_texts(input_texts + output_texts) input_seq = tokenizers.texts_to_sequences(input_texts) output_seq = tokenizers.texts_to_sequences(output_texts) ``` ### 解釋: - `input_texts` 和 `output_texts` 分別從 `data` 中提取出所有的問題和答案。 - `Tokenizer` 是 Keras 提供的文本處理工具,它可以將文本轉換成數字序列,方便後續的模型處理。`fit_on_texts` 用來建立一個詞彙表,並為每個單詞分配一個唯一的索引。 - `texts_to_sequences` 將每個文本轉換成對應的數字序列。 ## Step 3: 填充序列 由於每個問題和答案的長度可能不同,為了讓它們可以批次處理,需要將所有序列的長度填充為相同的值。 ```python= max_seq_len = max(max([len(seq) for seq in input_seq]), max([len(seq) for seq in output_seq])) input_seq = pad_sequences(input_seq, maxlen=max_seq_len, padding='post') output_seq = pad_sequences(output_seq, maxlen=max_seq_len, padding='post') ``` ### 解釋: - `max_seq_len` 計算了所有問題和答案中最大的長度,這樣我們可以為所有序列設置相同的長度。 - `pad_sequences` 會將短於最大長度的序列補充零,這樣所有序列的長度就會一致。`padding='post'` 表示補充的零會加在序列的尾部。 ## Step 4: 構建LSTM模型 構建LSTM模型來訓練問答系統。 ```python= vocab_size = len(tokenizers.word_index) + 1 output_seq = to_categorical(output_seq, num_classes=vocab_size) model = Sequential() model.add(Embedding(vocab_size, 128, input_length=max_seq_len)) model.add(Bidirectional(LSTM(128, return_sequences=True))) model.add(Bidirectional(LSTM(128))) model.add(Dense(vocab_size, activation='softmax')) ``` ### 解釋: - `vocab_size` 是詞彙表的大小(詞彙表中包含的所有單詞數量),它用來設定模型的輸入和輸出層。 - `to_categorical` 函數將答案序列轉換為 one-hot 編碼的形式,這有助於計算交叉熵損失。 - `Sequential()` 是 Keras 中的一個順序模型,表示模型層會一層一層地堆疊。 - `Embedding` 層將單詞索引轉換為嵌入向量,這些向量有助於捕捉詞彙間的語義關係。 - `Bidirectional(LSTM)` 層是雙向LSTM,它可以讓模型同時考慮過去和未來的上下文信息。 - `Dense` 層使用 softmax 激活函數,將模型的輸出轉換為概率分佈,用來預測每個單詞的機率。 ## Step 5: 編譯並訓練模型 編譯模型並開始訓練。 ```python= model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) model.fit(input_seq, output_seq, epochs=100, batch_size=64) ``` ### 解釋: - `compile` 用來設置模型的損失函數(`categorical_crossentropy`)、優化器(`adam`)和評估指標(`accuracy`)。這些是訓練過程中必須指定的參數。 - `fit` 函數用來開始訓練模型。訓練過程中,模型將根據訓練數據調整權重。`epochs=100` 表示訓練100個回合,每次回合會處理所有的訓練數據;`batch_size=64` 表示每次訓練中處理64個樣本。 ## Step 6: 預測回答 定義一個預測函數,根據用戶輸入的問題來預測答案。 ```python def predict(input_text): input_seq = tokenizers.texts_to_sequences([input_text]) input_seq = pad_sequences(input_seq, maxlen=max_seq_len, padding='post') prediction = model.predict(input_seq) predicted_seq = np.argmax(prediction, axis=-1) return tokenizers.sequences_to_texts([predicted_seq])[0] ``` ### 解釋: - `predict` 函數接受用戶的輸入文本,並將其轉換為數字序列。 - `pad_sequences` 用來確保輸入序列的長度與訓練數據一致。 - `model.predict` 用來生成模型的預測結果,返回的是每個單詞的概率分佈。 - `np.argmax(prediction, axis=-1)` 選擇概率最高的單詞索引,這就是模型預測的答案。 - 最後,`tokenizers.sequences_to_texts` 將預測的數字序列轉換回文本格式,並返回模型生成的答案。 ## 完整程式碼 ```python= import numpy as np import tensorflow as tf from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.utils import to_categorical from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, LSTM, Bidirectional, Embedding data = [ ("你好", "你好!"), ("你叫什麼名字?", "我是機器人"), ("你平常喜歡做什麼?", "不知道"), ("早安", "早安!") ] input_texts = [x[0] for x in data] output_texts = [x[1] for x in data] tokenizers = Tokenizer() tokenizers.fit_on_texts(input_texts + output_texts) input_seq = tokenizers.texts_to_sequences(input_texts) output_seq = tokenizers.texts_to_sequences(output_texts) max_seq_len = max(max([len(seq) for seq in input_seq]), max([len(seq) for seq in output_seq])) input_seq = pad_sequences(input_seq, maxlen=max_seq_len, padding='post') output_seq = pad_sequences(output_seq, maxlen=max_seq_len, padding='post') vocab_size = len(tokenizers.word_index) + 1 output_seq = to_categorical(output_seq, num_classes=vocab_size) model = Sequential() model.add(Embedding(vocab_size, 128, input_length=max_seq_len)) model.add(Bidirectional(LSTM(128, return_sequences=True))) model.add(Bidirectional(LSTM(128))) model.add(Dense(vocab_size, activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) model.fit(input_seq, output_seq, epochs=100, batch_size=64) def predict(input_text): input_seq = tokenizers.texts_to_sequences([input_text]) input_seq = pad_sequences(input_seq, maxlen=max_seq_len, padding='post') prediction = model.predict(input_seq) predicted_seq = np.argmax(prediction, axis=-1) return tokenizers.sequences_to_texts([predicted_seq])[0] print(predict("你叫什麼名字?")) print(predict("你平常喜歡做什麼?")) ```