Django全棧教程系列番外篇 - ElasticSearch CheatSheet
0x00 前言
本文為 Cheatsheet 類型文章,用於記錄我在日常編程中經常使用的 ElasticSearch 相關和命令。
最早使用 ElasticSearch 是兩年前了。最近準備用 Django 寫一個全棧式的應用,借用強大的 ES 來做搜索。
這是我在寫程序之餘寫這篇筆記的原因。
最近因為換工作的事情教程更新速度稍微慢一些,就把這篇筆記放出來吧。
不定期更新。
官網介紹 ElasticSearch 不僅僅是全文搜索,也可以結構化搜索(這裡用結構化查詢會更準確一些),處理人類語言,地理位置,以及關係。
然而,我在項目使用過程中還是主要用到了全文搜索以及推薦。
不用其他的主要原因是因為 ES 尺有所短寸有所長:
- geo 處理方面 postgis 完全就是神一般的存在。為什麼還要用 ES 呢?
- 關係型資料庫的核心不就是處理關係?複雜的關係肯定還是放在關係資料庫裡面。
- highlighted search
- search-as-you-type
- did-you-mean suggestions
我對 ElasticSearch 在後台組件里的作用在於搜索與推薦:
- 整站的搜索功能
- 全文搜索
- 推薦
- 依據某幾個維度的數據進行排序
知乎的文章居然不支持 toc, 實在是太蛋疼了。
文章目錄如下
▼ 0x00 前言 : section ▼ 0x01 安裝,配置,基本 shell 命令 : section 1. 安裝 : section 2. 配置 : section 3. 插件 : section 0x02 ElasticSearch 配套工具 : section ▼ 0x03 ElasticSearch 基礎概念 : section ▼ 3.1 Elasticsearch CRUDE 以及基本操作 : section CURDE : section 結構化搜索 : section 聚集搜索 : section ▼ 0x04 全文搜索的基本概念 : section 4.1 全文搜索遇到的挑戰 : section ▼ 4.2 全文搜索的索引時與查詢時 : section 1. 索引時 ES 做了什麼? : section 2. 查詢時 ES 做了什麼? : section 3. 全文搜索調優之中文分詞 : section 4. 全文搜索調優之停止詞 : section 5. 全文搜索調優之同義詞 : section 6. 全文搜索調優之拼寫錯誤 : section ▼ 7. 全文搜索調優之相關性 : section 索引時三因素 : section 查詢時 : section 計算公式 : section 0x05 搜索語法 : section 0x06 Python SDK : section 0x07 踩坑集 : section 0xEE 參考鏈接 : section
0x01 安裝,配置,基本 shell 命令
1. 安裝
具體在項目中的配置建議看一下我寫的配置文章 https://zhuanlan.zhihu.com/p/33920401 和並且參考現有代碼 https://github.com/twocucao/YaDjangoBlog
# 執行如下的命令curl http://localhost:9200/?pretty# 輸出結果{ "name" : "XOGvo8a", "cluster_name" : "docker-cluster", "cluster_uuid" : "fAwp341bQzalzBxRFyD1YA", "version" : { "number" : "6.2.1", "build_hash" : "7299dc3", "build_date" : "2018-02-07T19:34:26.990113Z", "build_snapshot" : false, "lucene_version" : "7.2.1", "minimum_wire_compatibility_version" : "5.6.0", "minimum_index_compatibility_version" : "5.0.0" }, "tagline" : "You Know, for Search"}
2. 配置
配置略
3. 插件
ES 的插件有很多,截止筆者寫這篇文章的時候,ES 最新的版本是 6.2.1 版本。
PS: 兩年前我用的還是 2.3.3 版本。新版本有很多插件配置起來已經有所不同了。比如說 head 現在已經被獨立出來作為一個單純的網頁,chrome 商店可以直接下載。
需要配 ik-analyser. 如果你在 YaDjangoBlog 中起了這個命令,則已經配置完畢。
0x02 ElasticSearch 配套工具
建議使用 Head 插件來進行簡單的查詢與調試。
0x03 ElasticSearch 基礎概念
3.1 Elasticsearch CRUDE 以及基本操作
CURDE
ES 使用的是 RESTFUL API 介面
這也就意味著:
- PUT 創建記錄
- GET 獲取記錄
- POST 更新記錄
- DELETE 刪除記錄
- HEAD 是否存在
結構化搜索
ES 寫複雜查詢的時候,語法亂,這個過程需要多翻看 guide 和手冊。
https://www.elastic.co/guide/en/elasticsearch/guide/current/structured-search.html
全文搜索
全文搜索包含兩個重要方面:
- 相關性:通過 TF/IDF , 距離 , 模糊相似度,以及其他演算法
- 分析:大文本 token 化,用於形成倒排索引。這個過程見 4.2
https://www.elastic.co/guide/en/elasticsearch/guide/current/full-text-search.html
聚集搜索
https://www.elastic.co/guide/en/elasticsearch/guide/current/aggregations.html
0x04 全文搜索的基本概念
4.1 全文搜索遇到的挑戰
在最初開源搜索引擎技術還不是很成熟的時候,我們一般都會使用 RDBMS 進行簡單搜索。
簡單搜索,也就是我們常常使用的 like 查詢(當然,有的資料庫可以使用正則表達式)
這種方式是簡單暴力的查詢方式,優點是實現起來簡單暴力。缺點是在這個場景下性能和準確度很差。
舉例:
- 假如站點裡文章數量比較大,並且文章內容比較長,則進行一次全表查詢,效率可想而知。當然,做好分庫分表讀寫分離也是能用的。
- 如果我要對搜索到的詞語進行高亮,則實現方式就只能是把查詢到的文章放在應用層裡面進行批量替換。
- RDBMS 似乎完全不懂語言與語言之間的區別。比如說:
- 『停止詞 / 常用詞』有的字我是不需要的,比如南京的狗,其實我想搜的是南京狗,這裡的『的』就不是我需要的。
- 『同義詞』有的字我需要的是他的同義詞,比如日本黃狗,其實我想搜的是柴犬。
- 『附加符號』假如說我們搜索一個聲調 [nǐ], 總不能讓用戶打出 [nǐ] 進行搜索吧?總歸要轉為 ni 才能方便搜索
- 『詞根形式』對於一個單詞,假如是動詞可能有時態上的區分,如果是名詞,可能有單複數的區分。假如我搜 mice, 其實同樣的 mouse 也應該被搜索出來。但有事用這種方式也會矯枉過正,比如 organizations 的 原型其實並不是 organization 而是,organ. (當然,overstemming 和 understemming 也是兩個不可忽視的問題)
- Number: fox, foxes
- Tense: pay, paid, paying
- Gender: waiter, waitress
- Person: hear, hears
- Case: I, me, my
- Aspect: ate, eaten
- Mood: so be it, were it so
- PS: 萬幸的是,中文處理中木詞根這個概念。我也就不深入這塊了。
- 『拼寫問題』 周傑棍與周杰倫
- 『分詞 / 識別詞』中文不像英文,詞和詞之間是完全沒有空格的,也就是說,中文天然要比英文多一個關於分詞的步驟。
是的,我們需要一種新的姿勢,來進行搜索。也就是本文所說的全文搜索。
4.2 全文搜索的索引時與查詢時
本小節先搞清楚兩個點,
- 索引時 ES 做了什麼?
- 查詢時 ES 做了什麼?
- 索引時,指的是 ElasticSearch 在存儲文檔的階段。
- 查詢時,指的是 ElasticSearch 在查詢文檔的階段。
1. 索引時 ES 做了什麼?
這裡我們略過定義 index,type,document 僅僅指某個 field 被賦值 document 被保存的時候針對這個被賦值的 text 類型 field 的處理。
- 第一步:文本經過 analyzer 處理
- 第二步:形成倒排索引
先看第一步:
通常在定義 field 的時候顯式指定 analyzer(分析器).
這個 analyzer 一般的作用如下:
- STEP 1: 令牌化文本為獨立的詞
- STEP 2: 詞語轉小寫
- STEP 3: 去除常見的停止詞
- STEP 4: 獲取詞的詞根的原型
不同的 analyzer 作用大同小異,拿我們常用的 https://github.com/medcl/elasticsearch-analysis-ik 的話,則也是類似的步驟(下面步驟是我猜測的,沒看源碼)
- 令牌化文本為獨立的詞語 - 分詞,並且令牌化文本為獨立的辭彙
- 除去常見的停止詞
- 匹配同義詞
- ....
可以定義欄位的時候可以指定 analyzer(索引時) 與 search_analyzer(查詢時)
先看經過第一步之後,就可以進入第二步形成倒排索引了,此時,倒排索引之於 ElasticSearch 可以類比於 btree 之於 MySQL 或者 Gist 之於 PostgreSQL.
那麼,倒排索引包含哪些東西呢?
- Terms dictionary
- 已排序完畢的 terms, 以及包含這些 terms 的 documents 的數量。
- Postings List
- 哪些 document 包含這些詞
- Terms frequency
- 每個 term 在這些文章的頻率
- Position
- 每個 term 在每個 document 的位置,這是為了便於 phrase query 和 proximity query
- 高頻詞的 phrase query 可能導致 上 G 的數據被讀取。雖然有 cache, 但是遠遠不夠。
- Offsets
- 每個 term 在每個 document 的開始和結束,便於高亮
- Norms
- 用於給短 field 更多權重的因素.(TODO: 啥玩意)
減少停止詞僅僅可以減少少部分 terms dictionary 和 postings list , 但是 positions 和 offsets data 對 index 的影響則是非常大的。
2. 查詢時 ES 做了什麼?
- 第一步:文本經過 analyzer 處理
- 第二步:查詢倒排索引
其實搜索的就是這個玩意。
- Terms dictionary
- Postings List
- Terms frequency
- Position
- Offsets
- Norms
於是,我們就必須關注如何更好的查詢文檔了。下面幾個小節,你就知道全文搜索是比較難調優的了。好,一個一個來。
3. 全文搜索調優之中文分詞
中文分詞以前是個難點,現在基本有成熟的解決方案,在沒有更加牛逼的分詞技術解決方案之前,現在分詞效果主要是拼詞典。
TODO: 這個話題可能比較大,先挖坑,以後填
4. 全文搜索調優之停止詞
使用停止詞是減少索引大小的一種方式(減小索引效果不明顯),那麼,哪些詞語可以唄當做停止詞呢?
- 低頻詞語:低頻詞語具備高權重
- 高頻詞語:高頻詞語具備低權重
當然,是否是高頻詞語依據個人經驗主要依據兩點來判斷:
- 具體場景:比如在英文中,and/the 之類的會比較多,但是中文會比較少。同樣的,中文裡面其他語言的東西會少一些。正文八經的文章出現不正經的辭彙的概率會低。在技術問裡面,『資料庫』屬於高頻辭彙,但是在比如簡書之類的,可能夢想 / 雞湯 / 超級 / 震驚會多一些。掘金的『前端』兩個字絕壁是高頻詞。
- 抽樣跑新詞發現的程序。社區里多的是新詞發現的腳本。對文章內容或者從搜索框記錄下來的搜索詞跑一下新詞發現的程序,然後人工篩選,應該可以發現更多的高頻和低頻的辭彙。
是不是用上停止詞就好了呢?並不是。
比如:
- 假如停止詞裡面包含了 not , 那麼 happy 和 not happy 搜索出來的結果則一致。
- 假如停止詞裡面包含了或,那麼,如果有個樂隊名字叫做『或或』, 則搜索不出來。
- 假如停止詞裡面包含了 to / be / not / or , 則莎士比亞的名言 『To be, or not to be』 則搜索不出來。
5. 全文搜索調優之同義詞
同義詞也有很多種:
- 平級關係:插、戳、刺、扎
- 包含關係:成人包含男人和女人
- 不容易分清楚關係:
- 炒,煎,貼,烹,炸,溜 - 汆,涮,煮,燉,煨,焐 - 蒸,鮓 - 鹵,醬,熏,烤,熗,腌,拌,焗
隨著場景的不同,上面有些同義詞也是不能輕而易舉同義的。
-索引時查詢時索引大小耗時變多,同義詞被索引,大小更大耗時幾乎不變相關性準確度下降,所有同義詞相同 IDF, 則在所有文檔的索引記錄中,常用詞和冷門詞權重相同準確度提升,每個同義詞的 IDF 將被校正性能性能下降,查詢需要漲到性能下降,查詢被重寫,用於查找同義詞靈活性變差,同義詞法則不改變已存在記錄,需重新索引不變,同義詞法則可被更新,無需重新索引
由此可見,大部分場景下的索引時如果沒有特別的需求,謹慎使用同義詞。
同義詞使用自定義 filter , 並且在新建 analyzer 並指定 filter 即可。
6. 全文搜索調優之拼寫錯誤
有的時候,用戶也會輸入錯誤:
- 手誤,把『周杰倫』拼成『周傑棍』
這個時候,搜索引擎應該提示一下,您搜索的是不是『周杰倫』呢?
這裡面就遇到了一個問題,我們顯然知道周傑棍和周杰倫是是相似的,為什麼呢?或者說,直觀上感知的詳細,能用數學方式表達出來嗎?
有人說,正則匹配 / 通配符匹配唄。這是一個思路。
Vladimir Levenshtein 和 frederic damerau 給出了一種相似度演算法 https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
一個片語通過轉換到另一個詞的步數就是其距離:
- 替換:『周杰倫』到『周傑棍』
- 插入:『周傑』到『周傑棍』
- 刪除:『周杰倫』到『周傑』
- 相鄰字元轉換:『周倫傑』和『周杰倫』 , 但是『周傑棍的雙節倫』到『周杰倫的雙節棍』 並不是相鄰字元轉換
用法:
指定 "fuzziness": step 即可
當 step >=2 的時候,ES 進行查詢的時候,每次查詢都會遍歷 terms 字典,所以,如果 fuzziness 大於 2 的時候遍歷 terms 的數量則非常驚人了。
- 方法一:設置 prefix_length, 單詞的前面一定長度不進行 fuzzy 匹配。一般設置為 3 (估計這是屬於英文的匹配,中文環境做不了參考);
- 方法二:設置 max_expansins, 類似於 RDBMS 的 limit, 查詢到一定記錄之後停止查詢。
fuzzy match query 也是支持的,比如說,假如你指定 "fuzziness" 為 1, 搜索周傑棍,則將周杰倫,周傑全搜索出來了。似乎搜索的很全面呀,但是問題來了:
依據 TF/IDF 的高頻低權重,低頻高權重的計算方式,周傑棍由於出現次數極少,反而獲得了極高的權重。
跑個題,這種因為『出現次數少,查詢的時候反而顯得權重較高』的情況。並不僅僅出現在 TF/IDF 演算法上。
這方面搜索引擎和人是一樣一樣的
- 小孩子聽膩了家長們所說的『帶著腦子去學習』, 反而覺得以前沒出現的新詞叫『刻意學習』牛逼到爆。
- 美女聽膩了直男癌說的『漂亮』, 反而覺得誇她『品質 / 品味』的話語詞詞入心。
回到正題
所以,一般情況下還是建議拼寫錯誤主要還是用於:
- Search as you type : completion suggester
- Did you mean : phrase suggester
7. 全文搜索調優之相關性
我們在接觸 RDBMS 的時候系統是沒有相關性的說法的,比如說,2017 年 12 月份 xxx 用戶的訂單,就是直接 select 出來這些訂單。因為 where 語句後面包含了界限明確的條件,而全文搜索則不然。
這個時候一個人拍著桌子站起來,說:不對呀,我要搜索包含周杰倫的所有文章。這咋沒有條件邊界。
嗯,稍等,『選出所有包含周杰倫的文章』條件很清晰。但問題是,排序怎麼做?按照日期排?按照點擊率排?這篇文章上周已經在在搜索靠前了,已經『長江後浪推前浪了』上了,這周是不是該差不多『前浪死在沙灘上』了?
Elasticsearch 中使用的計算 score 的公式叫做 practical scoring function, 這個公式借鑒於 TF/TDF 以及 矢量空間模型,但有更多的特徵比如,條件因素,欄位長度正態化,term / query clause boosting
全文搜索不僅僅找到匹配的 documents, 並且按照相關性進行排序(其實就是打分 score)。
為什麼需要打分呢?從相親角度來說,上海內環有房肯定是個超級大加分項。同樣是錄入信息,在上海內環有房的權重值可是設置的高一些。
嗯,其實相關性的調優是最難的部分。
索引時三因素
先看前兩個因素 TF/IDF
- tf(t in d) = sqrt(frequency)
- idf(t) = 1 + log (numDocs / (docFreq + 1))
再看後一個因素 Field-Length norm
標題越短,這個詞對這個 field 的代表性越強
- norm(d) = 1 / sqrt(numTerms)
查詢時
幾個詞 -> 幾維度 -> 尋求最佳匹配以及近似匹配
- 最佳匹配應該是通過計算長度(應該是,但不確定)
- 近似匹配,計算距離最近的 cos 值。
計算公式
這個公式調優的時候需要用到
score(q,d) = queryNorm(q) ·coord(q,d) ·∑(tf(t in d)·idf(t)2·t.getBoost()·norm(t,d)) (t in q)
0x05 搜索語法
Single document APIs
- Index API
- Get API
- Delete API
- Update API
- Multi-document APIs
Multi Get API
- Bulk API
- Delete By Query API
- Update By Query API
- Reindex API
0x06 Python SDK
官方提供了兩個 SDK 方便我們進行日常的開發:
- elasticsearch
- elasticsearch_dsl
我更喜歡 elasticsearch , 而不是 elasticsearch_dsl, 因為寫起來更容易結合 elasticsearch-head 進行 profile
前者偏底層一些,後者偏高層一些,高底層關係的有點類似於 sql 和 sqlalchemy core 之間的關係。
0x07 踩坑集
0xEE 參考鏈接
- https://www.zhihu.com/question/19645541
ChangeLog:
- 2018-02-15 重修文字
- 2018-03-31 編輯器居然不支持表格?
推薦閱讀:
※Elasticsearch Logstash Kibana(ELK)代碼和知識點總結(三)
※elastic search集群配置
※Elasticsearch與Solr的選擇?
※ElasticSearch優化系列二:機器設置(內存)
※elasticsearch-數據自動刪除
TAG:Python | Elasticsearch | 中文分詞 |