6.1 深度學(xué)習(xí)之文本處理
文本是序列數(shù)據(jù)傳播最廣泛的形式之一,它可以理解成一個(gè)字母序列或者詞序列,但是最常見的形式是詞序列。后面章節(jié)介紹的深度學(xué)習(xí)序列處理模型有文檔分類、情感分析、作者識(shí)別和限制語境問答(QA)。當(dāng)然了,要記住的是:這些深度學(xué)習(xí)模型并不是真正意義上以人的思維去理解文字,而只是書面語的統(tǒng)計(jì)結(jié)構(gòu)映射而已。基于深度學(xué)習(xí)的自然語言處理可以看作對(duì)字詞、句子和段落的模式識(shí)別,這有點(diǎn)像計(jì)算機(jī)視覺中對(duì)像素的模式識(shí)別。
跟其它所有神經(jīng)網(wǎng)絡(luò)一樣,深度學(xué)習(xí)模型并不是以原始文本為輸入,而是數(shù)值型張量。向量化文本是將文本轉(zhuǎn)換成數(shù)值張量的過程。有以下幾種方式可以做向量化文本:
- 將文本分割為詞,轉(zhuǎn)換每個(gè)詞為向量;
- 將文本分割為字(字母),轉(zhuǎn)換每個(gè)字為向量;
- 抽取詞或者字的n-gram,轉(zhuǎn)換每個(gè)n-gram轉(zhuǎn)換為向量。n-gram是多個(gè)連續(xù)詞或者字的元組。
將文本分割為字、詞或者n-gram的過程稱為分詞(tokenization),拆分出來的字、詞或者n-gram稱為token。所有文本向量化的過程都包含分詞和token轉(zhuǎn)換為數(shù)值型向量。這些向量封裝成序列張量“喂入”神經(jīng)網(wǎng)絡(luò)模型。有多種方式可以將token轉(zhuǎn)換為數(shù)值向量,但是本小節(jié)介紹兩種方法:one-hot編碼和詞嵌入。
圖6.1 文本向量化過程
n-gram和詞袋的理解
n-gram是指從句子中抽取的N個(gè)連續(xù)詞的組合。對(duì)于字也有相同的概念。
下面是一個(gè)簡(jiǎn)單的例子。句子“the cat sat on the mat”拆分成2-gram的集合如下:
{"The", "The cat", "cat", "cat sat", "sat", "sat on", "on", "on the", "the", "the mat", "mat"}
拆分成3-gram的集合如下:
{"The", "The cat", "cat", "cat sat", "The cat sat", "sat", "sat on", "on", "cat sat on", "on the", "the", "sat on the", "the mat", "mat", "on the mat"}
上面這些集合相應(yīng)地稱為2-gram的詞袋,3-gram的詞袋。術(shù)語詞袋(bag)是指token的集合,而不是一個(gè)列表或者序列:token是無序的。所有分詞方法的結(jié)果統(tǒng)稱為詞袋。
詞袋是一個(gè)無序的分詞方法,其丟失了文本序列的結(jié)構(gòu)信息。詞袋模型用于淺語言處理模型中,而不是深度學(xué)習(xí)模型。抽取n-gram是一種特征工程,但是深度學(xué)習(xí)是用一種簡(jiǎn)單粗暴的方法做特征工程,去代替復(fù)雜的特征工程。本章后面會(huì)講述一維卷積和RNN,它們能從字、詞的組合中學(xué)習(xí)表征。所以本書不再進(jìn)一步展開介紹n-gram。但是記住,在輕量級(jí)模型或者淺文本處理模型(邏輯回歸和隨機(jī)森林)中,n-gram是一個(gè)強(qiáng)有力、不可替代的特征工程工具。
6.1.1 字詞的one-hot編碼
one-hot編碼是最常見、最基本的文本向量化方法。在前面第三章的IMDB和Reuter例子中有使用過。one-hot編碼中每個(gè)詞有唯一的數(shù)值索引,然后將對(duì)應(yīng)的索引轉(zhuǎn)成大小為N的二值向量(N為字典的大小):詞所對(duì)應(yīng)的索引位置的值為1,其它索引對(duì)應(yīng)的值為0。
當(dāng)然,字級(jí)別也可以做one-hot編碼。為了予以區(qū)分,列表6.1和6.2分別展示詞和字的one-hot編碼。
#Listing 6.1 Word-level one-hot encoding
import numpy as np
'''
Initial data: one entry per sample (in this example,
a sample is a sentence,
but it could be an entire document)
'''
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
'''
Builds an index of all tokens in the data
'''
token_index = {}
for sample in samples:
'''
Tokenizes the samples via the split method.
In real life, you’d also strip punctuation
and special characters from the samples.
'''
for word in sample.split():
if word not in token_index:
'''
Assigns a unique index to each unique word.
Note that you don’t attribute index 0 to anything.
'''
token_index[word] = len(token_index) + 1
'''
Vectorizes the samples. You’ll only consider
the first max_length words in each sample.
'''
max_length = 10
'''
This is where you store the results.
'''
results = np.zeros(shape=(len(samples),
max_length,
max(token_index.values()) + 1))
for i, sample in enumerate(samples):
for j, word in list(enumerate(sample.split()))[:max_length]:
index = token_index.get(word)
results[i, j, index] = 1.
#Listing 6.2 Character-level one-hot encoding
import string
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
'''
All printable ASCII characters
'''
characters = string.printable
token_index = dict(zip(range(1, len(characters) + 1), characters))
max_length = 50
results = np.zeros((len(samples), max_length, max(token_index.keys()) + 1))
for i, sample in enumerate(samples):
for j, character in enumerate(sample):
index = token_index.get(character)
results[i, j, index] = 1.
Keras有內(nèi)建工具處理文本的one-hot編碼。建議你使用這些工具,因?yàn)樗鼈冇胁簧俟δ埽热纾瑒h除指定字符,考慮數(shù)據(jù)集中最常用的N個(gè)字(嚴(yán)格來講,是避免向量空間過大)。
#Listing 6.3 Using Keras for word-level one-hot encoding
from keras.preprocessing.text import Tokenizer
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
'''
Creates a tokenizer, configured to only take into account the 1,000 most common words
'''
tokenizer = Tokenizer(num_words=1000)
'''
Builds the word index
'''
tokenizer.fit_on_texts(samples)
'''
Turns strings into lists of integer indices
'''
sequences = tokenizer.texts_to_sequences(samples)
'''
You could also directly get the one-hot binary representations. Vectorization modes other than one-hot encoding are supported by this tokeniser.
'''
one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary')
'''
How you can recover the word index that was computed
'''
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))
one-hot 哈希(hash)編碼是one-hot編碼的一個(gè)變種,它主要用在字典太大難以處理的情況。one-hot 哈希編碼是將詞通過輕量級(jí)的哈希算法打散成固定長(zhǎng)度的向量,而不是像one-hot編碼將每個(gè)詞分配給一個(gè)索引。one-hot 哈希編碼最大的優(yōu)勢(shì)是節(jié)省內(nèi)存和數(shù)據(jù)的在線編碼。同時(shí)這種方法的一個(gè)缺點(diǎn)是碰到哈希碰撞沖突(hash collision),也就是兩個(gè)不同詞的哈希值相同,導(dǎo)致機(jī)器學(xué)習(xí)模型不能分辨這些詞。哈希碰撞沖突的 可能性會(huì)隨著哈希空間的維度越大而減小。
#Listing 6.4 Word-level one-hot encoding with hashing trick
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
'''
Stores the words as vectors of size 1,000. If you have close to 1,000 words (or more), you’ll see many hash collisions, which will decrease the accuracy of this encoding method.
'''
dimensionality = 1000
max_length = 10
results = np.zeros((len(samples), max_length, dimensionality))
for i, sample in enumerate(samples):
for j, word in list(enumerate(sample.split()))[:max_length]:
'''
Hashes the word into a random integer index
between 0 and 1,000
'''
index = abs(hash(word)) % dimensionality
results[i, j, index] = 1.
6.1.2 詞嵌入
另外一種常用的、高效的文本向量化方法是稠密詞向量,也稱為詞嵌入。one-hot編碼得到的向量是二值的、稀疏的(大部分值為0)、高維度的(與字典的大小相同),而詞嵌入是低維度的浮點(diǎn)型向量(意即,稠密向量),見圖6.2。前面的向量是通過one-hot編碼得到的,而詞嵌入是由數(shù)據(jù)學(xué)習(xí)得到,最常見的詞嵌入是256維、512維或者1024維。one-hot編碼會(huì)導(dǎo)致向量的維度甚至超過20,000維(此處以20,000個(gè)詞的字典舉例)。所以詞嵌入能夠用更少的維度表示更多的信息。
圖6.2 one-hot編碼和詞嵌入得到的向量對(duì)比
有兩種獲得詞嵌入的方式:
- 在解決文檔分類或者情感預(yù)測(cè)的任務(wù)中學(xué)習(xí)詞嵌入。一般以隨機(jī)詞向量維開始,然后在訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型權(quán)重的過程中學(xué)習(xí)到詞向量。
- 加載預(yù)訓(xùn)練的詞向量。預(yù)訓(xùn)練的詞向量一般是從不同于當(dāng)前要解決的機(jī)器學(xué)習(xí)任務(wù)中學(xué)習(xí)得到的。
下面學(xué)習(xí)前面的兩種方法。
學(xué)習(xí)詞嵌入:Embedding layer
詞與稠密向量相關(guān)聯(lián)的最簡(jiǎn)單方法是隨機(jī)向量化。但是,這種方法使得嵌入空間變得毫無結(jié)構(gòu):比如,單詞accurate和exact在大部分句子里是可互換的,但得到的嵌入可能完全不同。深度神經(jīng)網(wǎng)絡(luò)很難識(shí)別出這種噪音和非結(jié)構(gòu)嵌入空間。
更抽象一點(diǎn)的講,詞與詞之間的語義相似性在詞向量空間中應(yīng)該以幾何關(guān)系表現(xiàn)出來。詞嵌入可以理解成是人類語言到幾何空間的映射過程。例如,你會(huì)期望同義詞被嵌入為相似的詞向量;更一般地說,你期望任意兩個(gè)詞向量的幾何距離(比如,L2距離)和相關(guān)詞的語義距離是有相關(guān)性。除了距離之外,詞向量在嵌入空間的方向也應(yīng)該是有意義的。下面舉個(gè)具體的例子來說明這兩點(diǎn)。
圖6.3 詞嵌入空間的實(shí)例
在圖6.3中,cat、dog、wolf和tiger四個(gè)詞被嵌入到二維平面空間。在這里選擇的詞向量表示時(shí),這些詞的語義關(guān)系能用幾何變換來編碼表示。比如,從cat到tiger和從dog到wolf有著相同的向量,該向量可以用“從寵物到野生動(dòng)物”來解釋。同樣,從dog到cat和從wolf到tiger有相同的向量,該向量表示“從犬科到貓科動(dòng)物”。
在實(shí)際的詞嵌入空間中,常見的幾何變換例子是“gender”詞向量和“plural”詞向量。比如,將“female”詞向量加到“king”詞向量上,可以得到“queen”詞向量;將“plural”詞向量加到“king”詞向量上,可以得到“kings”詞向量。
那接下來就要問了,有完美的詞向量空間能匹配人類語言嗎?能用來解決任意種類的自然語言處理任務(wù)嗎?答案是可能有,但是現(xiàn)階段暫時(shí)沒有。也沒有一種詞向量可以向人類語言一樣有很多種語言,并且是不同形的,因?yàn)樗鼈兌际窃谔囟ㄎ幕吞囟ōh(huán)境下形成的。但是,怎么才能得到一個(gè)優(yōu)秀的詞嵌入空間呢?從程序?qū)崿F(xiàn)上講是因任務(wù)而異:英文影評(píng)情感分析模型對(duì)應(yīng)完美詞嵌入空間與英文文檔分類模型對(duì)應(yīng)的完美詞嵌入空間可能不同,因?yàn)椴煌蝿?wù)的語義關(guān)系重要性是變化的。
因此,對(duì)每個(gè)新任務(wù)來說,最好重新學(xué)習(xí)的詞嵌入空間。幸運(yùn)的是,反向傳播算法和Keras使得學(xué)習(xí)詞嵌入變得容易。下面學(xué)習(xí)Keras的Embedding layer權(quán)重。
#Listing 6.5 Instantiating an Embedding layer
from keras.layers import Embedding
'''
The Embedding layer takes at least two arguments: the number of possible tokens (here, 1,000: 1 + maximum word index) and the dimensionality of the embeddings (here, 64).
'''
embedding_layer = Embedding(1000, 64)
Embedding layer把詞的整數(shù)索引映射為稠密向量。它輸入整數(shù),在中間字典中查找這些整數(shù)對(duì)應(yīng)的向量。Embedding layer是一個(gè)高效的字典查表(見圖6.4)。
圖6.4 Embedding layer
Embedding layer的輸入是一個(gè)形狀為(樣本,序列長(zhǎng)度)[^(sample,sequence_length)]的 2D 整數(shù)型張量,該張量的每項(xiàng)都是一個(gè)整數(shù)序列。Embedding layer能嵌入變長(zhǎng)序列:比如,可以“喂入”形狀為(32,10)(長(zhǎng)度為10的序列數(shù)據(jù),32個(gè)為一個(gè)batch)或者(64,15)(長(zhǎng)度為15的序列數(shù)據(jù)64個(gè)為一個(gè)batch)。同一個(gè)batch中的所有序列數(shù)據(jù)必須有相同的長(zhǎng)度,因?yàn)樗鼈儠?huì)被打包成一個(gè)張量。所以比其它序列數(shù)據(jù)短的序列將用“0”填充,另外,太長(zhǎng)的序列會(huì)被截?cái)唷?/p>
Embedding layer返回一個(gè)形狀為(樣本,序列長(zhǎng)度,詞向量大小)[^(samples,sequence_ length,embedding_dimensionality)]的3D浮點(diǎn)型張量,該張量可以被RNN layer或者1D 卷積layer處理。
當(dāng)你實(shí)例化一個(gè)Embedding layer時(shí),它的權(quán)重(詞向量的中間字典)是隨機(jī)初始化,和其它layer一樣。隨著模型的訓(xùn)練,這些詞向量通過反向傳播算法逐漸調(diào)整,傳入下游模型使用。一旦模型訓(xùn)練完,嵌入空間會(huì)顯現(xiàn)出許多結(jié)構(gòu),不同的模型會(huì)訓(xùn)練出不同的特定結(jié)構(gòu)。
下面用熟悉的IMDB影評(píng)情感預(yù)測(cè)任務(wù)來說明上面的想法。首先,準(zhǔn)備數(shù)據(jù)集。限制選取詞頻為top 10,000的常用詞,只考慮影評(píng)前20個(gè)詞。神經(jīng)網(wǎng)絡(luò)模型將學(xué)習(xí)8維的詞嵌入,把輸入的整數(shù)序列(2D整數(shù)張量)轉(zhuǎn)化為嵌入序列(3D浮點(diǎn)張量)
#Listing 6.6 Loading the IMDB data for use with an Embedding layer
from keras.datasets import imdv
from keras import preprocessing
'''
Number of words to consider as features
'''
max_features = 10000
'''
Cuts off the text after this number of words (among the max_features most common words)
'''
maxlen = 20
'''
Loads the data as lists of integers
'''
(x_train, y_train), (x_test, y_test) = imdb.load_data( num_words=max_features)
'''
Turns the lists of integers into a 2D integer tensor of shape (samples, maxlen)
'''
x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)
#Listing 6.7 Using an Embedding layer and classifier on the IMDB data
from keras.models import Sequential
from keras.layers import Flatten, Dense
model = Sequential()
'''
Specifies the maximum input length to the Embedding layer so you can later flatten the embedded inputs. After the Embedding layer, the activations have shape (samples, maxlen, 8).
'''
model.add(Embedding(10000, 8, input_length=maxlen))
'''
Flattens the 3D tensor of embeddings into a 2D tensor of shape (samples, maxlen * 8)
'''
model.add(Flatten())
'''
Adds the classifier on top
'''
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
model.summary()
history = model.fit(x_train, y_train,
epochs=10,
batch_size=32,
validation_split=0.2)
上面的代碼得到了約76%的驗(yàn)證準(zhǔn)確度,這對(duì)于只考慮每個(gè)影評(píng)的前20個(gè)詞來說效果已經(jīng)不錯(cuò)了。注意,僅僅攤平嵌入序列,用單個(gè)Dense layer訓(xùn)練模型,會(huì)將輸入序列的每個(gè)詞隔離開,并沒有考慮詞之間的關(guān)系和句子結(jié)構(gòu)(例如,該模型可能認(rèn)為“this movie is a bomb”和“this movie is the bomb” 兩句話都是負(fù)面影評(píng))。所以在嵌入序列之上加入RNN layer或者1D卷積layer會(huì)將句子當(dāng)做整體來學(xué)習(xí)特征,后續(xù)小節(jié)會(huì)詳細(xì)講解這些。
預(yù)訓(xùn)練的詞嵌入
有時(shí),你只有很少的訓(xùn)練數(shù)據(jù)集來學(xué)習(xí)詞嵌入,那怎么辦呢?
你可以加載預(yù)計(jì)算好的詞嵌入向量,而不用學(xué)習(xí)當(dāng)前待解決任務(wù)的詞嵌入。這些預(yù)計(jì)算好的詞嵌入是高結(jié)構(gòu)化的,具有有用的特性,其學(xué)習(xí)到了語言結(jié)構(gòu)的泛化特征。在自然語言處理中使用預(yù)訓(xùn)練的詞嵌入的基本理論,與圖像分類中使用預(yù)訓(xùn)練的卷積網(wǎng)絡(luò)相同:當(dāng)沒有足夠的合適數(shù)據(jù)集來學(xué)習(xí)當(dāng)前任務(wù)的特征時(shí),你會(huì)期望從通用的視覺特征或者語義特征中學(xué)到泛化特征。
一些詞嵌入是用詞共現(xiàn)矩陣統(tǒng)計(jì)計(jì)算,用各種技術(shù),有些涉及神經(jīng)網(wǎng)絡(luò),有些沒有。用非監(jiān)督的方法計(jì)算詞的稠密的、低維度的嵌入空間是由Bengio在2000年提出的,但是直到2013年Google的Tomas Mikolov開發(fā)出著名的Word2vec算法才開始在學(xué)術(shù)研究和工業(yè)應(yīng)用上廣泛推廣。Word2vec可以獲取語義信息。
Keras的Embedding layer有各種預(yù)訓(xùn)練詞嵌入數(shù)據(jù)可以下載使用,Word2vec是其中之一。另外一個(gè)比較流行的詞表示是GloVe(Global Vector),它是由斯坦福研究組在2014開發(fā)。GloVe是基于詞共現(xiàn)矩陣分解的一種詞嵌入技術(shù),它的開發(fā)者預(yù)訓(xùn)練好了成千上萬的詞嵌入。
下面開始學(xué)習(xí)如何在Keras模型中使用GloVe詞嵌入。其實(shí)它的使用方法與Word2vec詞嵌入或者其它詞嵌入數(shù)據(jù)相同。
6.1.3 從原始文本到詞嵌入
這里的模型網(wǎng)絡(luò)和上面的類似,只是換作預(yù)訓(xùn)練詞嵌入。同時(shí),直接從網(wǎng)上下載原始文本數(shù)據(jù),而不是使用Keras分詞好的IMDB數(shù)據(jù)。
下載IMDB原始文本
首先,前往http://mng.bz/0tIo下載原IMDB數(shù)據(jù)集,并解壓。
接著,將單個(gè)訓(xùn)練影評(píng)裝載為字符串列表,同時(shí)影評(píng)label裝載為label的列表。
#Listing 6.8 Processing the labels of the raw IMDB data
import os
imdb_dir = '/Users/fchollet/Downloads/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, name))
texts.append(f.read())
f.close()
if label_type == 'neg':
labels.append(0)
else:
labels.append(1)
分詞
開始向量化文本,準(zhǔn)備訓(xùn)練集和驗(yàn)證集。因?yàn)轭A(yù)訓(xùn)練的詞嵌入是對(duì)訓(xùn)練集較少時(shí)更好,這里加入步驟:取前200個(gè)樣本數(shù)據(jù)集。所以你相當(dāng)于只看了200條影評(píng)就開始做影評(píng)情感分類。
#Listing 6.9 Tokenizing the text of the raw IMDB data
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
'''
Cuts off reviews after 100 words
'''
maxlen = 100
'''
Trains on 200 samples
'''
training_samples = 200
'''
Validates on 10,000 samples
'''
validation_samples = 10000
'''
Considers only the top 10,000 words in the dataset
'''
max_words = 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=maxlen)
labels = np.asarray(labels)
print('Shape of data tensor:', data.shape)
print('Shape of label tensor:', labels.shape)
'''
Splits the data into a training set and a validation set, but first shuffles the data, because you’re starting with data in which samples are ordered (all negative first, then all positive)
'''
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下載預(yù)訓(xùn)練的2014年英文維基百科的GloVe詞嵌入。它是一個(gè)822 MB的glove.6B.zip文件,包含400,000個(gè)詞的100維嵌入向量。
預(yù)處理GloVe嵌入
下面解析解壓的文件(a.txt)來構(gòu)建索引,能將詞映射為向量表示。
#Listing 6.10 Parsing the GloVe word-embeddings file
glove_dir = '/Users/fchollet/Downloads/glove.6B'
embeddings_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')
embeddings_index[word] = chefs
f.close()
print('Found %s word vectors.' % len(embeddings_index))
接著,構(gòu)建能載入Embedding layer的嵌入矩陣。它的矩陣形狀為(max_words, embedding_dim),其每項(xiàng)i是在參考詞索引中為i的詞對(duì)應(yīng)的embedding_dim維向量。注意,索引0不代表任何詞,只是個(gè)占位符。
#Listing 6.11 Preparing the GloVe word-embeddings matrix
embedding_dim = 100
embedding_matrix = np.zeros((max_words, embedding_dim))
for word, i in word_index.items():
if i < max_words:
embedding_vector = embeddings_index.get(word)
if embedding_vector is not None:
'''
Words not found in the embedding index will be all zeros.
'''
embedding_matrix[i] = embedding_vector
定義模型
使用前面相同的模型結(jié)構(gòu)。
#Listing 6.12 Model definition
from keras.models import Sequential
from 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()
加載GloVe詞嵌入
Embedding layer有一個(gè)權(quán)重矩陣:2D浮點(diǎn)型矩陣,每項(xiàng)i表示索引為i的詞對(duì)應(yīng)的詞向量。在神經(jīng)網(wǎng)絡(luò)模型中加載GloVe詞嵌入到Embedding layer
#Listing 6.13 Loading pretrained word embeddings into the Embedding layer
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False
此外,設(shè)置trainable為False,凍結(jié)Embedding layer。當(dāng)一個(gè)模型的部分網(wǎng)絡(luò)是預(yù)訓(xùn)練的(像Embedding layer)或者隨機(jī)初始化(像分類),那該部分網(wǎng)絡(luò)在模型訓(xùn)練過程中不能更新,避免模型忘記已有的特征。隨機(jī)初始化layer會(huì)觸發(fā)大的梯度更新,導(dǎo)致已經(jīng)學(xué)習(xí)的特征丟失。
訓(xùn)練和評(píng)估模型
編譯和訓(xùn)練模型。
#Listing 6.14 Training and evaluation
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')
現(xiàn)在繪制模型隨時(shí)間的表現(xiàn),見圖6.5和6.6。
#Listing 6.15 Plotting the results
import matplotlib.pyplot as pet
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()
圖6.5 使用預(yù)訓(xùn)練詞嵌入時(shí)的訓(xùn)練損失和驗(yàn)證損失曲線
圖6.6 使用預(yù)訓(xùn)練詞嵌入時(shí)的訓(xùn)練準(zhǔn)確度和驗(yàn)證準(zhǔn)確度曲線
模型訓(xùn)練在開始不久即出現(xiàn)過擬合,這在訓(xùn)練集較少的情況下很常見。驗(yàn)證準(zhǔn)確度有高的variance,不過也到50%了。
可能你的結(jié)果不同:因?yàn)橛?xùn)練集太少,導(dǎo)致模型效果嚴(yán)重依賴被選擇的200個(gè)樣本(這里選擇是隨機(jī)的)。
你也可以在不加載預(yù)訓(xùn)練詞嵌入和不凍結(jié)embedding layer的情況下訓(xùn)練相同的網(wǎng)絡(luò)模型。訓(xùn)練集也使用前面相同的200個(gè)樣本,見圖6.7和6.8。
#Listing 6.16 Training the same model without pretrained word embeddings
from keras.models import Sequential
from 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()
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))
圖6.7 未使用預(yù)訓(xùn)練詞嵌入時(shí)的訓(xùn)練損失和驗(yàn)證損失曲線
圖6.8 未使用預(yù)訓(xùn)練詞嵌入時(shí)的訓(xùn)練準(zhǔn)確度和驗(yàn)證準(zhǔn)確度曲線
這次的結(jié)果顯示驗(yàn)證準(zhǔn)確度不到50%。所以樣本量較少的情況下,預(yù)訓(xùn)練詞嵌入效果更優(yōu)。
最后,在測(cè)試數(shù)據(jù)集上評(píng)估模型。首先,對(duì)測(cè)試數(shù)據(jù)進(jìn)行分詞。
#Listing 6.17 Tokenizing the data of the test set
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, name))
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)
接著,加載并評(píng)估第一個(gè)模型。
#Listing 6.18 Evaluating the model on the test set
model.load_weights('pre_trained_glove_model.h5') model.evaluate(x_test, y_test)
返回測(cè)試準(zhǔn)確度56%的結(jié)果。
6.1.4 小結(jié)
你學(xué)到的知識(shí)有:
- 文本分詞
- 使用Keras的Embedding layer學(xué)習(xí)特定的詞嵌入
- 使用預(yù)訓(xùn)練的詞嵌入提升自然語言處理問題
未完待續(xù)。。。
Enjoy!
翻譯本書系列的初衷是,覺得其中把深度學(xué)習(xí)講解的通俗易懂。不光有實(shí)例,也包含作者多年實(shí)踐對(duì)深度學(xué)習(xí)概念、原理的深度理解。最后說不重要的一點(diǎn),F(xiàn)ran?ois Chollet是Keras作者。
聲明本資料僅供個(gè)人學(xué)習(xí)交流、研究,禁止用于其他目的。如果喜歡,請(qǐng)購買英文原版。
俠天,專注于大數(shù)據(jù)、機(jī)器學(xué)習(xí)和數(shù)學(xué)相關(guān)的內(nèi)容,并有個(gè)人公眾號(hào)分享相關(guān)技術(shù)文章。
若發(fā)現(xiàn)以上文章有任何不妥,請(qǐng)聯(lián)系我。