۱. قابِ ساختگی و علامتِ پیش‌بینی را تثبیت کنیم

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

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

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

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

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

۲. میانگین و مخرجِ ثابتِ خودکوواریانس را جدا کنیم

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

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

در تمامِ این جمع‌ها مخرج، طولِ ثابتِ قاب است؛ با زیادشدنِ lag، تعدادِ جفت‌ها کمتر می‌شود ولی مخرج تغییر نمی‌کند. این قرارداد معمولاً biased نامیده می‌شود؛ این نام، اثباتی دربارهٔ سوگیریِ آماریِ هر داده یا بهترین‌بودنِ انتخابِ ما نیست. جایگزین‌کردنِ مخرج با طول منهای lag، تعریفِ دیگری می‌سازد و الزاماً فقط تغییرِ مقیاسِ مشترک نیست.

مستنداتِ yule_walker در statsmodels، کم‌کردنِ میانگین، دو قراردادِ مخرج و دستگاهِ Toeplitz را توضیح می‌دهد. در این درس، قراردادِ مخرجِ ثابتِ N را انتخاب کرده‌ایم، نه پیش‌فرضِ adjusted آن رابط. statsmodels اجرا نشده و مثال، داده و قالبِ خروجیِ آن را تکرار نمی‌کنیم؛ از توضیحِ فرایندِ ایستا نیز ایستاییِ قابِ ساختگی نتیجه نمی‌گیریم.

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

۳. کد کاملِ دستگاهِ مرتبهٔ یک و دو

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

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

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

محاسبات از ابتدا Fraction هستند. مستنداتِ fractions پایتون توضیح می‌دهد که نسبتِ دو عددِ صحیح، مقدارِ گویا را نگه می‌دارد؛ تبدیلِ عددِ شناور، دقیقاً همان مقدارِ شناور را نگه می‌دارد و لزوماً کسرِ ده‌دهیِ موردِ انتظار نیست. به همین دلیل، این رابط شناور را ابتدا نمی‌پذیرد تا بعد با تقریبِ مخرج، معنای ورودی را تغییر دهد.

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

"""Exact, bounded order1/2 biased Yule-Walker frame audit; not speech analysis."""
from dataclasses import dataclass
from fractions import Fraction as F
from itertools import product
import json
import platform


@dataclass(frozen=True)
class FrameFit:
    mean: F
    centered: tuple
    covariance: tuple
    phi: tuple
    residual_mse: F


def eliminate(matrix, rhs):
    """Internal exact Gaussian elimination for the already validated tiny system."""
    rows = [list(row)+[value] for row, value in zip(matrix, rhs)]
    n = len(rows)
    for column in range(n):
        pivot = rows[column][column]
        if not pivot:
            raise ArithmeticError('singular exact Yule-Walker system')
        rows[column] = [value/pivot for value in rows[column]]
        for row in range(n):
            if row != column:
                factor = rows[row][column]
                rows[row] = [a-factor*b for a, b in zip(rows[row], rows[column])]
    return tuple(row[-1] for row in rows)


def fit_frame(frame, order=2):
    """Use factory only; manually constructed malformed FrameFit is unsupported."""
    if type(frame) is not tuple or any(type(value) is not int for value in frame):
        raise TypeError('frame must be an actual tuple of actual ints, excluding bool')
    if not 3 <= len(frame) <= 32 or any(abs(value) > 1000000 for value in frame):
        raise ValueError('frame length3..32 and absolute samples<=1000000 required')
    if type(order) is not int:
        raise TypeError('order must be an actual int')
    if order not in (1, 2):
        raise ValueError('only order1 or order2 supported')
    n = len(frame)
    mean = F(sum(frame), n)
    centered = tuple(F(value)-mean for value in frame)
    covariance = tuple(sum((centered[t]*centered[t-lag] for t in range(lag, n)), F(0))/n
                       for lag in range(order+1))
    if covariance[0] == 0:
        raise ValueError('constant centered frame has zero energy')
    matrix = tuple(tuple(covariance[abs(i-j)] for j in range(order)) for i in range(order))
    phi = eliminate(matrix, covariance[1:])
    residual_mse = covariance[0]-sum((a*b for a, b in zip(phi, covariance[1:])), F(0))
    return FrameFit(mean, centered, covariance, phi, residual_mse)


def gram_reference(frame, order):
    """Test only: explicit finite shifted columns, no lag covariance/Toeplitz builder.

    All rows t=0..N+order-1 are retained, including the zero-extended tail.
    Cramer's determinants are independent of the production row elimination.
    """
    n, total = len(frame), sum(frame)
    z = tuple(F(n*value-total, n) for value in frame)
    response = z+(F(0),)*order
    columns = tuple((F(0),)*lag+z+(F(0),)*(order-lag) for lag in range(1, order+1))
    dot = lambda a, b: sum((x*y for x, y in zip(a, b)), F(0))
    gram = tuple(tuple(dot(a, b) for b in columns) for a in columns)
    cross = tuple(dot(column, response) for column in columns)
    def determinant(a):
        return a[0][0] if len(a) == 1 else a[0][0]*a[1][1]-a[0][1]*a[1][0]
    denominator = determinant(gram)
    coefficients = tuple(determinant(tuple(tuple(cross[i] if j == column else gram[i][j]
                         for j in range(order)) for i in range(order)))/denominator
                         for column in range(order))
    errors = tuple(y-sum((a*column[t] for a, column in zip(coefficients, columns)), F(0))
                   for t, y in enumerate(response))
    return coefficients, dot(errors, errors)/n, gram, cross, errors


def rejected(exception, frame, order=2):
    try:
        fit_frame(frame, order)
    except exception:
        return True
    return False


def strings(values):
    return [str(value) for value in values]


def main():
    frame = (11, 10, 9, 10)
    fit = fit_frame(frame)
    reference = gram_reference(frame, 2)
    checks = {}
    checks['hand_mean_centering_and_biased_covariance'] = (
        fit.mean == 10 and fit.centered == (1, 0, -1, 0)
        and fit.covariance == (F(1, 2), F(0), F(-1, 4)))
    checks['hand_order_two_prediction_sign'] = fit.phi == (F(0), F(-1, 2))
    checks['full_zero_extended_residual_energy'] = (
        fit.residual_mse == F(3, 8) and reference[4] == (1, 0, F(-1, 2), 0, F(-1, 2), 0))
    one = fit_frame((0, 1, 0), 1)
    checks['hand_order_one_and_exact_residual'] = (
        one.mean == F(1, 3) and one.phi == (F(-2, 3),) and one.residual_mse == F(10, 81))
    shifted = fit_frame(tuple(value+123 for value in frame))
    checks['constant_offset_removed_before_estimation'] = (
        shifted.mean == fit.mean+123 and shifted.centered == fit.centered
        and shifted.covariance == fit.covariance and shifted.phi == fit.phi)
    scaled = fit_frame(tuple(-3*value for value in frame))
    checks['nonzero_scale_preserves_coefficients_not_energy'] = (
        scaled.phi == fit.phi and scaled.covariance == tuple(9*v for v in fit.covariance)
        and scaled.residual_mse == 9*fit.residual_mse)
    checks['time_reversal_preserves_covariance_coefficients'] = (
        fit_frame(tuple(reversed(frame))).covariance == fit.covariance
        and fit_frame(tuple(reversed(frame))).phi == fit.phi)
    checks['arbitrary_reordering_is_not_invariant'] = (
        fit_frame((11, 9, 10, 10)).phi != fit.phi)
    checks['inner_frame_error_is_a_different_objective'] = (
        sum((v*v for v in reference[4][2:4]), F(0))/2 == F(1, 8)
        and F(1, 8) != fit.residual_mse)
    short = fit_frame((1, -2, 1))
    adjusted = (short.covariance[0], short.covariance[1]*F(3, 2), short.covariance[2]*3)
    checks['lag_dependent_denominator_can_change_singularity'] = (
        short.covariance == (F(2), F(-4, 3), F(1, 3))
        and adjusted == (F(2), F(-2), F(1)) and adjusted[0]**2-adjusted[1]**2 == 0)
    checks['constant_frame_zero_energy_rejected'] = all(
        rejected(ValueError, (value,)*length) for value in (-1000000, 0, 7, 1000000) for length in (3, 32))
    checks['exact_type_rejections'] = all([
        rejected(TypeError, [1, 0, -1]), rejected(TypeError, (1, True, -1)),
        rejected(TypeError, (1, 0.0, -1)), rejected(TypeError, (1, F(0), -1)),
        rejected(TypeError, frame, True), rejected(TypeError, frame, 1.0)])
    boundary = fit_frame((-1000000, 1000000)*16)
    checks['length_sample_and_order_boundaries'] = all([
        rejected(ValueError, (1, 2)), rejected(ValueError, (1, 0)*16+(1,)),
        rejected(ValueError, (1000001, 0, -1)), rejected(ValueError, frame, 0),
        rejected(ValueError, frame, 3), len(boundary.centered) == 32,
        all(type(v) is F for v in boundary.phi), boundary.residual_mse > 0])
    cases, passed = 0, True
    for length in range(3, 7):
        for candidate in product((-1, 0, 1), repeat=length):
            if len(set(candidate)) == 1:
                continue
            for order in (1, 2):
                actual = fit_frame(candidate, order)
                expected, energy, gram, cross, _ = gram_reference(candidate, order)
                passed &= actual.phi == expected and actual.residual_mse == energy and energy > 0
                passed &= all(gram[i][j] == length*actual.covariance[abs(i-j)]
                              for i in range(order) for j in range(order))
                passed &= cross == tuple(length*v for v in actual.covariance[1:])
                cases += 1
    checks['independent_shifted_column_gram_cramer_oracle'] = bool(passed and cases == 2136)
    assert len(checks) == 14 and all(type(v) is bool and v for v in checks.values()), checks
    print(json.dumps(dict(python=platform.python_version(),checks=checks,
        example=dict(frame=frame,mean=str(fit.mean),centered=strings(fit.centered),
                     covariance=strings(fit.covariance),phi=strings(fit.phi),residual_mse=str(fit.residual_mse),
                     zero_extended_residual=strings(reference[4]),inner_frame_mse='1/8'),
        order_one=dict(frame=[0,1,0],phi=strings(one.phi),residual_mse=str(one.residual_mse)),
        denominator_witness=dict(frame=[1,-2,1],biased=strings(short.covariance),adjusted=strings(adjusted),
                                 adjusted_order_two_determinant='0'),
        independent_oracle_cases_in_one_group=cases,
        libraries_executed='Python standard library only; no statsmodels'),indent=2))


if __name__ == '__main__':
    main()

۴. مرجعِ مستقل را از ستون‌های جابه‌جا‌شده بسازیم

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

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

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

تمامِ قاب‌های غیریکسان با طولِ سه تا شش از الفبای منفی یک، صفر و یک، برای هر دو مرتبه بررسی شدند: ۲۱۳۶ مقایسه در یک گروهِ مرجع. تساویِ ضرایب، انرژی و نسبتِ Gram با خودکوواریانس، دقیق است و تلورانسِ شناوری ندارد. گروه‌های دیگر شاهدِ دستی، تغییرِ مبنا و مقیاس، وارونگیِ زمان، مرزِ ورودی و قابِ ثابت را می‌سنجند؛ مجموع چهارده گروه است.

اجرای واقعی Python 3.14.5 چهارده بررسیِ درست و stderr تهی ثبت کرد. منابع در ۱۸ اکتبرِ ۲۰۲۶ خوانده شدند؛ صفحهٔ statsmodels نسخهٔ 0.15.0 و مستنداتِ پایتون نسخهٔ 3.14.8 را نشان می‌دادند. این نسخه‌ها گزارشِ اجرای آن کتابخانه‌ها نیستند. مانندِ درسِ بانکِ mel، درستیِ تبدیلِ ویژگی را از نتیجهٔ سامانهٔ گفتار جدا نگه می‌داریم.

{
  "python": "3.14.5",
  "checks": {
    "hand_mean_centering_and_biased_covariance": true,
    "hand_order_two_prediction_sign": true,
    "full_zero_extended_residual_energy": true,
    "hand_order_one_and_exact_residual": true,
    "constant_offset_removed_before_estimation": true,
    "nonzero_scale_preserves_coefficients_not_energy": true,
    "time_reversal_preserves_covariance_coefficients": true,
    "arbitrary_reordering_is_not_invariant": true,
    "inner_frame_error_is_a_different_objective": true,
    "lag_dependent_denominator_can_change_singularity": true,
    "constant_frame_zero_energy_rejected": true,
    "exact_type_rejections": true,
    "length_sample_and_order_boundaries": true,
    "independent_shifted_column_gram_cramer_oracle": true
  },
  "example": {
    "frame": [
      11,
      10,
      9,
      10
    ],
    "mean": "10",
    "centered": [
      "1",
      "0",
      "-1",
      "0"
    ],
    "covariance": [
      "1/2",
      "0",
      "-1/4"
    ],
    "phi": [
      "0",
      "-1/2"
    ],
    "residual_mse": "3/8",
    "zero_extended_residual": [
      "1",
      "0",
      "-1/2",
      "0",
      "-1/2",
      "0"
    ],
    "inner_frame_mse": "1/8"
  },
  "order_one": {
    "frame": [
      0,
      1,
      0
    ],
    "phi": [
      "-2/3"
    ],
    "residual_mse": "10/81"
  },
  "denominator_witness": {
    "frame": [
      1,
      -2,
      1
    ],
    "biased": [
      "2",
      "-4/3",
      "1/3"
    ],
    "adjusted": [
      "2",
      "-2",
      "1"
    ],
    "adjusted_order_two_determinant": "0"
  },
  "independent_oracle_cases_in_one_group": 2136,
  "libraries_executed": "Python standard library only; no statsmodels"
}

۵. چهار پرسش دربارهٔ مرزِ قاب و تفسیرِ ضرایب

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

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

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

پرسش دوم: چرا برای قابِ یک، منفی دو، یک نمی‌توان مخرجِ N را با N منهای lag عوض کرد و انتظار داشت تنها یک ضریبِ مشترک از دستگاه حذف شود؟

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

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

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

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

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

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

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