Khi các bạn xem đang xem bài viết này có nghĩa là các bạn đang, đã hoặc sẽ quan tâm về Elasticsearch. Hoặc cũng có thể bạn vào đọc cho vui cũng không sao cả 😀
Bắt đầu nhé, trước tiên thì mình giới thiệu qua cách mà Elasticsearch tính điểm cho các document.
Elasticsearch sử dụng Boolean Model để tìm kiếm các documents, và 1 công thức được gọi là practical scoring function để tính toán sự liên quan. Công thức này mượn các khái niệm term frequency/inverse document frequency và vector space model nhưng thêm các tính năng hiện đại hơn như yếu tố phối hợp, chuẩn hóa độ dài term và thứ tự mệnh đề truy vấn.
Boolean Model
Boolean Model đơn giản dụng các điều kiện AND, OR và NOT được biểu thị trong truy vấn để tìm tất cả các tài liệu phù hợp. Một truy vấn cho:
full AND text AND search AND (elasticsearch OR lucene)
sẽ chỉ bao gồm các tài liệu chứa tất cả các terms full, text và search, cũng như elasticsearch hoặc lucene. Quá trình này rất đơn giản và nhanh chóng. Nó được sử dụng để loại trừ bất kỳ documents nào không có khả năng khớp với truy vấn.
Term Frequency/Inverse Document Frequency (TF/IDF)
Khi chúng ta tìm được danh sách các documents, chúng cần được đánh giá theo độ liên quan. Không phải tất cả các documents đều chứa tất cả các terms, và một số terms là quan trọng hơn các terms còn lại. Điểm liên quan của toàn bộ documents phụ thuộc (một phần) vào trọng số của mỗi cụm từ truy vấn xuất hiện trong tài liệu đó.
Term Frequency
Đã bao giờ em nghe, có vài điều khi yêu, một là không xạo chó, hai là không xạo chó nhiều lần. Một trường có chứa năm đề cập của cùng một cụm từ có nhiều khả năng có liên quan hơn một trường chỉ chứa một đề cập. Term frequency được tính như sau:
tf(t in d) = sqrt(số lần terms xuất hiện trong documents)
Nếu không quan tâm số lần terms xuất hiện trong documents, bạn chỉ cần set: “index_options”: “docs” khi đánh mapping:
{
"mappings": {
"doc": {
"properties": {
"text": {
"type": "string",
"index_options": "docs"
}
}
}
}
}
Inverse document frequency
Trái ngược với bên trên bạn có thể nhận thấy, các terms phổ biến như là hoặc không đóng góp ít cho mức độ liên quan khi chúng xuất hiện trong hầu hết các tài liệu, trong khi các terms như yêu hoặc xạo giúp chúng ta zoom in trên hầu hết các tài liệu liên quan. Inverse document frequency được tính như sau:
idf(t) = 1 + log ( numDocs / (docFreq + 1))
=> Logarithm của số các documents trong index, chia cho số các document chứa term.
Field-length norm
Nếu term xuất hiện trong một field ngắn, chẳng hạn như field title, có nhiều khả năng nội dung của field đó là về term hơn so với một term xuất hiện trong field body lớn hơn nhiều. field length norm được tính như sau:
norm(d) = 1 / sqrt(numTerms)
=> Nghịch đảo của căn bậc 2 số lượng term trong 1 field. Trong khi field length norm là quan trọng cho full-text search, nhưng rất nhiều fields khác lại không cần norms. Norms tiêu thụ xấp xỉ 1 byte trên string field trên document trong index. Có 2 cách để disable norms:
{
"mappings": {
"doc": {
"properties": {
"text": {
"type": "string",
"norms": { "enabled": false }, //cái này
"index": "not_analyzed", //hoặc cái này
}
}
}
}
}
Putting it together
3 nhân tố này: term frequency, inverse document frequency, and field-length norm được tính toán và lưu trữ tại index time. Chúng được sử dụng để tính trọng số của một term trong một document cụ thể.
Khi chúng ta chạy một truy vấn term đơn giản với explain được set bằng true, bạn sẽ thấy rằng các yếu tố duy nhất liên quan đến việc tính toán điểm số là những yếu tố được giải thích trong các phần bên trên:
PUT locahost:9200/ tuananhzippy/news/1
{ "text" : "quick brown fox" }
GET localhost:9200/tuananhzippy/news/_search?explain
{
"query": {
"term": {
"text": "fox"
}
}
}
weight(text:fox in 0) [PerFieldSimilarity]: 0.15342641
result of:
fieldWeight in 0 0.15342641 (1)
product of:
tf(freq=1.0), with freq of 1: 1.0 (2)
idf(docFreq=1, maxDocs=1): 0.30685282 (3)
fieldNorm(doc=0): 0.5 (4)
(1) Score cuối cùng của term fox trong field text trong document
(2) Term fox xuất hiện một lần trong field text trong document.
(3) Chỉ số inverse document frequency của term fox trong text field trong toàn bộ document trong index
(4) Chuẩn hóa field-length cho field này.
Tất nhiên, các truy vấn thường bao gồm nhiều hơn một term, vì vậy chúng ta cần một cách kết hợp trọng số của nhiều term. Đối với điều này, chúng ta chuyển sang vector space model.
Vector Space Model (cái này nó hơi trừu tượng, nói chung là vi diệu lắm)
Vector space model cung cấp một cách so sánh truy vấn multiterm dựa trên document. Đầu ra là một score duy nhất thể hiện mức độ phù hợp của document với truy vấn. Để làm điều này, model biểu diễn cả tài liệu và truy vấn dưới dạng vectơ. Thông tin thêm cho ai muốn tìm hiểu đến nơi đến chốn: https://en.wikipedia.org/wiki/Vector_space_model
Cũng hòm hòm rồi đó. Giờ thì đi vào vấn đề chính:
Elasticsearch có một tính năng vô cùng tuyệt vời gọi là function_score cho phép mình sửa đổi score của một document. Chỉ khi đi vào gặp phải bài toán thực tế tôi mới tìm ra tính năng này vì cái tên của nó không liên quan lắm và cũng khó hình dung. Cơ mà tí bạn sẽ thấy nó liên quan.
Ví dụ một câu query cơ bản nhé:
GET localhost:9200/tuananhzippy/products/_search
query: {
match: { title: “đồng hồ”},
}
Kết quả của câu query này sẽ trả về những sản phẩm có title chứa từ đồng hồ.
Để sử dụng function_score ta cần phải xác định một truy vấn và một hoặc nhiều hàm tính toán điểm cho mỗi tài liệu được truy vấn. Ví dụ:
GET localhost:9200/tuananhzippy/products/_search
query: {
function_score: {
functions: [ (3)
{
field_value_factor: { (4)
field: 'inventory',
missing: 0
},
},
{
random_score: {},
},
],
score_mode: 'avg', (2)
query: { (1)
match: { title: “đồng hồ”},
}
}
}
(1) Như đã nói ở trên, mỗi function_score sẽ đi với một query. Như ở ví vụ trên ở đây ta lấy hết document có chứa từ đồng hồ. Ở đây mình có thể kết hợp nhiều query lại với nhau bằng cách sử dụng bool query.
(2) Score_mode là cách kết hợp các điểm được tính toán bao gồm: Cái này dễ hiểu nên mình không dịch nha…
• multiply: scores are multiplied (default)
• sum: scores are summed
• avg: scores are averaged
• first: the first function that has a matching filter is applied
• max: maximum score is used
• min: minimum score is used
(3) Function là tập hợp các yếu tố tác động đến điểm số, ở trong ví dụ này mình chọn random_score vì thế mỗi lần query nó ra một kết quả khác nhau.
(4) Mình có cho thêm một yếu tố nữa đó là: field_value_factor có nghĩa là chúng ta sẽ sử dụng 1 trường trong document để làm 1 phần của điểm số, ví dụ: tồn kho, số like, số comment, … Các option có trong yếu tố này là:
• field: trường mà ta muốn trích xuất dữ liệu để tác động tới điểm. Ở đây là tồn kho(inventory)
• factor: trọng số đem đi tính avg(ở trên mình chọn score_mode là avg) mặc định là 1. Các bạn có trọng số tùy ý nhé.
• modifier: Đôi khi dữ liệu của trường inventory nó lớn quá thì ta có thể modifer lại đi một chút bằng cách sử dụng none, log, log1p, log2p, ln, ln1p, ln2p, square, sqrt, or reciprocal
• missing: nếu không có thì giá trị của yếu tố này sẽ là bao nhiêu. Mặc định là none
Ngoài ra còn có: script_score, weight, decay functions. Tùy vào những trường hợp mà mình sẽ dùng.
Hy vọng rằng qua bài viết này mọi người đã hiểu hơn về elasticsearch và áp dụng được function_score vào những bài toán mình gặp phải. Chúc các bạn thành công!
Tham khảo
https://www.elastic.co/guide/en/elasticsearch/guide/current/scoring-theory.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html









(3 lượt thả tim)



