فهرست مقاله
وقتی تابعِ زیانِ یک ماژولِ رگرسیون را عوض میکنیم، نامِ تازهٔ تابع برای حفظِ رفتار کافی نیست. ماژولِ پیشبینی، ماژولِ ارزیابی و آزمونهای نگهداری باید دربارهٔ علامتِ باقیمانده، واحد، نقطهٔ گذار و میانگینگیری توافق داشته باشند. این درس، همین قراردادِ کوچک را هنگامِ جایگزینیِ زیانِ مربعی با Huber بررسی میکند. دادهها کاملاً تألیفیاند؛ هیچ سرویس یکپارچه، مدلِ آموزشدیده، دادهٔ پرتِ شناساییشده یا افزایشِ دقتِ تجربی گزارش نمیشود.
۱. قراردادِ باقیمانده و واحدِ زیان را نگه داریم

باقیمانده را پیشبینی منهای مقدارِ مرجع تعریف میکنیم و واحدِ هر دو را میلیولت میگیریم. چهار باقیماندهٔ ساختگیِ منفی یک، صفر، یک و ده میلیولت داریم. اینجا خودِ دو ستونِ پیشبینی و مرجع را تولید نمیکنیم؛ ورودیِ ماژول، باقیماندهٔ آماده است. تطبیقِ هویتِ ردیفها و تبدیلِ واحد پیش از این مرز، مسئولیتِ فراخواننده است؛ هماندازهبودنِ دو فهرست بهتنهایی آنها را تأیید نمیکند.
دلتا یک عددِ مثبت با همان واحدِ باقیمانده است؛ در مثال دو میلیولت انتخاب شده است. تعریفِ HuberLoss در مستنداتِ PyTorch 2.14 برای قدرِ باقیماندهٔ کوچکتر از دلتا، نصفِ مربع و بیرونِ آن، حاصلِ ضربِ دلتا در اختلافِ قدرِ باقیمانده و نصفِ دلتا را میدهد. بنابراین زیانِ این قرارداد واحدِ میلیولتِ مربع دارد. دلتا از این داده تخمین زده نشده و مشخصهٔ تأییدشدهٔ نویزِ یک ابزار نیست.
چهار سهمِ زیان بهترتیب یکدوم، صفر، یکدوم و هجده میلیولتِ مربع هستند. مجموع نوزده و میانگین نوزدهچهارم است. در مقابل، نصفِ میانگینِ مربعِ همین باقیماندهها پنجاهویکچهارم میشود. این تفاوت، تغییرِ تابعِ هدف است؛ برازشِ وزندارِ کالیبراسیون نیز معیارِ خودش را داشت. این برنامه، ضرایبِ هیچیک از آن مدلها را برازش یا مقایسه نمیکند.
۲. مرزِ دلتا و مشتقِ محدودِ باقیمانده

مشتقِ زیان نسبت به باقیمانده، داخلِ گذار خودِ باقیمانده و بیرونِ آن، مثبت یا منفیِ دلتاست. برای چهار ورودیِ مثال، این مشتق منفی یک، صفر، یک و دو میلیولت است. مشتقِ میانگین نسبت به هر مختصهٔ مستقلِ باقیمانده، یکچهارمِ همان مقدار میشود: منفی یکچهارم، صفر، یکچهارم و یکدوم. حذفِ این تقسیم، قراردادِ میانگین را به مشتقِ مجموع تبدیل میکند.
در قدرِ باقیماندهٔ دقیقاً برابرِ دلتا، هر دو فرمولِ زیان مقدارِ نصفِ مربعِ دلتا را میدهند. مشتقهای دو طرف نیز به مثبت یا منفیِ دلتا میرسند. پس تابع، مشتقِ اولِ پیوسته دارد؛ مشتقِ دوم در مرز یکسان نیست، چون داخل یک و بیرون صفر است. در کد، مرز به شاخهٔ بیرونی میرود؛ یکسانیِ مقدار و مشتقِ اول، ابهامِ عددیِ تعریفشدهای در همان نقطه باقی نمیگذارد.
با دلتا برابر دو، باقیماندههای چهار، هشت و شانزده، زیانهای شش، چهارده و سی دارند، ولی مشتقِ نسبت به باقیمانده برای هر سه دو میماند. پس دادهٔ بزرگ حذف نشده و زیانش هنوز رشد میکند. این محدودیت، تضمینِ محدودبودنِ گرادیانِ هر پارامترِ مدل نیست؛ طبقِ قاعدهٔ زنجیره، مشتقِ باقیمانده نسبت به پارامتر نیز ضرب میشود. شاهدِ برنامه، ضریبهای یک و صد را به حاصلِ دو و دویست میرساند.
۳. Huber و Smooth L1 هممقیاس نیستند

فرمولِ SmoothL1Loss در PyTorch 2.14، با بتای مثبت و برابرِ دلتا، زیانِ Huber تقسیمبر دلتاست. برای باقیماندهٔ چهار و دلتا دو، اعدادِ متناظر شش و سهاند. این ضریب هنگامِ ترکیبِ چند زیان یا نگهداریِ آستانههای آزمون اهمیت دارد؛ تعویضِ نامِ تابع همراه با ثابتماندنِ بقیهٔ تنظیمات، خودکار همان معیارِ قبلی را حفظ نمیکند.
اگر باقیمانده و دلتا را کمیتِ بُعددارِ میلیولتی بدانیم، تقسیمِ زیانِ میلیولتِ مربع بر دلتا، کمیتی با واحدِ میلیولت میدهد. مساویشدنِ عددها در دلتا برابر یک، این دو بُعد را یکی نمیکند. مستنداتِ کتابخانه روی عددهای تانسور فرمول میدهند؛ ثبتِ واحد و نرمالسازیِ دادهٔ مهندسی، مسئولیتِ قراردادِ خودمان است. این کد فقط یک نمایهٔ اسکالرِ گویا دارد و PyTorch اجرا نشده است.
درونِ ناحیهٔ مربعی هم ضریبِ یکدوم را حفظ میکنیم: برای منفی یک، صفر و یک، میانگینِ Huber با دلتا دو، یکسوم است؛ میانگینِ مربعِ کامل دوسوم است. نتیجه از فرمول میآید. همچنین اگر اعدادِ باقیمانده و دلتا هر دو در k مثبت ضرب شوند، عددِ Huber در k مربع و عددِ زیانِ تقسیمشده بر دلتا در k ضرب میشود. تغییرِ مقیاسِ باقیمانده با دلتای ثابت، مسئلهٔ دیگری میسازد.
۴. کدِ کامل و مرجعِ کمینهسازیِ مستقل

ورودی باید tuple دقیق با یک تا ۳۲ عضو باشد. هر عضو و دلتا فقط int یا Fraction دقیقاند؛ bool، float، رشته و تبدیلِ ضمنی پذیرفته نیستند. قدرِ باقیمانده حداکثر شانزده، دلتا مثبت و حداکثر هشت و مخرجِ کسرِ سادهشده حداکثر شانزده است. اینها کرانهای آموزشیاند، با واحدِ میلیولت؛ پشتیبانیِ همهٔ شکلها، دستگاهها و نوعهای تانسورِ کتابخانه ادعا نمیشود.
"""Exact bounded scalar loss contract; no model training or real measurements."""
from fractions import Fraction
import json
import platform
def _rational(value, name, limit, positive=False):
if type(value) not in (int, Fraction):
raise ValueError(name)
value = Fraction(value)
if value.denominator > 16 or abs(value) > limit or (positive and value <= 0):
raise ValueError(name)
return value
def huber_report(residuals_mv, delta_mv):
"""Exact tuple1..32, residuals +/-16mV, delta(0,8]mV, denominators<=16."""
if type(residuals_mv) is not tuple or not 1 <= len(residuals_mv) <= 32:
raise ValueError("residual_shape")
delta = _rational(delta_mv, "delta_contract", 8, positive=True)
values = tuple(_rational(v, "residual_contract", 16) for v in residuals_mv)
losses, derivatives = [], []
for residual in values:
magnitude = abs(residual)
losses.append(residual * residual / 2 if magnitude < delta
else delta * (magnitude - delta / 2))
derivatives.append(min(delta, max(-delta, residual)))
total = sum(losses, Fraction())
return {"residuals_mv": values, "delta_mv": delta,
"losses_mv2": tuple(losses), "sum_loss_mv2": total,
"mean_loss_mv2": total / len(values),
"point_derivatives_mv": tuple(derivatives),
"mean_loss_derivatives_mv": tuple(v / len(values) for v in derivatives),
"scaled_smooth_l1_mean_mv": total / (len(values) * delta)}
def finite_minimum_oracle(residual, delta):
"""Own half-grid fixtures only: minimize quadratic+absolute, not Huber code."""
assert -8 <= residual <= 8 and Fraction(1, 2) <= delta <= 4
assert (2 * residual).denominator == (2 * delta).denominator == 1
candidates = (Fraction(k, 2) for k in range(-16, 17))
objective, minimizer = min(((residual - z) ** 2 / 2 + delta * abs(z), z)
for z in candidates)
return objective, residual - minimizer
def scaled_smooth_reference(residual, beta):
"""Independent displayed SmoothL1 scalar formula; valid fixtures only."""
return (residual ** 2 / (2 * beta) if abs(residual) < beta
else abs(residual) - beta / 2)
def rejected(call, expected):
try:
call()
except ValueError as error:
return str(error) == expected
return False
def run_example():
checks = {}
def check(name, value):
assert type(value) is bool
checks[name] = value
if not value:
raise AssertionError(name)
residuals = (-1, 0, 1, 10)
result = huber_report(residuals, 2)
check("literal_hand_losses_mean_and_per_residual_mean_derivatives",
result["losses_mv2"] == (Fraction(1, 2), 0, Fraction(1, 2), 18)
and result["sum_loss_mv2"] == 19
and result["mean_loss_mv2"] == Fraction(19, 4)
and result["mean_loss_derivatives_mv"] == (Fraction(-1, 4), 0, Fraction(1, 4), Fraction(1, 2)))
symmetric = True
for k in range(-32, 33):
r = Fraction(k, 2)
a, b = huber_report((r,), 2), huber_report((-r,), 2)
symmetric &= (a["mean_loss_mv2"] == b["mean_loss_mv2"]
and a["point_derivatives_mv"][0] == -b["point_derivatives_mv"][0])
check("loss_is_even_but_signed_residual_derivative_is_odd", bool(symmetric))
h, delta = Fraction(1, 2), Fraction(2)
at = huber_report((delta,), delta)
inside, outside = huber_report((delta - h,), delta), huber_report((delta + h,), delta)
check("transition_has_shared_value_first_derivative_but_not_second_derivative",
at["mean_loss_mv2"] == delta ** 2 / 2 == delta * (delta - delta / 2)
and at["point_derivatives_mv"] == (delta,)
and huber_report((-delta,), delta)["point_derivatives_mv"] == (-delta,)
and (at["point_derivatives_mv"][0] - inside["point_derivatives_mv"][0]) / h == 1
and (outside["point_derivatives_mv"][0] - at["point_derivatives_mv"][0]) / h == 0)
tails = [huber_report((r,), 2) for r in (4, 8, 16)]
check("tail_loss_keeps_growing_while_residual_derivative_is_bounded",
[r["mean_loss_mv2"] for r in tails] == [6, 14, 30]
and [r["point_derivatives_mv"][0] for r in tails] == [2, 2, 2])
scale_cases, scale_ok = 0, True
for k in range(-16, 17):
for j in range(1, 9):
r, d = Fraction(k, 2), Fraction(j, 2)
actual = huber_report((r,), d)
expected = scaled_smooth_reference(r, d)
scale_ok &= actual["mean_loss_mv2"] == d * expected
scale_ok &= actual["scaled_smooth_l1_mean_mv"] == expected
scale_cases += 1
check("positive_delta_smooth_l1_scaling_matches_separate_formula_one_family",
scale_cases == 264 and bool(scale_ok)
and huber_report((4,), 2)["mean_loss_mv2"] == 6
and huber_report((4,), 2)["scaled_smooth_l1_mean_mv"] == 3)
repeated = huber_report(residuals * 2, 2)
check("duplicated_batch_preserves_mean_scales_sum_and_halves_each_coordinate_gradient",
repeated["mean_loss_mv2"] == result["mean_loss_mv2"]
and repeated["sum_loss_mv2"] == 2 * result["sum_loss_mv2"]
and repeated["point_derivatives_mv"] == result["point_derivatives_mv"] * 2
and repeated["mean_loss_derivatives_mv"] == tuple(v / 2 for v in result["mean_loss_derivatives_mv"]) * 2)
oracle_cases, oracle_ok = 0, True
for k in range(-16, 17):
for j in range(1, 9):
r, d = Fraction(k, 2), Fraction(j, 2)
value, derivative = finite_minimum_oracle(r, d)
actual = huber_report((r,), d)
oracle_ok &= actual["mean_loss_mv2"] == value
oracle_ok &= actual["point_derivatives_mv"][0] == derivative
oracle_cases += 1
check("264_half_grid_infimal_minima_independently_check_value_and_derivative",
oracle_cases == 264 and bool(oracle_ok))
unit_fixture = (-2, 0, 3)
base, changed = huber_report(unit_fixture, 1), huber_report(tuple(2 * x for x in unit_fixture), 2)
wrong_delta = huber_report(tuple(2 * x for x in unit_fixture), 1)
check("scaling_residual_and_delta_scales_loss_quadratically_not_if_delta_forgotten",
changed["mean_loss_mv2"] == 4 * base["mean_loss_mv2"]
and changed["point_derivatives_mv"] == tuple(2 * x for x in base["point_derivatives_mv"])
and changed["scaled_smooth_l1_mean_mv"] == 2 * base["scaled_smooth_l1_mean_mv"]
and wrong_delta["mean_loss_mv2"] != 4 * base["mean_loss_mv2"])
influence = huber_report((4,), 2)["point_derivatives_mv"][0]
check("model_jacobian_can_amplify_a_bounded_residual_derivative",
influence == 2 and (influence * 1, influence * 100) == (2, 200))
half_squared = sum((Fraction(x) ** 2 for x in (-1, 0, 1)), Fraction()) / 6
check("all_quadratic_region_is_half_mean_square_not_full_mse",
huber_report((-1, 0, 1), 2)["mean_loss_mv2"] == half_squared == Fraction(1, 3)
and 2 * half_squared == Fraction(2, 3))
check("tuple_shape_and_inclusive_32_value_residual_bounds_are_explicit",
len(huber_report((-16, 16) * 16, Fraction(1, 16))["losses_mv2"]) == 32
and all(rejected(lambda values=values: huber_report(values, 2), "residual_shape")
for values in ([], (), [1], (0,) * 33, None)))
bad_residuals = (True, False, 1.0, "1", None, float("nan"), float("inf"),
Fraction(1, 17), -17, 17, 10 ** 80)
check("exact_residual_type_denominator_finiteness_and_range_contract",
all(rejected(lambda value=value: huber_report((value,), 2), "residual_contract")
for value in bad_residuals)
and huber_report((Fraction(1, 16),), 2)["mean_loss_mv2"] == Fraction(1, 512))
bad_deltas = (0, -1, 9, True, False, 1.0, "1", None, float("nan"),
float("inf"), Fraction(1, 17), 10 ** 80)
check("delta_must_be_positive_exact_bounded_rational_not_beta_zero_special_case",
all(rejected(lambda value=value: huber_report((0,), value), "delta_contract")
for value in bad_deltas)
and huber_report((0,), 8)["mean_loss_mv2"] == 0)
changed_result = huber_report(residuals, 2)
changed_result["mean_loss_mv2"] = -100
check("input_and_fresh_result_are_not_shared_mutable_state",
residuals == (-1, 0, 1, 10) and huber_report(residuals, 2) == result)
assert len(checks) == 14 and all(checks.values())
return {"runtime": {"python": platform.python_version(), "dependencies": "standard library only"},
"units": {"residual_and_delta": "mV", "Huber_loss": "mV**2",
"derivative_wrt_residual": "mV", "scaled_SmoothL1_loss": "mV"},
"main_fixture": result,
"half_squared_mean_main_fixture_mv2": Fraction(51, 4),
"model_chain_rule_witness": {"residual_derivative_mv": influence,
"dimensionless_model_jacobians": [1, 100],
"chain_products_mv": [influence, 100 * influence]},
"oracle_cases_inside_one_group": oracle_cases,
"smooth_l1_formula_cases_inside_one_group": scale_cases,
"contract": "Exact tuple1..32 int/Fraction excluding bool; |residual|<=16,0<delta<=8,denominator<=16. Physical residuals and delta both mV. No float/tensor API, training, model parameter clipping or empirical claim.",
"checks": checks}
if __name__ == "__main__":
print(json.dumps(run_example(), default=str, ensure_ascii=False, indent=2))
مرجعِ مستقل، مقدارِ کمینهٔ تابعِ نصفِ مربعِ r−z بهعلاوهٔ دلتا ضربدر قدرِ z را میگیرد. برای r نامنفی، کمینه بیرونِ بازهٔ صفر تا r نیست؛ درونِ بخشِ مثبت، مشتق نسبت به z برابر z−r+delta است. بنابراین محلِ کمینه صفر یا r−delta است؛ برای r منفی، تقارن به کار میرود. جایگذاریِ این جوابها، فرمولِ Huber را میسازد و r−z نیز مشتقِ نسبت به باقیمانده را میدهد.
در خانوادهٔ آزمون، r از منفی هشت تا هشت و دلتا از یکدوم تا چهار، با گامِ یکدوماند. محلِ کمینه نیز روی همین شبکه است و داخلِ بازه میماند؛ پس بررسیِ همهٔ ۳۳ نامزدِ z برای هر مورد کفایت دارد. این شمارش برای اعدادِ حقیقیِ دلخواه نیست. تمامِ ۲۶۴ زوج، یک گروهِ مرجعاند؛ یک خانوادهٔ جدا با همان تعداد، فرمولِ مقیاسِ Smooth L1 را کنترل میکند.
{
"runtime": {
"python": "3.14.5",
"dependencies": "standard library only"
},
"units": {
"residual_and_delta": "mV",
"Huber_loss": "mV**2",
"derivative_wrt_residual": "mV",
"scaled_SmoothL1_loss": "mV"
},
"main_fixture": {
"residuals_mv": [
"-1",
"0",
"1",
"10"
],
"delta_mv": "2",
"losses_mv2": [
"1/2",
"0",
"1/2",
"18"
],
"sum_loss_mv2": "19",
"mean_loss_mv2": "19/4",
"point_derivatives_mv": [
"-1",
"0",
"1",
"2"
],
"mean_loss_derivatives_mv": [
"-1/4",
"0",
"1/4",
"1/2"
],
"scaled_smooth_l1_mean_mv": "19/8"
},
"half_squared_mean_main_fixture_mv2": "51/4",
"model_chain_rule_witness": {
"residual_derivative_mv": "2",
"dimensionless_model_jacobians": [
1,
100
],
"chain_products_mv": [
"2",
"200"
]
},
"oracle_cases_inside_one_group": 264,
"smooth_l1_formula_cases_inside_one_group": 264,
"contract": "Exact tuple1..32 int/Fraction excluding bool; |residual|<=16,0<delta<=8,denominator<=16. Physical residuals and delta both mV. No float/tensor API, training, model parameter clipping or empirical claim.",
"checks": {
"literal_hand_losses_mean_and_per_residual_mean_derivatives": true,
"loss_is_even_but_signed_residual_derivative_is_odd": true,
"transition_has_shared_value_first_derivative_but_not_second_derivative": true,
"tail_loss_keeps_growing_while_residual_derivative_is_bounded": true,
"positive_delta_smooth_l1_scaling_matches_separate_formula_one_family": true,
"duplicated_batch_preserves_mean_scales_sum_and_halves_each_coordinate_gradient": true,
"264_half_grid_infimal_minima_independently_check_value_and_derivative": true,
"scaling_residual_and_delta_scales_loss_quadratically_not_if_delta_forgotten": true,
"model_jacobian_can_amplify_a_bounded_residual_derivative": true,
"all_quadratic_region_is_half_mean_square_not_full_mse": true,
"tuple_shape_and_inclusive_32_value_residual_bounds_are_explicit": true,
"exact_residual_type_denominator_finiteness_and_range_contract": true,
"delta_must_be_positive_exact_bounded_rational_not_beta_zero_special_case": true,
"input_and_fresh_result_are_not_shared_mutable_state": true
}
}
چهارده گروهِ نامدار در یک اجرای Python 3.14.5 با کتابخانهٔ استاندارد موفق شدهاند. تقارن، مرزِ گذار، رشدِ زیان، مقیاس، میانگین، ورودیِ نامعتبر و استقلالِ نتیجهها نیز بررسی میشوند. Fraction حسابِ این دادههای گویا را دقیق نگه میدارد؛ دربارهٔ گردکردنِ float یا اجرای خودکارِ مشتق در کتابخانه نتیجه نمیگیریم. دو مرجعِ نسخهٔ 2.14 در ۲۷ مهر ۱۴۰۵ خوانده شدهاند؛ تاریخِ اصلیِ انتشارِ صفحات مشخص نیست.
۵. چهار پرسش دربارهٔ مقیاس و مرزِ ادعا

پرسش اول: آیا باقیماندهٔ ده با دلتا دو نادیده گرفته میشود؟ پاسخ: خیر؛ سهمِ زیانش هجده و مشتقِ اسکالرش دو است. تابع، برچسبِ دادهٔ پرت یا مجوزِ حذفِ مشاهده تولید نمیکند. کمترشدنِ سهمِ یک باقیماندهٔ بزرگ، بهتنهایی دربارهٔ درستیِ مرجع یا دقتِ یک مدل قضاوت نمیکند.
پرسش دوم: چرا هنگامِ دوبرابرکردنِ مقیاس، دلتا هم تغییر میکند؟ پاسخ: دلتا مرزِ همان واحدِ باقیمانده است. با دوبرابرکردنِ هر دو، رابطهٔ همگنیِ زیان برقرار میماند؛ ثابتگذاشتنِ دلتا، جایگاهِ گذار را نسبت به داده عوض میکند. آزمون این فرق را با اعدادِ محدود نشان میدهد و تبدیلِ خودکارِ واحد انجام نمیدهد.
پرسش سوم: آیا تکرارِ تمامِ بسته مشتقها را ثابت نگه میدارد؟ پاسخ: میانگینِ زیان ثابت و مجموع دوبرابر میشود. مشتقِ هر سهم ثابت است، اما مشتقِ میانگین نسبت به هر مختصهٔ مستقل، بهسببِ دوبرابرشدنِ تعداد نصف میشود. مشتقِ یک پارامترِ مشترک، جمعِ جملههای قاعدهٔ زنجیره را لازم دارد؛ این سه مفهوم را یکی نگیریم.
پرسش چهارم: مشتقِ باقیماندهٔ محدود یعنی گرادیانِ مدل محدود است؟ پاسخ: خیر؛ ژاکوبینِ مدل میتواند آن را تقویت کند. آزمونِ گرادیانِ مدلِ خطی، پارامتر و مشتقِ دیگری را بررسی میکرد. این درس قراردادِ زیانِ اسکالر را نگهداری میکند؛ آموزش، همگرایی، مقاومشدنِ تضمینی یا موفقیتِ آزمونِ یکپارچگیِ سرویس از آن نتیجه نمیشود. حالتِ ویژهٔ بتای صفرِ Smooth L1 نیز بیرون از نمایهٔ مثبتِ ماست.
منابع و مطالعهٔ بیشتر
بررسی منابع: ۲۷ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

