這次我們來介紹下搜索引擎的原理,之後透過介紹下DPR(Dense Passage Retrieval for Open-Domain Question Answering),動手嘗試DPR做開放式問答機器人的效果如何。

搜索引擎原理

搜尋引擎是資料檢索(Information Retrieval, IR)的其中一個應用,目的是從大量資料中找尋結果。通常我們會下一些關鍵字查詢,然後搜尋引擎負責將出現過這個關鍵字的網站都找回來。

而這部分最麻煩的問題在於,怎麼樣高效地從大量網站中把包含關鍵字的結果找回來。

如果用一個個文檔排除比對的方法,每次查詢都是n複雜度。為了高效檢索,會用一個叫做inverted index的方法來,從紀錄文章變成紀錄關鍵字在甚麼文章出現。

每次查詢就可以根據查詢關鍵字找到相關的文章。

在inverted index之下,還會有一個問題,在擁有相同數量的關鍵字下,到底要講哪些結果放前面?換句話話,我們應該怎麼樣將搜尋的結果排序呢?

搜尋引擎的排序

一個蠻高效的方法 - BM25作為考慮的評分標準,得分越高的文檔,就會被排在前面。

計算分數的準則,則是希望讓:

  • 字出現的頻率越高應該越好,例如你要查詢 ir的原理,出現ir越多次的文章應該越相關

  • 字也不應該出現在所有文檔中,頻率太高字通常會是一些萬用字,如 ir的原理 中 的 這個字,在很多文章中都會出現,但沒有甚麼意思和重要性,因此要減低這類字的分數

  • 字出現的文檔,越短應該分數越高,符合的關鍵字在文本中佔比例越多,越重要。

大概原則會是如此,那麼基於inverted index和BM25做搜尋的效果如何呢?
我們用Elastic Search(ES)作為例子,elastic search是一個搜尋引擎和db,可以高效處理資料檢索的問題,而其中ElasticESarch的similarity就有用到inverted index和BM25:

下載安裝ES,ik斷詞器和相關環境

! pip install git+https://github.com/deepset-ai/haystack.git 
! pip install urllib3==1.25.4
! wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.9.2-linux-x86_64.tar.gz -q
! tar -xzf elasticsearch-7.9.2-linux-x86_64.tar.gz
! chown -R daemon:daemon elasticsearch-7.9.2

# install ik
! wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.9.2/elasticsearch-analysis-ik-7.9.2.zip
! cd elasticsearch-7.9.2/plugins/ && mkdir ik
! unzip elasticsearch-analysis-ik-7.9.2.zip -d elasticsearch-7.9.2/plugins/ik

啟動ES

import os
from subprocess import Popen, PIPE, STDOUT
es_server = Popen(['elasticsearch-7.9.2/bin/elasticsearch'],
                   stdout=PIPE, stderr=STDOUT,
                   preexec_fn=lambda: os.setuid(1)  # as daemon
                  )
print(es_server)
# wait until ES has started
! sleep 30

連結ES

# Connect to Elasticsearch
from haystack.document_store.elasticsearch import ElasticsearchDocumentStore
document_store_std = ElasticsearchDocumentStore(host="localhost", username="", password="", index="document_std",analyzer='standard')

建立文檔

document_store_std.write_documents([{"text": "測試文本"},{"text": "試測本文"},{"text": "本文試測"},{"text": "試測文本"}])

文本搜尋

document_store_std.client.search({"query":{
      "query_string":{
         "query":"測試" 
      }
   }})

結果

{'_shards': {'failed': 0, 'skipped': 0, 'successful': 3, 'total': 3},
 'hits': {'hits': [{'_id': '2fb749cc-3d8f-4797-a017-e0b6f22051ce',
    '_index': 'document_ik',
    '_score': 0.21072102,
    '_source': {'text': '測試文本'},
    '_type': '_doc'},
   {'_id': '7d66f117-c53f-4e3f-a4f4-d163c3ac4876',
    '_index': 'document_ik',
    '_score': 0.21072102,
    '_source': {'text': '試測本文'},
    '_type': '_doc'},
   {'_id': '93df3bbe-9c22-401f-ae4e-08f121ef65e7',
    '_index': 'document_ik',
    '_score': 0.21072102,
    '_source': {'text': '本文試測'},
    '_type': '_doc'},
   {'_id': '088039c5-10b1-44b3-9d1f-0f597fb31d60',
    '_index': 'document_ik',
    '_score': 0.21072102,
    '_source': {'text': '試測文本'},
    '_type': '_doc'}],
  'max_score': 0.21072102,
  'total': {'relation': 'eq', 'value': 4}},
 'timed_out': False,
 'took': 72}

傳統搜尋引擎的缺陷

Inverted index對於整個查詢至關重要。
如果斷詞做不好,使得意思被改變
或查詢與文本用字不一(反派和壞人
排序時,更適合的結果得不到高分,查詢結果就變差。
這個問題該如何改善呢? 留待下一篇來分析。