基於騰訊AI Lab詞向量進行未知詞、短語向量補齊與域內相似詞搜尋
(~免費廣告位一則~)
AI Lab開源大規模高質量中文詞向量資料,800萬中文詞隨你用,質量非常高,就是一個詞向量.txt檔案都有16G之多,太誇張了。。不過的確非常有特點:
- ⒈ 覆蓋率(Coverage):
該詞向量資料包含很多現有公開的詞向量資料所欠缺的短語,比如“不念僧面唸佛面”、“冰火兩重天”、“煮酒論英雄”、“皇帝菜”、“喀拉喀什河”等。以“喀拉喀什河”為例,利用騰訊AI Lab詞向量計算出的語義相似詞如下:
墨玉河、和田河、玉龍喀什河、白玉河、喀什河、葉爾羌河、克里雅河、瑪納斯河
- ⒉ 新鮮度(Freshness):
該資料包含一些最近一兩年出現的新詞,如“戀與製作人”、“三生三世十里桃花”、“打call”、“十動然拒”、“供給側改革”、“因吹斯汀”等。以“因吹斯汀”為例,利用騰訊AI Lab詞向量計算出的語義相似詞如下:
一顆賽艇、因吹斯聽、城會玩、厲害了word哥、emmmmm、扎心了老鐵、神吐槽、可以說是非常爆笑了
- ⒊ 準確性(Accuracy):
由於採用了更大規模的訓練資料和更好的訓練演算法,所生成的詞向量能夠更好地表達詞之間的語義關係。
騰訊AI Lab採用自研的Directional Skip-Gram (DSG)演算法作為詞向量的訓練演算法。DSG演算法基於廣泛採用的詞向量訓練演算法Skip-Gram (SG),在文字視窗中詞對共現關係的基礎上,額外考慮了詞對的相對位置,以提高詞向量語義表示的準確性。
文章目錄
1 Tencent_AILab_ChineseEmbedding讀入與高效查詢
來看一下一個比較常見的讀入方式:lvyufeng/keras_text_sum/load_embedding.py
import numpy as np def load_embedding(path): embedding_index = {} f = open(path,encoding='utf8') for index,line in enumerate(f): if index == 0: continue values = line.split(' ') word = values[0] coefs = np.asarray(values[1:],dtype='float32') embedding_index[word] = coefs f.close() return embedding_index load_embedding('/home/lv/data_set/Tencent_AILab_ChineseEmbedding/Tencent_AILab_ChineseEmbedding.txt')
這樣純粹就是以字典的方式讀入,當然用於建模沒有任何問題,但是筆者想在之中進行一些相似性操作,最好的就是重新載入gensim.word2vec系統之中,但是筆者發現載入半天都會報錯:
ValueError: invalid vector on line 418987 (is this really the text format?)
仔細一檢視,發現原來一些詞向量的詞就是數字,譬如-0.2121
或 57851
,所以一直匯入不進去。只能自己用txt讀入後,刪除掉這一部分,儲存的格式參考下面。
5 4
是 -0.119938 0.042054504 -0.02282253 -0.10101332
中國人 0.080497965 0.103521846 -0.13045108 -0.01050107
你 -0.0788643 -0.082788676 -0.14035964 0.09101376
我 -0.14597991 0.035916027 -0.120259814 -0.06904249
第一行是一共5個詞,每個詞維度為4.
然後清洗完畢之後,就可以讀入了:
wv_from_text = gensim.models.KeyedVectors.load_word2vec_format('Tencent_AILab_ChineseEmbedding_refine.txt',binary=False)
但是又是一個問題,佔用記憶體太大,導致不能查詢相似詞,所以這裡可以用一下這個神奇的函式,可以高效執行,這樣就可以順利使用most_similar
這類函數了:
wv_from_text.init_sims(replace=True) # 神奇,很省記憶體,可以運算most_similar
該操作是指model已經不再繼續訓練了,那麼就鎖定起來,讓Model變為只讀的,這樣可以預載相似度矩陣,對於後面得相似查詢非常有利。
2 未知詞、短語向量補齊與域內相似詞搜尋
這邊未知詞語、短語的補齊手法是參考FastText的用法:極簡使用︱Gemsim-FastText 詞向量訓練以及OOV(out-of-word)問題有效解決
這邊筆者借鑑了fasttext之中的方式,當出現未登入詞或短語的時候,會:
- 先將輸入詞進行n-grams
- 然後去詞表之中查詢
- 查詢到的詞向量進行平均
主要函式可見:
import numpy as np
def compute_ngrams(word, min_n, max_n):
#BOW, EOW = ('<', '>') # Used by FastText to attach to all words as prefix and suffix
extended_word = word
ngrams = []
for ngram_length in range(min_n, min(len(extended_word), max_n) + 1):
for i in range(0, len(extended_word) - ngram_length + 1):
ngrams.append(extended_word[i:i + ngram_length])
return list(set(ngrams))
def wordVec(word,wv_from_text,min_n = 1, max_n = 3):
'''
ngrams_single/ngrams_more,主要是為了當出現oov的情況下,最好先不考慮單字詞向量
'''
# 確認詞向量維度
word_size = wv_from_text.wv.syn0[0].shape[0]
# 計算word的ngrams片語
ngrams = compute_ngrams(word,min_n = min_n, max_n = max_n)
# 如果在詞典之中,直接返回詞向量
if word in wv_from_text.wv.vocab.keys():
return wv_from_text[word]
else:
# 不在詞典的情況下
word_vec = np.zeros(word_size, dtype=np.float32)
ngrams_found = 0
ngrams_single = [ng for ng in ngrams if len(ng) == 1]
ngrams_more = [ng for ng in ngrams if len(ng) > 1]
# 先只接受2個單詞長度以上的詞向量
for ngram in ngrams_more:
if ngram in wv_from_text.wv.vocab.keys():
word_vec += wv_from_text[ngram]
ngrams_found += 1
#print(ngram)
# 如果,沒有匹配到,那麼最後是考慮單個詞向量
if ngrams_found == 0:
for ngram in ngrams_single:
word_vec += wv_from_text[ngram]
ngrams_found += 1
if word_vec.any():
return word_vec / max(1, ngrams_found)
else:
raise KeyError('all ngrams for word %s absent from model' % word)
vec = wordVec('千奇百怪的詞向量',wv_from_text,min_n = 1, max_n = 3) # 詞向量獲取
wv_from_text.most_similar(positive=[vec], topn=10) # 相似詞查詢
compute_ngrams
函式是將詞條N-grams找出來,譬如:
compute_ngrams('萌萌的噠的',min_n = 1,max_n = 3)
>>> ['噠', '的噠的', '萌的', '的噠', '噠的', '萌萌的', '萌的噠', '的', '萌萌', '萌']
這邊沒有沿用fasttext之中的<>
來區分詞頭、詞尾。
wordVec
函式是計算未登入詞的,其中筆者小小加了一些內容,就是:當出現oov的情況下,最好先不考慮單字詞向量,如果能匹配到兩個字以上的內容就優先進行平均。
在得到未登入詞或短語的向量之後,就可以快速進行查詢,gensim裡面是支援給入向量進行相似詞查詢:
wv_from_text.most_similar(positive=[vec], topn=10)
其實,有了這麼一個小函式 + 稍微大記憶體的伺服器,就可以開始挖金礦了,筆者在此給出一部分可供參考與使用的小案例,案例中找出來的相似肯定還是不那麼幹淨,需要自行清洗一下:
網路用語挖掘:
vec = wordVec('天了嚕',wv_from_text,min_n = 1, max_n = 3)
wv_from_text.most_similar(positive=[vec], topn=20)
[('天了嚕', 1.0),
('天啦嚕', 0.910751223564148),
('天惹', 0.8336831331253052),
('我的天吶', 0.8315592408180237),
('天哪嚕', 0.8200887441635132),
('也是醉了', 0.8048921823501587),
('哦買噶', 0.7951157093048096),
('我也是醉了', 0.7925893664360046),
('我的天哪', 0.7903991937637329),
('天吶', 0.7862901091575623)
......
]
評論觀點
vec = wordVec('真難吃',wv_from_text,min_n = 1, max_n = 3)
wv_from_text.most_similar(positive=[vec], negative=['好吃'], topn=20)
[('真難', 0.8344259858131409),
('難吃', 0.8344259262084961),
('不好吃', 0.7413374185562134),
('難啊', 0.7120314836502075),
('難喝', 0.6996017694473267),
('難以下嚥', 0.6920732259750366),
('好難', 0.6856701374053955),
('挺好吃', 0.6801191568374634),
('真不容易', 0.6788320541381836),
('真的很難', 0.671592116355896),
('真的很好吃', 0.6692471504211426),
...
例子2:
vec = wordVec('環境乾淨',wv_from_text,min_n = 1, max_n = 3)
wv_from_text.most_similar(positive=[vec], topn=20)
[('環境乾淨', 0.9999998807907104),
('環境乾淨整潔', 0.8523852825164795),
('環境舒適', 0.8281853199005127),
('環境乾淨衛生', 0.8241869211196899),
('衛生乾淨', 0.8118663430213928),
('乾淨衛生', 0.7971832156181335),
('乾淨舒適', 0.796349287033081),
('環境清新', 0.7937666773796082),
('衛生好', 0.7925254702568054),
('環境整潔', 0.7919654846191406),
('環境好', 0.7814522981643677),
('房間乾淨', 0.7802159786224365),
('環境優雅', 0.7685255408287048),
同義詞挖掘
vec = wordVec('蘋果',wv_from_text,min_n = 1, max_n = 3)
wv_from_text.most_similar(positive=[vec],negative=['水果'], topn=20)
[('蘋果公司', 0.5877306461334229),
('蘋果開發', 0.5226757526397705),
('高通', 0.5215991735458374),
('谷歌', 0.5213730335235596),
('蘋果的iphone', 0.5150437355041504),
('微軟', 0.5127487778663635),
('蘋果新', 0.5012987852096558),
('pixel手機', 0.49072039127349854),
('蘋果高管', 0.4897959530353546),
('蘋果iphone', 0.4875335991382599),
('蘋果手機iphone', 0.4791686534881592),
('蘋果晶片', 0.47766292095184326),
('iphone', 0.4754045307636261),