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

۱. خروجیِ جاری از ضرایبِ قدیمی ساخته می‌شود

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

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

شرحِ الگوریتمِ LMS در CMSIS-DSP ترتیبِ فیلتر، خطا و اصلاحِ نمونه‌به‌نمونه را مستند می‌کند. چیدمانِ حافظهٔ ضرایب در API آن معکوس است؛ در کدِ آموزشیِ ما، وزنِ نمونهٔ جاری نخست قرار می‌گیرد. این تفاوتِ نمایش را پنهان نمی‌کنیم و فایل را پیاده‌سازیِ سازگارِ همان کتابخانه معرفی نمی‌کنیم.

این بار ضرایب در طولِ پردازش عوض می‌شوند. در درسِ تأخیرِ FIR متقارن، ضرایب معلوم و ثابت بودند و ویژگیِ فاز بررسی می‌شد. اینجا نه تقارن و پاسخِ فرکانسی طراحی می‌کنیم، نه تأخیرِ تماس را اندازه می‌گیریم. دامنهٔ ورودی، مرجع و خروجی بی‌بُعد است؛ اندیسِ نمونه هم ثانیه یا هرتز نیست.

۲. یک خطا، دو ضریب و به‌روزرسانیِ هم‌زمان

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

قراردادِ اصلاح، وزنِ تازه برابر وزنِ قدیمی به‌علاوهٔ حاصل‌ضربِ گام، خطا و مؤلفهٔ ورودی است. هر دو ضریب از همان خطای یک پیش‌بینی استفاده می‌کنند. منظور از هم‌زمانی، ساختنِ هر دو مقدارِ تازه پیش از جایگزینی است؛ اجرای موازی، وقفهٔ سخت‌افزاری یا تضمینِ اتمیک‌بودن موضوعِ این مدل نیست.

هزینهٔ یک نمونه را نصفِ مربعِ خطا تعریف می‌کنیم. مشتق نسبت به هر وزن، منفیِ خطا ضربدر مؤلفهٔ متناظر است؛ پس در قراردادِ گامِ ما ضریبِ دو یا میانگینِ کلِ بسته وجود ندارد. معادله‌های کاملِ LMS در مستنداتِ MathWorks نیز پیش‌بینی با وزنِ پیشین را جدا نشان می‌دهند. فقط حالتِ حقیقی و معمولی، با ضریبِ نشتِ یک را انتخاب کرده‌ایم؛ نسخهٔ نرمال‌شده، علامتی یا مختلط اجرا نمی‌شود.

وزن‌های آغازین یک‌دوم و منفی یک‌چهارم، نمونهٔ پیشین منفی یک، نمونهٔ جاری یک و مرجع یک باشند. پیش‌بینی سه‌چهارم و خطا یک‌چهارم است. با گامِ یک‌دوم، وزن‌های تازه پنج‌هشتم و منفی سه‌هشتم می‌شوند. اگر پس از اصلاحِ ضریبِ اول، خطا را دوباره بسنجیم، یک‌هشتم می‌گیریم و ضریبِ دوم منفی پنج‌شانزدهم می‌شود؛ این پاسخِ متفاوت، نتیجهٔ LMS تعریف‌شده نیست.

۳. حالتِ ادامه، وزن‌ها و نمونهٔ قبلی را با هم نگه می‌دارد

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

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

ورودی و مرجع باید tuple دقیق، هم‌طول و در مجموع حداکثر هشت نمونه باشند. هر نمونه int دقیقِ منفی دو تا دو است. گام، Fraction دقیقِ صفر تا یک با مخرجِ مقسوم‌علیهِ چهار است؛ یعنی فقط صفر، یک‌چهارم، یک‌دوم، سه‌چهارم و یک. bool، زیرکلاس و تبدیلِ ضمنی پذیرفته نمی‌شوند. تمامِ ورودی پیش از حلقه بررسی می‌شود؛ دادهٔ نامعتبر بی‌خبر به صفر تبدیل نمی‌شود.

حدِ حالت با شمارِ پردازش رشد می‌کند تا خروجیِ معتبرِ یک بسته برای ادامه قابل‌قبول بماند. در شمارِ c، مخرجِ هر وزن باید مقسوم‌علیهِ 4**(c+1) و قدرِ وزن حداکثر 5*13**c-1 باشد. از کرانِ اصلاح، حدِ سادهٔ نه برابرِ قدرِ قبلی به‌علاوهٔ چهار به دست می‌آید که در پوشِ گامِ بعد جا می‌گیرد. این پوشِ محافظه‌کارانه، کرانِ پایداریِ فیلتر یا محدودیتِ قطعهٔ واقعی نیست.

بستهٔ خالی خروجی ندارد و حالت را نگه می‌دارد. انتهای ورودی نیز صفرِ تازه یا مرجعِ تازه تولید نمی‌کند. افزودنِ صفر به نمونه، بدون تعیینِ مرجع و سیاستِ ادامهٔ یادگیری، «تخلیهٔ خودکارِ دنباله» محسوب نمی‌شود؛ تعدادِ ورودی و خروجی در این تابع برابر است.

۴. کد و مرجعِ مستقلِ تبدیل‌های آفین

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

کد فقط کتابخانهٔ استانداردِ Python را لازم دارد. Fraction عددهای همین قرارداد را دقیق نگه می‌دارد؛ بنابراین تلورانسِ شناور، epsilon پنهان یا تبدیل به اعشار نداریم. برای چاپ، کسرها به رشتهٔ ساده تبدیل می‌شوند. خروجیِ JSON، نسخهٔ اجرا، دادهٔ دستی، حالت‌های پیش و پس از اصلاح و چهارده گروهِ مستقلِ معنایی را کنار هم ثبت می‌کند.

"""Authored bounded ordinary real LMS; exact toy arithmetic, not an audio SDK."""
from fractions import Fraction
from itertools import product
import json
import platform
import sys

ZERO = Fraction(0)
INITIAL = (ZERO, ZERO, 0, 0)
MAX_SAMPLES = 8


def _state(state):
    if type(state) is not tuple or len(state) != 4:
        raise ValueError("state_shape")
    w0, w1, previous, count = state
    if type(count) is not int or not 0 <= count <= MAX_SAMPLES:
        raise ValueError("state_count")
    if type(previous) is not int or not -2 <= previous <= 2:
        raise ValueError("previous_sample")
    denominator_limit = 4 ** (count + 1)
    magnitude_limit = 5 * 13 ** count - 1
    for weight in (w0, w1):
        if type(weight) is not Fraction:
            raise ValueError("state_weight_type")
        if denominator_limit % weight.denominator or abs(weight) > magnitude_limit:
            raise ValueError("state_weight_bound")
    return w0, w1, previous, count


def lms_block(samples, desired, mu, state=INITIAL):
    """N pre-update predictions; carry both weights, previous input and count."""
    if type(samples) is not tuple or type(desired) is not tuple:
        raise ValueError("stream_container")
    if len(samples) != len(desired) or len(samples) > MAX_SAMPLES:
        raise ValueError("aligned_finite_streams")
    if any(type(v) is not int or not -2 <= v <= 2 for v in samples + desired):
        raise ValueError("synthetic_sample")
    if type(mu) is not Fraction or not 0 <= mu <= 1 or 4 % mu.denominator:
        raise ValueError("step")
    w0, w1, previous, count = _state(state)
    if count + len(samples) > MAX_SAMPLES:
        raise ValueError("total_sample_budget")
    trace, outputs, errors = [], [], []
    for sample, target in zip(samples, desired):
        old = (w0, w1)
        output = w0 * sample + w1 * previous
        error = target - output
        correction = mu * error
        new0 = w0 + correction * sample
        new1 = w1 + correction * previous
        after_error = target - (new0 * sample + new1 * previous)
        trace.append({"sample_index": count, "features": (sample, previous),
                      "desired": target, "old_weights": old, "output": output,
                      "error": error, "new_weights": (new0, new1),
                      "same_sample_error_after_update": after_error})
        outputs.append(output)
        errors.append(error)
        w0, w1, previous, count = new0, new1, sample, count + 1
    return {"output": tuple(outputs), "error": tuple(errors), "trace": trace,
            "state": (w0, w1, previous, count)}


def _matrix_product(left, right):
    return tuple(tuple(sum((left[i][k] * right[k][j] for k in range(3)), ZERO)
                       for j in range(3)) for i in range(3))


def affine_prefix_reference(samples, desired, mu, state):
    """Independent augmented affine composition, not the streaming LMS update."""
    identity = tuple(tuple(Fraction(i == j) for j in range(3)) for i in range(3))
    composed = identity
    initial = (state[0], state[1], Fraction(1))
    history = (state[2],) + samples
    weights = [(state[0], state[1])]
    for index, target in enumerate(desired):
        a, b = history[index + 1], history[index]
        transform = ((1 - mu * a * a, -mu * a * b, mu * target * a),
                     (-mu * a * b, 1 - mu * b * b, mu * target * b),
                     (ZERO, ZERO, Fraction(1)))
        composed = _matrix_product(transform, composed)
        current = tuple(sum((composed[i][j] * initial[j] for j in range(3)), ZERO)
                        for i in range(2))
        weights.append(current)
    outputs = tuple(weights[n][0] * history[n + 1] + weights[n][1] * history[n]
                    for n in range(len(samples)))
    errors = tuple(Fraction(d) - y for d, y in zip(desired, outputs))
    return {"weights": tuple(weights), "output": outputs, "error": errors}


def _rejects(*args):
    try:
        lms_block(*args)
    except ValueError:
        return True
    return False


def _json_value(value):
    if type(value) is Fraction:
        return str(value)
    if type(value) in (tuple, list):
        return [_json_value(v) for v in value]
    if type(value) is dict:
        return {k: _json_value(v) for k, v in value.items()}
    return value


def run_example():
    initial = (Fraction(1, 2), Fraction(-1, 4), -1, 0)
    samples, desired, mu = (1, 2, 0), (1, 0, -1), Fraction(1, 2)
    main = lms_block(samples, desired, mu, initial)
    expected_output = (Fraction(3, 4), Fraction(7, 8), Fraction(-13, 8))
    expected_error = (Fraction(1, 4), Fraction(-7, 8), Fraction(5, 8))
    expected_weights = ((Fraction(5, 8), Fraction(-3, 8)),
                        (Fraction(-1, 4), Fraction(-13, 16)),
                        (Fraction(-1, 4), Fraction(-3, 16)))
    checks = {}
    checks["literal_three_sample_predictions_errors_and_all_weight_snapshots"] = (
        main["output"] == expected_output and main["error"] == expected_error
        and tuple(x["new_weights"] for x in main["trace"]) == expected_weights
        and main["state"] == (Fraction(-1, 4), Fraction(-3, 16), 0, 3))
    first = main["trace"][0]
    wrong_recomputed_error = 1 - (Fraction(5, 8) * 1 + Fraction(-1, 4) * -1)
    wrong_second = Fraction(-1, 4) + mu * wrong_recomputed_error * -1
    checks["both_coordinates_use_one_old_snapshot_and_one_error"] = (
        first["old_weights"] == initial[:2] and wrong_recomputed_error == Fraction(1, 8)
        and wrong_second == Fraction(-5, 16) and wrong_second != first["new_weights"][1])
    order = lms_block((2,), (0,), ZERO, (Fraction(1), ZERO, -1, 0))
    checks["first_tap_is_current_input_not_reversed_API_storage"] = (
        order["output"] == (Fraction(2),) and order["trace"][0]["features"] == (2, -1)
        and order["output"] != (Fraction(-1),))
    negative_gradient = tuple(first["error"] * v for v in first["features"])
    wrong_double = tuple(w + 2 * mu * g for w, g in zip(initial[:2], negative_gradient))
    checks["half_single_sample_square_convention_has_no_factor_two_or_mean"] = (
        negative_gradient == (Fraction(1, 4), Fraction(-1, 4))
        and wrong_double == (Fraction(3, 4), Fraction(-1, 2))
        and wrong_double != first["new_weights"])
    frozen = lms_block(samples, desired, ZERO, initial)
    checks["zero_step_freezes_weights_but_continues_input_history"] = (
        frozen["output"] == (Fraction(3, 4), Fraction(3, 4), Fraction(-1, 2))
        and all(x["new_weights"] == initial[:2] for x in frozen["trace"])
        and frozen["state"][2:] == (0, 3))
    zero_input = lms_block((0,), (1,), Fraction(1), (Fraction(1, 2), Fraction(-1, 4), 0, 0))
    zero_error = lms_block((2,), (1,), Fraction(1), (Fraction(1, 2), Fraction(-1, 4), 0, 0))
    checks["zero_regressor_and_zero_error_are_distinct_reasons_for_no_update"] = (
        zero_input["error"] == (Fraction(1),) and zero_error["error"] == (ZERO,)
        and zero_input["state"][:2] == zero_error["state"][:2] == initial[:2])
    overshoot = lms_block((2,), (1,), Fraction(1))
    over = overshoot["trace"][0]
    checks["same_sample_error_can_grow_ninefold_in_square_without_a_step_guarantee"] = (
        over["output"] == 0 and over["error"] == 1
        and over["new_weights"] == (Fraction(2), ZERO)
        and over["same_sample_error_after_update"] == -3
        and over["same_sample_error_after_update"] ** 2 == 9 * over["error"] ** 2)
    split_results = []
    for cut in range(len(samples) + 1):
        left = lms_block(samples[:cut], desired[:cut], mu, initial)
        right = lms_block(samples[cut:], desired[cut:], mu, left["state"])
        split_results.append(left["trace"] + right["trace"] == main["trace"]
                             and left["output"] + right["output"] == main["output"]
                             and right["state"] == main["state"])
    checks["all_four_chunk_cuts_carry_weights_previous_input_and_count"] = all(split_results)
    left = lms_block(samples[:1], desired[:1], mu, initial)
    lost_history = lms_block(samples[1:], desired[1:], mu,
                            (left["state"][0], left["state"][1], 0, 1))
    reset = lms_block(samples[1:], desired[1:], mu)
    checks["carrying_weights_alone_and_resetting_are_both_detectable"] = (
        lost_history["output"][0] == Fraction(5, 4) != expected_output[1]
        and reset["output"][0] == 0 != expected_output[1])
    full = lms_block((2, -2) * 4, (1, -1) * 4, Fraction(3, 4))
    empty = lms_block((), (), mu, full["state"])
    checks["eight_sample_budget_and_empty_continuation_do_not_flush_a_tail"] = (
        len(full["output"]) == 8 and full["state"][3] == 8
        and empty == {"output": (), "error": (), "trace": [], "state": full["state"]}
        and _state(full["state"]) == full["state"]
        and _rejects((0,), (0,), mu, full["state"]))
    family_ok, profiles, steps = True, 0, 0
    pair_alphabet = ((-1, 0), (0, 1), (1, -1), (2, 1))
    for size in range(4):
        for pairs in product(pair_alphabet, repeat=size):
            xs, ds = tuple(p[0] for p in pairs), tuple(p[1] for p in pairs)
            for start in (INITIAL, initial):
                for rate in (ZERO, Fraction(1, 4), Fraction(3, 4)):
                    actual = lms_block(xs, ds, rate, start)
                    reference = affine_prefix_reference(xs, ds, rate, start)
                    family_ok = family_ok and actual["output"] == reference["output"] and actual["error"] == reference["error"]
                    family_ok = family_ok and tuple(x["old_weights"] for x in actual["trace"]) == reference["weights"][:-1]
                    family_ok = family_ok and tuple(x["new_weights"] for x in actual["trace"]) == reference["weights"][1:]
                    family_ok = family_ok and _state(actual["state"]) == actual["state"]
                    profiles += 1
                    steps += size
    checks["independent_affine_prefix_composition_510_profiles_ONE_family"] = (
        family_ok and profiles == 510 and steps == 1368)
    class SubInt(int):
        pass
    class SubFraction(Fraction):
        pass
    class SubTuple(tuple):
        pass
    consumed = []
    def lazy():
        consumed.append(True)
        yield 0
    bad = [([0], (0,), mu), ((0,), [0], mu), ((0,), (), mu),
           ((0,) * 9, (0,) * 9, mu), (SubTuple((0,)), (0,), mu),
           (lazy(), (), mu)]
    bad += [((x,), (0,), mu) for x in (True, 0.0, Fraction(0), "0", None, 1j, SubInt(0), -3, 3, 10 ** 100)]
    bad += [((0,), (x,), mu) for x in (True, 0.0, Fraction(0), None, SubInt(1), -3, 3)]
    bad += [((0,), (0,), rate) for rate in (0, True, 0.25, Fraction(-1, 4), Fraction(5, 4), Fraction(1, 3), SubFraction(1, 4))]
    bad_states = [[], (), (ZERO, ZERO, 0), (ZERO, ZERO, 0, True),
                  (ZERO, ZERO, 0, -1), (ZERO, ZERO, 0, 9),
                  (ZERO, ZERO, True, 0), (ZERO, ZERO, 3, 0),
                  (0, ZERO, 0, 0), (SubFraction(0), ZERO, 0, 0),
                  (Fraction(1, 8), ZERO, 0, 0), (Fraction(5), ZERO, 0, 0),
                  SubTuple(INITIAL)]
    bad += [((), (), mu, state) for state in bad_states]
    checks["strict_shapes_types_dyadic_step_state_bounds_and_prevalidation_ONE_group"] = (
        all(_rejects(*args) for args in bad) and not consumed)
    prefix_ok = all(lms_block(samples[:n], desired[:n], mu, initial)["trace"] == main["trace"][:n]
                    for n in range(4))
    changed = lms_block(samples, (1, 0, 2), mu, initial)
    reordered = lms_block(samples[::-1], desired[::-1], mu, initial)
    checks["prefix_causality_future_target_boundary_and_paired_order_sensitivity"] = (
        prefix_ok and changed["trace"][:2] == main["trace"][:2]
        and changed["trace"][2]["output"] == main["trace"][2]["output"]
        and changed["state"] != main["state"] and reordered["state"] != main["state"])
    saved_inputs = (samples, desired, initial)
    first_report = lms_block(samples, desired, mu, initial)
    first_report["trace"][0]["output"] = Fraction(999)
    again = lms_block(samples, desired, mu, initial)
    checks["fresh_nested_reports_and_immutable_caller_state"] = (
        again == main and first_report["trace"] is not again["trace"]
        and first_report["trace"][0] is not again["trace"][0]
        and saved_inputs == (samples, desired, initial))
    assert len(checks) == 14 and all(type(v) is bool and v for v in checks.values()), checks
    return _json_value({"python": platform.python_version(), "implementation": platform.python_implementation(),
        "dependencies": "standard library only", "CMSIS_executed": False, "MATLAB_executed": False,
        "units": "dimensionless synthetic input/reference/output; sample index, no physical time or rate",
        "convention": "ordinary real nonleaky LMS; y uses old w; e=d-y; wnew=wold+mu*e*v; half single-sample squared error",
        "state_contract": "(Fraction w0,Fraction w1,previous exactint,processed exactint); generated dyadic states remain admissible",
        "maximum_total_samples": MAX_SAMPLES, "weight_bound_at_count_c": "abs(w)<=5*13**c-1; denominator divides4**(c+1)",
        "step_contract": "exact Fraction0..1; denominator divides4, not an NLMS stability bound",
        "manual": {"samples": samples, "desired": desired, "mu": mu, "initial_state": initial, "result": main},
        "wrong_coordinate_second_weight": wrong_second,
        "same_sample_overshoot": overshoot,
        "cases_within_ONE_groups": {"affine_profiles": profiles, "affine_sample_steps": steps,
                                    "invalid_profiles": len(bad), "manual_chunk_cuts": len(split_results)},
        "checks": checks, "unique_executed_checks": len(checks), "all_passed": True,
        "limits": ["Exact finite authored recurrence, not CMSIS/MATLAB API/storage or hardware arithmetic",
                   "No NLMS, epsilon, leakage, fixed point, clipping, complex data or automatic tail",
                   "No convergence proof, optimal step, actual sound, echo/noise quality or real-time benchmark",
                   "Supplied reference and ordering are assumptions, not inferred real-world labels"]})


if __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8", newline="\n")
    print(json.dumps(run_example(), ensure_ascii=True, indent=2))

مرجعِ مستقل، گامِ اصلاح را به یک تبدیلِ آفینِ سه‌درسه روی دو وزن و یک مؤلفهٔ ثابت تبدیل می‌کند. بخشِ خطی، همانی منهای گام ضربدر حاصل‌ضربِ بیرونیِ ورودی است و ستونِ آخر از گام، مرجع و ورودی ساخته می‌شود. تبدیل‌ها برای هر پیشوند ترکیب و به وزنِ آغازین اعمال می‌شوند؛ حلقهٔ به‌روزرسانیِ تابعِ اصلی در مرجع فراخوانی نمی‌شود.

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

{
  "python": "3.14.5",
  "implementation": "CPython",
  "dependencies": "standard library only",
  "CMSIS_executed": false,
  "MATLAB_executed": false,
  "units": "dimensionless synthetic input/reference/output; sample index, no physical time or rate",
  "convention": "ordinary real nonleaky LMS; y uses old w; e=d-y; wnew=wold+mu*e*v; half single-sample squared error",
  "state_contract": "(Fraction w0,Fraction w1,previous exactint,processed exactint); generated dyadic states remain admissible",
  "maximum_total_samples": 8,
  "weight_bound_at_count_c": "abs(w)<=5*13**c-1; denominator divides4**(c+1)",
  "step_contract": "exact Fraction0..1; denominator divides4, not an NLMS stability bound",
  "manual": {
    "samples": [
      1,
      2,
      0
    ],
    "desired": [
      1,
      0,
      -1
    ],
    "mu": "1/2",
    "initial_state": [
      "1/2",
      "-1/4",
      -1,
      0
    ],
    "result": {
      "output": [
        "3/4",
        "7/8",
        "-13/8"
      ],
      "error": [
        "1/4",
        "-7/8",
        "5/8"
      ],
      "trace": [
        {
          "sample_index": 0,
          "features": [
            1,
            -1
          ],
          "desired": 1,
          "old_weights": [
            "1/2",
            "-1/4"
          ],
          "output": "3/4",
          "error": "1/4",
          "new_weights": [
            "5/8",
            "-3/8"
          ],
          "same_sample_error_after_update": "0"
        },
        {
          "sample_index": 1,
          "features": [
            2,
            1
          ],
          "desired": 0,
          "old_weights": [
            "5/8",
            "-3/8"
          ],
          "output": "7/8",
          "error": "-7/8",
          "new_weights": [
            "-1/4",
            "-13/16"
          ],
          "same_sample_error_after_update": "21/16"
        },
        {
          "sample_index": 2,
          "features": [
            0,
            2
          ],
          "desired": -1,
          "old_weights": [
            "-1/4",
            "-13/16"
          ],
          "output": "-13/8",
          "error": "5/8",
          "new_weights": [
            "-1/4",
            "-3/16"
          ],
          "same_sample_error_after_update": "-5/8"
        }
      ],
      "state": [
        "-1/4",
        "-3/16",
        0,
        3
      ]
    }
  },
  "wrong_coordinate_second_weight": "-5/16",
  "same_sample_overshoot": {
    "output": [
      "0"
    ],
    "error": [
      "1"
    ],
    "trace": [
      {
        "sample_index": 0,
        "features": [
          2,
          0
        ],
        "desired": 1,
        "old_weights": [
          "0",
          "0"
        ],
        "output": "0",
        "error": "1",
        "new_weights": [
          "2",
          "0"
        ],
        "same_sample_error_after_update": "-3"
      }
    ],
    "state": [
      "2",
      "0",
      2,
      1
    ]
  },
  "cases_within_ONE_groups": {
    "affine_profiles": 510,
    "affine_sample_steps": 1368,
    "invalid_profiles": 43,
    "manual_chunk_cuts": 4
  },
  "checks": {
    "literal_three_sample_predictions_errors_and_all_weight_snapshots": true,
    "both_coordinates_use_one_old_snapshot_and_one_error": true,
    "first_tap_is_current_input_not_reversed_API_storage": true,
    "half_single_sample_square_convention_has_no_factor_two_or_mean": true,
    "zero_step_freezes_weights_but_continues_input_history": true,
    "zero_regressor_and_zero_error_are_distinct_reasons_for_no_update": true,
    "same_sample_error_can_grow_ninefold_in_square_without_a_step_guarantee": true,
    "all_four_chunk_cuts_carry_weights_previous_input_and_count": true,
    "carrying_weights_alone_and_resetting_are_both_detectable": true,
    "eight_sample_budget_and_empty_continuation_do_not_flush_a_tail": true,
    "independent_affine_prefix_composition_510_profiles_ONE_family": true,
    "strict_shapes_types_dyadic_step_state_bounds_and_prevalidation_ONE_group": true,
    "prefix_causality_future_target_boundary_and_paired_order_sensitivity": true,
    "fresh_nested_reports_and_immutable_caller_state": true
  },
  "unique_executed_checks": 14,
  "all_passed": true,
  "limits": [
    "Exact finite authored recurrence, not CMSIS/MATLAB API/storage or hardware arithmetic",
    "No NLMS, epsilon, leakage, fixed point, clipping, complex data or automatic tail",
    "No convergence proof, optimal step, actual sound, echo/noise quality or real-time benchmark",
    "Supplied reference and ordering are assumptions, not inferred real-world labels"
  ]
}

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

۵. چهار پرسش دربارهٔ گام، توقف و حدِ یادگیری

ابزارِ تنظیمِ بی‌درجه در کنار محدودهٔ آموزشی و فضای ناشناخته
تصویر مفهومی: ابزارِ تنظیمِ بی‌درجه در کنار محدودهٔ آموزشی و فضای ناشناخته؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

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

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

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

پرسش چهارم: موفقیتِ حسابِ کسری، کیفیتِ صوت را تأیید می‌کند؟ پاسخ: خیر. اینجا فقط قراردادِ محدود و دادهٔ تألیفی بررسی می‌شود. مستنداتِ خوانده‌شده در ۲۹ مهر، صفحهٔ main از CMSIS و نسخهٔ R2026b از MathWorks هستند؛ تاریخِ نخستِ انتشارِ صفحات معلوم نبود. اجرای محلی، Python 3.14.5 است و هیچ کتابخانهٔ آن دو منبع اجرا نشده است. کیفیتِ مرجع، انتخابِ گام برای دادهٔ واقعی، همگرایی، پژواک و موعدِ اجرا به آزمایش‌های دیگری نیاز دارند.

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

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