1. 程式人生 > >keras單詞嵌入(word embeddings)

keras單詞嵌入(word embeddings)

將向量與單詞相關聯的另一種流行且有效的方法是使用密集的“單詞向量”,也稱為“單詞嵌入”。 雖然通過單熱編碼獲得的向量是二進位制的,稀疏的(主要由零組成)和非常高維的(與詞彙表中的單詞數相同的維度),“單詞嵌入”是低維浮點向量(即 “密集”向量,與稀疏向量相反)。 與通過one-hot編碼獲得的單詞向量不同,單詞嵌入是從資料中學習的。當處理非常大的詞彙表時,通常會看到256維,512維或1024維的單詞嵌入。 另一方面,one-hot編碼字通常導致20,000維或更高的向量(在這種情況下捕獲20,000標記的詞彙)。 因此,單詞嵌入將更多資訊打包到更少的維度中。

有兩種方法可以獲得嵌入詞:

預訓練的單詞嵌入

學習單詞嵌入與所關心的主要任務(例如文件分類或情緒預測)聯絡起來。在此設定中,將從隨機單詞向量開始,然後通過與學習神經網路權重相同的方式學習單詞向量。載入到模型中使用與所嘗試解決的機器學習任務不同的機器學習任務預先計算的單詞嵌入。這些被稱為“預訓練的單詞嵌入”。

“嵌入”圖層學習單詞嵌入
 

將密集向量與單詞相關聯的最簡單方法是隨機選擇向量。這種方法的問題在於得到的嵌入空間沒有結構:例如,單詞“準確”和“精確”可能最終會有完全不同的嵌入,即使它們在大多數句子中是可互換的。深度神經網路很難理解這種嘈雜的非結構化嵌入空間。

為了更抽象一點:單詞向量之間的幾何關係應該反映這些單詞之間的語義關係。Word嵌入旨在將人類語言對映到幾何空間。例如,在合理的嵌入空間中,我們期望將同義詞嵌入到相似的單詞向量中,並且通常我們期望任何兩個單詞向量之間的幾何距離(例如,L2距離)與相關單詞的語義距離相關。 (含義非常不同的詞語會被嵌入到遠離彼此的點,而相關的詞會更接近)。即使超出了距離,我們也可能希望嵌入空間中的特定__directions__具有意義。在現實世界的詞嵌入空間中,有意義的幾何變換的常見例子是“性別向量”和“複數向量”。例如,通過向向量“king”新增“女性向量”,可以獲得向量“女王”。通過新增“複數向量”,可以獲得“國王”。單詞嵌入空間通常具有數千個這樣的可解釋且可能有用的向量。

是否有一些“理想”的單詞嵌入空間可以完美地對映人類語言,並可用於任何自然語言處理任務?可能,但無論如何,我們還沒有計算任何型別的東西。此外,沒有“人類語言”這樣的東西,有許多不同的語言,它們不是同構的,因為語言是特定文化和特定語境的反映。但更務實的是,嵌入空間的好詞很大程度上取決於你的任務:英語電影評論情緒分析模型的完美單詞嵌入空間可能與英語法律文件分類模型的完美嵌入空間看起來非常不同,因為某些語義關係的重要性因任務而異。
  因此,為每個新任務__learn_一個新的嵌入空間是合理的。值得慶幸的是,backpropagation讓這非常簡單,而Keras讓它變得更加容易。它只是學習圖層的權重:“嵌入”圖層。

示例程式碼:

# _*_ coding:utf-8 _*_
from keras.layers import Embedding

# 嵌入層至少需要兩個引數:
# 可能的token數量,這裡是1000(1 +最大單詞索引),和嵌入的維度,這裡64。
embedding_layer = Embedding(1000, 64)

'''
“Embedding”層最好被理解為將整數索引(代表特定單詞)對映到密集向量的字典。
它將整數作為輸入,將這些整數查詢到內部字典中,並返回相關的向量。它實際上是一個字典查詢。

“Embedding”層將整數的二維張量作為輸入,形狀為“(samples,sequence_length)”,
其中每個條目是一個整數序列。它可以嵌入可變長度的序列,所以例如我們可以在批次之上輸入我們的嵌入層,
這些批次可以具有形狀`(32,10)`(32個序列長度為10的批次)或`(64,15)`(批量) 64個長度的序列15)。
然而,批處理中的所有序列必須具有相同的長度(因為我們需要將它們打包成單個張量),因此比其他序列短的序列應
該用零填充,並且應該截斷更長的序列。
該層返回一個形狀為“(samples,sequence_length,embedding_dimensionality)”的3D浮點張量。
然後可以通過RNN層或1D卷積層處理這樣的3D張量(兩者將在下面的部分中介紹)。
 
當你例項化一個'Embedding`層時,它的權重(它的token向量的內部字典)最初是隨機的,就像任何其他層一樣。
在訓練期間,這些單詞向量將通過反向傳播逐步調整,將空間構造成下游模型可以利用的東西。一旦完全訓練,嵌入空間將顯示
許多結構 - 一種專門針對訓練模型的特定問題的結構。
讓我們將這個想法應用到IMDB電影評論情緒預測任務中。準備資料。將電影評論限制在最常見的10,000個單詞中,並在僅20個
單詞後剪下評論。網路將簡單地為10,000個單詞中的每個單詞學習8維嵌入,將輸入整數序列(2D整數張量)轉換為嵌入序列
(3D浮動張量),將張量平坦化為2D,並訓練單個“密集”層最高分類。
'''
from keras.datasets import imdb
from keras import preprocessing

# 考慮作為特徵的單詞數
max_features = 10000
# 在max_features數量的單詞後剪下文字
max_len = 20

# 將資料作為整數列表進行載入
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)

# 這將我們的整數列表轉換為shape:`(samples,maxlen)`的2D整數張量
x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=max_len)
x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=max_len)

from keras.models import Sequential
from keras.layers import Flatten, Dense

model = Sequential()
# 指定嵌入層的最大輸入長度,以便稍後展平嵌入的輸入
model.add(Embedding(10000, 8, input_length=max_len))

# 在嵌入層之後,啟用形狀為`(samples,maxlen,8)`。
# 將嵌入的3D張量展平為2D張量的形狀`(samples,maxlen * 8)`
model.add(Flatten())

# 在頂部新增分類器
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
print(model.summary())

hist = model.fit(x_train, y_train,
                 epochs=10,
                 batch_size=32,
                 validation_split=0.2)

'''
我們得到的驗證準確率為~76%,考慮到我們只檢視每個評論中的前20個單詞,這是非常好的。但請注意,僅僅展平嵌入的序列並在頂部訓練
單個“密集”層會導致模型分別處理輸入序列中的每個單詞,而不考慮詞間關係和結構句子(例如,它可能會同時處理
兩個_“這部電影是狗屎“_和_”這部電影是狗屎“_ as as negative”評論“)。在嵌入序列的頂部新增迴圈層或1D卷積層會更好,以學習將每
個序列作為一個整體考慮在內的特徵。這就是我們將在接下來的幾節中關注的內容。

使用預先訓練的單詞嵌入
有時候,您可以獲得的培訓資料非常少,無法單獨使用您的資料來學習適當的任務特定的詞彙表嵌入。該怎麼辦?
您可以從已知高度結構化的預先計算的嵌入空間中載入嵌入向量,並展示有用的屬性,而不是學習嵌入與您想要解決的問題的嵌入向量,
從而捕獲語言結構的通用方面。在自然語言處理中使用預先訓練的單詞嵌入背後的基本原理與在影象分類中使用預訓練的網路非常相似:
我們沒有足夠的資料來學習我們自己的真正強大的功能,但我們期望這些功能我們需要相當通用,即常見的視覺特徵或語義特徵。在這種情況下,
重用在不同問題上學習的特徵是有意義的。
這樣的單詞嵌入通常使用單詞出現統計(關於在句子或文件中共同出現的單詞的觀察),使用各種技術來計算,一些涉及神經網路,另一些則不是。
Bengio等人最初探討了以無人監督的方式計算的密集,低維度的文字嵌入空間的想法。在21世紀初期,它釋出了最著名和最成功的一詞嵌入方案之後才
開始真正起飛研究和行業應用:2013年由Mikolov在谷歌開發的Word2Vec演算法.Word2Vec維度捕獲特定的語義屬性,例如性別。
有各種預先計算的字嵌入資料庫,可以下載並開始在Keras“嵌入”層中使用。 Word2Vec就是其中之一。另一個流行的被稱為“GloVe”,由斯坦福大學的
研究人員在2014年開發。它代表“用於詞表示的全域性向量”,它是一種基於對詞共現統計矩陣進行因式分解的嵌入技術。它的開發人員為數以百萬計的英語
令牌提供了預先計算的嵌入,從維基百科資料或公共爬網資料中獲得。
讓我們來看看如何開始在Keras模型中使用GloVe嵌入。當然,相同的方法對於Word2Vec嵌入或您可以下載的任何其他字嵌入資料庫都是有效的。
我們還將使用此示例來重新整理我們在幾段前介紹的文字標記化技術:我們將從原始文字開始,然後繼續前進。

把它們放在一起:從原始文字到文字嵌入
我們將使用類似於我們剛剛過去的模型 - 在向量序列中嵌入句子,展平它們並在頂部訓練“密集”層。但我們將使用預先訓練的字嵌入來實現,而不是使用
Keras中打包的預標記化IMDB資料,我們將從頭開始,通過下載原始文字資料。

以原始文字下載IMDB資料
首先,在 http://ai.stanford.edu/~amaas/data/sentiment/`下載原始IMDB資料集,解壓縮它。
現在將各個訓練評論收集到一個字串列表中,每個評論一個字串,然後還將評論標籤(正面/負面)收集到“標籤”列表中:
'''
import os

imdb_dir = 'aclImdb'
train_dir = os.path.join(imdb_dir, 'train')

labels = []
texts = []

for label_type in ['neg', 'pos']:
    dir_name = os.path.join(train_dir, label_type)
    for fname in os.listdir(dir_name):
        if fname[-4:] == '.txt':
            f = open(os.path.join(dir_name, fname), encoding='utf-8')
            texts.append(f.read())
            f.close()
            if label_type == 'neg':
                labels.append(0)
            else:
                labels.append(1)

# 標記資料
# 對我們收集的文字進行向量化,並準備培訓和驗證分割。
# 因為預訓練的單詞嵌入對於幾乎沒有可用訓練資料的問題特別有用(否則,特定於任務的嵌入可能勝過它們),
# 新增以下內容:將訓練資料限制在前200樣本。 因此,在查看了200個例子後,將學習如何對電影評論進行分類......

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np

max_len = 100  # 將100個單詞後的評論截斷
training_samples = 200  # 訓練200個樣本
validation_samples = 10000  # 進隊10000個樣本進行驗證
max_words = 10000  # 值考慮資料集中的前10000個詞

tokenizer = Tokenizer(num_words=max_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_len)

labels = 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]

x_train = data[:training_samples]
y_train = labels[:training_samples]

x_val = data[training_samples: training_samples + validation_samples]
y_val = labels[training_samples: training_samples + validation_samples]

'''
下載GloVe單詞嵌入
前往`https://nlp.stanford.edu/projects/glove/`(您可以在其中瞭解有關GloVe演算法的更多資訊),
並從2014英語維基百科下載預先計算的嵌入。 這是一個名為`glove.6B.zip`的822MB zip檔案,包含400,000個單詞(或非單詞標記)的100維嵌入向量。 解開它。

預處理嵌入
讓我們解析未壓縮的檔案(它是一個`txt`檔案)來構建一個索引,將單詞(作為字串)對映到它們的向量表示(作為數字向量)。
'''
glove_dir = 'glove'

embedding_index = {}
f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'), encoding='utf-8')
for line in f:
    values = line.split()
    word = values[0]
    coefs = np.asarray(values[1:], dtype='float32')
    embedding_index[word] = coefs
f.close()

'''
構建一個嵌入矩陣,將它載入到“嵌入”層。 它必須是形狀矩陣`(max_words,embedding_dim)`,
其中每個條目`i`在參考詞索引(在標記化期間建立)中包含索引`i`的單詞的'embedding_dim`維向量。
請注意,索引“0”不應代表任何單詞或標記 - 它是佔位符。
'''
embedding_dim = 100

embedding_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

# 定義一個模型
# 將使用與以前相同的模型架構:
model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=max_len))
model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
print(model.summary())

'''
將GloVe嵌入載入到模型中
“嵌入”層具有單個權重矩陣:2D浮點矩陣,其中每個條目“i”是意圖與索引“i”相關聯的單詞向量。
 很簡單。 將準備好的GloVe矩陣載入到'Embedding`層中,這是模型中的第一層:
'''
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = True

'''
另外,凍結嵌入層(將其“可訓練”屬性設定為“False”),遵循與預先訓練的訊號特徵背景下相同的基本原理:當模型的某些部分預先存在時
訓練(如'嵌入'層),部件隨機初始化(如我們的分類器),訓練前不應更新訓練前的部分,以免忘記他們已經知道的內容。
 由隨機初始化的層觸發的大梯度更新對於已經學習的特徵將是非常具有破壞性的。
'''
# 訓練和評估
model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
hist = 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 = hist.history['acc']
val_acc = hist.history['val_acc']
loss = hist.history['loss']
val_loss = hist.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()

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

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

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


# In[22]:


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()


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), encoding='utf-8')
            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=max_len)
y_test = np.asarray(labels)



model.load_weights('pre_trained_glove_model.h5')
model.evaluate(x_test, y_test)

結果:

Epoch 10/10

   32/20000 [..............................] - ETA: 1s - loss: 0.2026 - acc: 0.9062
  736/20000 [>.............................] - ETA: 1s - loss: 0.2640 - acc: 0.8981
 1568/20000 [=>............................] - ETA: 1s - loss: 0.2707 - acc: 0.9037
 2336/20000 [==>...........................] - ETA: 1s - loss: 0.2781 - acc: 0.8977
 3136/20000 [===>..........................] - ETA: 1s - loss: 0.2756 - acc: 0.8967
 3872/20000 [====>.........................] - ETA: 1s - loss: 0.2779 - acc: 0.8944
 4576/20000 [=====>........................] - ETA: 1s - loss: 0.2795 - acc: 0.8925
 5408/20000 [=======>......................] - ETA: 0s - loss: 0.2756 - acc: 0.8955
 6208/20000 [========>.....................] - ETA: 0s - loss: 0.2752 - acc: 0.8940
 6848/20000 [=========>....................] - ETA: 0s - loss: 0.2755 - acc: 0.8944
 7488/20000 [==========>...................] - ETA: 0s - loss: 0.2779 - acc: 0.8922
 8128/20000 [===========>..................] - ETA: 0s - loss: 0.2793 - acc: 0.8914
 8800/20000 [============>.................] - ETA: 0s - loss: 0.2813 - acc: 0.8892
 9440/20000 [=============>................] - ETA: 0s - loss: 0.2831 - acc: 0.8874
10176/20000 [==============>...............] - ETA: 0s - loss: 0.2850 - acc: 0.8870
11008/20000 [===============>..............] - ETA: 0s - loss: 0.2846 - acc: 0.8873
11840/20000 [================>.............] - ETA: 0s - loss: 0.2855 - acc: 0.8870
12480/20000 [=================>............] - ETA: 0s - loss: 0.2850 - acc: 0.8870
13216/20000 [==================>...........] - ETA: 0s - loss: 0.2844 - acc: 0.8870
13952/20000 [===================>..........] - ETA: 0s - loss: 0.2844 - acc: 0.8868
14688/20000 [=====================>........] - ETA: 0s - loss: 0.2840 - acc: 0.8871
15328/20000 [=====================>........] - ETA: 0s - loss: 0.2836 - acc: 0.8873
16064/20000 [=======================>......] - ETA: 0s - loss: 0.2849 - acc: 0.8861
16832/20000 [========================>.....] - ETA: 0s - loss: 0.2845 - acc: 0.8863
17664/20000 [=========================>....] - ETA: 0s - loss: 0.2850 - acc: 0.8860
18432/20000 [==========================>...] - ETA: 0s - loss: 0.2843 - acc: 0.8859
19264/20000 [===========================>..] - ETA: 0s - loss: 0.2839 - acc: 0.8860
20000/20000 [==============================] - 1s 74us/step - loss: 0.2839 - acc: 0.8861 - val_loss: 0.5302 - val_acc: 0.7464
Found 88582 unique tokens:
Shape of data tensor: (25000, 100)
Shape of label tensor: (25000,)

Epoch 10/10

 32/200 [===>..........................] - ETA: 0s - loss: 0.0131 - acc: 1.0000
128/200 [==================>...........] - ETA: 0s - loss: 0.0140 - acc: 1.0000
200/200 [==============================] - 1s 4ms/step - loss: 0.0141 - acc: 1.0000 - val_loss: 0.8506 - val_acc: 0.5600

Epoch 10/10

 32/200 [===>..........................] - ETA: 0s - loss: 0.0031 - acc: 1.0000
128/200 [==================>...........] - ETA: 0s - loss: 0.0032 - acc: 1.0000
200/200 [==============================] - 1s 5ms/step - loss: 0.0030 - acc: 1.0000 - val_loss: 0.7472 - val_acc: 0.5321