建立反向詞典,為你開啟神經網路的大門

全文共

8395

字,預計學習時長

17

分鐘

建立反向詞典,為你開啟神經網路的大門

本文將介紹怎樣用Word2Vec建立反向詞典。除了Word2Vec之外,使用其他文字嵌入模型也會得到同樣的結果。如果你不知道這意味著什麼也不要緊,我們會解釋清楚的。反向詞典就是一種詞典,需要先輸入定義,然後找出與該定義相匹配的單詞。

所需程式碼可以在companion repository中找到:https://github。com/unosviluppatore/reverse-dictionary

建立反向詞典,為你開啟神經網路的大門

自然語言處理的應用

自然語言處理是一個很棒的領域。這個領域很有趣,客戶也經常在應用程式中使用。因為自然語言處理是個很難進入的領域。你永遠不知道,一個問題是能夠在一天之內透過一個現成的庫得以解決?還是需要一個研發團隊研究兩年才能獲得不錯的結果。簡單點說:自然語言處理困難的地方不在於技術,而在於理解怎樣成功運用。

基於機器學習解決上述問題時就更突出了,你需要了解一點機器學習的背景。有時即使解決方案行得通,仍需幾個星期來調整引數和權重。

所以,本文中只列舉一種技術,也就是一個簡單的程式:反向詞典。也就是說,怎麼透過定義找到單詞。這是一款簡潔的程式,是買不到的,也無法用確定性演算法來建立。

建立反向詞典,為你開啟神經網路的大門

機器學習中的單詞表徵

機器學習的第一步是理解如何表達所處理的資料。最基本的方式就是使用one-hot編碼,有以下幾種方式:

· 收集所有的可能值(例如,10000個可能值)

· 每個可能值都用一個向量表示,這個向量包含儘可能多的分量(每個值由一個包含10000個元件的向量表示)

· 為了表達每個值,除了給一個元件賦值為1外,其他元件都賦值為0(即,每個元件都是0,除了一個元件是1)

將其應用於單詞,就意味著:

· 需要10000個單詞量

· 每個單詞都用一個向量表示,這個向量包含10000個元件

· 單詞 dog 表達為[1, 0, 0 …],單詞 cat 表達為 [0, 1, 0 …],等等

這種方式可以表達所有單詞,但還存在兩大缺點:

· 每個向量都非常稀疏;大多數向量都是0

· 表達不具備任何語義;“father”和“mother”意思非常接近,但你永遠不會看到這兩個單詞用one-hot編碼

解決之道:單詞嵌入

為了克服上面的兩大缺點,單詞嵌入應運而生。這種型別的單詞表徵的關鍵在於,具有相似含義的單詞使用相似的表達。為了捕捉單詞意思,或者與其他單詞相關聯,該表達允許有密集的向量。意思很簡單,嵌入的單詞並沒有真正捕捉到father的意思,但是它的表達將與mother的表達類似。

這是一個強大的特點,可以應用於各種情況。例如,可以解決下面這個問題:

什麼對於父親相當於母親對於女兒?

第一個單詞嵌入模型是 Word2Vec ,我們將用它來建立反向詞典。這一模型對該領域進行了革新,並催生了許多其他模型,例如FastText、GloVe這些模型之間有著細微差別——例如GloVe和Word2Vec是在單詞層面上訓練,而FastText則是在character n-grams上訓練的。但是它們的原理、應用以及結果都非常相似。

建立反向詞典,為你開啟神經網路的大門

Word2Vec的工作原理

Word2Vec的有效性得益於下面這個技巧:針對特定任務訓練神經網路,然後將這個神經網路用於其他用途。這個技巧並不是Word2Vec所獨有的,這是機器學習中最常用的一個技巧。基本上只需訓練神經網路來得到一個特定的輸出,然後去掉輸出層,只保留神經網路隱藏層的權重。

訓練過程和往常一樣:給神經網路一個輸入值以及一個與輸入值相對應的輸出。這樣,神經網路可以慢慢學習如何生成正確的輸出。

這個訓練任務是為了計算在給定輸入單詞的情況下,在上下文中出現某個單詞的機率。例如,如果有programmer這個單詞,那麼在上下文的短語中看到computer這個單詞的機率是多少?

Word2Vec訓練策略

Word2Vec有兩種典型的策略:CBOW和skip-gram——這兩個策略是可逆的。用CBOW輸入單詞的上下文,在輸出中得到想要的單詞。用skip-gram則相反:先輸入單詞,然後預測它所適用的上下文。

在最基本的層面上,“context (上下文)”這個術語僅僅是指目標單詞周圍的詞,比如目標單詞前後的詞。但是,“上下文”也可用於表達句法意義(例如,指主語,如果目標詞是動詞)。這裡只採用“上下文”最簡單的含義。

例如這句話:

the gentle giant graciously spoke

在CBOW中, 需要輸入[gentle, graciously]來得到輸出giant。在skip-gram中,我們將輸入“輸入giant”,同時輸入“輸出[gentle, graciously]”。訓練是用單個單詞完成的,所以,在實踐中,對CBOW來說:

· 第一次,輸入gentle,期待結果輸出giant。

· 第二次,輸入 graciously ,期待結果輸出 giant。

如果用skip-gram,就要把輸入和輸出顛倒過來。

建立反向詞典,為你開啟神經網路的大門

神經網路會帶來什麼

就像前面所說,訓練的最後會刪除輸出層,因為我們並不真正關心一個單詞出現在目標單詞附近的機率。例如,當輸入單詞flag時,我們並不真正關心 USA會有多大的機率出現在上下文中。然而,我們將保留神經網路隱藏層的權重,並用它來表達單詞。這有什麼用呢?

這是網路結構的特點所致。輸入是單詞的one-hot編碼。例如,dog使用[1,0,0 …]來表達。在訓練期間,輸出也是單詞的one-hot編碼(例如,對於the dog barks,使用CBOW應輸入dog,輸出barks)。最終,輸出層將會有一系列可能性。例如,給定輸入單詞cat,輸出層將有可能在cat的上下文中出現單詞dog。pet這個單詞也會有出現的可能性,依此類推。

建立反向詞典,為你開啟神經網路的大門

Word2Vec的神經網路

每個神經元對每個單詞都有權重,所以在訓練結束時,將有無數個神經元,每個神經元對每個單詞都有權重。此外,要記住,表達單詞的輸入向量除了一個分量是1外,其他都是零。

因此,在矩陣乘法的數學規則中,如果用神經元矩陣乘以一個輸入向量,輸入向量為0,則神經元中的大多數權重無效,每個神經元中,將剩下一個與輸入單詞相關聯的權重。每個神經元中的一系列非零權重將是Word2Vec中表達單詞的權重。

上下文相似的單詞會得到相似的輸出,所以在特定單詞的上下文中出現的機率也相似。換句話說,dog和cat會得到相似的輸出。因此,它們也將具有相似的權重。這就是為什麼意義相近的單詞在Word2Vec中用相近的向量來表示。

Word2Vec的判斷相當簡單:相似的單詞會出現在相似的短語中。然而,這一點也相當有效。當然,前提是有足夠大的資料集用於訓練。稍後會處理這個問題。

建立反向詞典,為你開啟神經網路的大門

Word2Vec的意義

現在知道了Word2Vec的工作原理,下面來看看怎麼用它建立反向詞典。反向詞典是透過輸入定義來查詢單詞的詞典。所以,理想情況下,輸入group of relative(一群親戚),程式會給出family(家族)。

從顯而易見的開始:同義詞詞典使用的是單詞嵌入法。如前所述,在這個系統中,相似的詞有相似的表達。因此,如果想要系統找到與輸入單詞相近的單詞,它會找到一個意思相似的單詞。例如,如果輸入happiness,可能會輸出joy。

由此,你也許認為,做與上面相反的事情也行得通,比如找到輸入單詞的反義詞。很不幸,這是不可能直接實現的,因為表達單詞的向量不能理解單詞之間那麼精確的關係。基本上,sad這個單詞的向量與 happy這個詞的向量並不是映象相反。

為什麼可以用Word2Vec

請看下面單詞向量的簡化表示。

建立反向詞典,為你開啟神經網路的大門

單詞關係的圖形表達

系統可以找到由?表達的單詞,僅僅因為它可以從father的向量中新增兩個給定單詞向量(mother 和 daughter)之間的差異。該系統確實能夠捕捉到單詞之間的關係,但是這種捕捉方式並不容易理解。換句話說:向量的位置是有意義的,但這也意味著這些位置的定義不是絕對的(例如,相反的),而是相對的(例如,一個看起來像是A-B的單詞)。

這也解釋了為什麼無法直接找到反義詞:在Word2Vec中沒有可以用來描述這種關係的數學運算。

反向詞典的工作原理

現在既然完全理解了單詞向量的作用,那就可以來理解怎麼用它們來建立一個反向詞典了。反向詞典基本可以用來查詢到一個與輸入內容(也就是定義)相似的單詞。這是可以實現的,因為系統使用的是向量數學。

例如,如果輸入 group of relatives,它就會找到 family。也可以在定義中使用否定的詞來幫助識別一個詞。例如,親屬團體決心組織起來。稍後將看到否定一個詞的確切含義。也可以在定義中使用否定詞來幫助識別單詞。例如,將group of -relatives解析為organization。稍後將會解釋否定一個單詞有什麼意義。

建立反向詞典,為你開啟神經網路的大門

Word2Vec模型的資料

既然理論都清楚了,下面可以看看程式碼,然後建立反向詞典。

第一步是建立字典。建立字典本身並不難,但是要花費很長時間。更重要的是,內容越多越好。對於普通使用者來說,下載和儲存大量資料並不輕鬆。僅英文維基百科的轉儲,提取時就可能需要超過50 GB(只包含文字)。通用爬網(可自由訪問的已爬網頁面)的資料可能會佔用數千兆位元組的儲存空間。

出於實用考慮,最好使用谷歌共享的資料,例如谷歌新聞資料預先訓練好的模型:GoogleNews-vectors-negative300。bin。gz。如果對這個檔案進行搜尋,很多地方都找得到。以前,谷歌程式碼專案是該檔案的官方來源,但現在最好的來源似乎是谷歌硬碟。該檔案是1。6GB的壓縮版,不需要解壓。

資料下載完成後,可以放在專案下的 models目錄中。有許多針對Word2Vec的庫,所以可以使用多種語言,但鑑於Python在機器學習中的流行度,本文會選擇Python作為示例。本文將使用gensim庫的Word2Vec,因為它是最優的。至於網路介面,則會使用Flask。

載入資料

首先,需要用3行簡單的程式碼將Word2Vec載入至記憶體。

model = KeyedVectors。load_word2vec_format(“。/models/GoogleNews-vectors-negative300。bin。gz”, binary=True)

model。init_sims(replace=True) #

model。syn0norm = model。syn0 # prevent recalc of normed vectors

第一行是唯一真正需要載入資料的行。另外兩行只是在開始時做一些初步的計算,所以後面就不需要為每個請求做這些計算了。

但是,這3行程式碼在普通計算機上執行可能需要2-3分鐘(用SSD)。一開始只需一些等待,所以這也不算太糟糕,但也不是很理想。另還有一種方法,就是可以一勞永逸地進行一些計算和最佳化,之後把它們儲存起來,以後每次開始時,先從磁碟中載入。

新增這個函式來建立最佳化版的資料。

def generate_optimized_version():

model = KeyedVectors。load_word2vec_format(“。/models/GoogleNews-vectors-negative300。bin。gz”, binary=True)

model。init_sims(replace=True)

model。save(‘。/models/GoogleNews-vectors-gensim-normed。bin’)

主函式每次都使用這段程式碼來載入Word2Vec的資料。

optimized_file = Path(‘。/models/GoogleNews-vectors-gensim-normed。bin’)

if optimized_file。is_file():

model = KeyedVectors。load(“。/models/GoogleNews-vectors-gensim-normed。bin”,mmap=‘r’)

else:

generate_optimized_version()

# keep everything ready

model。syn0norm = model。syn0 # prevent recalc of normed vectors

這將載入時間從幾分鐘縮短到幾秒鐘。

清理詞典

還有一件事要做:清理資料。一方面,谷歌新聞模型很棒,它是由一個非常大的資料集生成的,所以非常準確。這要比自己建立模型好的多。然而,由於它是基於新聞來建立的,所以包含許多拼寫錯誤,更重要的是,我們並不需要這個實體模型。

換句話說,它不僅僅包含單詞,還包含新聞中提到的建築物和機構名稱等內容。這可能會阻礙反向詞典的建立。例如,如果輸入一個類似a tragic event的定義,系統可能會發現,與這組輸入單詞最相似的是發生過悲劇事件的地方。然而,這並不是我們想要的。

因此,必須對模型輸出的所有項進行過濾,以確保輸出的是字典中可以找到的常用單詞。這類單詞列表來自SCOWL(Spell Checker Oriented Word Lists:http://wordlist。aspell。net/)。使用這個連結中的工具建立了一個自定義詞典,並將它放在了 models 資料夾中。

# read dictionary words

dict_words = []

f = open(“。/models/words。txt”, “r”)

for line in f:

dict_words。append(line。strip())

f。close()

# remove copyright notice

dict_words = dict_words[44:]

現在可以很容易地載入單詞列表,來比較Word2Vec系統過濾掉的條目。

建立反向詞典,為你開啟神經網路的大門

反向詞典

反向詞典的功能程式碼非常簡單。

def find_words(definition, negative_definition):

positive_words = determine_words(definition)

negative_words = determine_words(negative_definition)

similar_words = [i[0] for i in model。most_similar(positive=positive_words, negative=negative_words, topn=30)]

words = []

for word in similar_words:

if (word in dict_words):

words。append(word)

if (len(words) > 20):

words = words[0:20]

return words

從使用者的輸入中可以收到對所查詢單詞的肯定性描述,也可以接收到否定詞,必須從其他詞中減去這些詞的向量。

這一操作的用意更難理解:似乎並沒有將這個詞的反義詞包含進來。相反,還想去掉否定詞。例如,假設定義是group of -relatives,relatives是否定詞, group 和of是肯定詞。想要找到一個與輸入內容中所標識的單詞意思最接近的詞,但是去除了所有 relatives特別新增的意義。

第5行中有與肯定的定義意思最相似的前30個單詞。該函式會返回單詞和相關分數,我們只對單詞本身感興趣,所以可以忽略分數。

其餘的程式碼很容易理解。透過比較之前載入的詞典單詞資料庫中的單詞,從之前返回的單詞中,選擇出那些真正的單詞,而不是地點或事件。將單詞列表減少到最多20個單詞。

清除輸入的單詞

這一步透過find_words實現。第2行和第3行中有一個determine words函式。這個函式從輸入的字串中生成一個單詞列表。如果一個單詞前面有負號,則是一個否定單詞。

def determine_words(definition):

possible_words = definition。split()

for i in range(len(possible_words) - 1, -1, -1):

if possible_words[i] not in model。vocab:

del possible_words[i]

possible_expressions = []

for w in [possible_words[i:i+3] for i in range(len(possible_words)-3+1)]:

possible_expressions。append(‘_’。join(w))

ex_to_remove = []

for i in range(len(possible_expressions)):

if possible_expressions[i] in model。vocab:

ex_to_remove。append(i)

words_to_remove = []

for i in ex_to_remove:

words_to_remove += [i, i+1, i+2]

words_to_remove = sorted(set(words_to_remove))

words = [possible_expressions[i] for i in ex_to_remove]

for i in range(len(possible_words)):

if i not in words_to_remove:

words。append(possible_words[i])

return words

首先,這個函式會生成一個單詞列表,還會新增表示式。這是因為谷歌新聞模型除了簡單的單詞外,還有短語的向量表達。我們試圖透過簡單地組合3-gram單詞(即3個單詞的滑動集合)來找到表示式。如果在谷歌新聞模型(第14行)中發現了表示式,則必須將該表示式作為一個整體新增,並刪除單個單詞。

建立反向詞典,為你開啟神經網路的大門

網路App

為了便於使用,建立了一個簡單的Flask應用程式:這是一個基本的網路介面,可以讓使用者輸入定義並閱讀單詞列表。

def create_app():

app = Flask(__name__)

return app

app = create_app()

@app。route(‘/’, methods=[‘GET’, ‘POST’])

def index():

if request。method == ‘POST’:

words = request。form[‘definition’]。split()

negative_words = ‘’

positive_words = ‘’

for i in range(len(words)):

if(words[i][0] == ‘-’):

negative_words += words[i][1:] + ‘ ’

else:

positive_words += words[i] + ‘ ’

return jsonify(find_words(positive_words, negative_words))

else:

return render_template(‘index。html’)

該應用程式支援根目錄路徑。它會顯示帶有定義的頁面,並在收到定義時,輸出相應的單詞列表。多虧了Flask,用幾行程式碼就能搞定這些。

建立反向詞典,為你開啟神經網路的大門

正在執行的反向詞典

反向詞典執行得怎麼樣呢?它對於描述性定義非常有效,也就是說,如果使用的是對目標詞進行描述的定義,就很可能會在應用程式返回的單詞列表中找到正確的單詞。它可能不是列表中的第一個單詞,但肯定能在列表中找得到。但是對於邏輯性定義來說,它並不那麼有效,例如female spouse。因此,這個詞典當然不夠完美,但對於簡單的查詢來說,它還是非常有效的。

建立反向詞典,為你開啟神經網路的大門

總結

這篇文章用幾行程式碼建立了一個有效的反向詞典,這要歸功於Word2Vec和現成的資料庫。這對於一個小教程來說,是一個很好的結果。但是在機器學習方面,應該牢記:應用程式的成功不僅僅取決於程式碼。對於許多機器學習的應用程式來說,成功在很大程度上取決於擁有的資料,無論是使用的訓練資料,還是向程式提供資料的方法,都很重要。

例如,對於資料集的選擇,忽略了一些試驗和錯誤。因為我們知道谷歌新聞資料集包含新聞事件,所以已經預料到了會存在一些問題。所以嘗試基於英文維基百科建立自己的資料集。然而非常悲慘地失敗了,得到的結果比使用谷歌新聞資料集得到的更糟糕。一部分原因在於資料集較小,但真正的原因在於我們的能力不如谷歌的工程師。選擇合適的訓練引數需要一種“黑魔法”,只有積累大量的經驗才能掌握。最後,對我們來說,最好的解決方案就是使用谷歌新聞資料集,並過濾輸出,以消除應用程式不需要的結果。雖然這一點讓人感到羞愧,但還是值得牢記於心的。

建立反向詞典,為你開啟神經網路的大門

留言 點贊 關注

留言 點贊 關注

編譯組:殷睿宣、楊月

相關連結:

https://dzone。com/articles/reverse-dictionary-neural-network

如需轉載,請後臺留言,遵守轉載規範