در ارزیابی یک دسته‌بندِ عیب، عدد بزرگی مثل accuracy می‌تواند توجه را از کلاس کم‌نمونه دور کند. پیش از انتخاب معیار پذیرش، باید بدانیم هر عدد چه چیزی را می‌شمارد و با کلاسِ بدون نمونه چه می‌کند. در این درس سه نام healthy، bearing و winding صرفاً برچسب‌های ساختگیِ سالم، یاتاقان و سیم‌پیچ‌اند. هیچ حسگر، موتور واقعی یا مدل آموزش‌دیده‌ای در آزمایش نداریم؛ هدف، آزمودن محاسبه و انتخاب آگاهانهٔ معیار است.

۱. مسئلهٔ تک‌برچسبی و فضای کلاس ثابت

فضای برچسب ثابت پیش از محاسبهٔ معیار
تصویر مفهومی: فضای برچسب ثابت پیش از محاسبهٔ معیار؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

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

کلاس‌های اعلام‌شده حتی اگر در این نمونه دیده نشوند، باقی می‌مانند. ثابت‌ماندن این فهرست برای مقایسهٔ گزارش‌ها مهم است: اگر هر بار فقط نام‌های حاضر در داده را انتخاب کنیم، مخرج میانگین macro می‌تواند تغییر کند. نبود نمونهٔ یک کلاس، شاهدی دربارهٔ توانایی دسته‌بند در آن کلاس نیست. همراه هر معیار باید تعداد مشاهدات و سیاست برخورد با چنین کلاس‌هایی را هم ببینیم.

واحد ماتریس، تعداد مشاهده است؛ precision و recall و F1 بی‌بعدند. این نسبت‌ها احتمالِ کالیبره‌شدهٔ خرابی یا اطمینانِ پیش‌بینیِ یک نمونه نیستند. در مثال هشت‌تایی، چهار مرجع سالم، دو مرجع یاتاقان و دو مرجع سیم‌پیچ داریم. پیش‌بینی‌ها را خودمان از قبل نوشته‌ایم؛ پس عبارت «نتیجهٔ مدل» برای این خروجی، ادعای درستی نخواهد بود.

۲. جهت ماتریس خطا و شمارش مستقل هر کلاس

تمایز مرجع و پیش‌بینی در شمارش خطا
تصویر مفهومی: تمایز مرجع و پیش‌بینی در شمارش خطا؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

طبق قرارداد ماتریس خطای scikit-learn، سطر را مرجع و ستون را پیش‌بینی می‌گیریم. خانهٔ سطر سالم و ستون یاتاقان، تعداد نمونه‌های سالمی است که یاتاقان پیش‌بینی شده‌اند. محورهای جابه‌جا همان تفسیر را ندارند. ماتریس خام مثال، سطرهای [۳،۱،۰]، [۱،۱،۰] و [۱،۰،۱] دارد و جمع همهٔ خانه‌ها هشت است.

برای کلاس سالم، TP برابر سه است؛ دو مشاهدهٔ غیرسالم هم سالم پیش‌بینی شده‌اند، پس FP دو است. یک سالم با نام دیگری آمده، بنابراین FN یک است. precision برابر ۳/۵ و recall برابر ۳/۴ می‌شود. F1 را از ۲TP تقسیم بر ۲TP+FP+FN می‌سازیم؛ در این کلاس ۶/۹ یا ۲/۳ است. support نیز چهار، یعنی تعداد مرجعِ سالم است.

مرجع precision و recall تعریف این نسبت‌ها و میانگین‌ها را ارائه می‌کند. macro، میانگینِ بدون وزنِ معیار هر کلاس است؛ macro-F1 میانگین سه F1 خواهد بود و نباید آن را از میانگین precision و میانگین recall دوباره محاسبه کرد. micro ابتدا TP و FP و FN همهٔ کلاس‌ها را جمع می‌کند و سپس نسبت را می‌سازد.

۳. کد کامل و معیار تعریف‌نشده

قرارداد محاسبه و حالت بدون پشتیبانی
تصویر مفهومی: قرارداد محاسبه و حالت بدون پشتیبانی؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

کد استاندارد پایتون با نسخهٔ 3.14.5 اجرا شده است؛ scikit-learn در این آزمایش اجرا نشده و مرجع تعریف‌هاست. ورودی فقط list یا tuple محدود با رشته‌های دقیق، غیرخالی و بدون فاصلهٔ ابتدا و انتهاست. فضای برچسب تکراری، مشاهدهٔ ناشناخته، طول ناهماهنگ و دادهٔ خالی رد می‌شود. bool و NaN نیز برچسب رشته‌ای معتبر نیستند و خودکار تبدیل نمی‌شوند.

"""Original synthetic single-label multiclass acceptance-metric lesson."""
from fractions import Fraction
import itertools
import json
import math
import platform


def _labels(value, name):
    if type(value) not in (list, tuple):
        raise TypeError(name + ' must be a finite list or tuple')
    result = tuple(value)
    if any(type(x) is not str or not x or x.strip() != x for x in result):
        raise TypeError(name + ' entries must be exact nonempty trimmed str')
    return result


def classification_report(actual, predicted, labels):
    """Rows actual, columns predicted; all labels included, zero division -> 0."""
    universe = _labels(labels, 'labels')
    if not universe or len(set(universe)) != len(universe):
        raise ValueError('labels must be nonempty and unique')
    actual, predicted = _labels(actual, 'actual'), _labels(predicted, 'predicted')
    if not actual or len(actual) != len(predicted):
        raise ValueError('nonempty aligned observations required')
    index = {name: i for i, name in enumerate(universe)}
    if any(x not in index for x in actual + predicted):
        raise ValueError('observation outside explicit label universe')
    matrix = [[0 for _ in universe] for _ in universe]
    for truth, guess in zip(actual, predicted):
        matrix[index[truth]][index[guess]] += 1
    per_class = []
    for i, label in enumerate(universe):
        tp = matrix[i][i]
        support = sum(matrix[i])
        predicted_count = sum(row[i] for row in matrix)
        fp, fn = predicted_count - tp, support - tp
        pden, rden, fden = tp + fp, tp + fn, 2 * tp + fp + fn
        per_class.append({'label': label, 'support': support, 'tp': tp, 'fp': fp, 'fn': fn,
                          'precision': tp / pden if pden else 0.0,
                          'recall': tp / rden if rden else 0.0,
                          'f1': 2 * tp / fden if fden else 0.0,
                          'undefined': {'precision': pden == 0, 'recall': rden == 0, 'f1': fden == 0}})
    macro = {key: math.fsum(row[key] for row in per_class) / len(universe)
             for key in ('precision', 'recall', 'f1')}
    tp, fp, fn = (sum(row[key] for row in per_class) for key in ('tp', 'fp', 'fn'))
    micro = {'precision': tp / (tp + fp), 'recall': tp / (tp + fn),
             'f1': 2 * tp / (2 * tp + fp + fn)}
    return {'labels': list(universe), 'n': len(actual), 'matrix': matrix,
            'per_class': per_class, 'macro': macro, 'micro': micro,
            'accuracy': sum(matrix[i][i] for i in range(len(universe))) / len(actual)}


def _direct_fraction_reference(actual, predicted, labels):
    """Independent one-versus-rest scans, no matrix or production helper."""
    answer = []
    for label in labels:
        tp = sum(a == label and p == label for a, p in zip(actual, predicted))
        fp = sum(a != label and p == label for a, p in zip(actual, predicted))
        fn = sum(a == label and p != label for a, p in zip(actual, predicted))
        answer.append(tuple(Fraction(num, den) if den else Fraction(0)
                            for num, den in ((tp, tp + fp), (tp, tp + fn), (2 * tp, 2 * tp + fp + fn))))
    macro = tuple(sum(row[i] for row in answer) / len(labels) for i in range(3))
    accuracy = Fraction(sum(a == p for a, p in zip(actual, predicted)), len(actual))
    return answer, macro, accuracy


def _same_metrics(left, right):
    return all(math.isclose(left[avg][key], right[avg][key], rel_tol=1e-14, abs_tol=1e-15)
               for avg in ('macro', 'micro') for key in ('precision', 'recall', 'f1'))


def _raises(expected, function, *args):
    try:
        function(*args)
    except expected:
        return True
    return False


def run_example():
    labels = ['healthy', 'bearing', 'winding']
    actual = ['healthy'] * 4 + ['bearing'] * 2 + ['winding'] * 2
    predicted = ['healthy', 'healthy', 'healthy', 'bearing', 'bearing', 'healthy', 'healthy', 'winding']
    report = classification_report(actual, predicted, labels)
    original_inputs = (actual[:], predicted[:], labels[:])
    direct, macro, accuracy = _direct_fraction_reference(actual, predicted, labels)
    absent = classification_report(['healthy'], ['healthy'], labels)
    no_prediction = classification_report(['healthy', 'bearing'], ['healthy', 'healthy'], labels)
    reordered = classification_report(actual, predicted, list(reversed(labels)))
    reordered_rows = {row['label']: row for row in reordered['per_class']}
    permuted = classification_report(actual[::-1], predicted[::-1], labels)
    repeated = classification_report(actual * 3, predicted * 3, labels)
    swapped = classification_report(predicted, actual, labels)
    left = classification_report(actual[:4], predicted[:4], labels)
    right = classification_report(actual[4:], predicted[4:], labels)
    exhaustive_results = []
    for size in (1, 2, 3):
        sequences = list(itertools.product(labels, repeat=size))
        for truth in sequences:
            for guess in sequences:
                result = classification_report(truth, guess, labels)
                rows, expected_macro, expected_accuracy = _direct_fraction_reference(truth, guess, labels)
                matches = all(math.isclose(result['per_class'][i][key], float(rows[i][j]), abs_tol=1e-15)
                              for i in range(3) for j, key in enumerate(('precision', 'recall', 'f1')))
                matches = matches and all(math.isclose(result['macro'][key], float(expected_macro[j]), abs_tol=1e-15)
                                          for j, key in enumerate(('precision', 'recall', 'f1')))
                exhaustive_results.append(matches and all(result['micro'][k] == float(expected_accuracy)
                                                         for k in ('precision', 'recall', 'f1')))
    imbalanced = classification_report(['healthy'] * 90 + ['bearing'] * 10, ['healthy'] * 100,
                                      ['healthy', 'bearing'])
    accuracy_rule = imbalanced['accuracy'] >= 0.85
    recall_rule = imbalanced['per_class'][1]['recall'] >= 0.80
    invalid_universes = [[], ['healthy', 'healthy'], [''], [True], [float('nan')], 'healthy']
    invalid_observations = [([True], ['healthy']), ([1], ['healthy']),
                            ([float('nan')], ['healthy']), (['healthy'], [None]),
                            ('healthy', ['healthy']), ([' healthy'], ['healthy'])]
    checks = {
        'hand_rows_actual_columns_predicted': report['matrix'] == [[3, 1, 0], [1, 1, 0], [1, 0, 1]],
        'hand_healthy_counts_and_ratios': report['per_class'][0]['tp'] == 3 and report['per_class'][0]['fp'] == 2
            and report['per_class'][0]['fn'] == 1 and report['per_class'][0]['precision'] == 3 / 5
            and report['per_class'][0]['recall'] == 3 / 4 and report['per_class'][0]['f1'] == 2 / 3,
        'hand_macro_is_mean_of_class_metrics': math.isclose(report['macro']['precision'], 7 / 10)
            and math.isclose(report['macro']['recall'], 7 / 12) and math.isclose(report['macro']['f1'], 11 / 18),
        'micro_equals_accuracy_only_under_our_single_label_full_universe': report['accuracy'] == 5 / 8
            and all(v == 5 / 8 for v in report['micro'].values()),
        'absent_class_kept_with_undefined_flags_and_zero_policy': absent['per_class'][2]['support'] == 0
            and absent['per_class'][2]['undefined'] == {'precision': True, 'recall': True, 'f1': True}
            and absent['macro'] == {'precision': 1 / 3, 'recall': 1 / 3, 'f1': 1 / 3},
        'unsupported_prediction_precision_and_observed_recall_distinguished': no_prediction['per_class'][1]['undefined']
            == {'precision': True, 'recall': False, 'f1': False}
            and no_prediction['per_class'][1]['precision'] == no_prediction['per_class'][1]['recall'] == 0,
        'label_order_changes_axes_not_named_class_metrics': all(reordered_rows[r['label']] == r for r in report['per_class'])
            and _same_metrics(reordered, report),
        'observation_order_invariance': permuted == report,
        'repeat_dataset_scales_counts_not_ratios': _same_metrics(repeated, report)
            and repeated['matrix'] == [[3 * n for n in row] for row in report['matrix']],
        'swap_actual_prediction_transposes_matrix_and_exchanges_ratios': swapped['matrix'] == [list(x) for x in zip(*report['matrix'])]
            and all(swapped['per_class'][i]['precision'] == report['per_class'][i]['recall']
                    and swapped['per_class'][i]['recall'] == report['per_class'][i]['precision'] for i in range(3)),
        'chunk_counts_add_but_macros_are_not_pooled_by_simple_mean': report['matrix']
            == [[left['matrix'][i][j] + right['matrix'][i][j] for j in range(3)] for i in range(3)]
            and not math.isclose((left['macro']['f1'] + right['macro']['f1']) / 2, report['macro']['f1']),
        'independent_direct_fraction_reference_819_cases': len(exhaustive_results) == 819 and all(exhaustive_results),
        'synthetic_imbalance_acceptance_rules_disagree': accuracy_rule is True and recall_rule is False
            and imbalanced['accuracy'] == 0.9 and imbalanced['per_class'][1]['recall'] == 0,
        'empty_observations_rejected': _raises(ValueError, classification_report, [], [], labels),
        'unequal_observation_lengths_rejected': _raises(ValueError, classification_report, ['healthy'], [], labels),
        'invalid_label_universes_rejected': all(_raises((TypeError, ValueError), classification_report,
                                                      ['healthy'], ['healthy'], bad) for bad in invalid_universes),
        'unknown_or_invalid_observation_labels_rejected': _raises(ValueError, classification_report, ['other'], ['healthy'], labels)
            and all(_raises((TypeError, ValueError), classification_report, a, p, labels) for a, p in invalid_observations),
        'inputs_unchanged_and_hand_reference_exact_fractions': (actual, predicted, labels) == original_inputs
            and direct == [(Fraction(3, 5), Fraction(3, 4), Fraction(2, 3)),
                           (Fraction(1, 2), Fraction(1, 2), Fraction(1, 2)),
                           (Fraction(1), Fraction(1, 2), Fraction(2, 3))]
            and macro == (Fraction(7, 10), Fraction(7, 12), Fraction(11, 18)) and accuracy == Fraction(5, 8),
    }
    assert len(checks) == 18 and all(type(v) is bool and v for v in checks.values())
    return {'runtime': {'python': platform.python_version(), 'libraries': ['stdlib only; scikit-learn not executed']},
            'contract': {'task': 'synthetic single-label multiclass, all labels mandatory', 'axes': 'rows actual, columns predicted',
                         'zero_division': 'return0 and record undefined flag; retain absent classes in macro',
                         'units': 'counts observations; scores dimensionless, not probabilities'},
            'metrics': {'hand_report': report, 'hand_macro_exact': [str(x) for x in macro],
                        'independent_reference_cases': len(exhaustive_results),
                        'imbalance': {'n': 100, 'accuracy': imbalanced['accuracy'], 'bearing_recall': imbalanced['per_class'][1]['recall'],
                                      'illustrative_accuracy_at_least_0_85': accuracy_rule,
                                      'illustrative_bearing_recall_at_least_0_80': recall_rule}},
            'checks': checks,
            'limitations': ['No trained model, real faults, sensor data, confidence interval, cost/risk optimum or deployment approval.',
                            'One label per observation, no sample weights, multilabel, abstentions or excluded actual/predicted labels.',
                            'Zero-filled undefined scores are policy values, not observed evidence; class universe affects macro.',
                            'Acceptance thresholds are arbitrary teaching rules fixed for this synthetic fixture, not engineering safety requirements.']}


if __name__ == '__main__':
    print(json.dumps(run_example(), ensure_ascii=False, indent=2))

اگر هیچ پیش‌بینیِ یک کلاس وجود نداشته باشد، مخرج precision آن صفر است. اگر هیچ مرجع آن کلاس نباشد، recall تعریف نشده است. سیاست ما برای خروجی عددی، صفر قراردادی است و هم‌زمان پرچم undefined را ذخیره می‌کنیم. این صفر معادل اندازه‌گیریِ عملکرد صفر نیست؛ پرچم، علت عدد را حفظ می‌کند. کلاس بدون مرجع ولی دارای پیش‌بینی، با کلاس کاملاً غایب نیز یکسان نیست.

کد تمام کلاس‌های اعلام‌شده را در macro نگه می‌دارد؛ حذف نسبت‌های تعریف‌نشده یا جایگزینی یک به‌جای صفر، عدد دیگری می‌سازد. در نمونهٔ تک‌مشاهده‌ای که تنها سالم درست است، با همین سه کلاس، macro هر معیار یک‌سوم می‌شود؛ accuracy یک است. این تفاوت تناقض نیست: یکی میانگینِ کلاس‌های ثابت و دیگری نسبتِ مشاهده‌های درست است.

۴. جواب دستی، مرجع کسری و تصمیم پذیرش

میانگین کل و وزن برابر کلاس‌ها
تصویر مفهومی: میانگین کل و وزن برابر کلاس‌ها؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

در مثال اصلی، پنج پیش‌بینی از هشت درست‌اند؛ accuracy برابر ۵/۸ است. در مسئلهٔ تک‌برچسبی با پوشش تمام کلاس‌ها، micro precision و recall و F1 نیز همین مقدارند. macro precision برابر ۷/۱۰، macro recall برابر ۷/۱۲ و macro-F1 برابر ۱۱/۱۸ می‌شود. نمایش اعشاری ممکن است رقم پایانی اضافه داشته باشد؛ مرجع کسریِ جدا، جواب دقیقِ این شمارش‌های کوچک را نگه می‌دارد.

{
  "runtime": {
    "python": "3.14.5",
    "libraries": [
      "stdlib only; scikit-learn not executed"
    ]
  },
  "contract": {
    "task": "synthetic single-label multiclass, all labels mandatory",
    "axes": "rows actual, columns predicted",
    "zero_division": "return0 and record undefined flag; retain absent classes in macro",
    "units": "counts observations; scores dimensionless, not probabilities"
  },
  "metrics": {
    "hand_report": {
      "labels": [
        "healthy",
        "bearing",
        "winding"
      ],
      "n": 8,
      "matrix": [
        [
          3,
          1,
          0
        ],
        [
          1,
          1,
          0
        ],
        [
          1,
          0,
          1
        ]
      ],
      "per_class": [
        {
          "label": "healthy",
          "support": 4,
          "tp": 3,
          "fp": 2,
          "fn": 1,
          "precision": 0.6,
          "recall": 0.75,
          "f1": 0.6666666666666666,
          "undefined": {
            "precision": false,
            "recall": false,
            "f1": false
          }
        },
        {
          "label": "bearing",
          "support": 2,
          "tp": 1,
          "fp": 1,
          "fn": 1,
          "precision": 0.5,
          "recall": 0.5,
          "f1": 0.5,
          "undefined": {
            "precision": false,
            "recall": false,
            "f1": false
          }
        },
        {
          "label": "winding",
          "support": 2,
          "tp": 1,
          "fp": 0,
          "fn": 1,
          "precision": 1.0,
          "recall": 0.5,
          "f1": 0.6666666666666666,
          "undefined": {
            "precision": false,
            "recall": false,
            "f1": false
          }
        }
      ],
      "macro": {
        "precision": 0.7000000000000001,
        "recall": 0.5833333333333334,
        "f1": 0.611111111111111
      },
      "micro": {
        "precision": 0.625,
        "recall": 0.625,
        "f1": 0.625
      },
      "accuracy": 0.625
    },
    "hand_macro_exact": [
      "7/10",
      "7/12",
      "11/18"
    ],
    "independent_reference_cases": 819,
    "imbalance": {
      "n": 100,
      "accuracy": 0.9,
      "bearing_recall": 0.0,
      "illustrative_accuracy_at_least_0_85": true,
      "illustrative_bearing_recall_at_least_0_80": false
    }
  },
  "checks": {
    "hand_rows_actual_columns_predicted": true,
    "hand_healthy_counts_and_ratios": true,
    "hand_macro_is_mean_of_class_metrics": true,
    "micro_equals_accuracy_only_under_our_single_label_full_universe": true,
    "absent_class_kept_with_undefined_flags_and_zero_policy": true,
    "unsupported_prediction_precision_and_observed_recall_distinguished": true,
    "label_order_changes_axes_not_named_class_metrics": true,
    "observation_order_invariance": true,
    "repeat_dataset_scales_counts_not_ratios": true,
    "swap_actual_prediction_transposes_matrix_and_exchanges_ratios": true,
    "chunk_counts_add_but_macros_are_not_pooled_by_simple_mean": true,
    "independent_direct_fraction_reference_819_cases": true,
    "synthetic_imbalance_acceptance_rules_disagree": true,
    "empty_observations_rejected": true,
    "unequal_observation_lengths_rejected": true,
    "invalid_label_universes_rejected": true,
    "unknown_or_invalid_observation_labels_rejected": true,
    "inputs_unchanged_and_hand_reference_exact_fractions": true
  },
  "limitations": [
    "No trained model, real faults, sensor data, confidence interval, cost/risk optimum or deployment approval.",
    "One label per observation, no sample weights, multilabel, abstentions or excluded actual/predicted labels.",
    "Zero-filled undefined scores are policy values, not observed evidence; class universe affects macro.",
    "Acceptance thresholds are arbitrary teaching rules fixed for this synthetic fixture, not engineering safety requirements."
  ]
}

مرجع مستقل برای هر کلاس، جفت‌های مرجع و پیش‌بینی را مستقیم مرور می‌کند و از ماتریس یا تابع اصلی استفاده نمی‌کند. نسبت‌های آن با Fraction محاسبه می‌شوند. برای دنباله‌های طول یک تا سه روی سه کلاس، ۸۱۹ جفت دنباله بررسی شده است؛ همهٔ این موارد یک گروه از هجده گروه نام‌دارند. جابه‌جایی ترتیب مشاهده‌ها، تغییر ترتیب کلاس‌ها و تکرار داده نیز با ویژگی‌های مناسب آزموده شده‌اند.

جمع‌کردن ماتریس بسته‌های داده درست است، اما میانگین سادهٔ macro-F1 بسته‌ها لزوماً macro-F1 دادهٔ تجمیعی نیست. برای گزارش نهایی، شمارش‌ها را تجمیع و نسبت‌ها را دوباره محاسبه می‌کنیم. با عوض‌کردن جای مرجع و پیش‌بینی، ماتریس ترانهاده و precision و recall هر کلاس جابه‌جا می‌شوند؛ این آزمون می‌تواند اشتباه جهت محور را آشکار کند، ولی کیفیت مرجع‌گذاری انسانی را تأیید نمی‌کند.

نمونهٔ دوم صد برچسب ساختگی دارد: نود سالم و ده یاتاقان، با پیش‌بینی سالم برای همه. accuracy نود درصد و recall یاتاقان صفر است. شرط آموزشیِ «accuracy دست‌کم ۰٫۸۵» می‌گذرد و شرطِ «recall یاتاقان دست‌کم ۰٫۸۰» رد می‌شود. آستانه‌ها دلخواهِ همین تمرین‌اند؛ معیار ایمنی یا اجازهٔ استقرار صنعتی نیستند. انتخاب واقعی به هزینهٔ خطا، دادهٔ نماینده و نیازِ مسئله وابسته است.

آزمون IoU و Dice ماسک تصویر نمونهٔ دیگری از لزوم تعریف معیار است؛ اینجا مشاهده، برچسب کامل یک نمونه است، نه پیکسل. آزمون گرادیان مدل خطی نیز صحت یک محاسبه را از ارزیابی کاربرد جدا می‌کند. منابع در ۲۴ مهر ۱۴۰۵، با مستندات scikit-learn نسخهٔ 1.9.1 خوانده شدند؛ تاریخ اصلی انتشار صفحات مشخص نیست و این درس خبر پژوهشی تازه نیست.

۵. چهار پرسش با پاسخ کامل

انتخاب معیار پذیرش و بازبینی مستقل
تصویر مفهومی: انتخاب معیار پذیرش و بازبینی مستقل؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

پرسش اول: در کلاس سالمِ مثال، precision و recall چندند؟ پاسخ: با TP سه، FP دو و FN یک، precision برابر ۳/۵ و recall برابر ۳/۴ است؛ عوض‌کردن این دو نسبت معنای خطا را تغییر می‌دهد.

پرسش دوم: macro-F1 را از دو میانگین دیگر بسازیم؟ پاسخ: خیر؛ F1 هر کلاس را جدا محاسبه و میانگین می‌گیریم. جواب این داده ۱۱/۱۸ است، نه لزوماً میانگین هارمونیک macro precision و macro recall.

پرسش سوم: کلاس کاملاً غایب را حذف کنیم؟ پاسخ: در این قرارداد خیر؛ با support صفر، نسبت‌های تعریف‌نشده، صفر قراردادی و پرچم روشن باقی می‌ماند. تغییر این سیاست باید صریح باشد و مقایسهٔ گزارش‌ها را تحت تأثیر قرار می‌دهد.

پرسش چهارم: نود درصد accuracy یعنی ده عیب یاتاقان پیدا شده‌اند؟ پاسخ: خیر؛ در نمونهٔ نامتوازن ما هیچ‌کدام پیدا نشده و recall آن کلاس صفر است. پذیرش با یک عدد کلی باید با نیازِ مشخصِ هر کلاس و محدودیت داده بررسی شود.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۲۴ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.