Elasticsearch 詞頻統計的四種方案
1、詞頻相關實戰問題
最近詞頻統計問題被問到的非常多,詞頻統計問題清單如下:
Q1:Elasticsearch 可以根據檢索詞在 doc 中的詞頻進行檢索排序嘛?
Q2:求教 ES 可以查詢某個索引中某個 text 類型字段的詞頻數量最大值和詞所在文檔數最大值麼?例:索引中有兩個文檔 doc1:{"text":""} 分詞結果有兩個北京,一個南京 doc2:{"text":""} 分詞結果有一個北京想要一下結果:北京:詞頻 3,文檔量 2 南京:詞頻 1,文檔量 1
Q3:對某些文章的詞頻統計除了用 fielddata 之外還有沒有效率比較高的解決辦法呢?目前統計有時候會遇到十萬級的文章數直接在通過聚合效率上不是特別理想。
如上三個問題都可以歸結爲:Elasticsearch 文檔詞頻統計問題。該問題在檢索、統計領域應用的非常多。
那麼 Elasticsearch 如何實現詞頻統計呢?有必要梳理一下。
2、詞頻統計探討
之前的文章《Elasticsearch 詞頻統計實現與原理解讀》,解決的是:Q3 提及的某索引中特定關鍵詞統計的問題。
解決方案是:text 字段開啓 fielddata,咱們在《長津湖影評可視化》中詞雲的可視化本質也是這種方案。
那麼,對於給定文檔的詞頻統計呢?
原來開啓 fielddata 的方案就可以實現,舉例如下:
DELETE message_index
PUT message_index
{
"mappings": {
"properties": {
"message": {
"analyzer": "ik_smart",
"type": "text",
"fielddata": "true"
}
}
}
}
POST message_index/_bulk
{"index":{"_id":1}}
{"message":"沉溺於「輕易獲得高成就感」的事情:沉溺於有意無意地尋求用很小付出獲得很大「huibao」的偏方,哪怕huibao是虛擬的"}
{"index":{"_id":2}}
{"message":"過度追求“短期huibao”可以先思考這樣一個問題:爲什麼玩王者榮耀沉溺我們總是停不下來huibao"}
{"index":{"_id":3}}
{"message":"過度追求的努力無法帶來超額的huibao,就因此放棄了努力。這點在聰明人身上尤其明顯。以前念本科的時候身在沉溺"}
POST message_index/_search
{
"size": 0,
"query": {
"term": {
"_id": 1
}
},
"aggs": {
"messages": {
"terms": {
"size": 10,
"field": "message"
}
}
}
}
無非在聚合的時候,加上 query 語句指定了特定 id 進行檢索。
這種方法的缺點在於:正如 Q3 所說,聚合效率低。
看過上次直播的同學,可能會閃現一種想法,寫入前打 tag 的方式能解決嗎?
可以解決,但有個前提。先畫個圖解釋一下:
這個打 tag 的字段非全量,而是特定的指定腳本處理的部分。下一小節詳細實現一把。
其實,除了開啓 fielddata 和 打 tag 之外,在 Elasticsearch 中有 termvectors 接口也能實現文檔詞頻統計。下一小節一併實現。
3、詞頻統計實現
3.1 text 開啓 fielddata 後聚合方案
第 2 部分已有實現說明,不再贅述。
3.2 寫入前打 tag,寫入後聚合統計方案
還是用第 2 部分的數據,說明如下:
PUT _ingest/pipeline/add_tags_pipeline
{
"processors": [
{
"append": {
"field": "tags",
"value": []
}
},
{
"script": {
"description": "add tags",
"lang": "painless",
"source": """
if(ctx.message.contains('成就')){
ctx.tags.add('成就')
}
if(ctx.message.contains('王者榮耀')){
ctx.tags.add('王者榮耀')
}
if(ctx.message.contains('沉溺')){
ctx.tags.add('沉溺')
}
"""
}
}
]
}
POST message_index/_update_by_query?pipeline=add_tags_pipeline
{
"query": {
"match_all": {}
}
}
POST message_index/_search
{
"size":0,
"aggs": {
"terms_aggs": {
"terms": {
"field": "tags.keyword"
}
}
}
}
實現後,結果如下:
這種統計的依然是:關鍵詞(key)和文檔(doc_count)的統計關係。
什麼意思呢?
"key":“沉溺”,“doc_count”:3 本質含義是:“沉溺” 在三個不同的文檔中出現了。
細心的讀者會發現,文檔 1 中 “沉溺” 出現了 2 次,這種打 tag 統計是不準確的。
3.3 term vectors 統計
PUT message_index
{
"mappings": {
"properties": {
"message": {
"type": "text",
"term_vector": "with_positions_offsets_payloads",
"store": true,
"analyzer": "ik_max_word"
}
}
}
}
解釋一下:
-
term_vector: 檢索特定文檔字段中分詞單元的信息和統計信息。
-
store: 默認未開啓存儲,需要手動設置爲 true。
with_positions_offsets_payloads 是 Lucene 的參數之一,釋義如下:
可以理解爲:存儲分詞單元(term vector)、位置(Token position)、偏移值(offset)、有效負載(payload,猜測是 ES 新增的)。
默認會統計詞頻信息,默認 term information 爲 true。此外,還有 term statistics 和 field statistics 類型供設置和實現不同的統計,詳細內容參考官方文檔即可。
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-termvectors.html
執行:
GET message_index/_termvectors/1?fields=message
後的返回結果如下:
這種基於特定文檔的詞頻統計是傳統意義上我們理解的詞頻統計。
默認情況下,term vectors 是實時的,而不是接近實時的。可以通過將 realtime 參數設置爲 false 來更改。實時就意味着可能會有性能問題。
3.4 先分詞,後 term vectors 統計
在我擔心僅 termvectors 可能帶來的性能問題的時候,我想到了如下的解決方案。
前提:寫入之前除了存儲 message 字段,加了一個分詞結果組合字段,該字段每個詞用空格做分隔。
message 字段的前置分詞需要自己調用 analyzer API 實現。
有了切詞後的字段,再做統計會更快。
具體實現如下:
DELETE message_ext_index
PUT message_ext_index
{
"mappings": {
"properties": {
"message_ext": {
"type": "text",
"term_vector": "with_positions_offsets_payloads",
"store": true,
"analyzer": "whitespace"
}
}
}
}
POST message_ext_index/_bulk
{"index":{"_id":1}}
{"message_ext":"沉溺 於 輕易 獲得 高 成就感 的 事情 沉溺 有意 無意 地 尋求 用 很小 付出 獲得 很大 huibao 的 偏方 哪怕 huibao 是 虛擬 的"}
GET message_ext_index/_termvectors/1?fields=message_ext
強調一下:message_ext 使用的 whitespace 分詞器。
4、小結
關於詞頻統計,本文給出四種方案。只有第 3、4 種方案結合 termvectors 實現是嚴格意義上的詞頻統計,其他兩種是詞頻 - 文檔關係的統計。
考慮到方式 3 的實時分詞可能的性能問題,擴展想到方案 4 前置分詞的方式,能有效提高統計效率。本質也是空間換時間。
你的實戰中如何實現的詞頻統計呢?歡迎留言說一下你的實現方式和思考。
參考
https://titanwolf.org/Network/Articles/Article?AID=7c417f9f-5bde-4519-9bd5-39957d184a07 https://discuss.elastic.co/t/word-count-frequency-per-field/159910 https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-termvectors.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/S4AMVWIoNT4ZFKGs1biNnQ