iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 19
1
AI & Machine Learning

以100張圖理解 Neural Network -- 觀念與實踐系列 第 19

Day 19:自然語言處理的預訓詞向量(Pre-trained Word Vectors) -- 站在巨人的肩膀上

前言

我們在『Day 09:CNN 經典模型應用』討論到CNN的預先訓練好的模型,並在後續的篇幅,直接套用這些模型在『照片主體的相似性比對』、『畫風轉換』及『物體偵測(Object Detection) + 影像標題(Image Captioning)』等等的應用上,這些經典模型都是針對影像處理,在自然語言處理領域,是否有類似的作法呢? 有的,就是『詞向量』(Word Embedding or Word Vector)的資料集及其演算法。

『詞向量』(Word Embedding or Word Vector)

對於影像而言,我們關心的是辨識主體的構成像素,相對於語言而言,我們要辨識句子乃至文章段落,最重要的就是單字了(中文可能是詞,例如人類、窗戶...),詞向量的技術就希望將每個單字賦予一個向量,用來表示這個單字在各種維度(Dimenesion)所代表的相對意義,以向量區分每個單字與其他單字的相似程度。所謂『各種維度』的意思是指,例如,就生物角度來看,man(男人)與woman(女人)是很相似的,因為他們同屬人類,但是從另一個角度『性別』來看,他們恰似相反的,所以,要將一個單字轉換為向量,不能像『Day 16:情緒分析』只考慮一維,而是要有多面向來觀察,亦即多維(通常是100~1000),這就是『詞向量』(Word Embedding or Word Vector)的粗淺概念。

https://ithelp.ithome.com.tw/upload/images/20171229/20001976v9bchVX5WO.jpg
圖. 詞向量的示意圖,圖片來源:GloVe: Global Vectors for Word Representation

詞向量最有名的技術有兩個:

  1. Word2vec:由Google的Tomas Mikolov研究團隊所創造。
  2. GloVe:由史丹佛(Stanford)研究團隊所創造。

兩者的技術都是在計算單字在文件(Document)出現的次數,進而統計兩個單字共同出現的機率大小,以決定其相似性,也就是單字間的向量距離,距離越短表示越相似。另一方面,如果為每個單字都定義一個向量,所有單字就構成一個『向量空間』(Vector Space),我們就可以計算相對距離,來預測單字構成的文章是屬於哪一類,這就串接到 Neural Network 模型的分類概念了,先有個概念,我們在後續的篇章再仔細探討相關的技術及作法。

預訓詞向量

既然詞向量定義單字可以用向量來表達,那我們如何整理所有單字在各種維度代表的數值呢? 不是憑一己之力整理,當然是要站在巨人的肩膀上,史丹佛GloVe團隊已提供多種預訓詞向量,供我們使用,本例使用的是 glove.6B.100d.txt資料檔,6B代表使用60億的單字(Token)語料庫訓練,共含400K字彙,100d表100個維度,另外還有50d、200d、300d可供使用。檔案格式為csv(空白隔開),第一欄為單字,第二欄以後為各維度向量。

應用

預訓詞向量涵蓋了字典大部分的詞彙,而且已經幫我們轉成向量,我們就可以擷取任何語料庫當作訓練資料,將訓練資料以查表的方式,轉成向量,再輸入模型訓練,即可形成某一領域的應用模型了。我們現在就來看一個 Keras 官方提供的範例,說明文件請看Using pre-trained word embeddings in a Keras model

程式

程式來自Keras 官方提供的範例,我加了一些註解,可以自這裡下載。預訓詞向量檔 glove.6B.100d.txtnewsgroup 資料集請讀者自行下載。

'''This script loads pre-trained word embeddings (GloVe embeddings)
into a frozen Keras Embedding layer, and uses it to
train a text classification model on the 20 Newsgroup dataset
(classification of newsgroup messages into 20 different categories).

GloVe embedding data can be found at:
http://nlp.stanford.edu/data/glove.6B.zip
(source page: http://nlp.stanford.edu/projects/glove/)

20 Newsgroup data can be found at:
http://www.cs.cmu.edu/afs/cs.cmu.edu/project/theo-20/www/data/news20.html
'''

# 導入函式庫
from __future__ import print_function
import os
import sys
import numpy as np
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical
from keras.layers import Dense, Input, GlobalMaxPooling1D
from keras.layers import Conv1D, MaxPooling1D, Embedding
from keras.models import Model

# 參數設定
BASE_DIR = ''
GLOVE_DIR = os.path.join(BASE_DIR, 'glove.6B')
TEXT_DATA_DIR = os.path.join(BASE_DIR, '20_newsgroup')
MAX_SEQUENCE_LENGTH = 1000
MAX_NB_WORDS = 20000
EMBEDDING_DIM = 100
VALIDATION_SPLIT = 0.2

# 將 glove.6B.100d.txt 檔案轉成 dict,key:單字, value:詞向量
embeddings_index = {}
f = open(os.path.join(GLOVE_DIR, 'glove.6B.100d.txt'), encoding='utf8')
for line in f:
    values = line.split()
    word = values[0]
    coefs = np.asarray(values[1:], dtype='float32')
    embeddings_index[word] = coefs
f.close()

print('Found %s word vectors.' % len(embeddings_index))
# 顯示the單字的詞向量
print(len(embeddings_index["the"]))

# 讀取訓練資料檔,包含 20_newsgroup 資料夾下所有子目錄及檔案,共20類 
texts = []  # list of text samples
labels_index = {}  # dictionary mapping label name to numeric id
labels = []  # list of label ids
for name in sorted(os.listdir(TEXT_DATA_DIR)):
    path = os.path.join(TEXT_DATA_DIR, name)
    if os.path.isdir(path):
        label_id = len(labels_index)
        labels_index[name] = label_id
        for fname in sorted(os.listdir(path)):
            if fname.isdigit():
                fpath = os.path.join(path, fname)
                if sys.version_info < (3,):
                    f = open(fpath)
                else:
                    f = open(fpath, encoding='latin-1')
                t = f.read()
                i = t.find('\n\n')  # skip header
                if 0 < i:
                    t = t[i:]
                texts.append(t)
                f.close()
                labels.append(label_id)

# 20類的代號及名稱                
print(labels_index)
print('Found %s texts.' % len(texts))

# 將訓練資料的單字轉成向量
tokenizer = Tokenizer(num_words=MAX_NB_WORDS)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)

word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))

# 將訓練字句截長補短
data = pad_sequences(sequences, maxlen=MAX_SEQUENCE_LENGTH)
labels = to_categorical(np.asarray(labels))
print('Shape of data tensor:', data.shape)
print('Shape of label tensor:', labels.shape)

# 將訓練資料分為訓練組及驗證組
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]
num_validation_samples = int(VALIDATION_SPLIT * data.shape[0])

x_train = data[:-num_validation_samples]
y_train = labels[:-num_validation_samples]
x_val = data[-num_validation_samples:]
y_val = labels[-num_validation_samples:]

print('Preparing embedding matrix.')

# 轉成 Embedding 層的 input vector
num_words = min(MAX_NB_WORDS, len(word_index))
embedding_matrix = np.zeros((num_words, EMBEDDING_DIM))
for word, i in word_index.items():
    if i >= MAX_NB_WORDS:
        continue
    embedding_vector = embeddings_index.get(word)
    if embedding_vector is not None:
        # words not found in embedding index will be all-zeros.
        embedding_matrix[i] = embedding_vector

# 載入預訓模型,trainable = False 表示不重新計算
embedding_layer = Embedding(num_words,
                            EMBEDDING_DIM,
                            weights=[embedding_matrix],
                            input_length=MAX_SEQUENCE_LENGTH,
                            trainable=False)

# 訓練模型
print('Training model.')
# train a 1D convnet with global maxpooling
sequence_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32')
embedded_sequences = embedding_layer(sequence_input)
x = Conv1D(128, 5, activation='relu')(embedded_sequences)
x = MaxPooling1D(5)(x)
x = Conv1D(128, 5, activation='relu')(x)
x = MaxPooling1D(5)(x)
x = Conv1D(128, 5, activation='relu')(x)
x = GlobalMaxPooling1D()(x)
x = Dense(128, activation='relu')(x)
preds = Dense(len(labels_index), activation='softmax')(x)

model = Model(sequence_input, preds)
model.compile(loss='categorical_crossentropy',
              optimizer='rmsprop',
              metrics=['acc'])

# summarize the model
print(model.summary())

model.fit(x_train, y_train,
          batch_size=128,
          epochs=10,
          validation_data=(x_val, y_val))

    
# 模型存檔
model.save('embedding.h5')  # creates a HDF5 file 
      
# evaluate the model
loss, accuracy = model.evaluate(x_val, y_val, verbose=2)
print('Accuracy: %f' % (accuracy*100))    

程式執行

  1. glove.6B資料夾下要有 glove.6B.100d.txt 檔案。
  2. 這裡下載訓練資料,解壓縮放在20_newsgroup資料夾下。
  3. 在 DOS 內執行下列指令:
    python pretrained_word_embeddings.py
  4. 如要自己抓資料預測,可將資料檔放在predict_data資料夾下,在 DOS 內執行下列指令:
    python pretrained_word_embeddings_test.py

程式說明

  1. 訓練10個週期,準確率只有73%,也說明自然語言的辨識較複雜,如果,不從『特徵工程』(Feature Engineering)改善,光憑 Neural Network 著力,可能力有未逮(純屬個人淺見),因此,還是必須了解詞性、分詞、NER等相關技術,才能達到實用的階段。
  2. 各位一定會問中文也可以這樣處理嗎? 答案是可以的。中文的語料庫、詞向量在哪裡呢? 有沒有巨人可以依靠呢? 答案是悲觀的,對岸比我們用功多了。

上一篇
Day 18: 機器翻譯(Machine Translation)
下一篇
Day 20:自然語言處理的一些術語介紹
系列文
以100張圖理解 Neural Network -- 觀念與實踐31

尚未有邦友留言

立即登入留言