iLMS知識社群ePortfolioeeClass學習平台空大首頁登入
位置: 吳俊逸 > NLP
word2vec簡介
by 吳俊逸 2018-05-16 09:13:08, 回應(0), 人氣(4525)
REF: https://medium.com/pyladies-taiwan
  • word2vec 是 Google 的一個開源工具,能夠根據輸入的「詞的集合」計算出詞與詞之間的距離。
  • 它將「字詞」轉換成「向量」形式,可以把對文本內容的處理簡化為向量空間中的向量運算,計算出向量空間上的相似度,來表示文本語義上的相似度。
The amazing power of word vectors
  • word2vec 計算的是餘弦值 (cosine),距離範圍為 0–1 之間,值越大代表兩個詞關聯度越高。
  • 詞向量:用 Distributed Representation 表示詞,通常也被稱為「Word Representation」或「Word Embedding」。
簡言之:詞向量表示法讓相關或者相似的詞,在距離上更接近。

本次使用資料集

搜狗實驗室的新聞文本 (完整版 648MB、tar.gz 格式);建議第一次使用迷你版 ( 110KB ),但要記得下載 tar.gz 格式的資料集,因示範程式碼是以此格式做清理。下載連結

  1. 本次示範案例為搜狗實驗室的新聞文本完整版,有試做了兩個版本(簡體 vs 繁體),本次示範以 python2 進行,以繁體文本為主。
  2. 先講實作後的結果:因使用 jieba 套件,以分詞效果而言,簡體文本表現較好,完整程式碼也會附上簡繁體結果。
  3. 若你有自己的文本資料集可以跳至 PART2 斷詞開始。

另外提供 PTT 的中文的語料庫,是去年 PyLadies 辦 Data Mining Workshop 時,其中有兩組使用到的資料文本。下載連結

實作流程

  • 在 terminal 進行套件安裝以及資料格式檔案轉換
  • 在 jupyter notebook 透過 jieba 套件斷詞,再經由 word2vec 萃取文章特徵,轉換成以向量空間表示的詞向量
  • 視覺化呈現「相近詞」向量之結果

開始實作吧

PART 1 :套件+資料格式轉換

  1. 所需安裝套件:jiebaword2vecsklearnhanziconv(繁簡轉換)、matplotlib、下載 wqy-microhei.ttc (因中文的顯示需做特殊處理);可依個人習慣安裝套件(pip 直接安裝 or 安裝在虛擬環境當中)

2. 解壓縮新聞文本資料集

  • 與文本資料同一目錄底下,執行解壓縮指令:
# 同一目錄底下
youngmihuang in ~/downloads
# 解壓縮
$ tar -zvxf news_sohusite_xml.full.tar.gz
# 會產生 news_sohusite_xml.dat檔案
  • 取出 <content> </content> 之間的內容,並存成 corpus.txt 文件,執行:
$ cat news_sohusite_xml.full.dat | iconv -f gbk -t utf-8 -c | grep "<content>"  > corpus.txt

PART 2:斷詞與word2vec實現

上述資料格式轉換完成之後,就可以開啟 jupyter notebook 了,餵給word2vec 的文件是需要斷詞的,斷詞可以採用 jieba 套件實現,將前面已經安裝好的套件 import 進來,並將剛剛存好的 corpus.txt 的新聞內容讀進來:

# coding=utf-8
import jieba
from hanziconv import HanziConv
# 讀檔:一條一條讀進來
fileTrainRead = []
with open('/Users/youngmihuang/Downloads/corpus.txt') as fileTrainRaw:
for line in fileTrainRaw:
fileTrainRead.append(HanziConv.toTraditional(line)) # 簡轉繁
  1. 斷詞

接下來就要將每一條讀進來的新聞文章使用 jieba 斷詞,每一條讀進來的文章分詞後,併成一條更長的 list:

# 斷詞
fileTrainSeg=[]
for i in range(len(fileTrainRead)):
fileTrainSeg.append([' '.join(list(jieba.cut(fileTrainRead[i][9:-11],cut_all=False)))])
# 因為會跑很久,檢核是否資料有持續在跑
if i % 50000 == 0 :
print i

上述若使用共有 1,411,996 筆文章資料,如果是 MacBook Air 大約需要 1hr ,若在等待期間,有興趣可以玩玩看 jieba 支持的三種分詞模式:

  • 精確模式,試圖將句子最精確地切開,適合文本分析。
  • 全模式,把句子中所有的可以成詞的詞語都掃描出來, 速度非常快,但是不能解決歧義。
  • 搜索引擎模式,在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞。
# 精確模式、同時也是預設模式
seg_list1 = jieba.cut("一是嬰兒哭啼二是學遊戲三是青春物語四是碰巧遇見你", cut_all=False)
print "Default Mode: " + "/ ".join(seg_list1)
# Result
一是/ 嬰兒/ 哭啼/ 二是/ 學遊戲/ 三/ 是/ 青春/ 物語/ 四/ 是/ 碰/ 巧遇/ 見/ 你

# 全模式
seg_list2 = jieba.cut("一是嬰兒哭啼二是學遊戲三是青春物語四是碰巧遇見你", cut_all=True)
print "All Mode: " + "/ ".join(seg_list2)
# Result
一/ 是/ 嬰/ 兒/ 哭啼/ 二/ 是/ 學/ 遊/ 戲/ 三/ 是/ 青春/ 物/ 語/ 四/ 是/ 碰巧/ 巧遇/ 見/ 你

# 搜尋引擎模式
seg_list3 = jieba.cut_for_search("一是嬰兒哭啼二是學遊戲三是青春物語四是碰巧遇見你")
print "Search Mode: " + "/ ".join(seg_list3)
# Result
一是/ 嬰兒/ 哭啼/ 二是/ 學遊戲/ 三/ 是/ 青春/ 物語/ 四/ 是/ 碰/ 巧遇/ 見/ 你

這裡在精確模式 vs 全模式之間有明顯差異,精確模式整體相對分得較好,像在「嬰兒/ 學遊戲/ 物語」都分出來了,但全模式將「碰巧/ 巧遇」兩個字詞都保留下來了,「碰巧」是分得比精確模式的「巧遇」來得好的部分。

試了很多次不同的 sample 丟進精確模式 vs 搜尋引擎模式,分詞結果都一樣,使用 jieba 開發者本人的範例 -> 實際用精確模式 vs 搜尋引擎比較跑出的結果皆為相同:「小明/ 碩士/ 畢業/ 於/ 中國/ 科學院/ 計算/ 所/ ,/ 後/ 在/ 日本/ 京都/ 大學/ 深造」,大家也可以試玩看看,看是否能有不一樣的發現。

接著,我們如果好奇文章斷成哪些字詞的話可以下面的方法檢視;最後需將jieba 的斷詞結果存成 corpusSegDone.txt:

# 將jieba的斷詞產出存檔
fileSegWordDonePath ='corpusSegDone.txt'
with open(fileSegWordDonePath,'wb') as fW:
for i in range(len(fileTrainSeg)):
fW.write(fileTrainSeg[i][0].encode('utf-8'))
fW.write('\n')
# 檢視斷詞jieba的結果
def PrintListChinese(list):
for i in range(len(list)):
print list[i],
PrintListChinese(fileTrainSeg[10])

2. word2vec 轉成高維空間向量

將上述 jieba 斷詞後的 corpusSegDone.txt 轉成向量後存成 corpusWord2Vec.bin 檔:

import word2vec
# jieba分詞轉word2vec向量
word2vec.word2vec('corpusSegDone.txt', 'corpusWord2Vec.bin', size=300,verbose=True)

這裡將 size 設定為 300 維,透過調整 size 參數的大小決定「詞向量」的維度數,也可以是 400、200、100 維;verbose = True 是會印出 word2vec 執行的詳細狀況:

# 執行的詳細結果(繁體)
Starting training using file corpusSegDone.txt
Vocab size: 842956
Words in train file: 407852192

若要檢視文字轉為向量的樣子:

model = word2vec.load('corpusWord2Vec.bin')
print model.vectors #文字以向量形式呈現
# Result
[[ 0.08015626 0.08850129 -0.07670335 ..., -0.02626957 -0.03316621
0.0614953 ]
[-0.02138876 -0.06264357 -0.09334067 ..., 0.00015479 -0.05851945
0.04908165]
[-0.00995629 -0.01481206 -0.09394402 ..., -0.01619852 -0.06687199
0.02453057]
...,
[ 0.04989554 0.08304838 -0.05676759 ..., -0.00947851 0.07963122
-0.03377864]
[-0.01265288 0.07221718 0.10141564 ..., 0.01648477 0.1009445
-0.07451316]
[ 0.03596683 -0.08654522 0.03320961 ..., -0.07038685 0.09750576
-0.03816517]]

若要檢視字詞:

# 可檢視第996~1000個字詞是什麼
for i in range(995,1000):
print model.vocab[i]
# Result
達到

行為
觀眾
日訊

如何評價所建「詞向量」的好壞?

依照 word2vec 的原理,詞意相近的詞在向量空間當中的距離是接近的,但會因為「文本內容性質的差異」而有所不同,例如在新聞類的文本中,「台灣」字詞會常與地名或時事事件等字詞距離接近;然而若是跟 PTT 論壇相關的文本,「台灣」字詞可能會更常與「鄉民/島民/魯蛇/溫拿」這類聊天用語的字詞距離靠近;總之,可以透過所訓練之詞向量距離,觀察他們語意是否相近去做衡量:

# 顯示空間距離相近的詞
model = word2vec.load('corpusWord2Vec.bin')
indexes = model.cosine(u'畢業') # 此字詞有出現在corpusWord2Vec.bin當中
for index in indexes[0]:
print model.vocab[index]
# Result
畢業生
離校
考入
剛畢業
大四
學畢業
考上
開學
結業
放暑假

若是使用迷你版 ( 110KB ) 這一版的檔案,因為出現的字詞較少,建議可以嘗試「u'經濟'」、「u'企業'」等字詞;若是使用其他的資料集,這裡可以輸入上一步驟檢視有出現的字詞替換,才不會出現 Key error 的情形。


PART3:將距離相近的詞以視覺化呈現

  • 選擇要呈現在二維平面上的字詞們(建議可以先選擇種類不同的字詞,在二維平面做視覺化的分佈會分得較開):
# 選擇你想丟進去的字詞
# 顯示空間距離相近的詞
# 放入字詞: '寶寶'
indexes = model.cosine(u'寶寶')
for index in indexes[0]:
print model.vocab[index]
# Result
寶寶的
小寶寶
孩子
準媽媽
寶寶在
胎兒
媽媽們
小孩
小孩子
媽媽
# 放入字詞: '打車'
indexes = model.cosine(u'打車')
for index in indexes[0]:
print model.vocab[index]
# Result
坐車
開車
坐火車
買票
下車
買菜
上車
搭車
乘車
騎車
# 放入字詞: '楊冪'
indexes = model.cosine(u'楊冪')
for index in indexes[0]:
print model.vocab[index]
# Result
姚晨
林心如
穎兒
張智霖
劉愷威
馮紹峰
鄧超
周迅
柳岩
李晨
# 放入字詞: '騰訊'
indexes = model.cosine(u'騰訊')
for index in indexes[0]:
print model.vocab[index]
# Result
網易
新浪
百度
優酷
搜狗
凡客
窩窩團
電商
愛奇藝
百視通

將要視覺化的五組字詞,找出其相近詞後 append 起來成 array 形式:

# 在高維向量空間(k=300)找出與所選字詞距離最接近的前10名
index1,metrics1 = model.cosine(u'畢業')
index2,metrics2 = model.cosine(u'寶寶')
index3,metrics3 = model.cosine(u'打車')
index4,metrics4 = model.cosine(u'楊冪')
index5,metrics5 = model.cosine(u'騰訊')

# 所選字詞
index01 = np.where(model.vocab == u'畢業')
index02 = np.where(model.vocab == u'寶寶')
index03 = np.where(model.vocab == u'打車')
index04 = np.where(model.vocab == u'楊冪')
index05 = np.where(model.vocab == u'騰訊')
# 將所選字詞與其最接近之前10名合併 
index1 = np.append(index1,index01)
index2 = np.append(index2,index02)
index3 = np.append(index3,index03)
index4 = np.append(index4,index04)
index5 = np.append(index5,index05)

接下來,將上述高維空間的向量,變成二維平面:

import numpy as np
# 視覺化套件
import matplotlib
import matplotlib.pyplot as plt
# 主成分因子
from sklearn.decomposition import PCA

為什麼需要做 PCA?

PCA ( Principal Component Analysis ) 是一種常用的數據分析方法。PCA 通過線性變換將原始數據變換為一組各維度線性無關的表示,可用於提取數據的主要特徵分量,常用於高維數據的降維。若想了解數學原理及更詳細的介紹可以看這裡

PCA 降維示意圖:

Principal Component Analysis using truncated SVD
# 引入上述將文章斷詞後轉為300維向量的資料
rawWordVec = model.vectors
# 將原本300維向量空間降為2維
X_reduced = PCA(n_components=2).fit_transform(rawWordVec)

視覺化呈現步驟:

# 須先下載wqy-microhei.ttc,因中文顯示需做特殊處理
zhfont = matplotlib.font_manager.FontProperties(fname='/Users/youngmihuang/Downloads/wqy-microhei.ttc')
# 畫圖
fig = plt.figure()
ax = fig.add_subplot(111)

for i in index1:
ax.text(X_reduced[i][0],X_reduced[i][1],model.vocab[i], fontproperties=zhfont,color='C3')
for i in index2:
ax.text(X_reduced[i][0],X_reduced[i][1],model.vocab[i], fontproperties = zhfont,color= 'C1')
for i in index3:
ax.text(X_reduced[i][0],X_reduced[i][1],model.vocab[i], fontproperties=zhfont,color='C7')
for i in index4:
ax.text(X_reduced[i][0],X_reduced[i][1],model.vocab[i], fontproperties=zhfont,color='C0')
for i in index5:
ax.text(X_reduced[i][0],X_reduced[i][1],model.vocab[i], fontproperties=zhfont,color='C4')
ax.axis([0,0.5,-0.2,0.6])
plt.figure(figsize=(60,60))
plt.show()

結果如下:

透過各個相近群的字詞以不同顏色標記之後,可以看到各字詞群組之間相對都蠻聚合的,但因為他們是從 300 維空間當中投射至 2 維空間,「打車」和「騰訊」這兩組相近字(灰色、紫色)在視覺上看起來跟其他重疊性很高,若把它們移除結果如下:


結語

  1. 詞向量相近詞結果表現:會根據資料量大小( full / small 資料集)、以及繁/簡中文與分詞套件( jieba 擅長簡體分詞)而有所差異:
# jieba斷詞轉為word2vec結果 (繁體)
Starting training using file corpusSegDone.txt
Vocab size: 842956
Words in train file: 407852192
# jieba斷詞轉為word2vec結果 (簡體)
Starting training using file corpusSegDone.txt
Vocab size: 507882
Words in train file: 391829209

以分詞效果看來,簡體斷的較好;繁體相對來講在斷詞時,有些字詞無法斷在一起,導致字詞數較多,e.g. “晚自習” 在簡體會被分成 “晚自習”;但在繁體會被分成 “晚” 和 “自習” 兩個單詞。

2. word2vec 將文字轉為詞向量,僅為自然語言處理中表示單詞的最基本問題,但恭喜妳完成了入門題,一旦掌握了基礎要領之後,就可以往自然語言處理的關卡邁進,以下舉幾個 NLP 的任務:

簡單任務

  • 拼寫檢查 ( Spell Checking )
  • 關鍵字搜索 ( Keyword Search )
  • 尋找同義詞 ( Finding Synonyms )

中級任務

  • 從網頁和文檔解析信息 ( Parsing information from websites, documents, etc. )

複雜任務

  • 機器翻譯 ( Machine Translation )
  • 語義分析 ( Semantic Analysis )
  • 指代詞分析 ( Coreference ), 例如,”he” 和”it” 在文檔中指誰或什麼?
  • 問答系統 ( Question Answering )

PS. 這次實作用到的程式碼也都同步在 github ,歡迎取用參考。

參考資料:

  1. 自然語言處理(NLP)基礎概念 https://zhuanlan.zhihu.com/p/28894219
  2. word2vec 構建中文詞向量http://www.cnblogs.com/Newsteinwell/p/6034747.html