Chinese Search Sharing

Liang Bo Wang (亮亮), 2015-09-14

MLDM Monday, 2015-09-14

中文搜尋經驗分享

By Liang2 under CC 4.0 BY license

Esc to overview
to navigate

關於我

背景介紹

ES Search Basics

ES 搜尋的最小單位是 Term (Token)

Who controls the past controls the future. Who controls the present controls the past. — 1984, George Orwell

會被 ES Tokenizer 轉換成 Who / controls / the / past / controls / the / future / Who / controls / the / present / controls / the / past。
實務上會在 tokenizer 前後加上 filter 小寫轉換、去除無意義的 a、the。

這裡 term 有:controls(4), the(4), Who(2), past(2), future(1), present(1)

# python3 tf_idf.py 後背包
Searching 後背包 in 7 documents ...
['復古單寧帆布後雙背包',  '米色帆布橫式側背包',
 '藍色石紋斜背包',       '帆布眼鏡盒',
 '鱷魚來了帆布筆袋',     '拼木紋布質文庫書衣',
 '單眼相機用亮彩防水包布']
Top 3 similar documents are:
d1: 復古單寧帆布後雙背包 [sim=0.4969]
d3: 藍色石紋斜背包 [sim=0.3262]
d2: 米色帆布橫式側背包 [sim=0.3017]

Under the hood - cos similarity

q  = {後: 1.00, 背: 1.00, 包: 1.00}

d1 = {復: 2.25, 古: 2.25, 單: 1.85, 寧: 2.25, 帆: 1.34, 布: 1.00,
      後: 2.25, 雙: 2.25, 背: 1.56, 包: 1.34}
d2 = {米: 2.25, 色: 1.85, 帆: 1.34, 布: 1.00, 橫: 2.25, 式: 2.25,
      側: 2.25, 背: 1.56, 包: 1.34}
d3 = {藍: 2.25, 色: 1.85, 石: 2.25, 紋: 1.85, 斜: 2.25, 背: 1.56, 包: 1.34}
d4 = {帆: 1.34, 布: 1.00, 眼: 1.85, 鏡: 2.25, 盒: 2.25}
d5 = {鱷: 2.25, 魚: 2.25, 帆: 1.34, 布: 1.00, 筆: 2.25, 袋: 2.25}
d6 = {拼: 2.25, 木: 2.25, 紋: 1.85, 布: 1.00, 質: 2.25, 文: 2.25, 庫: 2.25,
      書: 2.25, 衣: 2.25}
d7 = {單: 1.85, 眼: 1.85, 相: 2.25, 機: 2.25, 用: 2.25, 亮: 2.25, 彩: 2.25,
      防: 2.25, 水: 2.25, 包: 1.34, 布: 1.00}

問題是一字字斷詞破壞中文詞意

Searching 包布 in 7 documents ...

Top 3 similar documents are:
d2: 米色帆橫式側背 [sim=0.2981]
d1: 復古單寧帆後雙背 [sim=0.2762]
d7: 單眼相機用亮彩防水包布 [sim=0.2459]

包布 ≠ 布包 ≠ 布○○包。我們需要更好的分詞模式。

中文的斷詞應該像這樣

過去很近,近的就在眼前,以致於你一集中焦距就看出了它的遠。
— 張惠菁。《你不相信的事》

Jieba
中文
斷詞

中文斷詞我們選擇 jieba

Jieba in action

'/'.join(jieba.cut(
      '過去很近,近的就在眼前,'
      '以致於你一集中焦距就看出了它的遠。'))
# 過去/很近/,/近/的/就/在/眼前/,/
# 以致於/你/一/集中/焦距/就/看出/了/它/的/遠/。

'/'.join(jieba.cut('穿過縣界長長的隧道,便是雪國。'))
# 穿過/縣界/長長的/隧道/,/便是/雪國/。

Jieba HMM in action

'/'.join(jieba.cut('便是雪國', HMM=True))
# 便是/雪國
'/'.join(jieba.cut('便是雪國', HMM=False))
# 便是/雪/國

在中文,二字二字成詞比後者的組合機會高得多
(極端的例子:「科科科科科」也會被拆成「科科/科科科」

Some people, when confronted with a Chinese segmentation porblem, think "I know, I'll use a dictionary look-up." Now they have two problems.(An super old meme)

cut('相機包布')           # 相機包 / 布
cut('台中太陽餅')         # 台 / 中 / 太陽 / 餅
cut('側背包 側肩背包')     # 側背包 / 側肩 / 背包

實際上 jieba 內建的字典並沒有這個問題,但我覺得這個例子比較簡單

側背包≠ 側肩 + 背包?!

  • 使用者查「側背包」
  • 產品裡有個「側肩 / 背包」
  • ES 的行為:
    1. 側背包 in 側肩?否
    2. 側背包 in 背包?否
    3. 回傳找不到相關商品
  • 使用者查「側 / 背包」
  • 產品裡有個「側肩 / 背包」
  • ES 的行為:
    1. 側 in 側肩?是
    2. 背包 in 背包?是
    3. 回傳找到相關商品

斷詞不是切越長越好!查不到比查不準還慘……有沒有改進方法?

Jieba search(index) mode

解決上述複合詞斷詞問題,能要求 jieba 把可能的斷詞組合都列出。

cut_for_search('側背包')     cut_for_search('帆布鞋')
# 背包 / 側背包              # 帆布 / 布鞋 / 帆布鞋

但並不能完全解決問題,有時候會缺字,在這裡就少了個「側」詞。使用者能查到背包但缺乏「側」的訊息。
另外詞變多了,會增加搜尋的負擔。

註:推估原因是 jieba 內建的 HMM 沒有繁體字 ,所以 \(P(\text{側}|B) = 0\) 新詞

斷詞詞典與詞頻要小心

萌典、g0v 中文斷詞

中文斷詞有很多經驗算法……最直覺的做法,就是拿一個字典,比對最長相同的部份斷詞……我們手邊剛好有一個很大的詞典,萌典。唐鳳,COSCUP 2013

2013 年,我坐在台下聽

2015 年,我用到它了

萌典

# /a/狂狷.json
{"h": [
  {"d": [
    {"q": ["`論語~.`子路~:「`子~`曰~:『`不得~`中行~`而~……』」",
           "`抱朴子~.`外篇~.`逸民~:「`昔~`狂狷~`華~`士~,……」"],
     "f":"`過於~`激進~`與~`過於~`保守~`的~`人~。"}],
   "p": "kuáng juàn",
   "b": "ㄎㄨㄤˊ ㄐㄩㄢˋ",
   "=": "2654000071"}],
 "t": "`狂~`狷~"}

讓 jieba 使用萌典

單用詞頻斷詞就有不錯的效果

增加文本量更新詞頻

維基百科處理

Jieba 總結與補充

ES Search 總結

Semantic Search

仍有一成的搜尋找不(太)到結果

  1. 其實找到使用者想找的東西了
  2. 使用者想找的是同義的商品,但用詞不同
  3. 下太多關鍵字,站上就沒有這樣的商品

也許沒有完全一樣的,但也許很像的

Word2vec theory

  • 一個詞會被轉換到 \(V\) 維度的空間中,\(V \in [200, 1000]\)
  • 這個轉換透過給的文本前後文去學習,例如 \( C = 10\) 表示看了前後 5 個字
  • Unsupervised:只要一直餵給他斷好詞的文本就可以了
  • 使用 wiki,一次 iteration 後就有效果

Word2vec 空間特性

  • 概念上接近的東西會很接近
  • 可能因為他們在文本中經常一起出現、在文本中的位置很接近
  • 雖然未必精準(鑰匙、門鎖、門、木板)

實際上座標空間有 V 維,這個可以想成映射在某個平面空間時

  • 每個維度大致上控制「某個概念」
  • Unsupervised learning 並無法掌控、指定哪個維度應該是什麼
  • 概念可以相加
  • 近年 LSTM RNN 這類模型也許可以做得更好,但相較而言, word2vec 架構簡單多了

註:這是很理想的狀況,現實中可能不會這麼理想,相加的概念會被高頻的單字 dominate。例如「○○茶」基本上就是「茶」,除非○○的詞頻夠足以讓 word2vec 的向量夠明確,例如「高山茶」就能與「茶」有所區別;像「伯爵茶」的「伯爵」概念就會被實際的「伯爵」所影響。
這時候就需要更精細的斷詞系統。

還有一些很有趣的特性

\[ \begin{align*} &m_{東京} - m_{日本} \\ \approx& m_{柏林} - m_{德國} \end{align*} \]

因為維基的敘述像「東京是日本的首都」、「柏林是德國的首都」

使用中文維基的 word2vec

m.most_similar(['項鍊'])
墜子       	0.798
項鏈       	0.736
墜        	0.707
墜飾       	0.684
鏈長       	0.682
鎖骨       	0.681
鏈        	0.664
鍊        	0.662
墬        	0.650
純銀       	0.649
m.most_similar(['背包'])
束口       	0.713
圓筒       	0.617
書包       	0.591
背袋       	0.587
側        	0.582
中型       	0.572
手提包       0.571
肩背       	0.571
後背       	0.544
斜背       	0.539
m.most_similar(['高山茶'])
烏龍茶      	0.722
阿里山      	0.715
茶        	0.708
蜜香       	0.685
金萱       	0.683
包種茶      	0.681
紅茶       	0.681
高山       	0.677
烏龍       	0.675
茶農       	0.670
m.most_similar(['高山', '茶'])
阿里山      	0.837
烏龍       	0.818
烏龍茶      	0.795
紅茶       	0.794
金萱       	0.786
茶葉       	0.769
高山茶      	0.765
綠茶       	0.752
茶園       	0.742
手採       	0.741
m.most_similar(['白毫', '烏龍'])
烏龍茶      	0.861
金萱       	0.809
凍頂       	0.804
紅茶       	0.788
綠茶       	0.780
阿里山       0.779
蜜香       	0.763
青心       	0.759
甘潤       	0.747
東方美人茶    0.744
m.most_similar(['藍色'])
綠色       	0.870
紫色       	0.862
黃色       	0.851
粉色       	0.827
紅色       	0.814
桃紅色      	0.798
橘色       	0.794
藍        	0.785
白色       	0.767
淺        	0.766
m.most_similar(['涼宮', '春日'])
朝比奈實     	0.660
小涼宮      	0.653
有希       	0.635
虛妹       	0.630
nyoron   	0.593
阿虛       	0.592
小鶴屋      	0.583
喜綠江      	0.574
屋學       	0.564
實玖瑠的     	0.559
m.most_similar(['玲音'])
狼雨       	0.564
texhnolyze	0.561
flcl     	0.558
clamp    	0.542
沙羅鬼      	0.539
寶魔       	0.535
亡念       	0.534
碧奇魂      	0.527
chobits  	0.526
蟲師       	0.526

FYI: nyorontexhnolyze
wiki 很宅der

在論文上看得到的比較

紐約 - 美國 = ? - 英國
倫敦       	0.837
逛        	0.817
一線       	0.816

北京 - 中國 = ? - 德國
柏林       	0.792
榮獲       	0.762
旅德       	0.746

WTF 系列

宅男 - 男生 = ? - 女生
正妹       	0.836
宅女       	0.829
宅男女神     	0.823

腐女 - 女生 = ? - 男生
御宅族      	0.779
傲嬌       	0.750
yaoi     	0.749

Demo

Demo 2

🔍 寬肩點點連身洋裝

  • 大點點罩衫洋裝
  • 雙色點點棉洋裝
  • 點點蝴蝶結背心長洋裝
  • 長長背心裙洋裝
  • 點點洋裝

Word2vec 總結

今天用的未必是最好的方法

「咦你剛剛說用△△△,但那不是………為什麼不用○○○效果更好」

What's next for ?

我們關心並努力改善來自各地的使用者體驗

Pinkoiis hiring

Back-End | QA | Data | Search
Front-End | iOS | Android

Questions?

Misc. Topics

以下言論不代表
公司立場 XD

高記憶體
用量調整

  • gensim 拿 w2v 機制
  • 加上自己的 model 可能就需要占用 4GB 以上記憶體
  • load 4GB 記憶也讓 service 啟動時間變長

Disk caching

  • 用 database?還沒試
  • 有個 library 叫 HDF5
  • 不是 HDFS
  • 大型矩陣存取
  • 實測效能沒問題
  • 省了近 2GB RAM

以前 COSCUP 結束後,會眾紛紛撰寫網誌,紀錄一年一度的盛會,現在參與人數這麼多,實際紀錄的人卻更少,而且鮮少是著墨議題本身,反過來說,日本朋友紀錄的內容還比台灣會眾清楚 (?)

— Jim Huang (@jserv) August 19, 2015

  會成為勇敢、強壯而溫柔的人嗎?會成為絕頂聰明,卻憤世嫉俗的人嗎?或者,會像絕大多數的人一樣,成為一個專業、庸俗、社會化完全的「大人」?我們自少年時極力奮鬥的,不就是要抗拒這樣的馴化,拒絕體制將我們抹壓成同一張臉孔?……
  ……我希望他們每個人都能從青春裡直接長成正直而勇敢的大人。能不能不要忘記我們曾經衝撞過的理想,流的眼淚,對共同體的愛,為彼此寫的詩,把那種熱情和感動在長長的歲月裡捶打成綿長不絕的,日夜不竭的,為台灣這塊土地的奮鬥?
— 許菁芳。《The NTU Brothers / 台大男生》

Fork me on Github