فهرست مقاله
پیدا کردن سندهایی که واژهٔ «حسگر» را دارند، مرحلهٔ اول جستوجو است. برای مرتبکردن آنها میتوان پرسید واژه چند بار تکرار شده، سند چقدر طول دارد و آن واژه در چند سند مجموعه دیده میشود. در این آموزش این سه اثر را با یک مدل کوچک BM25 جدا میکنیم. چهار سند ساختگی مهندسی، جواب دستی و هجده بررسی اجراشده داریم. موتور واقعی Lucene یا Elasticsearch، مدل زبانی و سنجش کیفیت ارتباط نتایج اجرا نمیشوند.
۱. TF، DF و طول سند کمیتهای جدا هستند

ورودی از ابتدا فهرست توکن است. سند A شامل حسگر و ولتاژ، سند B شامل حسگر، حسگر، ولتاژ و راهنما، سند C شامل کنترل و موتور و سند D شامل ولتاژ، راهنما، کنترل و موتور است. طولها دو، چهار، دو و چهار توکناند؛ دوازده توکن در چهار سند، میانگین طول سه میدهد. نامها و محتوا برای آموزش ساخته شدهاند و سند محصول یا مشتری واقعی نیستند.
برای واژهٔ حسگر، TF در B برابر دو است، اما DF آن در کل مجموعه دو است؛ چون فقط A و B آن را دارند. تکرار دوباره در یک سند، تعداد سندهای حاوی واژه را زیاد نمیکند. DF ولتاژ سه است. در مرجع Lucene 10.3.1، آمار بر فیلدِ مورد جستوجو تعریف میشود. اینجا همهٔ سندها یک فیلد غیرخالی دارند؛ بنابراین N همان چهار است.
طول این مدل تعداد توکنهای دادهشده است؛ تعداد نویسه یا بایت نیست. برنامه جداسازی واژه، ریشهیابی، مترادفسازی و نرمالسازی فارسی ندارد. توکنهای یکسان دقیقاً با برابری رشته تشخیص داده میشوند. نمایهٔ معکوس و جستوجوی بولی مرحلهٔ پیدا کردن عضویت سندها را نشان میدهد؛ تمرین حاضر وزن عددیِ مشارکت واژهها را آشکار میکند.
۲. فرمول، اشباع تکرار و اثر طول را مشخص کنیم

وزن واژه را با لگاریتم طبیعی میسازیم: IDF=ln(1+(N-DF+0.5)/(DF+0.5)). برای حسگر، حاصل ln(2) و برای ولتاژ ln(10/7) است. در همین مجموعهٔ ثابت، واژهٔ کمفراوانتر IDF بزرگتری دارد. این وزن به معنی احتمال درست بودن سند یا اهمیت فنیِ مستقل از پرسوجو نیست؛ با تغییر مجموعه، مقدار آن هم تغییر میکند.
عامل تکرار را چنین انتخاب میکنیم: TFfactor=f/(f+k1*(1-b+b*dl/avgdl)). f تکرار در سند، dl طول سند و avgdl میانگین طول مجموعه است. مشارکت هر واژه IDF ضرب در این عامل و امتیاز سند جمع مشارکتهاست. برای f صفر، سهم را مستقیم صفر میگذاریم؛ این کار در حالت k1 صفر نیز از تقسیم صفر بر صفر جلوگیری میکند.
مستندات Elastic نقش k1 در اشباع تکرار و b در نرمالسازی طول را شرح میدهد و پیشفرضهای ۱٫۲ و ۰٫۷۵ را اعلام میکند. برای حساب دستی، k1 را یک و b را نیم میگیریم. با b صفر، اثر طول حذف میشود؛ برای k1 مثبت، با b مثبت و تکرار برابر، طول بیشتر وزن کمتری میگیرد. زیاد شدن تکرار در طول ثابت هم سود افزایشیِ یکنواخت ندارد؛ عامل بهتدریج اشباع میشود.
۳. قرارداد مقایسه و مرز پیادهسازی واقعی را حفظ کنیم

فرمول عامل بالا در بخش explainTF کد رسمی همین نسخهٔ Lucene دیده میشود؛ در صورتِ آن ضریب k1+1 نداریم. اگر در یک مدلِ دیگر همهٔ سهمها را با ضریب مثبت ثابت یکسان ضرب کنیم، ترتیب همان امتیازها تغییر نمیکند، اما عدد مطلقشان عوض میشود. برای مقایسهٔ پیادهسازیها باید مقیاس فرمول را هم ثبت کرد.
همچنان برابری عددی با Lucene ادعا نمیکنیم. آن پیادهسازی از حساب float، بازنویسی فرمول و norm ذخیرهشده استفاده میکند؛ مستندات Similarity ثبت فشردهٔ طول را توضیح میدهد. مدل ما طول دقیق فهرست، محاسبهٔ سادهٔ پایتون و آمار یک مجموعهٔ کوچک دارد. موقعیت توکن، boost، فیلدهای متعدد، قطعههای نمایه و تحلیلگر متن در آن پیاده نشدهاند.
سیاست پرسوجو نیز صریح است: توکن تکراری را یک بار وزن میدهیم و ترتیب توکنها اثری ندارد. پس «حسگر، حسگر، ولتاژ» با «حسگر، ولتاژ» نتیجهٔ یکسان میدهد؛ تکرار در سند همچنان در TF و طول حساب میشود. این سیاستِ برنامهٔ ماست و رفتار عمومی همهٔ queryهای Lucene نیست. برای دیده شدن حالتهای مرزی، حتی سندهای با امتیاز صفر را برمیگردانیم؛ تساوی دقیق امتیاز با شناسهٔ سند شکسته میشود.
۴. کد کامل و پاسخ دستیِ چهار سند

فایل bm25_ranking.py فقط کتابخانهٔ استاندارد میخواهد؛ اجرای واقعی با Python 3.14.5 ثبت شده است. برای A، عامل طول در مخرج ۵/۶ و عامل TF برای هر دو واژه ۶/۱۱ میشود. در B، عامل طول ۷/۶ است؛ سهم تکرار حسگر عامل ۱۲/۱۹ و ولتاژ عامل ۶/۱۳ دارد. D فقط سهم ولتاژ را با ۶/۱۳ میگیرد و C سهمی ندارد.
پیش از اجرای رتبهبند، مرجع دستی را با همین ضریبهای کسری بنویسید. امتیاز A تقریباً ۰٫۵۷۲۶۳۰، B برابر ۰٫۶۰۲۳۹۶، D برابر ۰٫۱۶۴۶۱۹ و C صفر است؛ ترتیب B، A، D، C میشود. این ترتیب فقط جواب عددیِ فرمول برای دادهٔ ماست. برای تعیین اینکه B واقعاً پاسخ مفیدتری برای کاربر دارد، داوری ارتباط و پرسوجوی واقعی لازم است.
"""Small pretokenized BM25-like ranking exercise at Lucene10.3.1 TF scale.
No Lucene/Elasticsearch calls, analyzer, norm encoding, index or quality benchmark.
"""
from collections import Counter
from fractions import Fraction
import json
import math
import platform
import re
def token_list(values, maximum, allow_empty=False):
if type(values) not in (list, tuple):
raise ValueError('token_container')
if not (0 if allow_empty else 1) <= len(values) <= maximum:
raise ValueError('token_count')
if any(type(t) is not str or not 1 <= len(t) <= 32 or any(c.isspace() for c in t) for t in values):
raise ValueError('token')
return tuple(values)
def parameters(k1, b):
if type(k1) not in (int, float) or not 0 <= k1 <= 100 or not math.isfinite(k1):
raise ValueError('k1')
if type(b) not in (int, float) or not 0 <= b <= 1 or not math.isfinite(b):
raise ValueError('b')
return float(k1), float(b)
class SmallBM25:
def __init__(self, documents):
if type(documents) not in (list, tuple) or not 1 <= len(documents) <= 64:
raise ValueError('documents')
copied = {}
for row in documents:
if type(row) not in (list, tuple) or len(row) != 2:
raise ValueError('document_pair')
doc_id, terms = row
if type(doc_id) is not str or re.fullmatch(r'[A-Za-z][A-Za-z0-9_-]{0,15}', doc_id) is None:
raise ValueError('doc_id')
if doc_id in copied:
raise ValueError('duplicate_doc_id')
copied[doc_id] = token_list(terms, 256)
self._documents = copied
self._counts = {key: Counter(terms) for key, terms in copied.items()}
self._df = Counter(term for terms in copied.values() for term in set(terms))
self._n = len(copied)
self._average_length = sum(map(len, copied.values())) / self._n
def statistics(self):
return {'document_count': self._n, 'average_length_tokens': self._average_length,
'length_tokens': {key: len(value) for key, value in self._documents.items()},
'document_frequency': dict(sorted(self._df.items()))}
def rank(self, query, k1=1.2, b=.75):
k1, b = parameters(k1, b)
# Deliberate policy: repeated query terms carry no additional weight.
terms = sorted(set(token_list(query, 16, allow_empty=True)))
ranked = []
for doc_id, tokens in self._documents.items():
contributions = []
for term in terms:
frequency = self._counts[doc_id].get(term, 0)
df = self._df.get(term, 0)
idf = math.log1p((self._n - df + .5) / (df + .5))
if frequency == 0:
tf_factor = 0.0 # Also avoids0/0 when k1=0.
else:
length_factor = 1 - b + b * len(tokens) / self._average_length
tf_factor = frequency / (frequency + k1 * length_factor)
contributions.append({'term':term,'tf':frequency,'df':df,'idf':idf,
'tf_factor':tf_factor,'score':idf * tf_factor})
score = math.fsum(item['score'] for item in contributions)
if not math.isfinite(score):
raise ArithmeticError('nonfinite_score')
ranked.append({'doc_id':doc_id,'score':score,'contributions':contributions})
return sorted(ranked, key=lambda item: (-item['score'], item['doc_id']))
def fails(function, message):
try:
function()
except ValueError as error:
return str(error) == message
return False
def run_example():
documents = [('A', ['حسگر','ولتاژ']),
('B', ['حسگر','حسگر','ولتاژ','راهنما']),
('C', ['کنترل','موتور']),
('D', ['ولتاژ','راهنما','کنترل','موتور'])]
model = SmallBM25(documents)
query = ['حسگر','ولتاژ']
ranked = model.rank(query, k1=1, b=.5)
by_id = {row['doc_id']:row for row in ranked}
ln_sensor, ln_voltage = math.log(2), math.log(10 / 7)
# Independent hand coefficients: N4, avg3, lengths2/4/2/4, k1=1, b=1/2.
hand = {'A':float(Fraction(6,11)) * (ln_sensor + ln_voltage),
'B':float(Fraction(12,19)) * ln_sensor + float(Fraction(6,13)) * ln_voltage,
'C':0.0, 'D':float(Fraction(6,13)) * ln_voltage}
near = lambda a,b: math.isclose(a,b,rel_tol=1e-13,abs_tol=1e-13)
contributions = lambda result, doc: {x['term']:x for x in next(r for r in result if r['doc_id']==doc)['contributions']}
a = contributions(ranked, 'A')
bdoc = contributions(ranked, 'B')
without_length = model.rank(['ولتاژ'], k1=1, b=0)
full_length = model.rank(['ولتاژ'], k1=1, b=1)
# Controlled mathematical subcase: fixed dl4/avg3, each f1..4 is feasible.
factors = [float(Fraction(f,1) / (Fraction(f,1) + Fraction(7,6))) for f in range(1,5)]
observed_factors = []
for frequency in range(1,5):
changed_documents = [(key, ['حسگر'] * frequency + ['راهنما'] * (4-frequency) if key=='B' else tokens)
for key,tokens in documents]
changed_model = SmallBM25(changed_documents)
observed_factors.append(contributions(changed_model.rank(['حسگر'],k1=1,b=.5),'B')['حسگر']['tf_factor'])
gains = [right-left for left,right in zip(observed_factors,observed_factors[1:])]
presence = model.rank(query, k1=0, b=1)
tie = SmallBM25([('Z',['حسگر']),('A',['حسگر'])]).rank(['حسگر'])
expanded = SmallBM25(documents + [('E',['راهنما'])])
expanded_sensor = contributions(expanded.rank(query,k1=1,b=.5),'A')['حسگر']
before_mutation = model.rank(query,k1=1,b=.5)
documents[0][1].append('حسگر')
checks = {
'hand_corpus_counts_lengths_average_and_df_distinct_from_tf': model.statistics()['document_count']==4 and model.statistics()['length_tokens']=={'A':2,'B':4,'C':2,'D':4} and model.statistics()['average_length_tokens']==3.0 and model.statistics()['document_frequency']['حسگر']==2 and bdoc['حسگر']['tf']==2,
'idf_matches_independent_log2_and_log10_over7': near(a['حسگر']['idf'],ln_sensor) and near(a['ولتاژ']['idf'],ln_voltage),
'short_A_has_hand_factor_six_over_eleven_for_both_terms': near(a['حسگر']['tf_factor'],6/11) and near(a['ولتاژ']['tf_factor'],6/11) and near(by_id['A']['score'],hand['A']),
'B_factors_and_complete_ranking_match_independent_hand_oracle': near(bdoc['حسگر']['tf_factor'],12/19) and near(bdoc['ولتاژ']['tf_factor'],6/13) and all(near(by_id[key]['score'],value) for key,value in hand.items()) and [r['doc_id'] for r in ranked]==['B','A','D','C'],
'no_match_unknown_query_and_empty_query_return_declared_zero_rows': by_id['C']['score']==0 and all(r['score']==0 for r in model.rank(['ناشناخته'])) and [r['doc_id'] for r in model.rank([])]==['A','B','C','D'] and all(r['score']==0 for r in model.rank([])),
'b_zero_disables_length_effect_for_equal_term_frequency': near(contributions(without_length,'A')['ولتاژ']['score'],contributions(without_length,'D')['ولتاژ']['score']),
'b_one_gives_shorter_document_more_weight_at_equal_tf': contributions(full_length,'A')['ولتاژ']['score'] > contributions(full_length,'D')['ولتاژ']['score'],
'fixed_length_tf_saturates_with_positive_decreasing_marginal_gains': all(near(a,b) for a,b in zip(observed_factors,factors)) and all(0<x<1 for x in observed_factors) and all(x>0 for x in gains) and gains[0]>gains[1]>gains[2] and near(factors[1],bdoc['حسگر']['tf_factor']),
'k1_zero_uses_presence_and_avoids_zero_frequency_division': all(near(contributions(presence,doc)['حسگر']['tf_factor'],1.0) for doc in ['A','B']) and contributions(presence,'C')['حسگر']['tf_factor']==0,
'rarer_query_term_has_larger_idf_in_same_frozen_corpus': a['حسگر']['df']==2 and a['ولتاژ']['df']==3 and a['حسگر']['idf']>a['ولتاژ']['idf']>0,
'query_duplicates_and_query_order_do_not_change_own_policy_result': model.rank(['ولتاژ','حسگر','حسگر'],k1=1,b=.5)==ranked,
'corpus_insertion_order_does_not_change_score_or_final_tie_policy': SmallBM25(list(reversed([('A',['حسگر','ولتاژ']),('B',['حسگر','حسگر','ولتاژ','راهنما']),('C',['کنترل','موتور']),('D',['ولتاژ','راهنما','کنترل','موتور'])]))).rank(query,k1=1,b=.5)==ranked,
'exact_score_ties_sort_by_explicit_document_id': [r['doc_id'] for r in tie]==['A','Z'] and tie[0]['score']==tie[1]['score'],
'query_container_count_empty_or_whitespace_tokens_are_checked': fails(lambda:model.rank('حسگر'),'token_container') and fails(lambda:model.rank(['حسگر']*17),'token_count') and all(fails(lambda t=t:model.rank([t]),'token') for t in ('','دو واژه',1,True,'x'*33)),
'corpus_empty_duplicate_ids_empty_docs_bad_pairs_and_tokens_rejected': fails(lambda:SmallBM25([]),'documents') and fails(lambda:SmallBM25([('A',['x']),('A',['y'])]),'duplicate_doc_id') and fails(lambda:SmallBM25([('A',[])]),'token_count') and fails(lambda:SmallBM25([('A',['x']*257)]),'token_count') and fails(lambda:SmallBM25([('A',['x'],1)]),'document_pair') and fails(lambda:SmallBM25([('1bad',['x'])]),'doc_id') and fails(lambda:SmallBM25([('A',[False])]),'token'),
'finite_bounded_parameters_exclude_bools_and_invalid_ranges': all(fails(lambda value=value:model.rank(query,k1=value),'k1') for value in (-1,101,10**10000,math.inf,math.nan,True,'1')) and all(fails(lambda value=value:model.rank(query,b=value),'b') for value in (-.1,1.1,10**10000,math.inf,math.nan,False,'0')),
'adding_unrelated_document_changes_corpus_statistics_and_scores': expanded.statistics()['document_count']==5 and expanded.statistics()['average_length_tokens']==2.6 and expanded_sensor['df']==2 and expanded_sensor['idf']>a['حسگر']['idf'] and expanded.rank(query,k1=1,b=.5)!=ranked,
'token_snapshot_is_independent_of_later_caller_list_mutation': documents[0][1]==['حسگر','ولتاژ','حسگر'] and model.rank(query,k1=1,b=.5)==before_mutation,
}
assert len(checks)==18 and all(type(value)is bool and value for value in checks.values()), checks
return {'python':platform.python_version(),'dependencies':'standard library only',
'scope':'Authored pretokenized one-field score model at unscaled Lucene10.3.1 TF convention; no analyzer, real index, Lucene/Elasticsearch execution or retrieval quality improvement claim',
'corpus_statistics':model.statistics(),'query_terms':query,'fixture_parameters':{'k1':1,'b':.5},
'ranked_documents':ranked,'independent_hand_scores':hand,
'independent_hand_tf_coefficients':{'A_sensor':'6/11','A_voltage':'6/11','B_sensor':'12/19','B_voltage':'6/13','D_voltage':'6/13'},
'fixed_length_tf_factors_f1_to4':factors,'checks':checks}
if __name__=='__main__':
print(json.dumps(run_example(),ensure_ascii=False,indent=2,allow_nan=False))
مرجع دستی از ضریبهای Fraction و log استفاده میکند؛ مسیر اصلی از شمارندهٔ توکنها و log1p به امتیاز میرسد. مقایسهٔ عددیِ همین دادهٔ کوچک، تلورانس نسبی و مطلق ۱۰ به توان منفی سیزده دارد. در آزمون اشباع نیز چهار مدل با تکرار یک تا چهار ساخته میشوند؛ طول سند، میانگین طول و DF واژهٔ حسگرِ مورد آزمون ثابت میمانند و عاملهای واقعی با ضریبهای کسری مستقل مقایسه میشوند.
{
"python": "3.14.5",
"dependencies": "standard library only",
"scope": "Authored pretokenized one-field score model at unscaled Lucene10.3.1 TF convention; no analyzer, real index, Lucene/Elasticsearch execution or retrieval quality improvement claim",
"corpus_statistics": {
"document_count": 4,
"average_length_tokens": 3.0,
"length_tokens": {
"A": 2,
"B": 4,
"C": 2,
"D": 4
},
"document_frequency": {
"حسگر": 2,
"راهنما": 2,
"موتور": 2,
"ولتاژ": 3,
"کنترل": 2
}
},
"query_terms": [
"حسگر",
"ولتاژ"
],
"fixture_parameters": {
"k1": 1,
"b": 0.5
},
"ranked_documents": [
{
"doc_id": "B",
"score": 0.6023963715642388,
"contributions": [
{
"term": "حسگر",
"tf": 2,
"df": 2,
"idf": 0.6931471805599453,
"tf_factor": 0.6315789473684211,
"score": 0.43777716666943917
},
{
"term": "ولتاژ",
"tf": 1,
"df": 3,
"idf": 0.35667494393873234,
"tf_factor": 0.46153846153846156,
"score": 0.16461920489479956
}
]
},
{
"doc_id": "A",
"score": 0.5726302497265515,
"contributions": [
{
"term": "حسگر",
"tf": 1,
"df": 2,
"idf": 0.6931471805599453,
"tf_factor": 0.5454545454545455,
"score": 0.37808028030542473
},
{
"term": "ولتاژ",
"tf": 1,
"df": 3,
"idf": 0.35667494393873234,
"tf_factor": 0.5454545454545455,
"score": 0.19454996942112676
}
]
},
{
"doc_id": "D",
"score": 0.16461920489479956,
"contributions": [
{
"term": "حسگر",
"tf": 0,
"df": 2,
"idf": 0.6931471805599453,
"tf_factor": 0.0,
"score": 0.0
},
{
"term": "ولتاژ",
"tf": 1,
"df": 3,
"idf": 0.35667494393873234,
"tf_factor": 0.46153846153846156,
"score": 0.16461920489479956
}
]
},
{
"doc_id": "C",
"score": 0.0,
"contributions": [
{
"term": "حسگر",
"tf": 0,
"df": 2,
"idf": 0.6931471805599453,
"tf_factor": 0.0,
"score": 0.0
},
{
"term": "ولتاژ",
"tf": 0,
"df": 3,
"idf": 0.35667494393873234,
"tf_factor": 0.0,
"score": 0.0
}
]
}
],
"independent_hand_scores": {
"A": 0.5726302497265514,
"B": 0.6023963715642386,
"C": 0.0,
"D": 0.1646192048947996
},
"independent_hand_tf_coefficients": {
"A_sensor": "6/11",
"A_voltage": "6/11",
"B_sensor": "12/19",
"B_voltage": "6/13",
"D_voltage": "6/13"
},
"fixed_length_tf_factors_f1_to4": [
0.46153846153846156,
0.631578947368421,
0.72,
0.7741935483870968
],
"checks": {
"hand_corpus_counts_lengths_average_and_df_distinct_from_tf": true,
"idf_matches_independent_log2_and_log10_over7": true,
"short_A_has_hand_factor_six_over_eleven_for_both_terms": true,
"B_factors_and_complete_ranking_match_independent_hand_oracle": true,
"no_match_unknown_query_and_empty_query_return_declared_zero_rows": true,
"b_zero_disables_length_effect_for_equal_term_frequency": true,
"b_one_gives_shorter_document_more_weight_at_equal_tf": true,
"fixed_length_tf_saturates_with_positive_decreasing_marginal_gains": true,
"k1_zero_uses_presence_and_avoids_zero_frequency_division": true,
"rarer_query_term_has_larger_idf_in_same_frozen_corpus": true,
"query_duplicates_and_query_order_do_not_change_own_policy_result": true,
"corpus_insertion_order_does_not_change_score_or_final_tie_policy": true,
"exact_score_ties_sort_by_explicit_document_id": true,
"query_container_count_empty_or_whitespace_tokens_are_checked": true,
"corpus_empty_duplicate_ids_empty_docs_bad_pairs_and_tokens_rejected": true,
"finite_bounded_parameters_exclude_bools_and_invalid_ranges": true,
"adding_unrelated_document_changes_corpus_statistics_and_scores": true,
"token_snapshot_is_independent_of_later_caller_list_mutation": true
}
}
قرارداد ورودی یک تا ۶۴ سند غیرخالی با شناسههای یکتا و حداکثر ۲۵۶ توکن در هر سند میپذیرد. توکن یک تا ۳۲ نویسه دارد و فاصلهٔ سفید نمیگیرد. پرسوجو پیش از حذف تکرار حداکثر شانزده توکن دارد. k1 متناهی از صفر تا صد و b از صفر تا یک است؛ سقف صد محدودیت آموزشی ماست. نوع بولی، عدد نامتناهی، شناسهٔ تکراری و سند خالی رد میشوند.
۵. چهار پرسش پاسخدار دربارهٔ تفسیر نتیجه

پرسش اول: آیا دو بار دیدن حسگر در B، DF آن را سه میکند؟ پاسخ: خیر؛ TF آن سند دو است، اما فقط دو سند حسگر دارند. DF با شمارش حضور در مجموعهٔ توکنهای یکتای هر سند ساخته میشود.
پرسش دوم: وقتی b صفر باشد، تفاوت طول A و D چه اثری بر سهم یک ولتاژ دارد؟ پاسخ: با تکرار یکسان و همان آمار، اثر طول حذف میشود و سهمشان برابر است. این برابری لزوماً به معنی ارتباط یکسانِ محتوای دو سند نیست.
پرسش سوم: چرا افزودن سند E با تنها واژهٔ راهنما، امتیازها را عوض میکند؟ پاسخ: N پنج و میانگین طول ۱۳/۵ میشود؛ DF حسگر دو میماند، اما IDF و نرمالسازی وابسته به مجموعهاند. تغییر امتیاز، بهتنهایی بهبود کیفیت را ثابت نمیکند.
پرسش چهارم: آیا میتوان امتیاز این مدل را با MaxSim بردارهای توکنی هممقیاس گرفت؟ پاسخ: خیر؛ ورودی و تعریف امتیاز آن درس متفاوتاند. ارزیابی هر روش باید قرارداد، مجموعه و معیار ارتباط خودش را داشته باشد. منابع در ۲۴ مهر ۱۴۰۵ خوانده شدهاند؛ نسخهٔ مرجع Lucene ثابت و مشخص است، تاریخ نخستین انتشار صفحات معلوم نیست و این آموزش پایه، خبر پژوهشی یا نتیجهٔ استقرار واقعی معرفی نمیشود.
منابع و مطالعهٔ بیشتر
- Lucene10.3.1 BM25Similarity؛ IDF، میانگین طول و پارامترها
- Elasticsearch؛ تنظیم similarity و پارامترهای BM25
- منبع رسمی Lucene10.3.1؛ مقیاس TF و جزئیات امتیاز
- Lucene10.3.1 Similarity؛ ثبت طول و norm در نمایه
بررسی منابع: ۲۴ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

