Sense2vec with spaCy and Gensim

若是你在2015年作過文本分析項目,那麼你大機率用的是word2vec模型。Sense2vec是基於word2vec的一個新模型,你能夠利用它來獲取更詳細的、與上下文相關的詞向量。本文主要介紹該模型的思想以及一些簡單的實現。git

多義性:word2vec遇到的問題

當人們編寫字典和辭典時,咱們會列出每一個詞語的不一樣含義。在天然語言處理過程當中,利用文檔的統計信息來定義詞典的概念每每很是有效,其中word2vec系列模型是最多見的用於建立詞典的模型。給定一個大規模的文本數據,word2vec模型將建立一個用於儲存詞語含義的詞典,其中每行的數值表明一個詞語的內在含義。此時要計算詞典中兩個單詞之間的類似度,等價於計算這兩行數據之間的類似性。github

word2vec模型的問題在於詞語的多義性。好比duck這個單詞常見的含義有水禽或者下蹲,但對於 word2vec 模型來講,它傾向於將全部概念作歸一化平滑處理,獲得一個最終的表現形式。Nalisnick & Ravi注意到這個問題,他們認爲模型應該考慮到詞向量的多義性,這樣咱們能夠更好地構建那些複雜的詞向量。咱們想要實現的功能是將不一樣含義的詞語賦值成不一樣的詞向量,同時咱們也想知道給定上下文狀況時,某個詞語對應的具體含義。所以,咱們須要分析上下文的內容,這正好是spaCy的用武之地。多線程

Sense2vec: 利用 NLP 方法來構建更精確的詞向量

sense2vec 模型的思想很是簡單,若是要處理duck的多義性問題,咱們只須要將兩個不一樣含義的詞語賦值成不一樣的詞向量便可,即 duckNduckN 和 duckVduckV。咱們一直在嘗試實現這個模型,因此當Trask et al公佈了其良好的模型試驗結果後,咱們很容易地認爲這個想法是可行的。app

咱們跟隨 Trask 等人的思路,並將部分的語音標籤和名字標籤歸入詞向量中。此外,咱們還合併了基本的名詞短語和命名實體,從而獲取了單一的詞向量。雖然當前的模型只是個簡單的草案,可是咱們很是高興能夠獲得這樣的結果。沿着該模型的思路咱們還能夠作不少事情,好比處理多詞問題或者單詞拆解問題。ide

下述代碼是數據預處理函數,考慮到篇幅問題,我將剩餘部分的代碼託管在Github函數

def transform_texts(texts):
    # Load the annotation models
    nlp = English() 
    # Stream texts through the models. We accumulate a buffer and release
    # the GIL around the parser, for efficient multi-threading.
    for doc in nlp.pipe(texts, n_threads=4):
        # Iterate over base NPs, e.g. "all their good ideas"
        for np in doc.noun_chunks:
            # Only keep adjectives and nouns, e.g. "good ideas"
            while len(np) > 1 and np[0].dep_ not in ('amod', 'compound'):
                np = np[1:]
            if len(np) > 1:
                # Merge the tokens, e.g. good_ideas
                np.merge(np.root.tag_, np.text, np.root.ent_type_)
            # Iterate over named entities
            for ent in doc.ents:
                if len(ent) > 1:
                    # Merge them into single tokens
                    ent.merge(ent.root.tag_, ent.text, ent.label_)
        token_strings = []
        for token in tokens:
            text = token.text.replace(' ', '_')
            tag = token.ent_type_ or token.pos_
            token_strings.append('%s|%s' % (text, tag))
        yield ' '.join(token_strings)

雖然須要這些預處理過程,可是咱們仍然能夠利用該模型進行大規模的建模分析。由於 spaCy 使用 Cython 寫的,它容許多線程操做,在四線程環境中該模型每秒能夠處理 100,000 個單詞。網站

數據預處理以後,咱們能夠利用常規的方法來訓練詞向量,好比原始的 C 語言代碼、Gensim或者 GloVe。只要數據集中單詞由空格分隔,且句子由換行符分隔開就沒有問題。惟一須要注意的地方是該模型不該該試圖利用其自身的符號,不然可能會錯誤地拆分標籤信息。ui

咱們利用 Gensim 中的基於負抽樣方法的 Skip-Gram 模型來訓練詞向量,其中頻數閥值爲10 或 5。模型訓練後咱們將頻數閥值設爲50,從而減小模型的運算時間。idea

案例

當咱們利用這些詞向量來分析問題時,咱們發現了許多有趣的事情,如下是一些簡單的說明:spa

語義合成性

該模型訓練出來的詞向量能夠很好地提取合成詞的語義信息,好比該模型知道 fair game 不是一個遊戲類型,而 multiplayer game 是一種遊戲類型。

>>> model.similarity('fair_game|NOUN', 'game|NOUN')
0.034977455677555599
>>> model.similarity('multiplayer_game|NOUN', 'game|NOUN')
0.54464530644393849

一樣地,該模型知道 class action 和 action 之間的類似度很低,而 class action lawsuit 和 lawsuit 之間有很高的類似度:

>>> model.similarity('class_action|NOUN', 'action|NOUN')
0.14957825452335169
>>> model.similarity('class_action_lawsuit|NOUN', 'lawsuit|NOUN')
0.69595765453644187

詞語之間的類似性

如下是 Reddit 網上關於川普的詞向量信息:

>>> model.most_similar(['Donald_Trump|PERSON'])
(u'Sarah_Palin|PERSON', 0.854670465),
(u'Mitt_Romney|PERSON', 0.8245523572),
(u'Barrack_Obama|PERSON', 0.808201313),
(u'Bill_Clinton|PERSON', 0.8045649529),
(u'Oprah|GPE', 0.8042222261),
(u'Paris_Hilton|ORG', 0.7962667942),
(u'Oprah_Winfrey|PERSON', 0.7941152453),
(u'Stephen_Colbert|PERSON', 0.7926792502),
(u'Herman_Cain|PERSON', 0.7869615555),
(u'Michael_Moore|PERSON', 0.7835546732)]

該模型返回了與‘川普’之間類似度較高的詞語,從上述結果中能夠看出該模型很好地識別出川普政治家和真人秀明星的身份。我對模型返回的 Michael Moore 很是感興趣,我懷疑不少人都是他兩的粉絲。若是我必須選擇出一個異常值的話,那麼我會選擇 Oprah,該詞條和其餘詞語的類似度較低。

該模型發現 Oprah|GPE 和 Oprah_Winfrey|PERSON 之間的類似度較高,這意味着命名實體識別器還存在必定的問題,具備提高的空間。

word2vec模型能夠很好地識別出命名實體,特別是音樂領域的信息。這讓我想起我曾經獲取推薦音樂的方式:留意常常和我喜歡的樂隊一塊兒被提到的歌手。固然如今咱們已經擁有更強大的推薦模型,好比觀察成千上萬人的行爲進而得出相應的規律。可是對我來講,該模型在分析樂隊類似度時仍存在一些奇怪的問題。

如下是該模型揭示的 Carrot Top 和 Kate Mara 之間潛在的聯繫:

>>> model.most_similar(['Carrot_Top|PERSON'])
[(u'Kate_Mara|PERSON', 0.5347248911857605),
 (u'Andy_Samberg|PERSON', 0.5336876511573792),
 (u'Ryan_Gosling|PERSON', 0.5287898182868958),
 (u'Emma_Stone|PERSON', 0.5243821740150452),
 (u'Charlie_Sheen|PERSON', 0.5209298133850098),
 (u'Joseph_Gordon_Levitt|PERSON', 0.5196050405502319),
 (u'Jonah_Hill|PERSON', 0.5151286125183105),
 (u'Zooey_Deschanel|PERSON', 0.514430582523346),
 (u'Gerard_Butler|PERSON', 0.5115377902984619),
 (u'Ellen_Page|PERSON', 0.5094753503799438)]

我花了好多時間在思考這個問題,可是並無獲得任何有意義的結果。也許這裏面存在更深層次的邏輯關係,咱們須要進一步探究才能獲得結果。可是當咱們往模型中加入更多的數據時,該現象就消失了,就和 Carrot Top 同樣。

食品領域

>>> model.most_similar(['onion_rings|NOUN'])
[(u'hashbrowns|NOUN', 0.8040812611579895),
 (u'hot_dogs|NOUN', 0.7978234887123108),
 (u'chicken_wings|NOUN', 0.793393611907959),
 (u'sandwiches|NOUN', 0.7903584241867065),
 (u'fries|NOUN', 0.7885469198226929),
 (u'tater_tots|NOUN', 0.7821801900863647),
 (u'bagels|NOUN', 0.7788236141204834),
 (u'chicken_nuggets|NOUN', 0.7787706255912781),
 (u'coleslaw|NOUN', 0.7771176099777222),
 (u'nachos|NOUN', 0.7755396366119385)]

Reddit 網站上關於食品的一些評論很是有趣,好比 bacon 和 brocoll 之間的類似度很是高:

>>> model.similarity('bacon|NOUN', 'broccoli|NOUN')
0.83276615202851845

此外,模型的結果顯示熱狗和沙拉之間也很是類似:

>>> model.similarity('hot_dogs|NOUN', 'salad|NOUN')
0.76765100035460465
>>> model.similarity('hot_dogs|NOUN', 'entrails|NOUN')
0.28360725445449464

Using the demo

你能夠經過搜索單詞或短語來探索相關概念。若是你想要更精確的信息,你能夠在查詢語句中加入標籤信息,好比query phrase|NOUN。若是你沒有添加標籤信息,那麼該模型將會返回關聯度最高的單詞。標籤信息主要由包含了上下文信息的統計模型預測所得。

若是你輸入 serve,該模型將從 serve|VERBserve|NOUNserve|ADJ 等標籤信息中搜尋相關單詞。因爲serve|VERB 是最多見的標籤信息,該模型將返回這個結果。可是若是你輸入 serve|NOUN,你將獲得徹底不同的結果,由於 serve|NOUN 和網球之間的關係很是緊密,而動詞形式則表示其餘含義。

咱們採用了基於頻率的方法來區分大小寫的狀況。若是你的查詢命令是小寫單詞且沒有標籤信息,咱們將假設它是不區分大小寫的,同時尋找最多見的標籤和單詞。若是你的查詢命令中包含大寫字母或者標籤信息,咱們將假設你的查詢命令是區分大小寫的。


原文連接:https://spacy.io/blog/sense2vec-with-spacy

原文做者:MATTHEW HONNIBAL

譯者:Fibears

相關文章
相關標籤/搜索