Use word2vec to improve search result
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.gitignore
LICENSE
README.md
cut.py
getBlog.py
getUrl.py
train_word2vec_model.py

README.md

Word2Vec

有一對情侶正為晚餐不知道要吃什麼而煩惱...

  • 男:寶貝,晚餐想吃什麼?
  • 女:都可以呀!
  • 男:我們去吃久久神鍋好不好?
  • 女:很熱欸~
  • 男:那去轉角吃涼麵怎麼樣?
  • 女:那又沒有冷氣吹~
  • 男:找一家有帥哥店員的好不好?
  • 女:好!讓我們去有帥哥氣氛又好的餐廳吧~
  • 男:😂😂😂

男友不禁想問,為什麼找一家餐廳會這麼困難呢?氣氛要好又要有帥哥店員的餐廳到底在哪裡?換個角度想,炎炎夏日,為什麼找不到一家主打充滿陽光、沙灘、比基尼這種夏日風情的餐廳?我們常常在餐廳享受的不只是食物,而是享受餐廳營造的氣氛,餐廳營造的可以是一個人與食物的寧靜對話,可以是三五好友歡聚的燒烤派對,當然也可以是家人一同聚餐的溫馨。

這是一篇關於如何透過情境搜尋餐廳的文章,可以直接往下看結果。

專案緣起

不知道大家最近有沒有看到一篇 PTT 上的文章?作者對約 X 信進行語義分析,內容專業到讓人無法直視,看的當下就覺得:哇!原來語意分析還有這種應用的方式!程式語言是用來幫忙解決問題的好工具,能把它應用到生活大小事上真的很棒!這篇文章燃起了我心中對文字探勘的熱情,剛好最近有個比賽會需要這方面的技術~~(才不是因為 PIXNET HACKTHON 的比賽入選機率極低而抱著不服輸的心態呢!)~~

由於本身對資料分析很有興趣,尤其是圖像分析和語意分析,因為他們能夠應用的範疇很廣,而且已經不知不覺地影響著這個世界的運行,例如無人車駕駛、Alphago、SkyRec、IBM Waston 等等。這個領域的知識又深又廣,既然要朝這個方向持續邁進,平常就要有所累積。公司正在朝著將情境與餐廳建立連結轉型,正好和我有興趣的領域有所重疊,所以對於這個專案我也有比平常玩玩還認真的使命感,於是便花了星期六和星期日的上午把做了一些有趣的東西。

工具介紹

大多數的公司使用 Solr, Elasticsearch 建置搜尋引擎,有些還會直接嵌入 Google 搜尋列,前者運用的方法是 TF-IDF,後者就是借助 Google 強大的搜尋演算法。但是沒有一種方法是完美的,如果今天下的關鍵字比較多,想要透過「酒館」和「義式」搜尋餐廳,就會發現沒有我要的結果,但是這兩個關鍵字如果分開來就都能得到結果,事實上,這兩個關鍵應該是有著相關性的,但是透過 TF-IDF 卻無法知道。如果可以將這兩個關鍵字的特性相加之後得到一個新的特性,再用新的特性去尋找最相近的餐廳就太棒了!

這就是本篇文章會運用到的概念,這個概念的基礎是 Word2Vec,它很適合用來對文字、圖像、聲音進行模糊搜尋,由於運作原理一言難盡~~(死線要到啦)~~,我就直接說重點:透過訓練將詞會投影到高維度的向量空間,相近的詞彙有相近的詞向量。

而詞向量有個特性是可以透過相加減獲得有趣的結果,例如:國王 - 男人 + 女人 ~= 皇后

操作流程

1. 收集連結:收集公司合作餐廳的食記連結,並且把餐廳資訊與餐廳食記連結輸出成文字檔,檔案內沒有提供連結。

執行python getUrl.py

2. 爬網站 & 擷取資訊:過濾非 Pixnet 連結透過 requests 結合 multiprocessing 爬網站的 Html。使用 PyQuery 擷取網站標題與內容,並且存到 Sqlite。

執行python getBlog.py

3. 斷詞:使用 Jieba 對文字進行預處理,轉換成 Word2Vec 能夠訓練的格式。

執行python cut.py

4. 訓練:透過在 gensim 中實作的 Word2Vec,設定參數對資料進行訓練,產生詞向量與模型。(直接使用 52nlp 提供的檔案)

執行python train_word2vec_model.py training_sentence.txt model_name.model vector_name.vector

注意事項

在專案中主要遇到兩個困難:

  • 處理中文字遇到的編碼問題:Unicode、UTF-8 與 ASCII 之間錯綜複雜的關係,不同套件的輸入的接口要求的編碼可能不一樣。
  • 使用 multiprocess 要小心:原因還沒有弄清楚,只要在 the_function 裡面加入了 pyquery 或是 sqlite 的 function 就會 crash,但是放 requests 就沒事。
from multiprocessing import Pool
p = Pool(5)
results = p.map(the_fucntion, inputs)

專案成果

本次訓練的文章共有 5899 篇食記,訓練結果為 35962 個 500 維的詞向量。由於訓練數量蠻小的,所以訓練速度很快。

  1. 先載入 gensim 套件與剛剛訓練好的 model
import gensim
# print result
def ps(result):
  if result:
  for e in result:
    print e[0], e[1]
# load fresh model
model = gensim.models.Word2Vec.load("articles_rname.model")

接下來就可以開始玩啦!我很愛吃水餃,但是昨天才剛吃過,有沒有什麼類似的料理可以推薦給我呢?

>>> result = model.most_similar(u"水餃",topn=5)
>>> ps(result)
'叉燒包' 0.732798755169
'蔥油餅' 0.717539072037
'三鮮' 0.706256866455
'餃子' 0.703786492348
'包子' 0.694874405861

如果我們想要知道泰式之於瓦城,就像是義式之於什麼餐廳,就可以這樣計算: 瓦城 - 泰式 + 義式 = ?

>>> result = model.most_similar(positive=u"義式 瓦城".split(" "), negative=[u"泰式"],topn=5)
>>> ps(result)
'義大利餐廳' 0.754565596581
'NINI' 0.750810861588
'帕莎' 0.707847952843
'廚子市場' 0.703588843346
'蒂娜' 0.700719475746

得出來的結果是在義式餐廳中,風格與瓦城最像的前五名餐廳。但是,在這裡我們發現了一個問題,「義大利餐廳」是哪一家?答案是義大皇家酒店的義大利餐廳,NINI 呢?原來是 Le NINI 樂尼尼義式餐廳,帕莎和蒂娜應該要合起來叫做帕蒂娜莎,產生問題的原因是之前在處理餐廳名稱的時候不夠謹慎。目前會先用 mapping 的方式找回原本的餐廳名稱。

接下來我們要進入重頭戲,情境搜尋了!

>>> result = model.most_similar(u"夏天 海灘 餐廳".split(" "), topn=5)
>>> ps(result)
'皮椅' 0.694285988808
'LOUNGE' 0.688326239586
'HALEAKALA' 0.68235886097
'仿舊' 0.674678146839
'小徑' 0.662850260735

我們想要的結果是根據情境找到符合的餐廳,但是得出的結果只有2家餐廳,2個名詞,1個形容詞。因此我們必須建立涵蓋訓練集餐廳的 list,把這個 list 的詞向量拿出來與我們想要搜尋的情境詞向量進行相似度的計算。特別感謝 jimgoo 提供的 most_similar_in_list function,讓我可以直接使用。

>>> print(rname_list[:5])
[u'Follow', u'Bellini', u'TABLE', u'Taverna', u'90']
>>> print(mapping(rname_list[:5]))
[u'Follow Lady 法蕾蒂', u'Bellini Pasta Pasta', u'TABLE JOE 喬桌子廚房', u'Taverna De  Medici 梅帝騎小酒館', u'90 a la sante 酒食歐風朝']

>>> result = model.most_similar_in_list(u"夏天 海灘".split(" "), topn=5, restrict_vocab=rname_list)
>>> ps([[mapping(rname[0]), rname[1]] for rname in result])
'VG Cafe & Bistro' 0.650945961475
'HALEAKALA 夏威夷酒吧餐廳' 0.615895032883
'Eslite Tea Room' 0.599678635597
'URBAN331-台北慕軒飯店' 0.579266548157
'Destino 妳是我的命運' 0.57857131958

>>> result = model.most_similar_in_list(u"消暑 下午 茶".split(" "), topn=3, restrict_vocab=rname_list)
>>> ps([[mapping(rname[0]), rname[1]] for rname in result])
'Caldo Cafe咖朵咖啡' 0.47723197937
'Eslite Tea Room' 0.468698769808
'H-Bar' 0.460396289825

>>> result = model.most_similar_in_list(u"正妹 店員".split(" "), topn=3, restrict_vocab=rname_list)
>>> ps([[mapping(rname[0]), rname[1]] for rname in result])
'etage15-高雄Hotel dua' 0.468320250511
'Sunny Cafe' 0.447554886341
'Kevin Coffee' 0.418976008892

>>> result = model.most_similar_in_list(u"音樂 悠閒".split(" "), topn=3, restrict_vocab=rname_list)
>>> ps([[mapping(rname[0]), rname[1]] for rname in result])
'Mammoth lounge' 0.670462846756
'HALEAKALA 夏威夷酒吧餐廳' 0.626639544964
'RUBY CAFE' 0.6229159832

>>> result = model.most_similar_in_list(u"工程師".split(" "), topn=3, restrict_vocab=rname_list)
>>> ps([[mapping(rname[0]), rname[1]] for rname in result])
'American Steakhouse' 0.802481234074
'i sweet' 0.785791814327
'Silk Road Feast 絲路宴餐廳' 0.776082456112

>>> result = model.most_similar_in_list(u"求婚 紀念".split(" "), topn=3, restrict_vocab=rname_list)
>>> ps([[mapping(rname[0]), rname[1]] for rname in result])
'Ton Up Cafe' 0.62010884285
'TABLE JOE 喬桌子廚房' 0.608335494995
'URBAN331-台北慕軒飯店' 0.606325864792

>>> result = model.most_similar_in_list(u"酒 餐廳 美式 跨年".split(" "), topn=3, restrict_vocab=rname_list)
>>> ps([[mapping(rname[0]), rname[1]] for rname in result])
'bar & restaurant a³ 新義式餐廳' 0.665651142597
'MW 時尚義法料理&酒品' 0.665459275246
'Le Bar 吧 戶外餐廳' 0.644170224667

這些結果很有趣,有空再來分析~

未來方向

  • 餐廳食記收集的數量要夠多夠平均,不然會影響到訓練結果
  • 換成訓練餐廳評論結果可能會更精準
  • 將多維向量降維,可以拿來做 clustering,另外可以畫個 2D 的圖
  • 文字處理不容易但是很重要,還有許多可以優化的地方
  • 多國語言搜尋也能配合 Word2Vec

參考資源

Python 有很多好用的套件,像是中文斷詞有 jieba,word2vec 有 gensim,用起來上手很快,而且還有熱心的大大們不吝分享與教學。


有什麼建議或是需要其他資訊都歡迎與我聯繫!

Paris Tsai paristsaiswing@gmail.com