1. 程式人生 > 其它 >相關性搜尋簡介——常規技術與應用

相關性搜尋簡介——常規技術與應用

原文地址:相關性搜尋簡介——常規技術與應用

參考資料:《資訊檢索導論》 // 《Lucene in action, 2ed》 & code // Lucene 8.11 Doc // 《Practical Apache Lucene 8》 // 《相關性搜尋》 & code // 《精通特徵工程》 //

《Lucene in action, 2ed》基於老版 Lucene 3.0(2021 年 Lucene 已經發展到 8.xx,歷史版本可參考 Lucene Change log),但依舊可以使用這本書瞭解 Lucene 和資訊檢索的基本概念

本文是我對搜尋技術與應用學習的總結,包括搜尋的底層資料結構與基礎演算法。機器學習在搜尋中的應用將在其他博文中介紹。閱讀本文需要有一定的演算法基礎和搜尋經驗,最好使用過 ElasticSearch、Solr 或者 Lucene 等搜尋相關工具

相關性搜尋

相關性是一種改進使用者搜尋結果的實踐,它在使用者體驗的具體上下文中滿足使用者的資訊需求,同時平衡排名業務需求的影響

隨著時代的發展,我們被海量的資訊所包圍,快速有效的獲取資訊是非常重要的研究課題。現階段,相關性搜尋是我們獲取資訊的重要技術手段。資訊檢索(Information Retrieval, IR)是相關性搜尋的技術基礎

相關性搜尋有其自身的複雜性,因為不同場景下的相關性有不同的意義,而且搜尋引擎除了滿足使用者相關性需求外也要滿足業務需求,所以搜尋業務需要技術人員和業務人員一起努力實現

下面是不同場景下對搜尋的要求

  1. Web 搜尋中為了剔除虛假網站,Google 開發出了 PageRank 等演算法,用網頁之間的相關性剔除惡意網頁
  2. Amazon 等電商網站沒有虛假網站的問題,但為了盈利,搜尋引擎需要考慮其他方面的因素,例如清理庫存等。從使用者的角度來說,除了相關性,物美價廉是電商搜尋的預設因素
    1. 租房搜尋網站還需要考慮距離、學校、房間數等因素
  3. 對於百科、空間、部落格相關網站的搜尋工具,過濾過激、暴力和違法等資訊也是非常重要的功能
  4. 專家搜尋。在法律、醫療和研究等領域,專業詞語之間的轉換需要專業的知識
  5. 其他領域。圖片、歌曲、視訊的相關性搜尋也有各自的技術特點。結合深度學習實現多媒體的搜尋也是相關性檢索的重要應用場景

構造相關性搜尋工具

graph LR A[蒐集資訊和需求] B[設計搜尋應用] C[部署監控與改進] A-->B B-->C
  1. 蒐集資訊和需求
    1. 理解使用者及資訊需求;蒐集必要的搜尋資訊,例如文件、位置、價格等等
      1. 領域專家與專業使用者的建議非常重要。反饋與內容管理
        是搜尋引擎進行改進的核心驅動
    2. 明確業務需求,免費還是付費等
  2. 設計搜尋應用
    1. 實用搜索引擎有多種改善搜尋體驗的工具,例如返回搜尋文件時返回部分高亮文件,用於告知使用者為什麼文件會命中;搜尋介面 UI 簡單易用,提供一些額外的功能,比如拼寫糾正、搜尋提示等
  3. 部署監控與改進
    1. 保留使用者搜尋行為日誌,便於後續分析改進搜尋策略。行為日誌包括但不限於使用者搜尋詞、搜尋結果、頁面停留時間、轉化率、使用者黏度和翻頁資訊等
      1. 監控資料可以使用者機器學習的素材,使用統計方式提升搜尋體驗
    2. 其他

檢索的技術基礎

graph TB A[search tech] B[正/倒排索引] C[搜尋模型] D[bool/向量/概率] E[特徵提取] F[分詞] G[語義提取] H[TFIDF] I[非文字] J[音訊<br/>Parsons code] K[視訊語義] L[特徵擴充套件] M[同義詞] O[文字特徵] P[地理位置] A-->B A-->C C-->D A-->E E-->O O-->F O-->G O-->H E-->I I-->J I-->K E-->L L-->M I-->P

倒(正)排索引

相關知識請參考其他書籍或 ES 官方簡介正排索引是與倒排索引相關的概念

倒排索引中的 token 還有很多其他屬性,比如:文件頻率、詞頻、詞位置、詞偏移量、負載資料、儲存欄位、文件值等,這些屬性可以用於相關性分析

搜尋只能搜尋到被索引的詞項(Term),所以文件的檢索過程會深刻影響搜尋結果。比如過濾了 stopword 的搜尋引擎如果不做其他處理即使新增相關文章也搜尋不到 to be or not to be ,此時應該新增短語提取模型

相關性排序

檢索到的文件展現給使用者時是有順序的,排序使用的是相關性。ElasticSearch (ES) 中的 relevancy explains,是對檢索結果得分的詳細說明,是底層打分細節,在分析檢索結果時非常有用

搜尋理論模型

  1. 布林查詢,沒有匹配得分。文件要麼匹配要麼不匹配查詢語句。細節可以參考 Lucene 布林查詢。Lucene 的布林查詢提供了 must(+)、should 和 must_not(-) 三種形式,例如 black -cat +dog
  2. 向量模型,查詢語句和文件都被轉換為高維向量,查詢結果是兩個向量的距離,比如餘弦距離。向量模型常用於查詢相似項,因為推薦和搜尋息息相關,所以向量模型在推薦系統中也比較常見
  3. 概率模型,計算查詢語句和文件的相似度,使用概率度量(常基於貝葉斯)。典型的概率模型是 BM25

文字特徵化

特徵是原始資料的數值表示。在機器學習流程中,特徵是資料和模型之間的紐帶,正確的特徵可以減輕構建模型的難度,從而使機器學習流程輸出更高質量的結果

上面對特徵和機器學習之間關係的描述也適用於相關性搜尋,文字特徵化是一切搜尋的前提。搜尋研發很大一部分工作是在做特徵提取,發現與構造特徵。相關性搜尋中常見的特徵處理方法有詞袋、N 元詞袋等。基於樸素詞袋的方法無法提取詞語之間的語義,而且如果不做過濾,簡單的詞袋會充斥著大量的無效單詞(比如 stopword)和海量沒有實際意義的短語,造成儲存與計算資源的浪費

分詞

分詞是用來提取語義而非單詞的

分詞一般同時用在查詢語句和待查資料。分詞不是簡單的提取單詞,而是將真實語義轉化為 token,比較常用的技術手段有標準化、差異化、詞幹分析和語音分析等。例如 apples 標準化為 apple,甚至直接轉化為詞幹 appl。不少應用場景會同時使用多種形式的分析器處理同一個欄位(ES 中支援同一個欄位使用多個分析器:multi-filed mapping ),從而獲得更為豐富的語義 token

  1. 分隔符處理。例如:縮略詞:I.B.M -> IBM、N.E.W->new enginerring week;不同型別的電話號碼分詞
  2. 獲得同義詞的語義 & 為專指性建模。例如 dog 可以轉化為 beagle、poodle
  3. 分詞應用場景的多樣性:數值、日期、地理位置、影象、形狀、質地和聲音等等
    1. 簡單的音訊分詞可以直接將音訊轉換為U、D 和 R三種類型的文字序列(Parsons code),U/D/R分別表示當前音相比於前置音高/低/不變。結合 N 元詞袋等方法可實現搜尋
短語和詞位置 & PhraseQuery & shingle

單詞的相對位置有時是重要的資訊。有些短語不能隨意把單詞拆開作為獨立的單詞進行使用,比如禮服鞋 dress shoes ,如果將短語拆開查詢,返回的頁面可能包含女士服裝,這個一般不是使用者需要的。Lucene 提供了 PhraseQuery 介面用於查詢單詞間有位置關係的短語

ES 提供了 shingle 過濾器,可以個性化生成 n-grams 短語 token

有效的語義 token

為提升詞袋法的有效性,可以使用不同的方法處理原始資料,比如 TF·IDF、詞幹提取、word2vec、短語搭配提取(Collocations)等。這裡需要注意,不相鄰的單詞也有可能組成有效的短語

以統計方法(包括機器學習)為例,用於訓練短語提取模型的語料庫越豐富,模型的短語提取能力越強。當然,由專業領域語料庫訓練出的模型在其他場景的表現就不一定好。隨著時代的發展,常用短語也在不斷的變化,演算法與語料庫的持續更新也是必需的

為提取有效的語義 token,使用不同演算法與工具處理相同文字是常用的手段

TF·IDF

不同屬性不同位置的 token 其重要性一般不同,TF·IDF 是用於描述 token 顯著性的一種手段

  1. TF 是單詞在文件中出現的頻率,出現的越多說明越重要。但對 the、a 等 stopword 無效,所以需要 IDF 降權
  2. IDF 是逆文件頻率,在大部分文件中都出現的單詞,其重要性有所降低。IDF 用於描述單詞的罕見程度

樸素的 TFIDF 也有一定的缺點,比如出現在文章標題處的單詞和出現在引文中的單詞,其權值不應該相同,所以各搜尋引擎對 TFIDF 做了一定的修正,比如 Lucene 中所使用的 Okapi BM25

  1. 真實的 TF 對評分的影響一般不是線性的,常常被抑制,比如使用 \(sqrt(TF)\)
  2. 文章的長度也應該考慮在內,長文件出現更多單詞的概率也更大
  3. 相同的單詞出現在文章不同位置分數不同

使用搜索引擎時因為查詢語句較短,所以查詢語句一般不使用 TF 來評價單詞的重要性。搜尋引擎一般會提供方式來提升(boost)某些單詞的顯著性

除錯搜尋結果

graph TB A[概念] B[查全/查準率] C[tools] D[ES/Solr] E[DSL->query Str] F[explain] G[signal] H[boost<br/>shingle] I[best/most<br/>field] J[詞/欄位為中心] K[白化象<br/>認知偏差] L[cross field] M[dis max] N[聚合欄位<br/>copy to] O[欄位同步] P[組合查詢] Q[boost 分層] R[bool<br/>fun score] S[相關性反饋] T[個性化搜尋與推薦] A-->B A-->J A-->G J-->K K-->L K-->M G-->P J-->I C-->D D-->E D-->F G-->H K-->N L-->O H-->Q P-->R A-->S

下面的示例源自《相關性搜尋》,細節請參考原書

查全率(Recall) & 查準率(Precision)

查全率和查準率有一定的相互作用,相同的演算法一個指標的提升有可能會伴隨著另一個指標的下降

  1. 查全率:查詢結果集中返回的相關性文件佔總相關文件的百分比
  2. 查準率:查詢結果集中具有相關性的文件在結果集中的百分比

以水果為例,如果單以顏色(黃、綠、紅)作為條件查詢蘋果,那麼查全率幾乎是 100%,但櫻桃、橙子、梨、芒果和榴蓮等水果也會混入到結果集中,查準率就比較低。查詢時新增額外的條件可以改變查全率和查準率,比如大小、口感和種屬等

使用樸素詞袋演算法進行文字查詢可以獲得較高的查準率,但查全率就會比較低,因為只有精確匹配的文件才會返回。所以常使用詞幹提取、小寫轉換、關鍵字檔案等手段對查詢語句和待查文字進行處理從而提高查全率。這從另一個角度說明分析不是簡單的將單詞轉化為 token,而應該是將語義和使用者意圖轉換為 token

ES DSL 與 Query String

ES 提供了一些介面,可以將 ES DSL 查詢轉換為底層 Lucene 使用的查詢語句,示例如下:

GET /tmdb/movie/_validate/query?explain
{
  "query": {
    "multi_match": {
      "query": "basketball with cartoon aliens",
      "fields": ["title^10", "overview"] # 同時搜尋標題和簡介,並提升標題重要程度
    }
  }
}

下面是上面呼叫返回的 Lucene 查詢語句,可以結合 Lucene 布林查詢 進行分析

"+((overview:basketball overview:with overview:cartoon overview:aliens) | (title:basketball title:with title:cartoon title:aliens)^10.0) #*:*"

從 Lucene 查詢語句可知,單詞 with 是一個非常重要的搜尋因子,這是不合理的,從 ES 的返回結果也可以看出:

Num	Relevance Score		Movie Title
1	85.56929		    Aliens
2	73.71077		    The Basketball Diaries
...
9	45.221096		    Friends with Benefits

通過分析,可以修改預設分詞器,從而過濾 stopword。ES 不支援直接在已有索引上修改分析器,一般需要先建立一個新的索引,配置好 mapping 後再 _reindex,細節請參考這裡

相關性計算細節

使用查詢介面時如果置 explain 為 true,ES 將返回相關性計算細節,如下所示:

GET /tmdbenglish/_search
{
  "explain": true,
  "size": 100,
  "_source": ["title"],
  "query": { ... }
}

下面是打分過程的部分片段,從 explain 中可以看到 TF 和 IDF 等因素的計算細節

"_explanation" : {
	"value" : 12.882349,
	"description" : "max of:",
	"details" : ...
}

訊號建模

A signal is any component of the relevance-scoring calculation corresponding to meaningful, measurable user or business information

一些資料如果不做處理可能不利於搜尋引擎索引。儲存西方人名的關係型資料庫常將 first/middle/last name 儲存在不同的欄位中,直接使用這類資料建立索引將割裂人名各部分的相關性,降低搜尋精度

ES 與相關性建模

Elasticsearch has no concept of inner objects. Therefore, it flattens object hierarchies into a simple list of field names and values

相關性建模是搜尋引擎和關係資料庫一個非常重要的區別,後者常需要保證欄位與欄位、表與表之間資料的獨立性,而前者需要建立相關性,將有一定關聯的資料放在一起。例如搜尋引擎常合併欄位,在為原始資料建立索引時新增或者直接將原始資料的多個分立欄位合併為一個新欄位,形成一個新的可用訊號

ElasticSearch(ES)預設會修改原始 JSON 文件中內嵌物件的儲存格式,細節請參考 Nested field type。ES 預設會將 JSON 的陣列型別扁平化從而加強部分欄位的相關性(ES 提供配置方式修改預設資料儲存格式),不過某些場景下會降低部分欄位的相關性,具體使用需要看應用場景

boosting

多欄位搜尋時部分欄位應該賦予更高的排序權重,ES 提供了 boosting 方式,方便使用者指定欄位的排序權重

shingle

shingle 是 ES 提供的 token filter,可以生成 n-grams 短語 token,方便實現個性化短語查詢

ES 支援為同一個欄位使用不同的分析器,所以同一個欄位可以同時使用常規分析器和 n-gram 分析器。使用不同分析器的欄位會生成不同名稱的索引項,檢索時指定索引項名稱即可,細節請參考 ES:Multi-fields with multiple analyzers

Term/Field Centric

ES 支援詞為中心(term-centric)和欄位為中心(field-centric)的兩種搜尋排序方式,參考 ES 官網 或者這裡以詞為中心的搜尋更關注搜尋詞而不是內容

ES 中的 multi_match 方法預設使用 best_field 實現 field-centric 查詢,另一種 field-centric 查詢方法是 most_field;term-centric 常用查詢方法是 cross_field。這三種查詢方式的區別可以參考下面的 Lucene 語法。除上面介紹的三種查詢方式, multi match 還支援 phrase/phrase_prefix/bool_prefix

# field-centric
(name:Best name:Laptop ) | (description:Best description:Laptop) # best_field, max(x,y...)
(name:Best name:Laptop ) + (description:Best description:Laptop) # most_field, avg(x,y...)

# term-centric,注意 cross 的表現方式
(name:Best description:Best) + (name:Laptop description:Laptop) # cross_field, avg(x,y...)

不同搜尋場景需要使用不同的相關性排序方式,搜尋電影的時候使用者可能只想搜尋到明確的電影名、演員或者導演,這個時候用 best field 模型比較合適;論文或者網頁搜尋的時候使用者更關心返回結果和搜尋內容的相關性,此時 most field 可能比較合適;部分場景可以結合 best/most field 模型,比如電商搜尋等

best_field 一般只計算最佳欄位與搜尋內容的相關性得分,ES 支援使用 tie_breaker 加入非最佳欄位與搜尋內容相關性得分的計算。下面公式假設 title 是最佳匹配欄位,最終返回評分同時考慮了最佳欄位和其他欄位的相關性,只不過非最佳欄位的重要程度被 tie_breaker 降權

\[score =S_{title }+ { tie\_breaker } \times(S_{overview}+S_{cast.name}+S_{directors.name}) \]

如果 tie_breaker 被置為 1.0,那麼 best_field 就十分類似 most_field

白化象 & 認知偏差

白化象問題(albino elephant problem)是遇見這個問題的工程師對問題描述時舉的例子,後來就用例子來表示這類問題。如下圖所示,從直覺上看本應得高分的右側文件卻和左側文件得到了一樣的分數。這是以欄位以中心查詢方式固有的缺陷,以欄位為中心的查詢方式獨立計算文件中不同欄位的得分,沒有從整個文件的角度計算相關性

認知偏差被很多中文書籍直譯為訊號衝突(signal discordance)。認知偏差是搜尋引擎技術實現和使用者對搜尋引擎功能認識之間的差別。以論文搜尋為例,搜尋引擎內部常將論文的標題、簡介、正文等不同部分作為不同的欄位分別進行處理,然而在部分使用者的搜尋意識裡,論文的標題、簡介和正文應該一同處理。因為認知偏差的存在,很多時候搜尋引擎會返回一些讓人感覺莫名其妙的搜尋結果

Disjunction Max Query

disjunction max query 也被稱為 dis_max 查詢,可以部分解決白化象問題。其中 dis 表示分離(或者離散),max 表示最大,從字面上來講 dis_max 查詢是分別使用子查詢語句查詢文件,返回分值最高的子查詢分數作為最終分數。示例可以參考這裡

下面查詢語句會使用兩條查詢分別查詢文件,使用最大子查詢值作為最終文件評分。如果將 dis_max 修改為 bool,那麼文件的分數將整合兩條語句的分數作為最終文件評分

GET /_search
{
  "query": {
    "dis_max": {
      "queries": [
        { "term": { "title": "Quick pets" } },
        { "term": { "body": "Quick pets" } }
      ],
      "tie_breaker": 0.7
    }
  }
}

如果多個欄位的查詢語句相同,使用 multi_match 並設定 type 為 best_field 可實現 dis_max 查詢並簡化查詢語句

以詞為中心的搜尋

以詞為中心的搜尋方式可以部分解決認知偏差和白化象問題。以詞為中心的搜尋場景下,每個詞在計算相關性得分時會考慮所有欄位

欄位同步

欄位同步是以詞為中心的查詢方式相比於欄位查詢方式的一個缺陷

以詞為中心的查詢方法會使用一個 token 查詢多個欄位,不同欄位可能有不同的分析器,查詢不同欄位具體應該以哪個欄位的分析器為準?可以考慮不同欄位遍歷一邊所有分析器,這樣時間複雜度就是 \(O(m*n)\),其中 m 表示欄位的個數,n 表示所有欄位存在的分析器種類個數。\(O(n^2)\) 時間複雜度的演算法會極大的影響系統性能,且不同欄位使用不同的分析器也會讓文件的評分變得複雜,所以 ES 使用了最簡單的方式,所有查詢使用預設的分析器,這個現象稱為欄位同步

自定義聚合欄位

自定義聚合欄位就是將多個欄位合併成一個欄位,在 ES 或者 Solr 中自定義聚合欄位也被稱為拷貝欄位(Copy field)。拷貝欄位雖然造成資料的冗餘,但解決了白化象問題

ES 中有關自定義聚合欄位的功能可以參考官網 copy_to 。ES 6 之前 ES 預設為每一條記錄添加了一個組合欄位 _all,系統自動將其他欄位合併到這個欄位中,ES 6 之後 _all棄用

合理的對相似欄位進行聚合可以減少一些問題,且可以簡化搜尋。《相關性搜尋》第六章給出了一個例子,將電影中導演的名字和演員的名稱進行了分組與聚合,部分解決了以欄位為中心的白化象問題

cross fields

cross fields 方法也有欄位同步的問題,如果不同欄位之間沒有通用的解析器,查詢可能會報錯。以詞為中心的搜尋在 ES 的 multi_match 中通過設定 type 為 corss_field 即可實現。

組合查詢(嚴格&寬鬆)

利用 ES 提供的聚合查詢介面對同一個查詢語句使用不同的查詢方式,最終綜合查詢結果來解決相關問題

以下面查詢語句為例,第二個 multi_match 用於提升查全率,而第一個 multi_match 用於提升查準率。第二條查詢語句使用了非常寬鬆貪婪的查詢條件,只要有對應單詞的文件就會被返回;第一條查詢語句相對比較嚴格,使用了二元 token,匹配的文件會獲得更高的分數

{"query": {
    "bool": {
      "should": [{
          "multi_match": {
            "query": "usersSearch",
            "fields": ["directors.name.bigramed", "cast.name.bigramed"],
            "type": "cross_fields"}
        },{
          "multi_match": {
            "query": "usersSearch",
            "fields": ["overview", "title", "directors.name", "cast.name"],
            "type": "cross_fields"
}}]}}}

組合查詢也有自身的缺陷,一旦寬鬆查詢有著比嚴格查詢更顯著的訊號,那麼查詢結果不會盡如人意。可以使用 boost 減少寬鬆語句的打分權重,細節可以參考這篇文章

查詢解析器

Lucene 提供了支援 bool 查詢語法的查詢解析器,例如:

(name:Best description:Best) + (name:Laptop description:Laptop)

ES 中使用 query_string 支援 Lucene 的查詢解析器,合理的使用 Lucene 查詢語句可以實現以詞為中心的查詢。ES 版本迭代較快,query_string 在不同版本的功能區別可能較大,比如 ES 6 以後就直接移除了 use_dis_max 屬性,所以 ES6 之前可以使用查詢解析器實現 dismax 查詢,ES6 之後就不行了

評分調整

查詢的排名的優先順序在不同的場景下不同,以電影搜尋為例:精確匹配 > 部分精確 + 熱度 > 某些特殊欄位的匹配 > 其他欄位的匹配

新聞搜尋應給出時事要聞、餐廳搜尋一般把距離較近的餐館放在前面,不同場景對搜尋結果有不同的要求,所以搜尋也需要一定的定製化,需要對相關性搜尋的評分做一些調整

一個簡單的評分調整方式是使用上面介紹的組合查詢:基準(寬鬆)查詢 + 放大(嚴格)查詢。使用寬鬆和嚴格的組合查詢方式有個問題,嚴格查詢語句的 boost 值應該設定為多少?這其實是個無法回答的問題,所以儘量還是不要使用這類方式

boost & boost 分層

boost 是最基礎的評分調整手段,即為不同的欄位賦予不同的權值,ES 中 bool 查詢是支援 boost 的,其他支援 boost 的查詢可以參考 ES 官網

為不同重要程度的訊號賦予有明確層級的 boost 分值,可以很方便區分訊號之間的重要程度。比如將最重要的訊號 boost 設定為 1000;次要的設定為 100;其他的設定為 10 或者更低

function score

使用 function_score 提升符合一定要求記錄的評分,ES 對 function_score 的支援可以參考官網。使用 function score 方法可以為滿足要求的記錄評分乘以一個 boost 數值,從而提升或者降低相關度。搜尋時事新聞時可以使用 function score 根據新聞日期修改文件得分

function score 提供了現成的工具可以實現按照指定欄位屬性的權值衰減,比如按照記錄的時間以指數的形式衰減其搜尋評分。細節可參考《相關性搜尋》第 7.4.7 小節

filter

使用 filter 可以直接過濾掉滿足要求的記錄

精確匹配

使用者查詢語句精確匹配欄位時可以考慮將相關記錄放在返回結果前(給一個相當大 的 boost 值)。以使用者查詢演員名稱而言,精確匹配之後可以按照電影上映時間逆序返回相關電影。為實現精確匹配,我們需要使用一些工具

基於 SENTINEL

在某些 token 的前後插入標記,插入過程可以使用自定義 ES 外掛,搜尋時使用短語搜尋工具,比如 phrase。插入標記的欄位在查詢時也需要攜帶標記,比如:SENTINEL_BEG query-string SENTINEL_END

基於 function score

使用自定義的評分過程增加精確匹配的分值

constant score

精確匹配時可以忽略 TFIDF 值,此時可以使用 constant score 查詢

使用已有評分資訊

已經上映的電影有使用者的打分,可以考慮直接使用這個分數來修改記錄的查詢得分,此時可以利用 ES 中的 field_value_factor 。其他可用的評分資訊還有票房收入、人氣指數和頁面訪問量等等

相關性反饋

相關性反饋通過與搜尋使用者的互動來改善使用者的搜尋結果和搜尋體驗,可用方式如下:

  1. 向用戶解釋查詢與匹配過程,幫助使用者更好的理解搜尋結果。例如可以使用 ES 提供的 highlight 實現關係資訊的高亮
  2. 糾正使用者的輸入錯誤,在使用者搜尋的同時提供一些服務
    1. 實時搜尋(使用 match_phrase_prefix 在輸入的同時展示搜尋結果);搜尋補全與搜尋建議(可以利用 ES 提供的 Suggests 工具)
  3. 向用戶推薦多樣化結果,比如其他型別的相關搜尋
  4. 向用戶展示搜尋結果的分佈情況,便於使用者過濾搜尋結果
  5. 其他

語義和個性化搜尋

個性化搜尋會結合使用者的個性與特點返回符合使用者口味的搜尋結果,個性化搜尋已經開始有了推薦的意味;語義搜尋通過分析使用者的意圖,而不是簡單的使用搜索詞

個性化搜尋需要結合使用者的歷史行為,這個分析過程和大資料息息相關,本文不做深入介紹。以使用者年齡為例,同齡人有一定的相似性,某個年齡段使用者搜尋時可以將同年齡段其他使用者喜歡的產品放在前面。結合協同過濾等推薦演算法可以進一步改進搜尋結果

給物料文件建立索引時可以將其受眾資訊一同加入到索引中,使用者查詢時系統自動新增使用者資訊到查詢語句中,如此便可以實現產品與使用者之間的關聯

構建語義的方法

同義詞 / 機器學習

推薦是廣義的搜尋

其他內容

使用 ES 進行實驗

為了簡化操作我們可以使用 ES7 的機器學習模組匯入資料或者直接使用 Dev Tools 上傳,請參考這裡,但實驗資料比較大時 Dev Tools 上傳不方便。《相關性搜尋》所使用的 程式碼倉庫 內容相對比較老,可以下載並使用 anaconda2 的控制檯視窗進行實驗。書籍倉庫中的程式碼需要做一些修改才能在新版本 ES7 中執行,比如發起 HTTP 請求時如果 http body 中有資料,需要顯式說明 body 資料型別:

resp = requests.post("http://localhost:9200/_bulk", 
                     data=bulkMovies,
                     headers={"Content-Type":"application/json"}) # 需要手動新增
print resp # 檢視返回結果

Kibana 返回的資料型別是 JSON 格式,為方便檢視,可以使用這個網站將 hits 陣列轉換為 CSV

Lucene 簡介

Lucene 是 ES/Solr 底層的儲存與檢索工具,類似於 InnoDB 和 MySQL 的關係。Lucene 除了提供查詢介面,也提供了配置與分析介面,比如執行時索引合併與修改、歷史查詢日誌等。Lucene 4 的組合和結構圖可以參考 Apache Lucene 4

概念

  1. IR,information retrieval
  2. Search Query(搜尋分析),將使用者輸入的單詞或者短語轉化為 Lucene 可識別的語法
  3. boosting,Lucene 中的 boosting 可以用在索引和查詢文件時,通過給不同的域設定不同的分數(加權)以提升或降低其在查詢中的相關性。boosting 可以應用在文件和欄位上,以提升某些文件和欄位的相關性
  4. 其他

Lucene 的特點

  1. Lucene 可不儲存原始資料。插入 lucene 索引文件並建立索引的欄位,其值域資料並不一定需要寫入索引中。單獨存放的值域資料在查詢時也會被匹配,但預設情況下返回值是不包含這類值域資料的
  2. Lucene 中的資料沒有統一的模式。加入同一個索引的文件可以有不同的欄位
  3. Lucene 支援增量索引。部分搜尋庫新增新文件後需要重新索引所有文件,lucene 利用段和段的週期性合併實現增量索引
  4. 靈活的配置。相同文件的不同欄位可以有不同的屬性,比如不同的分詞器、是否儲存、是否索引等選項

檔案檢索過程

  1. 提取,從資料來源提取為文件,比如從資料庫中、從 Web 等。只有資料轉化為文件才能被搜尋引擎索引,ES 使用 JSON 格式的文件
  2. 充實,將有助於相關性檢索的資訊加入到文件中,例如新增額外的描述
    1. 清理,例如糾正拼寫錯誤、去重、去除 Markup 標記(HTML、XML 標籤)等
    2. 強化(提取詞幹、新增同義詞、近音詞、類別屬性等),分類/聚類、情感分析等。一些場景下的索引會提取文件中特定的內容到指定的欄位,比如日期、地址、標題、摘要等等
    3. 合併,填充缺失的一些資訊等
  3. 分析,將文件轉化為可匹配的 token
    1. 任何資料型別都可以被分詞從而生成 token,例如圖片、音訊、視訊等
    2. 搜尋引擎在匹配 token 的時候需要逐位元組對應,所以分析過程非常重要
      1. 字元過濾。以 HTML 頁面為例,字元過濾將去除頁面中無用的 tag ;有些語言的重音去重操作
      2. 分詞處理。引入同義詞,去除詞根等等
      3. token 過濾。所有單詞小寫,去除 stop words
  4. 索引,生成倒排索引

Lucene 3 索引檔案格式

Lucene 底層使用多個索引段(Segment)管理屬於同一個索引的索引資料,每個段檔案格式與功能相同,只是儲存著不同的文件的索引資訊,下圖展示了同一個索引下的兩個索引段 0/1,統一個索引段中的檔案有著相同的檔案字首。查詢時 Lucene 會訪問所有段目錄,綜合不同段的結果後返回最終結果

索引段預設使用多個不同檔案分別儲存不同型別的資料,例如項向量(.tvf)、域資料(.fdx & .fdt)、歸一化資訊(.nrm)、項頻率(.frq)和項位置(.prx)等,細節可以參考 Lucene 3 官網文件

段檔案(Segments File,.gen & _N 檔案)是 Lucene 管理索引段的工具,查詢時 Lucene 會先開啟段檔案檢視哪些段和段檔案是有效的,然後對有效的段進行查詢。存在的段目錄並不一定都是有效的,Lucene 在做段合併(merge)時未完成合並的臨時段目錄是無效的。儲存到磁碟上的索引檔案是不允許修改的,所以 Lucene 只會在合併時完成修改並在合併完成後刪除舊的段目錄

按照 Lucene 的設計,檢索與刪除可以同時進行,檢索在刪除動作將所有內容提交到磁碟前使用的依舊是老資料。未重新整理的 IndexReader 物件可能無法感知檔案的刪除動作,所以儘量不要使用永續性質的 IndexReader 物件(隨著Lucene 的發展,IndexReader 可能會自動重新整理可用段)

Lucene 索引可以使用 luke 視覺化,正排和倒排索引的細節本文不做介紹,可以參考其他資料,例如 Lucene 倒排索引簡述 / 《Practical Apache Lucene 8》,注意不同版本的 Lucene 索引檔案有一定的區別。為了提高資料的查詢速度,Lucene 使用了一些不常見的資料結構和演算法,比如 Burst-trie 等

新文件與索引段

每次新增文件時 Lucene 都會建立一個新段來儲存新增文件的索引資訊,Lucene 通過頻繁建立/合併索引段實現增量索引功能

頻繁建立/合併段需要開啟大量檔案描述符,但大部分系統對單一程序可開啟的檔案描述符個數有限制。Lucene 可以通過頻繁合併小的索引段減少索引段的個數,但這會增加系統負擔。為減少頻繁建立/合併索引段造成的系統負擔,Lucene 支援複合索引段,即將分立索引段中的多個檔案打包成一個檔案(.cfs),這樣建立/合併索引時每個執行緒只需使用三個檔案描述符。Lucene 支援在執行時切換分立索引段和符合索引段

刪除文件

為提升系統性能,對文件執行但刪除操作並不會實時觸發磁碟操作,Lucene 會先標記文件已被刪除,後續索引合併時才會回收對應磁碟空間。Lucene 的標記刪除特性非常適合系統的橫向擴充套件,增加計算機器就可以提升整個系統的查詢吞吐量,而儲存機器在磁碟 IO 沒有佔滿的前提下可以不用擴充套件

標記/合併 刪除

Lucene 使用了和 InnoDB/Redis 類似的刪除策略,刪除資料時資料會被打上刪除標籤,此時其依舊佔用磁碟空間。被刪除資料所佔用的磁碟空間只有在段合併時才會被釋放。Lucene 3 的索引段使用 .del 檔案儲存標記刪除資訊

部分介面

查詢

Lucene 提供了很多種類的查詢介面,比如:

  1. TermQuery,通過 term(項)進行查詢
  2. TermRangeQuery/NumericRangeQuery/PrefixQuery
  3. BooleanQuery,組合查詢。Lucene 3 組合查詢示例如下
  4. PhraseQuery,短語查詢,查詢的短語其組成單詞不一定相鄰,PhraseQuery 可以設定短語間單詞距離的規則
  5. WildcardQuery,萬用字元查詢;FuzzyQuery,編輯距離查詢
  6. QueryParser,表示式查詢:+pubdate:[20100101 TO 20101231] Java AND (Lucene OR Apache) 。QueryParser 會將查詢語句轉換為上面介紹的查詢物件然後進行查詢,有相關工具可以查詢轉換過程於細節。Lucene 3.1 時當前物件提供的查詢語法不能覆蓋所有 Lucene 提供的查詢功能
  7. 其他

優化/評分細節

Lucene 提供了 optimize 介面可以用於索引優化,比如指定索引段段個數。索引合併的過程會佔用大量的 CPU 和 IO 資源,合併期間需要不小於已有資料 3 倍大小的磁碟空間。索引合併可以提升檢索速度但不會提升索引速度

Lucene 提供 explain 介面用於顯示文件的評分細節