Kaggle word2vec NLP 教程 第二部分:詞向量
第二部分:詞向量
程式碼
第二部分的教程程式碼在這裡。
分散式詞向量簡介
本教程的這一部分將重點介紹使用 Word2Vec 演算法建立分散式單詞向量。 (深度學習的概述,以及其他一些教程的連結,請參閱“什麼是深度學習?”頁面)。
第 2 部分和第 3 部分比第 1 部分假設你更熟悉Python。我們在雙核 Macbook Pro 上開發了以下程式碼,但是,我們還沒有在 Windows 上成功執行程式碼。如果你是 Windows 使用者並且使其正常執行,請在論壇中留言如何進行操作!更多詳細資訊,請參閱“配置系統”頁面。
Word2vec,由 Google 於 2013 年發表,是一種神經網路實現,可以學習單詞的
Word2Vec 不需要標籤來建立有意義的表示。這很有用,因為現實世界中的大多數資料都是未標記的。如果給網路足夠的訓練資料(數百億個單詞),它會產生特徵極好的單詞向量。具有相似含義的詞出現在簇中,並且簇具有間隔,使得可以使用向量數學來再現諸如類比的一些詞關係。著名的例子是,通過訓練好的單詞向量,“國王 - 男人 + 女人 = 女王”。
檢視 Google 的程式碼,文章和附帶的論文。 此演示也很有幫助。 原始程式碼是 C 寫的,但它已被移植到其他語言,包括 Python。 我們鼓勵你使用原始 C 工具,但如果你是初學程式設計師(我們必須手動編輯標頭檔案來編譯),請注意它不是使用者友好的。
最近斯坦福大學的工作也將深度學習應用於情感分析;他們的程式碼以 Java 提供。 但是,他們的方法依賴於句子解析,不能直接應用於任意長度的段落。
分散式詞向量強大,可用於許多應用,尤其是單詞預測和轉換。 在這裡,我們將嘗試將它們應用於情感分析。
在 Python 中使用 word2vec
在 Python 中,我們將使用gensim
包中的 word2vec 的優秀實現。 如果你還沒有安裝gensim
,則需要安裝它。 這裡有一個包含 Python Word2Vec 實現的優秀教程。
雖然 Word2Vec 不像許多深度學習演算法那樣需要圖形處理單元(GPU),但它是計算密集型的。 Google 的版本和 Python 版本都依賴於多執行緒(在你的計算機上並行執行多個程序以節省時間)。 為了在合理的時間內訓練你的模型,你需要安裝 cython(
為訓練模型做準備
現在到了細節! 首先,我們使用pandas
讀取資料,就像我們在第 1 部分中所做的那樣。與第 1 部分不同,我們現在使用unlabeledTrain.tsv
,其中包含 50,000 個額外的評論,沒有標籤。 當我們在第 1 部分中構建詞袋模型時,額外的未標記的訓練評論沒有用。 但是,由於 Word2Vec 可以從未標記的資料中學習,現在可以使用這些額外的 50,000 條評論。
import pandas as pd
# 從檔案讀取資料
train = pd.read_csv( "labeledTrainData.tsv", header=0,
delimiter="\t", quoting=3 )
test = pd.read_csv( "testData.tsv", header=0, delimiter="\t", quoting=3 )
unlabeled_train = pd.read_csv( "unlabeledTrainData.tsv", header=0,
delimiter="\t", quoting=3 )
# 驗證已讀取的評論數量(總共 100,000 個)
print "Read %d labeled train reviews, %d labeled test reviews, " \
"and %d unlabeled reviews\n" % (train["review"].size,
test["review"].size, unlabeled_train["review"].size )
我們為清理資料而編寫的函式也與第 1 部分類似,儘管現在存在一些差異。 首先,為了訓練 Word2Vec,最好不要刪除停止詞,因為演算法依賴於句子的更廣泛的上下文,以便產生高質量的詞向量。 因此,我們將在下面的函式中,將停止詞刪除變成可選的。 最好不要刪除數字,但我們將其留作讀者的練習。
# Import various modules for string cleaning
from bs4 import BeautifulSoup
import re
from nltk.corpus import stopwords
def review_to_wordlist( review, remove_stopwords=False ):
# 將文件轉換為單詞序列的函式,可選地刪除停止詞。 返回單詞列表。
#
# 1. 移除 HTML
review_text = BeautifulSoup(review).get_text()
#
# 2. 移除非字母
review_text = re.sub("[^a-zA-Z]"," ", review_text)
#
# 3. 將單詞轉換為小寫並將其拆分
words = review_text.lower().split()
#
# 4. 可選地刪除停止詞(預設為 false)
if remove_stopwords:
stops = set(stopwords.words("english"))
words = [w for w in words if not w in stops]
#
# 5. 返回單詞列表
return(words)
接下來,我們需要一種特定的輸入格式。 Word2Vec 需要單個句子,每個句子都是一列單詞。 換句話說,輸入格式是列表的列表。
如何將一個段落分成句子並不簡單。 自然語言中有各種各樣的問題。 英語句子可能以“?”,“!”,“"”或“.”等結尾,並且間距和大寫也不是可靠的標誌。因此,我們將使用 NLTK 的punkt
分詞器進行句子分割。為了使用它,你需要安裝 NLTK 並使用nltk.download()
下載punkt
的相關訓練檔案。
# 為句子拆分下載 punkt 分詞器
import nltk.data
nltk.download()
# 載入 punkt 分詞器
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
# 定義一個函式將評論拆分為已解析的句子
def review_to_sentences( review, tokenizer, remove_stopwords=False ):
# 將評論拆分為已解析句子的函式。
# 返回句子列表,其中每個句子都是單詞列表
# 1. 使用 NLTK 分詞器將段落拆分為句子
raw_sentences = tokenizer.tokenize(review.strip())
#
# 2. 遍歷每個句子
sentences = []
for raw_sentence in raw_sentences:
# 如果句子為空,則跳過
if len(raw_sentence) > 0:
# 否則,呼叫 review_to_wordlist 來獲取單詞列表
sentences.append( review_to_wordlist( raw_sentence, \
remove_stopwords ))
# 返回句子列表(每個句子都是單詞列表,
# 因此返回列表的列表)
return sentences
現在我們可以應用此函式,來準備 Word2Vec 的輸入資料(這將需要幾分鐘):
sentences = [] # 初始化空的句子列表
print "Parsing sentences from training set"
for review in train["review"]:
sentences += review_to_sentences(review, tokenizer)
print "Parsing sentences from unlabeled set"
for review in unlabeled_train["review"]:
sentences += review_to_sentences(review, tokenizer)
你可能會從BeautifulSoup
那裡得到一些關於句子中 URL 的警告。 這些都不用擔心(儘管你可能需要考慮在清理文字時刪除 URL)。
我們可以看一下輸出,看看它與第 1 部分的不同之處:
>>> # 檢查我們總共有多少句子 - 應該是 850,000+ 左右
... print len(sentences)
857234
>>> print sentences[0]
[u'with', u'all', u'this', u'stuff', u'going', u'down', u'at', u'the', u'moment', u'with', u'mj', u'i', u've', u'started', u'listening', u'to', u'his', u'music', u'watching', u'the', u'odd', u'documentary', u'here', u'and', u'there', u'watched', u'the', u'wiz', u'and', u'watched', u'moonwalker', u'again']
>>> print sentences[1]
[u'maybe', u'i', u'just', u'want', u'to', u'get', u'a', u'certain', u'insight', u'into', u'this', u'guy', u'who', u'i', u'thought', u'was', u'really', u'cool', u'in', u'the', u'eighties', u'just', u'to', u'maybe', u'make', u'up', u'my', u'mind', u'whether', u'he', u'is', u'guilty', u'or', u'innocent']
需要注意的一個小細節是 Python 列表中+=
和append
之間的區別。 在許多應用中,這兩者是可以互換的,但在這裡它們不是。 如果要將列表列表附加到另一個列表列表,append
僅僅附加外層列表; 你需要使用+=
才能連線所有內層列表。
譯者注:原文中這裡的解釋有誤,已修改。
訓練並儲存你的模型
使用精心解析的句子列表,我們已準備好訓練模型。 有許多引數選項會影響執行時間和生成的最終模型的質量。 以下演算法的詳細資訊,請參閱 word2vec API 文件以及 Google 文件。
- 架構:架構選項是 skip-gram(預設)或 CBOW。 我們發現 skip-gram 非常慢,但產生了更好的結果。
- 訓練演算法:分層 softmax(預設)或負取樣。 對我們來說,預設效果很好。
- 對頻繁詞彙進行下采樣:Google 文件建議值介於
.00001
和.001
之間。 對我們來說,接近0.001的值似乎可以提高最終模型的準確性。 - 單詞向量維度:更多特徵會產生更長的執行時間,並且通常(但並非總是)會產生更好的模型。 合理的值可能介於幾十到幾百;我們用了 300。
- 上下文/視窗大小:訓練演算法應考慮多少個上下文單詞? 10 似乎適用於分層 softmax(越多越好,達到一定程度)。
- 工作執行緒:要執行的並行程序數。 這是特定於計算機的,但 4 到 6 之間應該適用於大多數系統。
- 最小詞數:這有助於將詞彙量的大小限制為有意義的單詞。 在所有文件中,至少沒有出現這個次數的任何單詞都將被忽略。 合理的值可以在 10 到 100 之間。在這種情況下,由於每個電影出現 30 次,我們將最小字數設定為 40,來避免過分重視單個電影標題。 這導致了整體詞彙量大約為 15,000 個單詞。 較高的值也有助於限制執行時間。
選擇引數並不容易,但是一旦我們選擇了引數,建立 Word2Vec 模型就很簡單:
# 匯入內建日誌記錄模組並配置它,以便 Word2Vec 建立良好的輸出訊息
import logging
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s',\
level=logging.INFO)
# 設定各種引數的值
num_features = 300 # 詞向量維度
min_word_count = 40 # 最小單詞數
num_workers = 4 # 並行執行的執行緒數
context = 10 # 上下文視窗大小
downsampling = 1e-3 # 為頻繁詞設定下采樣
# 初始化並訓練模型(這需要一些時間)
from gensim.models import word2vec
print "Training model..."
model = word2vec.Word2Vec(sentences, workers=num_workers, \
size=num_features, min_count = min_word_count, \
window = context, sample = downsampling)
# 如果你不打算再進一步訓練模型,
# 則呼叫 init_sims 將使模型更具記憶體效率。
model.init_sims(replace=True)
# 建立有意義的模型名稱並儲存模型以供以後使用會很有幫助。
# 你可以稍後使用 Word2Vec.load() 載入它
model_name = "300features_40minwords_10context"
model.save(model_name)
在雙核 Macbook Pro 上,使用 4 個工作執行緒來執行,花費不到 15 分鐘。 但是,它會因你的計算機而異。 幸運的是,日誌記錄功能可以列印帶有資訊的訊息。
如果你使用的是 Mac 或 Linux 系統,則可以使用終端內(而不是來自 Python 內部)的top
命令,來檢視你的系統是否在模型訓練時成功並行化。 鍵入:
> top -o cpu
在模型訓練時進入終端視窗。 對於 4 個 worker,列表中的第一個程序應該是 Python,它應該顯示 300-400% 的 CPU 使用率。
如果你的 CPU 使用率較低,則可能是你的計算機上的 cython 無法正常執行。
探索模型結果
恭喜你到目前為止成功通過了一切! 讓我們來看看我們在 75,000 個訓練評論中建立的模型。
doesnt_match
函式將嘗試推斷集合中哪個單詞與其他單詞最不相似:
>>> model.doesnt_match("man woman child kitchen".split())
'kitchen'
我們的模型能夠區分意義上的差異! 它知道男人,女人和孩子彼此更相似,而不是廚房。 更多的探索表明,該模型對意義上更微妙的差異敏感,例如國家和城市之間的差異:
>>> model.doesnt_match("france england germany berlin".split())
'berlin'
…雖然我們使用的訓練集相對較小,但肯定不完美:
>>> model.doesnt_match("paris berlin london austria".split())
'paris'
我們還可以使用most_similar
函式來深入瞭解模型的單詞簇:
>>> model.most_similar("man")
[(u'woman', 0.6056041121482849), (u'guy', 0.4935004413127899), (u'boy', 0.48933547735214233), (u'men', 0.4632953703403473), (u'person', 0.45742249488830566), (u'lady', 0.4487500488758087), (u'himself', 0.4288588762283325), (u'girl', 0.4166809320449829), (u'his', 0.3853422999382019), (u'he', 0.38293731212615967)]
>>> model.most_similar("queen")
[(u'princess', 0.519856333732605), (u'latifah', 0.47644317150115967), (u'prince', 0.45914226770401), (u'king', 0.4466976821422577), (u'elizabeth', 0.4134873151779175), (u'antoinette', 0.41033703088760376), (u'marie', 0.4061327874660492), (u'stepmother', 0.4040161967277527), (u'belle', 0.38827288150787354), (u'lovely', 0.38668593764305115)]
鑑於我們特定的訓練集,“Latifah”與“女王”的相似性最高,也就不足為奇了。
或者,與情感分析更相關:
>>> model.most_similar("awful")
[(u'terrible', 0.6812670230865479), (u'horrible', 0.62867271900177), (u'dreadful', 0.5879652500152588), (u'laughable', 0.5469599962234497), (u'horrendous', 0.5167273283004761), (u'atrocious', 0.5115568041801453), (u'ridiculous', 0.5104714632034302), (u'abysmal', 0.5015234351158142), (u'pathetic', 0.4880446791648865), (u'embarrassing', 0.48272213339805603)]
因此,似乎我們有相當好的語義意義模型 - 至少和詞袋一樣好。 但是,我們如何才能將這些花哨的分散式單詞向量用於監督學習呢? 下一節將對此進行一次嘗試。