# 使用Pyserini建立反向索引-資料檢索(HW3)
## 作業規定
下面是老師給的作業規定
說明:
使用Pubmed200k (The PubMed 200k RCT dataset is described in Franck Dernoncourt, Ji Young Lee. PubMed 200k RCT: a Dataset for Sequential Sentence Classification in Medical Abstracts. International Joint Conference on Natural Language Processing (IJCNLP). 2017.) 資料集可從 https://www.dropbox.com/s/miyb2awm2esrcpk/pubmed%20220%20train.txt?dl=0 下載。
使用Pyserini (Follow https://github.com/castorini/pyserini#how-do-i-index-and-search-my-own-documents 中的說明) 建立文章等級的反向索引。
範例
輸入 查詢 "cancer"
回傳:{id, contents}
29de267408dcfcb1ab8f9711c1ccb61f.png
需求:比較暴力搜尋法(i.e., linear scan)與使用反向索引(retreive by inverted index)所需的時間差異。
繳交:程式碼
## 安裝問題
### No module named 'faiss'
這個部分是比較有問題的,因為我的cpu是m1 pro不知道為啥用了好幾個裝法都裝不起來,最後我是用下面這種方式解決問題
```
pip install faiss-cpu --no-cache
```
### java
還有因為Pyserini底層有call到java所以也要把java裝好
## 實作步驟
老師要求的項目有兩個
1. 暴力法
2. 反向索引表(Pyserini)
### 將檔案轉為Pyserini要的格式
Pyserini可以幫助你建立反向索引表,他有規定的三種檔案格式
首先你要先將你的文章以及id使用以下的格式
```
{
"id": "doc1",
"contents": "this is the contents."
}
```
檔案的放至可以參考他github的三種方法
1. Folder with each JSON in its own file, like [this](https://github.com/castorini/pyserini/tree/master/tests/resources/sample_collection_json).
1. Folder with files, each of which contains an array of JSON documents, like [this](https://github.com/castorini/pyserini#how-do-i-index-and-search-my-own-documents).
1. Folder with files, each of which contains a JSON on an individual line, like [this]([https:/](https://github.com/castorini/pyserini/tree/master/tests/resources/sample_collection_jsonl)/) (often called JSONL format).
基於txt的讀取方式我不熟,所以我選擇第一種方式比較簡單
程式碼如下
``` python
import json
line = f.readline()
contents = ''
count = 0
while line:
print(count)
if line[0] == "#" and line[1] == "#" and line[2] == "#":
id = line.strip()
id = id.replace("#", "")
elif line.isspace():
contents = contents.replace("\t", " ")
output = {
"id": id,
"contents": contents
}
id = ""
contents = ""
name = "./data/output" + str(count) +".json"
count = count + 1
with open(name, "w") as t:
json.dump(output, t)
else:
line = line.strip()
contents += line
line = f.readline()
```
說明
因為檔案裡面是規律,有以下幾個點!
[Uploading file..._p5kyub3hi]()
* 每篇論文開頭都是###再加上id
* 結束的時候都會有一行空白
所以程式碼架構就是
先將txt檔逐行讀取,若是開頭為###就認定文章開始。這邊先處理id的部分,之後開始處理content的部分,中間的if是當發現這行爲空時,這時候為文章的尾端,我們要做兩件事情
1. 將id及content寫為字典,將它寫出為json格式
2. 將id 清空以及content清空
### 建立反向索引表
上一步做好之後就可以建立自記得反向索引表了
這邊如果將input改成自己的檔案路徑不知道為什麼會出錯,所以我是直接將我的檔案放到他指定的路徑啦
(就直接建立一模一樣的資料夾)
```
python -m pyserini.index.lucene \
--collection JsonCollection \
--input tests/resources/sample_collection_jsonl \
--index indexes/sample_collection_jsonl \
--generator DefaultLuceneDocumentGenerator \
--threads 1 \
--storePositions --storeDocvectors --storeRaw
```
出現下面的結果就是成功了
```
2023-03-09 12:00:46,852 INFO [main] index.IndexCollection (IndexCollection.java:633) - Indexing Complete! 190,654 documents indexed
2023-03-09 12:00:46,852 INFO [main] index.IndexCollection (IndexCollection.java:634) - ============ Final Counter Values ============
2023-03-09 12:00:46,852 INFO [main] index.IndexCollection (IndexCollection.java:635) - indexed: 190,654
2023-03-09 12:00:46,853 INFO [main] index.IndexCollection (IndexCollection.java:636) - unindexable: 0
2023-03-09 12:00:46,853 INFO [main] index.IndexCollection (IndexCollection.java:637) - empty: 0
2023-03-09 12:00:46,853 INFO [main] index.IndexCollection (IndexCollection.java:638) - skipped: 0
2023-03-09 12:00:46,853 INFO [main] index.IndexCollection (IndexCollection.java:639) - errors: 0
2023-03-09 12:00:46,857 INFO [main] index.IndexCollection (IndexCollection.java:642) - Total 190,654 documents indexed in 00:01:01
```
### time模組
使用time模組來檢測測試速度
``` python
start = time.time()
#要測試的程式碼
end = time.time()
print("執行時間:%f 秒" % (end - start))
```
### 使用反向索引search
Pyserini有提供search的函式,他在建立反向索引表的時候會有score的機制。預設是回傳前10名
可以透過參數k來更改,我設那個數是一定達不到啦所以會全部回傳
``` python
from pyserini.search.lucene import LuceneSearcher
import time
searcher = LuceneSearcher('indexes/sample_collection_jsonl')
start = time.time()
hits = searcher.search('cancer', k=300000)
end = time.time()
#for i in range(len(hits)):
#print(f'{i+1:2} {hits[i].docid:4} {hits[i].score:.5f}')
print("執行時間:%f 秒" % (end - start))
```
### 實作暴力法
程式碼
``` python
import os
import json
searchKey = 'cancer'
folder_path = './tests/resources/sample_collection_jsonl'
list = []
start = time.time()
for filename in os.listdir(folder_path):
if filename.endswith('.json'):
file_path = os.path.join(folder_path, filename)
with open(file_path, 'r') as f:
json_data = json.load(f)
#json_data['contents']
if searchKey in json_data['contents']:
list.append(json_data)
end = time.time()
print("執行時間:%f 秒" % (end - start))
```
說明如下
將整個資料夾的json都讀進來,然後去檢查要的關鍵字(key)有沒有在裡面。
###### tags: `資料檢索` `作業` `Pyserini` `反向索引`