使用預先訓練好的單詞向量識別影評的正負能量

m53469發表於2021-09-09

上一節我們討論路單詞向量化的演算法原理。演算法的實現需要有大量的資料,一般而言你要收集到單詞量在四十億左右的文字資料才能透過上一節的演算法訓練處精準的單詞向量,問題在於你很難獲取如此巨量的資料來訓練單詞向量,那你該怎麼辦呢?

上一章節,我們採取拿來主義,直接使用別人訓練過的卷積網路來實現精準的影像識別,我們本節也嘗試使用拿來主義,用別人透過大資料訓練好的單詞向量來實現我們自己專案的目的。目前在英語中,業界有兩個極有名的訓練好的單詞向量資料庫,一個來自於人工智慧的鼻祖Google,他們訓練了一個精準的單詞向量資料庫叫Word2Vec,另一個來自於史丹佛大學,後者採用了一種叫做"GloVe"的向量化演算法,透過吸收Wikipedia的所有文字資料後訓練出了很精準的單詞向量。

本節我們嘗試使用史丹佛大學訓練的單詞向量資料庫到我們自己的專案中。我們還是像上一節的專案那樣,使用單詞向量,把相同情緒的單詞進行分組,於是表示讚賞或正面情緒的單詞向量集中在一起,表示批評或負面情緒的單詞向量會集中在一起,當我們讀取一片影評時,透過查詢影評中單詞的向量,看這些向量偏向於哪個向量集合,從而判斷影評文字的情緒是褒揚還是貶義。

我們先把資料下載到本地進行解壓,資料的URL如下:,你也可以從課堂附件中直接下載。資料下載解壓後,進入目錄,然後再進入“train"目錄後,看到情形如下:

圖片描述

螢幕快照 2018-08-27 下午5.37.16.png


我們可以看到,在資料有兩份一份在資料夾"neg"下,一部分在資料夾"pos"下,前者存放的是含有負面情緒的影評,後者存放的是含有正面情緒的影評。我們把從"neg"資料夾下的影評賦予一個標籤0,把從”pos"資料夾下讀到的影評賦予一個標籤1,這樣資料就能作為網路的訓練材料。接下來我們將用程式碼把每條影評讀入,把影評中的所有單詞連線成一個大字串,然後每個字串對應一個0或1的標籤,程式碼如下:

import os
imdb_dir = '/Users/chenyi/Documents/人工智慧/aclImdb/'
           train_dir = os.path.join(imdb_dir, 'train')
labels = []
texts = []for label_type in ['neg', 'pos']:    '''
    遍歷兩個資料夾下的文字,將文字里面的單詞連線成一個大字串,從neg目錄下讀出的文字賦予一個標籤0,
    從pos資料夾下讀出的文字賦予標籤1
    '''
    dir_name = os.path.join(train_dir, label_type)    for file_name in os.listdir(dir_name):        if file_name[-4:] == '.txt':
            file = open(os.path.join(dir_name, file_name))
            texts.append(file.read())
            file.close()            if label_type == 'neg':
                labels.append(0)            else:
                labels.append(1)

使用預先訓練好的單詞向量往往能得到良好的分類效果,因為預先訓練的單詞向量來源於大資料文字,因此精確度能有很好的保證,因此它們特別使用與我們面臨的資料流不足的情形。由於單詞向量訓練的質量較好,我們在用文字訓練網路時,需要使用的資料兩就能大大減少,這次我們嘗試使用200篇影評作為訓練資料即可,程式碼如下:

from keras.preprocessing.text import Tokenizerfrom keras.preprocessing.sequence import pad_sequencesimport numpy as np

maxlen = 100 #最多讀取影評的前100個單詞training_samples = 20000validation_samples = 2500 #用2500個影評作為校驗資料max_words = 10000  #只考慮出現頻率最高的10000個單詞#下面程式碼將單詞轉換為one-hot-vectortokenizer = Tokenizer(num_words = max_words)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)

word_index = tokenizer.word_index
print('總共有 %s 個不同的單詞' % len(word_index))
data = pad_sequences(sequences, maxlen=maxlen)

labels = np.asarray(labels)
print("資料向量的格式為:", data.shape)
print("標籤向量的格式為:", labels.shape)'''
將資料分成訓練集合校驗集,同時把資料打散,讓正能量影評和負能量影評隨機出現
'''indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]

x_train = data[:training_samples]
y_train = labels[:training_sampes]
x_val = data[training_samples: training_samples + validation_samples]
y_val = labels[training_samples: training_samples + valdiation_samples]

接著我們把預先訓練好的單詞向量資料下載下來,URL如下:
,它總共有八百多兆,下完需要一定時間,你也可以從課堂附件中獲取我已經下完的資料,下載完後解壓縮,裡面是一系列文字檔案:

圖片描述

螢幕快照 2018-08-28 下午3.25.22.png


資料格式如下所示:

the 0.418 0.24968 -0.41242 0.1217 0.34527 -0.044457 -0.49688 -0.17862 -0.00066023 -0.6566 0.27843 -0.14767 -0.55677 0.14658 -0.0095095 0.011658 0.10204 -0.12792 -0.8443 -0.12181 -0.016801 -0.33279 -0.1552 -0.23131 -0.19181 -1.8823 -0.76746 0.099051 -0.42125 -0.19526 4.0071 -0.18594 -0.52287 -0.31681 0.00059213 0.0074449 0.17778 -0.15897 0.012041 -0.054223 -0.29871 -0.15749 -0.34758 -0.045637 -0.44251 0.18785 0.0027849 -0.18411 -0.11514 -0.78581, 0.013441 0.23682 -0.16899 0.40951 0.63812 0.47709 -0.42852 -0.55641 -0.364 -0.23938 0.13001 -0.063734 -0.39575 -0.48162 0.23291 0.090201 -0.13324 0.078639 -0.41634 -0.15428 0.10068 0.48891 0.31226 -0.1252 -0.037512 -1.5179 0.12612 -0.02442 -0.042961 -0.28351 3.5416 -0.11956 -0.014533 -0.1499 0.21864 -0.33412 -0.13872 0.31806 0.70358 0.44858 -0.080262 0.63003 0.32111 -0.46765 0.22786 0.36034 -0.37818 -0.56657 0.044691 0.30392. 0.15164 0.30177 -0.16763 0.17684 0.31719 0.33973 -0.43478 -0.31086 -0.44999 -0.29486 0.16608 0.11963 -0.41328 -0.42353 0.59868 0.28825 -0.11547 -0.041848 -0.67989 -0.25063 0.18472 0.086876 0.46582 0.015035 0.043474 -1.4671 -0.30384 -0.023441 0.30589 -0.21785 3.746 0.0042284 -0.18436 -0.46209 0.098329 -0.11907 0.23919 0.1161 0.41705 0.056763 -6.3681e-05 0.068987 0.087939 -0.10285 -0.13931 0.22314 -0.080803 -0.35652 0.016413 0.10216

首先是單詞內容,接著是幾百個數字,對應的是向量中的每個元素,對格式有了解後,我們可以用程式碼將其讀入記憶體:

glove_dir = "/Users/chenyi/Documents/人工智慧/glove.6B"embedding_index = {}
f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'))for line in f:    #依照空格將一條資料分解成陣列
    values = line.split()
    word = values[0]
    coefs = np.asarray(values[1:], dtype='float32')
    embedding_index[word] = coefs
f.close()print("總共有 %s 個單詞向量."%len(embedding_index))

上面程式碼執行後得到的結果是總共有400000個單詞向量,由此可見資料量還是不小的。我們把載入進來的四十萬條單詞向量集合在一起形成一個矩陣,我們從影評中抽取出每個單詞,並在四十萬條單詞向量中找到對應單詞的向量,由於影評中的單詞最多10000個,於是我們就能形成維度為(10000, 100)的二維矩陣,其中100就是單詞向量的元素個數。相應程式碼如下:

embedding_dim = 100embedding_matrix = np.zeros((max_words, embedding_dim))for word, i in word_index.items():
    embedding_vector = embedding_index.get(word)    if i < max_words:        if embedding_vector is not None:
            embedding_matrix[i] = embedding_vector

上面程式碼構造的矩陣embedding_matrix就是我們上一節使用的Embedding層。於是我們就可以依照上節程式碼構造一個神經網路:

from keras.models import Sequentialfrom keras.layers import Embedding, Flatten, Dense

model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()

在上面程式碼中,我們加了Embedding層,它其實是空的,我們可以把前面構造的單詞向量形成的矩陣“倒入”這個Embedding層裡:

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False

由於單詞向量已經是訓練好的,因此我們不能讓網路在迭代時修改這一層資料,要不然就會破壞掉原來訓練好的效果。有了這些準備後,我們就可以把影評資料輸入網路,對網路進行訓練:

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])history = model.fit(x_train, y_train,
                    epochs=10,
                    batch_size=32,
                    validation_data=(x_val, y_val))
model.save_weights('pre_trained_glove_model.h5')

執行上面程式碼訓練網路後,我們把訓練的結果繪製出來:

import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

上面程式碼執行後所得結果如下:

圖片描述

1.png

圖片描述

2.png

從上圖我們看到,網路對訓練資料的識別率在增長,而對校驗資料的識別率卻只能維持在50%左右,這意味著出現了過度擬合現象,導致這個問題的原因主要就是我們的訓練資料量太少,只有兩萬條,因此沒能重複發揮預先訓練向量的作用,資料量少永遠是人工智慧的大敵人。前幾節我們沒有用預先訓練單詞向量,但準確度卻達到了70%以上,原因在於那時候單詞向量的維度很小,只有8%,我們現在使用的單詞向量維度很大,達到了100,但維度變大,但是訓練資料量沒有等量級的增加時,過度擬合就出現了。

我們將測試資料輸入到模型中,看看最終準確率如何:

test_dir = os.path.join(imdb_dir, 'test')
labels = []
texts = []for label_type in ['neg', 'pos']:
    dir_name = os.path.join(test_dir, label_type)    for fname in sorted(os.listdir(dir_name)):        if fname[-4:] == '.txt':
            f = open(os.path.join(dir_name, fname))
            texts.append(f.read())
            f.close()            if label_type == 'neg':
                labels.append(0)            else:
                labels.append(1)
                
sequences = tokenizer.texts_to_sequences(texts)
x_test = pad_sequences(sequences, maxlen=maxlen)
y_test = np.asarray(labels)

model.evaluate(x_test, y_test)

上面程式碼執行後,得到網路對測試資料的準確率只有慘淡的48%作用,主要原因還是在於,單詞向量的維度增加了,但資料量沒有按照相應數量級進行增加造成的。

透過這幾節的研究,我們至少掌握了幾個要點,一是懂得如何把原始文字資料轉換成神經網路可以接受的資料格式;二是,理解什麼叫單詞向量,並能利用單詞向量從事文字相關的專案開發;三是,懂得使用預先訓練好的單詞向量到具體專案實踐中;四是,瞭解到單詞向量的維度增加時,訓練資料也必須按照相應的數量級增加,要不然就會出現過度擬合現象。



作者:望月從良
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2819/viewspace-2817852/,如需轉載,請註明出處,否則將追究法律責任。

相關文章