1. 程式人生 > >Kaggle word2vec NLP 教程 第二部分:詞向量

Kaggle word2vec NLP 教程 第二部分:詞向量

第二部分:詞向量

程式碼

第二部分的教程程式碼在這裡

分散式詞向量簡介

本教程的這一部分將重點介紹使用 Word2Vec 演算法建立分散式單詞向量。 (深度學習的概述,以及其他一些教程的連結,請參閱“什麼是深度學習?”頁面)。

第 2 部分和第 3 部分比第 1 部分假設你更熟悉Python。我們在雙核 Macbook Pro 上開發了以下程式碼,但是,我們還沒有在 Windows 上成功執行程式碼。如果你是 Windows 使用者並且使其正常執行,請在論壇中留言如何進行操作!更多詳細資訊,請參閱“配置系統”頁面。

Word2vec,由 Google 於 2013 年發表,是一種神經網路實現,可以學習單詞的

分散式表示。在此之前已經提出了用於學習單詞表示的其他深度或迴圈神經網路架構,但是這些的主要問題是訓練模型所需時長間。 Word2vec 相對於其他模型學習得快。

Word2Vec 不需要標籤來建立有意義的表示。這很有用,因為現實世界中的大多數資料都是未標記的。如果給網路足夠的訓練資料(數百億個單詞),它會產生特徵極好的單詞向量。具有相似含義的詞出現在簇中,並且簇具有間隔,使得可以使用向量數學來再現諸如類比的一些詞關係。著名的例子是,通過訓練好的單詞向量,“國王 - 男人 + 女人 = 女王”。

檢視 Google 的程式碼,文章和附帶的論文此演示也很有幫助。 原始程式碼是 C 寫的,但它已被移植到其他語言,包括 Python。 我們鼓勵你使用原始 C 工具,但如果你是初學程式設計師(我們必須手動編輯標頭檔案來編譯),請注意它不是使用者友好的。

最近斯坦福大學的工作也將深度學習應用於情感分析;他們的程式碼以 Java 提供。 但是,他們的方法依賴於句子解析,不能直接應用於任意長度的段落。

分散式詞向量強大,可用於許多應用,尤其是單詞預測和轉換。 在這裡,我們將嘗試將它們應用於情感分析。

在 Python 中使用 word2vec

在 Python 中,我們將使用gensim包中的 word2vec 的優秀實現。 如果你還沒有安裝gensim,則需要安裝它。 這裡有一個包含 Python Word2Vec 實現的優秀教程。

雖然 Word2Vec 不像許多深度學習演算法那樣需要圖形處理單元(GPU),但它是計算密集型的。 Google 的版本和 Python 版本都依賴於多執行緒(在你的計算機上並行執行多個程序以節省時間)。 為了在合理的時間內訓練你的模型,你需要安裝 cython(

這裡是指南)。 Word2Vec 可在沒有安裝 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)]

因此,似乎我們有相當好的語義意義模型 - 至少和詞袋一樣好。 但是,我們如何才能將這些花哨的分散式單詞向量用於監督學習呢? 下一節將對此進行一次嘗試。