iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 25
0
AI & Data

輕鬆掌握 Keras 及相關應用系列 第 25

Day 25:Keras 自然語言處理(NLP)實作

前言

自然語言處理主要是指文字(Text)相關的應用,例如:

  1. 文字分類(Text Classification):例如情緒分析(Sentiment Analysis)、主題的分類、垃圾信(Spam)的辨識、...等,乃至於聊天機器人(ChatBot)。
  2. 文字生成(Text Generation):例如文本摘要(Text Summary)、作詞、作曲、製造假新聞(Fake News)、影像標題(Image captioning)...等。
  3. 翻譯(Text Translation):多國語言互轉。
  4. 其他:克漏字、錯字更正、命名實體識別(NER)、著作風格的比對,例如紅樓夢最後幾個章節是不是曹雪芹寫的。

主流的演算法

之前自然語言的處理主要是使用【循環神經網路】(Recurrent Neural Network, RNN),這幾年在演算法有長足的進展,從簡單的RNN、LSTM、GRU開始,後來有【注意力】(Attention)的補強,之後又有 Transformer 出現,相繼有 BERT、ELMO、XLNet、GPT ...等,甚至 BERT 也支援中文,光要了解這些演算法的原理,就一個頭兩個大了。

https://ithelp.ithome.com.tw/upload/images/20200924/20001976vtc3XxSA4Q.jpg
圖一. NLP 演算法討論熱度,圖片來源:Major trends in NLP: a review of 20 years of ACL research

本系列文章主要是介紹 Keras 撰寫,相關演算法原理就請各位讀者自行google了,也可以參考筆者之前寫了一些文章

本篇先從簡單入門,看看能走到哪裡。

簡單 RNN

RNN 主要用於時間序列型的資料,如股價、氣候資料,或者上下文相關(Context Sensitive)的資料,例如文章字句有前後關聯,我們需要以較早期發生的資料作為訓練資料,預測當期或未來。

因與上下文相關,RNN 的輸入除了特徵(X)外,還會餵入上一筆隱藏層的輸出,如下圖:
https://ithelp.ithome.com.tw/upload/images/20200925/20001976GzQlGkpwOL.png
圖二. RNN vs. Dense,圖片來源:RNN Simplified- A beginner’s guide

另外,當前資料會受到上一筆的影響,上一筆又受到【上上一筆】的影響,類似遞迴的概念,因此,稱為【循環神經網路】(Recurrent Neural Network, RNN),捲起來就如下圖左方。
https://ithelp.ithome.com.tw/upload/images/20200925/20001976pjlz1ErdbF.png
圖三. RNN,圖片來源:RNN Simplified- A beginner’s guide

RNN 基於共享權值(Shared Weights)的假設,遞迴的結果使權值(W)連乘,W>1時,會造成【梯度爆炸】(exploding gradient),反之,W<1時,則會造成【梯度消失】(vanishing gradient),故有改良的的演算法如 LSTM(Long Short Term Memory)、GRU(Gated Recurrent Unit)...等,多維護一條【記憶】處理流程。
https://ithelp.ithome.com.tw/upload/images/20200925/20001976TlanB2yqVi.png
圖四. LSTM及GRU,圖片來源:RNN Simplified- A beginner’s guide

如果要徹底了解RNN/LSTM/GRU,可參閱經典文章【Understanding LSTM Networks】,也可以找到很多中文翻譯

實作

Keras 實作RNN/LSTM/GRU神經層,分別為SimpleRNN/LSTM/GRU,命名空間(Namespace)為 tensorflow.keras.layers,模型結構的第一層必須為嵌入層(Embedding layer),它將文字轉為緊密的實數空間,使輸入變為向量,才能進行後續的運算。

嵌入層(Embedding layer)的重要參數說明如下:

  • input_dim: int > 0。字彙表大小。
  • output_dim: int >= 0。詞向量的維度。
  • input_length: 輸入文字的長度,如果後面接 Flatten 和 Dense 層,則此參數勢必填的。
  1. 先看一個簡單的例子,使用亂數資料作為輸入,看看 Embedding 的處理結果。
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

model = tf.keras.Sequential()

# 字彙表最大為1000,輸出維度為 64,輸入的字數為 10
model.add(layers.Embedding(input_dim=1000, output_dim=64))

# 產生亂數資料,32筆資料,每筆 10 個數字
input_array = np.random.randint(1000, size=(32, 10))

# 指定損失函數
model.compile('rmsprop', 'mse')

# 預測
output_array = model.predict(input_array)
print(output_array.shape)
output_array[0]

Embedding輸出維度設為64,而輸入為10個數字,故每個數字被轉成64個實數,最後結果的維度 = (32, 10, 64)。

  1. 改用真實的資料,每句單字個數須固定,長度不足則後面補空白。
import tensorflow as tf
from tensorflow.keras import layers
from numpy import array
from tensorflow.keras.preprocessing.text import one_hot
from tensorflow.keras.preprocessing.sequence import pad_sequences

# 測試資料
docs = ['Well done!',
		'Good work',
		'Great effort',
		'nice work',
		'Excellent!',
		'Weak',
		'Poor effort!',
		'not good',
		'poor work',
		'Could have done better.']


vocab_size = 50
maxlen = 4

# 先轉成 one-hot encoding
encoded_docs = [one_hot(d, vocab_size) for d in docs]

# 轉成固定長度,長度不足則後面補空白
padded_docs = pad_sequences(encoded_docs, maxlen=maxlen, padding='post')

# 模型只有 Embedding
model = tf.keras.Sequential()
model.add(layers.Embedding(vocab_size, 64, input_length=maxlen))
model.compile('rmsprop', 'mse')

# 預測
output_array = model.predict(padded_docs)
output_array.shape
  1. 後面加上一般的完全連接層(Dense),預測每個句子的極性(Polarity),也就是正面或負面的情緒,執行結果準確度約 80%。
import tensorflow as tf
from tensorflow.keras import layers
from numpy import array
from tensorflow.keras.preprocessing.text import one_hot
from tensorflow.keras.preprocessing.sequence import pad_sequences

# define documents
docs = ['Well done!',
		'Good work',
		'Great effort',
		'nice work',
		'Excellent!',
		'Weak',
		'Poor effort!',
		'not good',
		'poor work',
		'Could have done better.']

# define class labels
labels = array([1,1,1,1,1,0,0,0,0,0])

vocab_size = 50
maxlen = 4
encoded_docs = [one_hot(d, vocab_size) for d in docs]
padded_docs = pad_sequences(encoded_docs, maxlen=maxlen, padding='post')

model = tf.keras.Sequential()
model.add(layers.Embedding(vocab_size, 8, input_length=maxlen))
model.add(layers.Flatten())
# 加上一般的完全連接層(Dense)
model.add(layers.Dense(1, activation='sigmoid'))
# compile the model
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# summarize the model
print(model.summary())
# fit the model
model.fit(padded_docs, labels, epochs=50, verbose=0)
# evaluate the model
loss, accuracy = model.evaluate(padded_docs, labels, verbose=0)
print('Accuracy: %f' % (accuracy*100))
  1. 再加 RNN,果然有效,準確度提升至 100%。
model = tf.keras.Sequential()
model.add(layers.Embedding(vocab_size, 8, input_length=maxlen))
# Add a RNN layer with 128 internal units.
model.add(layers.SimpleRNN(128))
model.add(layers.Dense(1, activation='sigmoid'))
  1. 使用詞向量(Word2Vec),替換嵌入層(Embedding)轉換的功能,直接利用 GloVe檔案 glove.6B.100d.txt,將每個單字轉為100維的詞向量。
  • GloVe檔第一欄為單字,之後接100維的詞向量,以下程式碼將GloVe內容轉為字典資料型的變數,方便搜尋。
# load the whole embedding into memory
embeddings_index = dict()
f = open('./glove/glove.6B.100d.txt', encoding='utf8')
for line in f:
	values = line.split()
	word = values[0]
	coefs = np.array(values[1:], dtype='float32')
	embeddings_index[word] = coefs
f.close()
  • 之後將每一段文字中的單字轉換為,100維的詞向量。
# prepare tokenizer
t = Tokenizer()
t.fit_on_texts(docs)
# integer encode the documents
encoded_docs = t.texts_to_sequences(docs)

padded_docs = pad_sequences(encoded_docs, maxlen=maxlen, padding='post')

# 轉換為GloVe 100維的詞向量
embedding_matrix = np.zeros((vocab_size, 100))
for word, i in t.word_index.items():
	embedding_vector = embeddings_index.get(word)
	if embedding_vector is not None:
		embedding_matrix[i] = embedding_vector
  • 輸入已經轉為向量,嵌入層(Embedding)不用再轉換,故 trainable=False。
model = tf.keras.Sequential()

# trainable=False
model.add(layers.Embedding(vocab_size, 100, weights=[embedding_matrix], input_length=maxlen, trainable=False))

# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))
model.add(layers.Dense(1, activation='sigmoid'))
# compile the model
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

結論

以上先對嵌入層及RNN/LSTM/GRU 做簡單的實驗,幾行程式就可以進行文字分類(Text Classification),下一篇繼續深入研究。相關程式碼修改自:

  1. How to Use Word Embedding Layers for Deep Learning with Keras
  2. Embedding layer
  3. Working with RNNs

本篇範例包括 25_01_RNN.ipynb,可自【這裡】下載。


上一篇
Day 24:機器學習永遠不會跟你講錯 -- Keras 除錯技巧
下一篇
Day 26:Keras 自然語言處理(NLP)應用
系列文
輕鬆掌握 Keras 及相關應用30

尚未有邦友留言

立即登入留言