索引過程以下:php
┏━━━━━┓ ┏━━━━┓ ┏━━━━━━┓
┃ 預處理 ┃┈┈→┃ 分詞 ┃┈┈→┃ 反向索引 ┃
┗━━━━━┛ ┗━━━━┛ ┗━━━━━━┛
標點符號對文本檢索沒有做用,能夠去除。java
中止詞通常是虛詞、語氣詞。極度高頻詞對檢索沒有意義。英文中止詞通常有:web
a an and are as at be but by for if in into is it no not of on or such that the their then there these they this to was will with
中文中止詞通常有:算法
也 了 仍 從 以 使 則 卻 又 及 對 就 並 很 或 把 是 的 着 給 而 被 讓 在 還 比 等 當 與 於 但
爲了提升轉寫結果的準確性,須要對字符進行轉換處理,包括:數據庫
"Milos Raonic (born 1990) is a Canadian professional tennis player." --> "milos raonic born 1990 is a canadian professional tennis player"
"contact@cnblogs.com" --> "contact@cnblogs.com"
"①③⑨⑤⑥⑨⑧⑤⑤⑧③" --> "13956985583"
拼音語言存在複數、時態、陰性陽性、等詞形變換,須要進行詞根還原。elasticsearch
"connected", "connecting", "connection" --> "connect"
有的搜索引擎不使用詞根還原。考慮詞形變換很是複雜,爲了提升recall採用詞根還原,使用table lookup策略。分佈式
分詞是爲了識別出文檔中的詞,詞是最小的可以獨立活動的有意義的語言成分。信息處理只要涉及到句法、語義就須要以詞爲基本單位。this
英文詞和詞之間有天然分割,中文是沒有分隔符的。中文分詞的精確度受到歧義、未登陸詞的影響,形成很大的困難。中文分詞的通常方法:搜索引擎
咱們使用ik分詞組件,ik有兩種分詞策略:smart策略、max word策略。例如這個句子:spa
1939年的德國,9歲的小女孩莉賽爾和弟弟被迫送往慕尼黑遠郊的 寄養家庭。6歲的弟弟不幸死在了路途中。在冷清的葬禮後,莉賽爾 意外獲得她的第一本書《掘墓人手冊》。
看分詞的結果,先看smart策略:
1939年/德國/9歲/小女孩/莉/賽/爾/和/弟弟/被迫/送往/慕尼黑/遠郊/ 寄養/家庭/6歲/弟弟/不幸/死/路/途中/冷清/葬禮/後/莉/賽/爾/意外/ 得/到她/第一/本書/掘墓人/手冊
分詞結果中一些詞不許確,好比「1939年」、「得/到她」,由於分詞不許確,當用戶搜索「1939」、「獲得」就檢索不到這個文檔,儘管文檔中出現了這些詞。還有「莉/賽/爾」,ik不能識別這個詞,分割成了三個獨立的字。若是用戶搜索「賽莉爾」,只要不嚴格規定出現的順序,也能搜出文檔。
再使用max word策略:
1939/年/德國/9/歲/小女孩/小女/女孩/莉/賽/爾/和/弟弟/被迫/迫/送 往/慕尼黑/慕/尼/黑/遠郊/郊/寄養/寄/養家/家庭/家/庭/6/歲/弟弟/不 幸/死/路途/途中/途/中/冷清/冷/清/葬禮/葬/禮/後/莉/賽/爾/意外/得 到/到她/第一本/第一/一本書/一本/一/本書/本/書/掘墓人/墓/人手/ 手冊/冊
max word多分出了不少詞,這樣就增長了檢索的召回率,可是正確率有所下降,也增長了索引的體積。平臺的索引策略:
Elasticsearch支持分詞接口,好比這個接口: http://localhost:9200/cr/_analyze?text=1939年的德國&analyzer=ik_smart 能夠執行一個分詞計算,使用的分詞器是ik_smart。在分詞結果中能夠看到每一個詞的位置和類型。
ik組件可使用自定義詞典,配置文件:/server/elasticsearch/config/ik/IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴展配置</comment>
<!--用戶能夠在這裏配置本身的擴展字典 -->
<entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
<!--用戶能夠在這裏配置本身的擴展中止詞字典-->
<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
<!--用戶能夠在這裏配置遠程擴展字典 -->
<entry key="remote_ext_dict">http://localhost/ext_dict.php</entry>
<!--用戶能夠在這裏配置遠程擴展中止詞字典-->
<entry key="remote_ext_stopwords">http://localhost/ext_stopwords.php</entry>
</properties>
修改本地詞典和中止詞文件,須要在每一個Elasticsearch節點上修改文件,修改後重啓。
使用遠程字典能夠集中維護字典文件,而且不須要重啓Elasticsearch。ik每分鐘向遠程地址發出請求,請求有一個「If-Modified-Since」消息頭。若是字典沒有變更,返回「304 Not Modified」,ik不會更新字典。若是字典內容有變更,返回字典內容和新的「Last-Modified」,ik更新字典。
反向索引也叫倒排索引,存儲了詞在文檔中的位置。好比有下面三個文檔,已經作好了分詞:
首先統計這三個文檔中出現的全部的詞,給這些詞編號,而且統計詞頻:
詞 | 編號 | 詞頻 |
---|---|---|
A | 1 | 1 |
BC | 2 | 3 |
D | 3 | 2 |
EF | 4 | 1 |
XY | 5 | 2 |
Z | 6 | 2 |
E | 7 | 1 |
MN | 8 | 1 |
而後統計這些詞出如今哪些文檔中,以及出現的位置:
詞編號 | 文檔和位置 |
---|---|
1 | (doc1,0) |
2 | (doc1,1),(doc2,0),(doc3,0) |
3 | (doc1,2),(doc2,1) |
4 | (doc1,3) |
5 | (doc2,2),(doc3,3) |
6 | (doc2,3,4) |
7 | (doc3,1) |
8 | (doc3,2) |
這樣反向索引就創建起來了。如今進行檢索,輸入「BDC」。首先對輸入條件進行分詞,獲得「BC/D」,分別檢索BC和D的集合,查反向索引:
{doc1, doc2, doc3} AND {doc1, doc2} = {doc1, doc2}
獲得搜索結果集合:doc1和doc2。
Elasticsearch是一個分佈式的全文檢索數據庫,封裝了Lucene的功能。Lecene實現了分詞和倒排索引的功能。Lucene的索引是分域存儲的,比純粹的文本索引更加複雜,具體的原理能夠看Lucene代碼。
term檢索的條件寫法,用SQL表達就是:
SELECT * FROM index/type WHERE status='yes'
term是不分詞檢索,也就是對檢索條件不分詞。因此通常對非文本字段、不分詞的文本字段使用這樣的檢索。對分詞文本字段用term檢索要慎重。好比對這樣的文檔:
fulltext: AB/CD/E/FG
若是用term檢索:
WHERE fulltext='CDE'
因爲文檔的fulltext字段中不存在「CDE」這個詞,因此檢索不到文檔。
matchQuery條件寫法:
SELECT * FROM index/type WHERE fulltext=matchQuery('ABE')
matchQuery是分詞檢索,先對檢索條件進行分詞,獲得「AB/E」,而後尋找fulltext中同時包含「AB」和「E」的文檔。咱們對fulltext進行ik max word分詞,matchQuery使用ik smart分詞,這樣可以最大限度的避免分詞不許確對效果的影響。
spanNearQuery條件寫法:
SELECT * FROM index/type WHERE fulltext=spanNearQuery('ABE')
spanNearQuery是分詞檢索,同時要求條件在文檔中必定要連續出現。因此spanNearQuery要求索引和檢索必定要使用徹底相同的分詞策略。好比對「遠郊寄養家庭」,分詞後是「遠郊/郊/寄養/寄/養家/家庭/家/庭」,若是檢索條件是:
WHERE fulltext=spanNearQuery('寄養家庭')
搜索時先對條件使用一樣的分詞策略,而後判斷fulltext中是否連續出現搜索詞。
query條件寫法:
SELECT * FROM index/type WHERE fulltext=query('chin*')
query也是分詞檢索,首先在字典中尋找符合「chin*」通配符的詞,好比「china」、「chinese」等等。而後尋找含有任一詞的文檔。
把關鍵詞從文檔的海洋中檢索出來只是萬里長征走完了第一步,後面還有一件更重要、難度也更大的事情:排序。對於海量數據來講,排序不合理和檢索不正確形成的後果其實沒有多大的區別(甚至要更嚴重,我的觀點)。
Lucene默認的排序方式是根據關鍵詞文檔相關性,默認的算法是TF-IDF。TF詞頻(Term Frequency),IDF逆向文件頻率(Inverse Document Frequency)。
具體的公式這裏就不寫了,維基百科上有個例子,https://zh.wikipedia.org/wiki/TF-IDF ,這裏說明一下:
首先計算單詞的TF-IDF:假如一篇文件的總詞語數是100個,而詞語「母牛」出現了3次,那麼「母牛」一詞在該文件中的詞頻就是3/100=0.03。一個計算文件頻率(DF)的方法是測定有多少份文件出現過「母牛」一詞,而後除以文件集裏包含的文件總數。因此,若是「母牛」一詞在1,000份文件出現過,而文件總數是10,000,000份的話,其逆向文件頻率就是log(10,000,000 / 1,000)=4。最後的TF-IDF的分數爲0.03 * 4=0.12。
再計算關鍵詞與文檔的相關性:根據關鍵字k1,k2,k3進行搜索結果的相關性就變成TF1 * IDF1 + TF2 * IDF2 + TF3 * IDF3。好比document1的term總量爲1000,k1,k2,k3在document1出現的次數是100,200,50。包含了k1, k2, k3的document總量分別是 1000,10000,5000。document set的總量爲10000。 TF1 = 100/1000 = 0.1 TF2 = 200/1000 = 0.2 TF3 = 50/1000 = 0.05 IDF1 = In(10000/1000) = In (10) = 2.3 IDF2 = In(10000/10000) = In (1) = 0; IDF3 = In(10000/5000) = In (2) = 0.69 這樣關鍵字k1,k2,k3與document1的相關性 = 0.1 * 2.3 + 0.2 * 0 + 0.05 * 0.69 = 0.2645 其中k1比k3的比重在document1要大,k2的比重是0.
這是理論上的相關性算法,實際上能夠根據其餘因素來修正。好比Google的page rank算法,會提早根據鏈接數和引用數算出網頁的page rank得分,計算關鍵詞相關性的時候還會考慮關鍵詞在網頁上出現的位置(title、meta、正文標題、正文內容、側邊欄等等),給出不一樣的相關度。也能夠根據某個業務屬性強制排序(好比create_time等等)。