倒排索引
基本概念
- 正常索引: 通過唯一id, 查找對應的數據記錄, 比如MySql索引
- 倒排索引: 通過數據內容, 查找包含此內容的id集合
id | title | detail |
---|---|---|
1 | lucene study | base of lucene |
2 | java study | java is the best language |
我們對title
字段建立倒排索引可以得到
term | ids | offset |
---|---|---|
lucene | [1] | 0 |
java | [2] | 5 |
study | [1, 2] | 11 |
term
: 內容在分詞后的基本單元.ids
: 包含此term
的記錄id列表.offset
:ids
存儲在索引文件的offset位置
FST
如何找到term
呢? lucence使用了FST(Direct Construction of Minimal Acyclic Subsequential Transducers)算法. 本質上是構建一個最小化的無環有限自動機, 并在此基礎上加上轉換
功能
- 確定(Deterministic): 對給定的任何
input
, 最多只有一條路徑可以到達 - 無環(Acyclic): 對給定的任何
input
, 不會重復訪問已經過的狀態 - 接受(Acceptor): 給定的
input
, 當且僅當input
的最后一個字符指向的狀態為終結狀態時, 表示接受 - 轉換(Transducer):在自動機每接受
input
的一個字符的時候, 可以產生一個值, 當到達接受狀態時, 這個值就是output
lucene中的實現
在lucene中, term
就是input
, offset
就是output
, 構建FST有幾個要求
- term是按照字典表排好序的
- term元素數量不可變
簡單示例
先看個示例最終生成的FST結構
# input/output
ca/1
cat/5
dog/10
dot/15
-
每條邊標識可接受的字符, 邊本身有值, 沒有值的表示值為
0
-
c/1
: 接受字符c
, 此邊的值為1
-
a
: 等價于a/0
-
每個圈代表一個狀態, 一般情況下沒有值, 在特殊情況也會有值
加粗的表邊標識到達的狀態為終結狀態
output為從開始狀態到終結狀態所經過的邊的值總和. 特殊情況下加上最終狀態的值.
特殊情況示例
ca/5
cat/1
caz/2
構建的過程
很難簡短的說清楚構建的細節, 制作了一個頁面, 可以自己設計input/output, 并觀察FST的變化, 來幫助理解
http://xiaozhukk.com/web/index.html#/fst(可能加載有點慢)
FST的功能
在了解了FST的結構之后, 可以看到查詢指定的term只跟term本身長度有關, 可以認為時間復雜度為O(1)
基本的搜索場景: 搜索java
在FST中查找term的offset, 再根據offset找到包含此term的數據記錄id列表. 然后通過id獲取原始記錄就類似于數據庫了.
SkipList
假設我要檢索同時包含java
和lucene
的記錄呢. 或者只要包含java
或lucene
的記錄呢. 方法是分別在FST中找出java
和lucene
的id列表, 然后做交集或并集.
lucene選擇使用SkipList來索引id列表, SkipList是已排序的結構, 在多個已排序的列表做交集或并集, 時間復雜度為O(n), 利用上SkipList中的跳躍指針, 能更加優化平均時間復雜度.
評分
到目前為止, 我們可以快速的檢索出相關的記錄. 我們希望與查詢相關程度高的評分更高, 排在前面.
TF-IDF權重計算
TF(term frequencey): term在一個文檔中出現的次數, TF越大表示這個文檔與term關聯越高
DF(document frequency): 包含term的文檔總數目, DF越大表示這個term的區分度越小, 一個查詢能被分成多個term, 意味著包含DF高的term的文檔關聯性反而要小
IDF(inverse document frequency): = log(文檔總數/DF), 經過計算DF越小, IDF值越高, 文檔權重越高,
我們定義一個term在一個文檔(document)中TF-IDF的值為
假設一個查詢(query)分詞后有q個term, 那么一個文檔在這個查詢下的總分為各個term權重的總和:
lucene實際的評分模型構建在TF-IDF的思想之上, 實際細節不做論述