序
- 自己在看這個開源代碼中看到了這個相似性算法和一些工程中的技巧,感覺很不錯,算是多了點兒見識,以前還從沒有用過稀疏矩陣這個存儲結構,這裏就寫一個文檔簡單記錄一下
python小知識
- Python中關於eval函數與ast.literal_eval使用的區別介紹(圖文)
- https://www.php.cn/python-tutorials-376459.html
- numpy的廣播機制(具體沒有用,算是回顧一下):
- https://www.cnblogs.com/jiaxin359/p/9021726.html
BM25
- 首先,我們拋卻一切,需要知道這個算法是什麼,推導過程以及背景什麼的就不敘述了,可以參考但不限於 這篇博客
- 歸根結底,我們的算法其實就是一個計算公式,如下:
普通代碼
代碼工程優化
- 這個開源庫一共從兩個方面優化了計算方式,具體可以測試跑跑代碼,個人測試數據就是CDQA這個開源庫的數據:
-
- 稀疏存儲: 節約空間。
-
- 向量化矩陣計算:加速以及簡潔。
-
- 在上述向量化的基礎上,作者只針對非零元素進行計算bm分數,進一步加速計算速度。
代碼
- 原始庫裏的代碼邏輯如圖:
- 自己把這塊代碼單獨從庫裏提出來實驗了一下,自己註釋的有點亂
依賴和超參
import numpy as np
import scipy.sparse as sp
from sklearn.utils.validation import check_is_fitted, check_array, FLOAT_DTYPES
from sklearn.feature_extraction.text import _document_frequency
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
from ast import literal_eval
use_idf = True
floor = None
k1 = 2.0
b = 0.75
norm = None
主體函數
def fit(X):
"""
Parameters
----------
X : sparse matrix, [n_samples, n_features]
document-term matrix
"""
X = check_array(X, accept_sparse=("csr", "csc"))
# x是否爲sparse類型
# 壓縮稀疏列矩陣
if not sp.issparse(X):
X = sp.csc_matrix(X)
# 如果用idf
if use_idf:
n_samples, n_features = X.shape
# (57164,),所有文檔對應單詞的頻率
df = _document_frequency(X)
# (57164,)
idf = np.log((n_samples - df + 0.5) / (df + 0.5))
if floor is not None:
idf = idf * (idf > floor) + floor * (idf < floor)
# 創建對角稀疏矩陣,其實就是一維變成2維
_idf_diag = sp.spdiags(idf, diags=0, m=n_features, n=n_features)
print(_idf_diag.shape)
# Create BM25 features
# Document length (number of terms) in each row
# Shape is (n_samples, 1)
dl = X.sum(axis=1)
# Number of non-zero elements in each row
# Shape is (n_samples, )
sz = X.indptr[1:] - X.indptr[0:-1]
# In each row, repeat `dl` for `sz` times
# Shape is (sum(sz), )
# Example
# -------
# dl = [4, 5, 6]
# sz = [1, 2, 3]
# rep = [4, 5, 5, 6, 6, 6]
# 每一個單詞對應的文檔長度
rep = np.repeat(np.asarray(dl), sz)
print(rep.shape)
# Average document length
# Scalar value
avgdl = np.average(dl)
# Compute BM25 score only for non-zero elements
# 實驗一下整個計算
# X: 每個文檔中每一個單詞的個數
# print(X.shape)
# print(X.data.shape)
# print(rep.shape)
# print(avgdl.shape)
# (非零元素_num,)
# X.data表示的是其中非零元素,算是加速計算
data = (
X.data
* (k1 + 1)
/ (X.data + k1 * (1 - b + b * rep / avgdl))
)
# 恢復結構
X = sp.csr_matrix((data, X.indices, X.indptr), shape=X.shape)
if norm:
X = normalize(X, norm=norm, copy=False)
_doc_matrix = X
return _doc_matrix
函數調用
# 出於安全考慮,對字符串進行類型轉換的時候,最好使用ast.literal_eval()函數!
rdf = pd.read_csv('/home/lixiang/桌面/cdqa/cdQA-master/path-to-directory/bnpp_newsroom-v1.1.csv', converters={'paragraphs': literal_eval})
raw_documents = rdf["paragraphs"].apply(lambda x: " ".join(x))
wow = CountVectorizer( input="content",
encoding="utf-8",
decode_error="strict",
strip_accents=None,
lowercase=True,
preprocessor=None,
tokenizer=None,
analyzer="word",
stop_words=None,
token_pattern=r"(?u)\b\w\w+\b",
ngram_range=(1, 2),
max_df=1.0,
min_df=1,
max_features=None,
vocabulary=None,
binary=False,
dtype=np.float64)
X = wow.fit_transform(raw_documents=raw_documents)
# 傳入的是:每一個樣本,對應單詞的個數
a = fit(X)
END
- 本文沒有寫過多細節的介紹,工程這個東西還是需要讀源碼自己體會一下。