فهرست مقاله
عدد بین صفر و یک، بهخودیِ خود احتمال معتبرِ پشتیبانی یک پاسخ نیست. حتی اگر دو روش، تعداد تصمیم درست یکسانی داشته باشند، عددهای احتمالی آنها میتوانند زیان متفاوتی بگیرند. این درس با چهار برچسب و احتمالهای کاملاً ساختگی، زیان Brier و یک جدول فراوانی را حساب میکند. هیچ مدل، پاسخ تولیدشده، تماس واقعی یا قضاوت دربارهٔ شواهد بیرونی در برنامه وجود ندارد.
۱. برچسب پشتیبانی را پیش از عدد احتمال تعریف کنیم

برچسب یک در تمرین یعنی بازبینِ فرضی، پشتیبانیِ موردنیاز را برای همان نمونه کافی دانسته است؛ صفر یعنی کافی ندانسته است. اینها دادهٔ تألیفیاند و متن پاسخ یا سند واقعی همراهشان نداریم. در پروژهٔ واقعی باید معلوم باشد «کافی» چه معنایی دارد و اختلاف بازبینها چگونه بررسی میشود؛ تابعِ امتیاز، آن دستورالعمل را تولید نمیکند.
احتمالِ ورودی، مقدار پیشنهادی برای کلاس یک است. آن را از امتیاز شباهت سند یا اعتمادِ خوداظهاریِ یک مدل استخراج نکردهایم. راهنمای کالیبراسیون scikit-learn رابطهٔ احتمالِ اعلامشده و فراوانیِ کلاس مثبت را توضیح میدهد؛ داشتن یک عدد ظاهراً مناسب، جای بررسی آن رابطه روی دادهٔ مناسب را نمیگیرد.
در درس آستانهٔ ارجاع تماس، امتیاز جهتِ تصمیم را مشخص میکرد و احتمال کالیبره معرفی نشده بود. اینجا هزینهٔ ارجاع و انتخاب آستانه نداریم. فقط بررسی میکنیم اگر عددی با معنای احتمال پیشنهاد شود، فاصلهٔ آن از برچسب چگونه گزارش شود و چه تعبیرهایی هنوز مجاز نیستند.
۲. زیان Brier را با احتمال کلاس مثبت حساب کنیم

قرارداد دودوییِ ما، میانگین مربعِ اختلافِ احتمال و برچسب است. فرمول دودویی Brier در مستندات رسمی با بازهٔ صفر تا یک آمده است. قرارداد چندکلاسهٔ بدون نصفکردن میتواند بازهٔ دیگری داشته باشد؛ نامِ یکسانِ معیار، مجوز مخلوطکردن مقیاسها نیست. برنامهٔ حاضر فقط احتمال کلاس مثبتِ دودویی را میگیرد.
چهار برچسب بهترتیب صفر، یک، صفر و یک هستند. روش ساختگی A احتمالهای یکدهم، یکدهم، نهدهم و نهدهم میدهد. مربع اختلافها یکصدم، هشتادویکصدم، هشتادویکصدم و یکصدماند؛ جمعِ ۱٫۶۴ تقسیم بر چهار، ۰٫۴۱ میشود. دو خطای پرجهت، سهم بزرگی در این زیان دارند.
روش ساختگی B برای هر چهار ردیف یکدوم میدهد؛ هر مربعِ اختلاف یکچهارم و میانگین هم ۰٫۲۵ است. اگر صرفاً برای مقایسه، احتمال بزرگتر یا مساوی یکدوم را کلاس مثبت بگیریم، هر دو روش صحتِ یکدوم دارند. بنابراین برابر بودنِ صحتِ تصمیم، برابر بودنِ زیان احتمالی را نشان نمیدهد.
۳. فراوانی در بازهها به مرزبندی وابسته است

دو بازه میگیریم: صفر تا کمتر از یکدوم، سپس یکدوم تا یک با احتساب یک. در هر بازه، میانگین احتمال و سهم برچسبهای یک را گزارش میکنیم. در روش A، هر بازه دو ردیف و سهم مثبتِ یکدوم دارد؛ میانگین احتمالهای دو بازه یکدهم و نهدهم است. قدرمطلق اختلاف در هرکدام ۰٫۴ میشود.
خلاصهٔ قراردادیِ جدول، میانگینِ قدرمطلق این شکافها با وزنِ تعداد نمونههای هر بازه است؛ در اینجا باز هم ۰٫۴ میدهد. این خلاصه را تعریف عمومی و یکتای خطای کالیبراسیون نمینامیم. در روش B، بازهٔ پایین تهی است؛ میانگین و سهم مثبتش مقدار تهی میگیرند، نه صفرِ ساختگی. هر چهار نمونه در بازهٔ بالا هستند.
اگر برای روش A فقط یک بازهٔ بزرگِ صفر تا یک بگذاریم، میانگین احتمال و سهم مثبت هر دو یکدوم میشوند و شکاف صفر است؛ درحالیکه زیان Brier همچنان ۰٫۴۱ باقی میماند. دو اختلافِ خلافجهت با گروهبندیِ درشت پنهان شدهاند. پس مرز بازه و شمار نمونه را همراه خلاصه گزارش میکنیم.
۴. کد و پاسخ دستی را با مرجع صحیح بسنجیم

فایل answer_probability_score.py با Python 3.14.5 و کتابخانهٔ استاندارد اجرا شده است. احتمالها با عدد صحیح یا Fraction دقیق میآیند؛ عدد شناور و رشته بیصدا تبدیل نمیشوند. صورت و مخرج در خروجی ثبت میشوند. scikit-learn فقط مرجع تعریف و تفسیر است و در این آزمایش اجرا نشده است.
"""Exact binary probability scores for synthetic answer-support labels."""
from fractions import Fraction
import itertools
import json
import platform
def _prob(value):
if type(value) not in (int, Fraction):
raise ValueError("an exact int or Fraction probability is required")
value = Fraction(value)
if not 0 <= value <= 1 or value.denominator > 1000:
raise ValueError("probability range/denominator contract")
return value
def evaluate_probabilities(labels, probabilities, edges=(0, Fraction(1, 2), 1)):
if type(labels) not in (list, tuple) or not 1 <= len(labels) <= 32:
raise ValueError("one to thirty-two explicit labels")
if any(type(y) is not int or y not in (0, 1) for y in labels):
raise ValueError("actual binary integer labels")
if type(probabilities) not in (list, tuple) or len(probabilities) != len(labels):
raise ValueError("one probability for each label")
p = [_prob(value) for value in probabilities]
if type(edges) not in (list, tuple) or not 2 <= len(edges) <= 9:
raise ValueError("one to eight explicit bins")
cuts = [_prob(value) for value in edges]
if cuts[0] != 0 or cuts[-1] != 1 or any(a >= b for a, b in zip(cuts, cuts[1:])):
raise ValueError("strictly increasing edges spanning zero to one")
count = len(labels)
brier = sum(((q - y) ** 2 for y, q in zip(labels, p)), Fraction(0)) / count
accuracy = Fraction(sum(int(q >= Fraction(1, 2)) == y for y, q in zip(labels, p)), count)
bins = []
weighted_gap = Fraction(0)
for index, (low, high) in enumerate(zip(cuts, cuts[1:])):
rows = [(y, q) for y, q in zip(labels, p)
if low <= q < high or (index == len(cuts) - 2 and q == high)]
if rows:
mean_p = sum((q for _, q in rows), Fraction(0)) / len(rows)
positive_fraction = Fraction(sum(y for y, _ in rows), len(rows))
gap = abs(mean_p - positive_fraction)
weighted_gap += Fraction(len(rows), count) * gap
else:
mean_p = positive_fraction = gap = None
bins.append({"low": low, "high": high, "upper_inclusive": index == len(cuts) - 2,
"count": len(rows), "mean_probability": mean_p,
"fraction_positive": positive_fraction, "absolute_gap": gap})
return {"sample_count": count, "binary_positive_label": 1, "brier": brier,
"threshold_for_accuracy_only": Fraction(1, 2), "accuracy": accuracy,
"bins": bins, "weighted_absolute_bin_gap": weighted_gap}
def integer_tenths_reference(labels, p10):
"""Independent fixed lookup squares and integer aggregates for grid checks."""
square_cost = {0: {0: 0, 5: 25, 10: 100}, 1: {0: 100, 5: 25, 10: 0}}
n = len(labels)
brier = Fraction(sum(square_cost[y][p] for y, p in zip(labels, p10)), 100 * n)
accuracy = Fraction(sum((p >= 5) == bool(y) for y, p in zip(labels, p10)), n)
buckets = [[(y, p) for y, p in zip(labels, p10) if (p < 5) == low] for low in (True, False)]
gap = Fraction(sum(abs(sum(p for _, p in rows) - 10 * sum(y for y, _ in rows))
for rows in buckets), 10 * n)
return brier, accuracy, gap, [len(rows) for rows in buckets]
def _jsonable(value):
if isinstance(value, Fraction):
return {"numerator": value.numerator, "denominator": value.denominator}
if isinstance(value, dict):
return {k: _jsonable(v) for k, v in value.items()}
if isinstance(value, (list, tuple)):
return [_jsonable(v) for v in value]
return value
def _rejects(function):
try:
function()
except ValueError:
return True
return False
def run_example():
labels = [0, 1, 0, 1]
p_a = [Fraction(1, 10), Fraction(1, 10), Fraction(9, 10), Fraction(9, 10)]
p_b = [Fraction(1, 2)] * 4
a = evaluate_probabilities(labels, p_a)
b = evaluate_probabilities(labels, p_b)
coarse = evaluate_probabilities(labels, p_a, (0, 1))
checks = {}
checks["hand_brier_scores_41_over_100_and_one_quarter"] = a["brier"] == Fraction(41, 100) and b["brier"] == Fraction(1, 4)
checks["same_hard_accuracy_different_probability_loss"] = a["accuracy"] == b["accuracy"] == Fraction(1, 2) and a["brier"] > b["brier"]
checks["hand_two_bins_mean_and_label_frequency"] = [row["count"] for row in a["bins"]] == [2, 2] and [row["mean_probability"] for row in a["bins"]] == [Fraction(1, 10), Fraction(9, 10)] and all(row["fraction_positive"] == Fraction(1, 2) for row in a["bins"]) and a["weighted_absolute_bin_gap"] == Fraction(2, 5)
checks["one_coarse_bin_hides_opposite_signed_gaps"] = coarse["weighted_absolute_bin_gap"] == 0 and coarse["brier"] == a["brier"]
checks["empty_bin_has_unknown_means_not_fabricated_zero"] = b["bins"][0]["count"] == 0 and all(b["bins"][0][key] is None for key in ("mean_probability", "fraction_positive", "absolute_gap"))
endpoints = evaluate_probabilities([0, 1], [0, 1])
checks["last_upper_edge_includes_probability_one_once"] = [row["count"] for row in endpoints["bins"]] == [1, 1] and endpoints["bins"][-1]["upper_inclusive"]
checks["internal_equal_edge_belongs_to_upper_bin"] = [row["count"] for row in b["bins"]] == [0, 4] and sum(row["count"] for row in b["bins"]) == 4
checks["perfect_and_opposite_extreme_probabilities"] = endpoints["brier"] == 0 and evaluate_probabilities([0, 1], [1, 0])["brier"] == 1
checks["half_probability_is_not_a_binary_class_prediction"] = b["brier"] == Fraction(1, 4) and b["accuracy"] == Fraction(1, 2) and b["weighted_absolute_bin_gap"] == 0
checks["paired_sample_permutation_preserves_report"] = evaluate_probabilities(list(reversed(labels)), list(reversed(p_a))) == a
complement = evaluate_probabilities([1 - y for y in labels], [1 - p for p in p_a])
checks["positive_label_complement_preserves_brier"] = complement["brier"] == a["brier"]
checks["label_and_probability_types_ranges_are_strict"] = all(_rejects(lambda ys=ys: evaluate_probabilities(ys, p_a)) for ys in [[True, 1, 0, 1], [0, 2, 0, 1], [0.0, 1, 0, 1]]) and all(_rejects(lambda p=p: evaluate_probabilities([0], [p])) for p in [True, 0.5, "0.5", Fraction(-1, 2), Fraction(3, 2), Fraction(1, 1001)])
checks["bin_edges_require_order_span_and_bounded_count"] = all(_rejects(lambda e=e: evaluate_probabilities(labels, p_a, e)) for e in [(0, 0, 1), (0, 1, Fraction(1, 2)), (Fraction(1, 10), 1), (0, Fraction(9, 10)), (0, 0.5, 1), [Fraction(i, 9) for i in range(10)], "01"])
checks["sample_count_length_and_container_contract"] = all(_rejects(f) for f in [lambda: evaluate_probabilities([], []), lambda: evaluate_probabilities([0], []), lambda: evaluate_probabilities([0] * 33, [0] * 33), lambda: evaluate_probabilities("01", [0, 1]), lambda: evaluate_probabilities([0], "0")])
old_labels, old_p = labels.copy(), p_a.copy()
evaluate_probabilities(labels, p_a)
checks["caller_data_remain_unchanged"] = labels == old_labels and p_a == old_p
oracle_ok = True
oracle_cases = 0
for ys, ps in itertools.product(itertools.product([0, 1], repeat=4), itertools.product([0, 5, 10], repeat=4)):
got = evaluate_probabilities(ys, tuple(Fraction(p, 10) for p in ps))
ref = integer_tenths_reference(ys, ps)
oracle_ok &= (got["brier"], got["accuracy"], got["weighted_absolute_bin_gap"], [row["count"] for row in got["bins"]]) == ref
oracle_cases += 1
checks["independent_integer_lookup_and_bin_aggregate_oracle"] = bool(oracle_ok and oracle_cases == 1296)
assert all(type(v) is bool and v for v in checks.values()), checks
return {"python": platform.python_version(), "dependencies": {"standard_library_only": True, "scikit_learn_executed": False},
"example": _jsonable({"synthetic_labels": labels, "probabilities_A": p_a, "probabilities_B": p_b,
"A_two_bins": a, "B_two_bins": b, "A_one_bin": coarse}),
"oracle_cases_inside_one_group": oracle_cases, "checks": checks,
"limitations": ["Supplied synthetic answer-support labels/probabilities; no model, generation or evidence judgment runs",
"Binary Brier convention in [0,1], not multiclass unscaled score",
"Finite binned gap depends on bins; lower Brier alone and zero gap do not prove population calibration"]}
if __name__ == "__main__":
print(json.dumps(run_example(), ensure_ascii=False, indent=2))
یک تا سیودو برچسبِ صحیح صفر یا یک داریم؛ بولی جای برچسب پذیرفته نمیشود. تعداد احتمالها باید دقیقاً برابر باشد، مقدارشان داخل صفر تا یک و مخرج کسر حداکثر هزار باشد. یک تا هشت بازه با لبههای صریح و کاملاً افزایشی تعریف میشوند؛ ابتدا صفر و انتها یک است. ورودیِ نامعتبر حذف یا اصلاح نمیشود.
شانزده گروه بررسی اجرا شدهاند. مرجع مستقل از جدولِ صحیحِ مربعخطا برای احتمالهای صفر، نیم و یک استفاده میکند و شمار و جمعهای هر بازه را با واحدِ دهم میسنجد. شانزده ترکیبِ چهارتاییِ برچسب و هشتادویک ترکیب احتمال، ۱۲۹۶ حالت را داخل یک گروه میسازند؛ اینها ۱۲۹۶ گروه مستقل نیستند.
جواب دستیِ دو روش، مرزِ دقیق یکدوم، قرارگرفتن احتمال یک در آخرین بازه، بازهٔ تهی و جابهجاییِ زوجِ برچسب و احتمال نیز جدا بررسی میشوند. جابهجاییِ فقط احتمالها همان آزمایش نیست، چون رابطهٔ آنها با برچسبها عوض میشود. دادههای فراخواننده تغییر نمیکنند و هیچ انتقال تماس یا تصمیم اجرایی از نتیجه صادر نمیشود.
۵. امتیاز نمونه را کالیبراسیون واقعی ننامیم

راهنمای رسمی هشدار میدهد که زیان Brier بهتنهایی فقط کالیبراسیون را نمیسنجد؛ توان تفکیک و نامعینی داده نیز در تفسیر آن نقش دارند. از کوچکتر شدن ۰٫۴۱ به ۰٫۲۵، حکم عمومیِ بهتر بودن کالیبراسیون نمیدهیم. همچنین شکاف صفرِ چهار نمونهٔ روش B، رابطهٔ درستِ احتمال و فراوانی را در جمعیت واقعی ثابت نمیکند.
{
"python": "3.14.5",
"dependencies": {
"standard_library_only": true,
"scikit_learn_executed": false
},
"example": {
"synthetic_labels": [
0,
1,
0,
1
],
"probabilities_A": [
{
"numerator": 1,
"denominator": 10
},
{
"numerator": 1,
"denominator": 10
},
{
"numerator": 9,
"denominator": 10
},
{
"numerator": 9,
"denominator": 10
}
],
"probabilities_B": [
{
"numerator": 1,
"denominator": 2
},
{
"numerator": 1,
"denominator": 2
},
{
"numerator": 1,
"denominator": 2
},
{
"numerator": 1,
"denominator": 2
}
],
"A_two_bins": {
"sample_count": 4,
"binary_positive_label": 1,
"brier": {
"numerator": 41,
"denominator": 100
},
"threshold_for_accuracy_only": {
"numerator": 1,
"denominator": 2
},
"accuracy": {
"numerator": 1,
"denominator": 2
},
"bins": [
{
"low": {
"numerator": 0,
"denominator": 1
},
"high": {
"numerator": 1,
"denominator": 2
},
"upper_inclusive": false,
"count": 2,
"mean_probability": {
"numerator": 1,
"denominator": 10
},
"fraction_positive": {
"numerator": 1,
"denominator": 2
},
"absolute_gap": {
"numerator": 2,
"denominator": 5
}
},
{
"low": {
"numerator": 1,
"denominator": 2
},
"high": {
"numerator": 1,
"denominator": 1
},
"upper_inclusive": true,
"count": 2,
"mean_probability": {
"numerator": 9,
"denominator": 10
},
"fraction_positive": {
"numerator": 1,
"denominator": 2
},
"absolute_gap": {
"numerator": 2,
"denominator": 5
}
}
],
"weighted_absolute_bin_gap": {
"numerator": 2,
"denominator": 5
}
},
"B_two_bins": {
"sample_count": 4,
"binary_positive_label": 1,
"brier": {
"numerator": 1,
"denominator": 4
},
"threshold_for_accuracy_only": {
"numerator": 1,
"denominator": 2
},
"accuracy": {
"numerator": 1,
"denominator": 2
},
"bins": [
{
"low": {
"numerator": 0,
"denominator": 1
},
"high": {
"numerator": 1,
"denominator": 2
},
"upper_inclusive": false,
"count": 0,
"mean_probability": null,
"fraction_positive": null,
"absolute_gap": null
},
{
"low": {
"numerator": 1,
"denominator": 2
},
"high": {
"numerator": 1,
"denominator": 1
},
"upper_inclusive": true,
"count": 4,
"mean_probability": {
"numerator": 1,
"denominator": 2
},
"fraction_positive": {
"numerator": 1,
"denominator": 2
},
"absolute_gap": {
"numerator": 0,
"denominator": 1
}
}
],
"weighted_absolute_bin_gap": {
"numerator": 0,
"denominator": 1
}
},
"A_one_bin": {
"sample_count": 4,
"binary_positive_label": 1,
"brier": {
"numerator": 41,
"denominator": 100
},
"threshold_for_accuracy_only": {
"numerator": 1,
"denominator": 2
},
"accuracy": {
"numerator": 1,
"denominator": 2
},
"bins": [
{
"low": {
"numerator": 0,
"denominator": 1
},
"high": {
"numerator": 1,
"denominator": 1
},
"upper_inclusive": true,
"count": 4,
"mean_probability": {
"numerator": 1,
"denominator": 2
},
"fraction_positive": {
"numerator": 1,
"denominator": 2
},
"absolute_gap": {
"numerator": 0,
"denominator": 1
}
}
],
"weighted_absolute_bin_gap": {
"numerator": 0,
"denominator": 1
}
}
},
"oracle_cases_inside_one_group": 1296,
"checks": {
"hand_brier_scores_41_over_100_and_one_quarter": true,
"same_hard_accuracy_different_probability_loss": true,
"hand_two_bins_mean_and_label_frequency": true,
"one_coarse_bin_hides_opposite_signed_gaps": true,
"empty_bin_has_unknown_means_not_fabricated_zero": true,
"last_upper_edge_includes_probability_one_once": true,
"internal_equal_edge_belongs_to_upper_bin": true,
"perfect_and_opposite_extreme_probabilities": true,
"half_probability_is_not_a_binary_class_prediction": true,
"paired_sample_permutation_preserves_report": true,
"positive_label_complement_preserves_brier": true,
"label_and_probability_types_ranges_are_strict": true,
"bin_edges_require_order_span_and_bounded_count": true,
"sample_count_length_and_container_contract": true,
"caller_data_remain_unchanged": true,
"independent_integer_lookup_and_bin_aggregate_oracle": true
},
"limitations": [
"Supplied synthetic answer-support labels/probabilities; no model, generation or evidence judgment runs",
"Binary Brier convention in [0,1], not multiclass unscaled score",
"Finite binned gap depends on bins; lower Brier alone and zero gap do not prove population calibration"
]
}
دروازهٔ شواهد و تعارض منابع RAG مسئولیت متفاوتی دارد: وجود شاهد و رابطهٔ آن با ادعا را باید بررسی کرد. عدد احتمال یا زیان کم، شاهدِ غایب را نمیسازد. این مثال فقط برچسبهای دادهشده را میپذیرد و صحت علمی، مجوز دسترسی یا کافیبودن واقعی اسناد را تعیین نمیکند.
برای سامانهٔ واقعی، تولید احتمال، تعریف برچسب، دادهٔ مستقلِ ارزیابی و بررسی تغییر توزیع جدا لازماند. اینجا کالیبراتور آموزش ندادهایم و تجزیهٔ Brier یا فاصلهٔ اطمینانِ محصول برآورد نشده است. منابع در ۲۵ مهر ۱۴۰۵ خوانده شدند؛ نسخهٔ مستندات 1.9.1 و اجرای محلی 3.14.5 جدا ثبت شدهاند و مطلب، خبر پژوهشی تازه نیست.
پرسش اول: چرا صحت دو روش برابر ولی زیان متفاوت است؟ پاسخ: صحت فقط سمتِ آستانه را میبیند؛ زیان، اندازهٔ فاصلهٔ خودِ احتمال از برچسب را هم حساب میکند.
پرسش دوم: چرا شکافِ یک بازهٔ بزرگ صفر شد؟ پاسخ: میانگینِ احتمالهای کم و زیاد یکدوم شد و اختلافهای خلافجهت پنهان شدند؛ ریزکردن بازهها همان داده را متفاوت خلاصه میکند.
پرسش سوم: بازهٔ تهی چرا فراوانی صفر ندارد؟ پاسخ: مشاهدهای ندارد که مخرجِ معتبر بسازد؛ تعداد صفر ثبت میشود ولی میانگین و نسبت، تهی میمانند.
پرسش چهارم: آیا زیان کمتر، پاسخِ بدون منبع را مجاز میکند؟ پاسخ: خیر؛ احتمال و برچسبِ ساختگی، سند یا مجوز ایجاد نمیکنند. ارزیابی شواهد و سیاست پاسخگویی باید مستقل انجام شوند.
منابع و مطالعهٔ بیشتر
- scikit-learn؛ تعریف کالیبراسیون احتمال و محدودیت تفسیر Brier
- scikit-learn؛ زیان Brier، کلاس مثبت و قرارداد دودویی
بررسی منابع: ۲۵ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

