Kaggle word2vec NLP 教程 第三部分:詞向量的更多樂趣
第三部分:詞向量的更多樂趣
程式碼
第三部分的程式碼在這裡。
單詞的數值表示
現在我們有了訓練好的模型,對單詞有一些語義理解,我們應該如何使用它? 如果你看它的背後,第 2 部分訓練的 Word2Vec 模型由詞彙表中每個單詞的特徵向量組成,儲存在一個名為syn0
的numpy
陣列中:
>>> # Load the model that we created in Part 2
>>> from gensim.models import Word2Vec
>>> model = Word2Vec.load("300features_40minwords_10context" )
2014-08-03 14:50:15,126 : INFO : loading Word2Vec object from 300features_40min_word_count_10context
2014-08-03 14:50:15,777 : INFO : setting ignored attribute syn0norm to None
>>> type(model.syn0)
<type 'numpy.ndarray'>
>>> model.syn0.shape
(16492, 300)
syn0
中的行數是模型詞彙表中的單詞數,列數對應於我們在第 2 部分中設定的特徵向量的大小。將最小單詞計數設定為 40 ,總詞彙量為 16,492 個單詞,每個詞有 300 個特徵。 可以通過以下方式訪問單個單詞向量:
>>> model["flower"]
…返回一個 1x300 的numpy
陣列。
從單詞到段落,嘗試 1:向量平均
IMDB 資料集的一個挑戰是可變長度評論。 我們需要找到一種方法來獲取單個單詞向量並將它們轉換為每個評論的長度相同的特徵集。
由於每個單詞都是 300 維空間中的向量,我們可以使用向量運算來組合每個評論中的單詞。 我們嘗試的一種方法是簡單地平均給定的評論中的單詞向量(為此,我們刪除了停止詞,這隻會增加噪音)。
以下程式碼基於第 2 部分的程式碼構建了特徵向量的平均值。
import numpy as np # Make sure that numpy is imported
def makeFeatureVec(words, model, num_features):
# 用於平均給定段落中的所有單詞向量的函式
#
# 預初始化一個空的 numpy 陣列(為了速度)
featureVec = np.zeros((num_features,),dtype="float32")
#
nwords = 0.
#
# Index2word 是一個列表,包含模型詞彙表中的單詞名稱。
# 為了獲得速度,將其轉換為集合。
index2word_set = set(model.index2word)
#
# 遍歷評論中的每個單詞,如果它在模型的詞彙表中,
# 則將其特徵向量加到 total
for word in words:
if word in index2word_set:
nwords = nwords + 1.
featureVec = np.add(featureVec,model[word])
#
# 將結果除以單詞數來獲得平均值
featureVec = np.divide(featureVec,nwords)
return featureVec
def getAvgFeatureVecs(reviews, model, num_features):
# 給定一組評論(每個評論都是單詞列表),計算每個評論的平均特徵向量並返回2D numpy陣列
#
# 初始化計數器
counter = 0.
#
# 為了速度,預分配 2D numpy 陣列
reviewFeatureVecs = np.zeros((len(reviews),num_features),dtype="float32")
#
# 遍歷評論
for review in reviews:
#
# 每 1000 個評論列印一次狀態訊息
if counter%1000. == 0.:
print "Review %d of %d" % (counter, len(reviews))
#
# 呼叫生成平均特徵向量的函式(定義如上)
reviewFeatureVecs[counter] = makeFeatureVec(review, model, \
num_features)
#
# 增加計數器
counter = counter + 1.
return reviewFeatureVecs
現在,我們可以呼叫這些函式來為每個段落建立平均向量。 以下操作將需要幾分鐘:
# ****************************************************************
# 使用我們在上面定義的函式,
# 計算訓練和測試集的平均特徵向量。
# 請注意,我們現在刪除停止詞。
clean_train_reviews = []
for review in train["review"]:
clean_train_reviews.append( review_to_wordlist( review, \
remove_stopwords=True ))
trainDataVecs = getAvgFeatureVecs( clean_train_reviews, model, num_features )
print "Creating average feature vecs for test reviews"
clean_test_reviews = []
for review in test["review"]:
clean_test_reviews.append( review_to_wordlist( review, \
remove_stopwords=True ))
testDataVecs = getAvgFeatureVecs( clean_test_reviews, model, num_features )
接下來,使用平均段落向量來訓練隨機森林。 請注意,與第 1 部分一樣,我們只能使用標記的訓練評論來訓練模型。
# 使用 100 棵樹讓隨機森林擬合訓練資料
from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier( n_estimators = 100 )
print "Fitting a random forest to labeled training data..."
forest = forest.fit( trainDataVecs, train["sentiment"] )
# 測試和提取結果
result = forest.predict( testDataVecs )
# 寫出測試結果
output = pd.DataFrame( data={"id":test["id"], "sentiment":result} )
output.to_csv( "Word2Vec_AverageVectors.csv", index=False, quoting=3 )
我們發現這產生了比偶然更好的結果,但是表現比詞袋低了幾個百分點。
由於向量的元素平均值沒有產生驚人的結果,或許我們可以以更聰明的方式實現? 加權單詞向量的標準方法是應用“tf-idf”權重,它衡量給定單詞在給定文件集中的重要程度。 在 Python 中提取 tf-idf 權重的一種方法,是使用 scikit-learn 的TfidfVectorizer
,它具有類似於我們在第 1 部分中使用的CountVectorizer
的介面。但是,當我們嘗試以這種方式加權我們的單詞向量時,我們發現沒有實質的效能改善。
從單詞到段落,嘗試 2:聚類
Word2Vec 建立語義相關單詞的簇,因此另一種可能的方法是利用簇中單詞的相似性。 以這種方式來分組向量稱為“向量量化”。 為了實現它,我們首先需要找到單詞簇的中心,我們可以通過使用聚類演算法(如 K-Means)來完成。
在 K-Means 中,我們需要設定的一個引數是“K”,或者是簇的數量。 我們應該如何決定要建立多少個簇? 試錯法表明,每個簇平均只有5個單詞左右的小簇,比具有多個詞的大簇產生更好的結果。 聚類程式碼如下。 我們使用 scikit-learn 來執行我們的 K-Means。
具有較大 K 的 K-Means 聚類可能非常慢;以下程式碼在我的計算機上花了 40 多分鐘。 下面,我們給 K-Means 函式設定一個計時器,看看它需要多長時間。
from sklearn.cluster import KMeans
import time
start = time.time() # Start time
# 將“k”(num_clusters)設定為詞彙量大小的 1/5,或每個簇平均 5 個單詞
word_vectors = model.syn0
num_clusters = word_vectors.shape[0] / 5
# 初始化 k-means 物件並使用它來提取質心
kmeans_clustering = KMeans( n_clusters = num_clusters )
idx = kmeans_clustering.fit_predict( word_vectors )
# 獲取結束時間並列印該過程所需的時間
end = time.time()
elapsed = end - start
print "Time taken for K Means clustering: ", elapsed, "seconds."
現在,每個單詞的聚類分佈都儲存在idx
中,而原始 Word2Vec 模型中的詞彙表仍儲存在model.index2word
中。 為方便起見,我們將它們壓縮成一個字典,如下所示:
# 建立單詞/下標字典,將每個詞彙表單詞對映為簇編號
word_centroid_map = dict(zip( model.index2word, idx ))
這有點抽象,所以讓我們仔細看看我們的簇包含什麼。 你的簇可能會有所不同,因為 Word2Vec 依賴於隨機數種子。 這是一個迴圈,打印出簇 0 到 9 的單詞:
# 對於前 10 個簇
for cluster in xrange(0,10):
#
# 列印簇編號
print "\nCluster %d" % cluster
#
# 找到該簇編號的所有單詞,然後將其打印出來
words = []
for i in xrange(0,len(word_centroid_map.values())):
if( word_centroid_map.values()[i] == cluster ):
words.append(word_centroid_map.keys()[i])
print words
結果很有意思:
Cluster 0
[u'passport', u'penthouse', u'suite', u'seattle', u'apple']
Cluster 1
[u'unnoticed']
Cluster 2
[u'midst', u'forming', u'forefront', u'feud', u'bonds', u'merge', u'collide', u'dispute', u'rivalry', u'hostile', u'torn', u'advancing', u'aftermath', u'clans', u'ongoing', u'paths', u'opposing', u'sexes', u'factions', u'journeys']
Cluster 3
[u'lori', u'denholm', u'sheffer', u'howell', u'elton', u'gladys', u'menjou', u'caroline', u'polly', u'isabella', u'rossi', u'nora', u'bailey', u'mackenzie', u'bobbie', u'kathleen', u'bianca', u'jacqueline', u'reid', u'joyce', u'bennett', u'fay', u'alexis', u'jayne', u'roland', u'davenport', u'linden', u'trevor', u'seymour', u'craig', u'windsor', u'fletcher', u'barrie', u'deborah', u'hayward', u'samantha', u'debra', u'frances', u'hildy', u'rhonda', u'archer', u'lesley', u'dolores', u'elsie', u'harper', u'carlson', u'ella', u'preston', u'allison', u'sutton', u'yvonne', u'jo', u'bellamy', u'conte', u'stella', u'edmund', u'cuthbert', u'maude', u'ellen', u'hilary', u'phyllis', u'wray', u'darren', u'morton', u'withers', u'bain', u'keller', u'martha', u'henderson', u'madeline', u'kay', u'lacey', u'topper', u'wilding', u'jessie', u'theresa', u'auteuil', u'dane', u'jeanne', u'kathryn', u'bentley', u'valerie', u'suzanne', u'abigail']
Cluster 4
[u'fest', u'flick']
Cluster 5
[u'lobster', u'deer']
Cluster 6
[u'humorless', u'dopey', u'limp']
Cluster 7
[u'enlightening', u'truthful']
Cluster 8
[u'dominates', u'showcases', u'electrifying', u'powerhouse', u'standout', u'versatility', u'astounding']
Cluster 9
[u'succumbs', u'comatose', u'humiliating', u'temper', u'looses', u'leans']
我們可以看到這些簇的質量各不相同。 有些是有道理的 - 簇 3 主要包含名稱,而簇 6- 8包含相關的形容詞(簇 6 是我最喜歡的)。 另一方面,簇 5 有點神祕:龍蝦和鹿有什麼共同之處(除了是兩隻動物)? 簇 0 更糟糕:閣樓和套房似乎屬於一個東西,但它們似乎不屬於蘋果和護照。 簇 2 包含…可能與戰爭有關的詞? 也許我們的演算法在形容詞上效果最好。
無論如何,現在我們為每個單詞分配了一個簇(或“質心”),我們可以定義一個函式將評論轉換為質心袋。 這就像詞袋一樣,但使用語義相關的簇而不是單個單詞:
def create_bag_of_centroids( wordlist, word_centroid_map ):
#
# 簇的數量等於單詞/質心對映中的最大的簇索引
num_centroids = max( word_centroid_map.values() ) + 1
#
# 預分配質心向量袋(為了速度)
bag_of_centroids = np.zeros( num_centroids, dtype="float32" )
#
# 遍歷評論中的單詞。如果單詞在詞彙表中,
# 找到它所屬的簇,並將該簇的計數增加 1
for word in wordlist:
if word in word_centroid_map:
index = word_centroid_map[word]
bag_of_centroids[index] += 1
#
# 返回“質心袋”
return bag_of_centroids
上面的函式將為每個評論提供一個numpy
陣列,每個陣列的特徵都與簇數相等。 最後,我們為訓練和測試集建立了質心袋,然後訓練隨機森林並提取結果:
# 為訓練集質心預分配一個數組(為了速度)
train_centroids = np.zeros( (train["review"].size, num_clusters), \
dtype="float32" )
# 將訓練集評論轉換為質心袋
counter = 0
for review in clean_train_reviews:
train_centroids[counter] = create_bag_of_centroids( review, \
word_centroid_map )
counter += 1
# 對測試評論重複
test_centroids = np.zeros(( test["review"].size, num_clusters), \
dtype="float32" )
counter = 0
for review in clean_test_reviews:
test_centroids[counter] = create_bag_of_centroids( review, \
word_centroid_map )
counter += 1
# 擬合隨機森林並提取預測
forest = RandomForestClassifier(n_estimators = 100)
# 擬合可能需要幾分鐘
print "Fitting a random forest to labeled training data..."
forest = forest.fit(train_centroids,train["sentiment"])
result = forest.predict(test_centroids)
# 寫出測試結果
output = pd.DataFrame(data={"id":test["id"], "sentiment":result})
output.to_csv( "BagOfCentroids.csv", index=False, quoting=3 )
我們發現與第 1 部分中的詞袋相比,上面的程式碼給出了相同(或略差)的結果。