پرش به محتوا
2/30فصل 2 از 30

تابع زیان از کجا می‌آید: درست‌نمایی، نه قرارداد

سه خط روی همان ۲۰ اندازه‌گیری و سه قانون امتیازدهی که سه برنده متفاوت می‌سازند. خطای مربعی یک انتخاب است.

در این صفحه

تیغه‌ای که قطعه‌ها را می‌بُرد فرسوده می‌شود. طی یک شیفت ده‌ساعته آن‌قدر تیزی از دست می‌دهد که قطعه‌ها وقتی از نوار بیرون می‌آیند، کسری از میلی‌متر پهن‌تر از شروع کارند، و وقتی از 23.5 میلی‌متر بگذرند بازرسی آن‌ها را رد می‌کند. هیچ‌کس در کارخانه نمی‌داند این اتفاق چه زمانی می‌افتد. چیزی که دارند یک کولیس، یک دفترچه، و بیست قرائت از سه‌شنبه گذشته است: ساعت‌های گذشته از زمان تعویض تیغه، و پهنای قطعه که همان لحظه اندازه‌گیری شده است.

یک نفر از میان نقطه‌ها خطی می‌کشد. نفر دیگری خط کمی متفاوتی می‌کشد. نفر سوم خط سوم را. هر سه روی کاغذ معقول به نظر می‌رسند، و درباره زمان تعویض تیغه چند ساعت اختلاف دارند — در این کارخانه، همین اختلاف می‌تواند مرز بین یک هفته آرام و یک بچ اسقاطی باشد.

کدام خط بهتر است؟

به این شکل که پرسیده شده، این سؤال پاسخی ندارد. نه پاسخی دشوار — اصلاً هیچ پاسخی. «بهتر» خاصیت یک خط مثل شیب آن نیست؛ خاصیت یک خط همراه با قاعده‌ای برای امتیازدهی به خط‌ها است، و تا وقتی کسی آن قاعده را ننویسد چیزی برای محاسبه وجود ندارد. این فصل آن جمله را جدی می‌گیرد، و با این کشف تمام می‌شود که رایج‌ترین قاعده در machine learning یک قرارداد نیست، بلکه پیامد ادعایی درباره جهان است — ادعایی که می‌توانید آزمایش کنید، و گاهی نادرست است.

یک اعتراف پیش از نخستین خط کد. این بیست قرائت از یک کارخانه واقعی نیستند: آن‌ها را از خطی که خودم انتخاب کردم، width=20.00+0.30h\text{width} = 20.00 + 0.30 \cdot h، به‌علاوه نویز تصادفی با پراکندگی حدود یک‌دهم میلی‌متر تولید کردم. این مهم است، چون همه آنچه در ادامه می‌آید درباره این است که آیا یک روش حقیقتی را بازیابی می‌کند یا نه، و تنها راه بررسی آن این است که حقیقت را از قبل بدانیم. پس: 0.30 میلی‌متر در ساعت، جواب آخر کتاب است. اجازه ندارید از آن استفاده کنید، فقط می‌توانید خودتان را با آن بسنجید.

این‌ها قرائت‌ها و سه خط هستند، با سه شیوه امتیازدهی: خطای مربعی، چیزی که همه سراغش می‌روند؛ خطای مطلق، چیزی که شاید یک آمارگر انتخاب کند؛ و بدترین خطا، چیزی که ماشین‌کار انتخاب می‌کند، چون بازرس به میانگین شما اهمیت نمی‌دهد — او همان تک‌قطعه‌ای را رد می‌کند که خارج از تلرانس است.

NumPy اینجا، یک فصل پس از پرسپترون pure-Python، فقط به یک دلیل وارد می‌شود: تا پایان این فصل چهارصد هزار خط کاندیدا را هرکدام در برابر بیست قرائت ارزیابی می‌کنیم، و حلقه Python ابزار نادرستی برای این کار است. همین‌طور، نمادگذاری‌ای است که هر منبعی که در ادامه به آن ارجاع داده شده با آن نوشته شده است.

loss.pyPYTHON
import numpy as np

# Hours since the blade was changed, and the width of the part measured then.
SHIFT = np.array([
    (0.5, 20.17), (1.0, 20.28), (1.5, 20.53), (2.0, 20.61), (2.5, 20.69),
    (3.0, 20.94), (3.5, 21.21), (4.0, 21.31), (4.5, 21.27), (5.0, 21.35),
    (5.5, 21.58), (6.0, 21.80), (6.5, 21.67), (7.0, 22.07), (7.5, 22.10),
    (8.0, 22.31), (8.5, 22.48), (9.0, 22.66), (9.5, 22.90), (10.0, 23.13),
])
h, y = SHIFT[:, 0], SHIFT[:, 1]

LINES = {"A": (20.10, 0.26), "B": (20.20, 0.28), "C": (20.30, 0.26)}

for name, (a, b) in LINES.items():
    r = y - (a + b * h)                                      
    print(f"{name}   mean square {np.mean(r**2):.5f}"
          f"   mean absolute {np.mean(np.abs(r)):.5f}"
          f"   worst {np.max(np.abs(r)):.3f}")               

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

TEXT
A   mean square 0.02699   mean absolute 0.12600   worst 0.430
B   mean square 0.02524   mean absolute 0.13700   worst 0.350
C   mean square 0.03179   mean absolute 0.15000   worst 0.320

ستون‌ها را بخوانید، نه سطرها را. خطای مربعی می‌گوید B، خطای مطلق می‌گوید A، بدترین خطا می‌گوید C: سه قاعده، سه برنده، روی همان بیست نقطه.

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

اول یک موضوع کوچک‌تر، چون سه خط نداریم بلکه بی‌نهایت خط داریم. فعلاً خطای مربعی را بگیرید، چون همان چیزی است که همه می‌گیرند، و مسئله را با همان ترفندی که در فصل 1 یازده هزار epoch پرسپترون را نجات داد به یک عدد کاهش دهید: میانگین را از هر دو ستون کم کنید. وقتی ابر نقطه‌ها روی مبدأ مرکزگذاری شد، بهترین خط از نظر خطای مربعی دقیقاً از مبدأ می‌گذرد — پس عرض از مبدأ تعیین شده و فقط شیب برای انتخاب باقی می‌ماند.

loss.py (continued)PYTHON
u, v = h - h.mean(), y - y.mean()        # 5.25 hours, 21.553 mm

def mse(theta):
    return np.mean((v - theta * u) ** 2)

grid = np.arange(0.0, 0.6001, 0.001)
curve = np.array([mse(t) for t in grid])
print(grid.size, "candidates ->", f"theta={grid[curve.argmin()]:.3f}", f"mse={curve.min():.6f}")
TEXT
601 candidates -> theta=0.293 mse=0.010115

ششصد و یک شیب کاندیدا، یک برنده: 0.293 میلی‌متر در ساعت در برابر حقیقت 0.300. بیست قرائت نویزی و یک for-loop به فاصله یک‌صدم میلی‌متر در ساعت رسیدند — دو و یک‌سوم درصد.

بخش جالب برنده نیست، بلکه شکل جست‌وجو است. کل منحنی را چاپ کنید، چرخانده‌شده طوری که زیان از چپ به راست حرکت کند:

loss.py (continued)PYTHON
ts = np.arange(0.0, 0.6001, 0.04)
ls = np.array([mse(t) for t in ts])
for t, l in zip(ts, ls):
    col = round(l / ls.max() * 50)
    print(f"theta={t:.2f} |{' ' * col}*{' ' * (50 - col)}| mse={l:7.4f}")
TEXT
theta=0.00 |                                              *    | mse= 0.7244
theta=0.04 |                                  *                | mse= 0.5428
theta=0.08 |                        *                          | mse= 0.3878
theta=0.12 |                *                                  | mse= 0.2593
theta=0.16 |          *                                        | mse= 0.1575
theta=0.20 |     *                                             | mse= 0.0822
theta=0.24 |  *                                                | mse= 0.0336
theta=0.28 | *                                                 | mse= 0.0116
theta=0.32 | *                                                 | mse= 0.0161
theta=0.36 |   *                                               | mse= 0.0473
theta=0.40 |       *                                           | mse= 0.1050
theta=0.44 |            *                                      | mse= 0.1894
theta=0.48 |                   *                               | mse= 0.3004
theta=0.52 |                            *                      | mse= 0.4379
theta=0.56 |                                      *            | mse= 0.6021
theta=0.60 |                                                  *| mse= 0.7928

این یک دره است، از کنار دیده‌شده. یک کف دارد، دیواره‌ها از هر دو طرف نرم بالا می‌روند، و — این همان بخشی است که پلکان فصل 1 نمی‌توانست بدهد — روی تک‌تک نقطه‌های آن جهت مشخصی به نام «سراشیبی» وجود دارد. این شکل را به خاطر بسپارید. فصل 3 کاملاً درباره پایین‌رفتن از آن بدون بازدید از هر ششصد و یک نقطه است، و درباره اینکه وقتی یک دره بیش از یک کف داشته باشد چه چیزی تغییر می‌کند.

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

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

این ادعایی است که «کدام خط بهتر است» را به پرسشی دارای پاسخ تبدیل می‌کند.

فرض کنید پهنای یک قطعه برابر خط به‌علاوه یک خطای تصادفی است، و فرض کنید آن خطا از یک Gaussian — همان منحنی زنگوله‌ای — با میانگین صفر و انحراف معیار σ\sigma کشیده شده است:

yi=θxi+εi,εiN(0,σ2)y_i = \theta x_i + \varepsilon_i, \qquad \varepsilon_i \sim \mathcal{N}(0, \sigma^2)

چگالی Gaussian برابر است با

p(ε)=1σ2πexp ⁣(ε22σ2)p(\varepsilon) = \frac{1}{\sigma\sqrt{2\pi}} \exp\!\left(-\frac{\varepsilon^2}{2\sigma^2}\right)

حالا کاری کنید که پرسپترون نمی‌توانست. برای یک شیب کاندیدای داده‌شده θ\theta، هر قرائت یک باقیمانده دارد، و فرمول بالا آن باقیمانده را به عددی تبدیل می‌کند: اگر این شیب حقیقت باشد، خطایی دقیقاً با این اندازه چقدر قابل‌قبول است؟ قرائتی که روی خط باشد عدد بزرگی می‌گیرد، قرائتی که نیم میلی‌متر دور باشد عدد کوچکی.

قرائت‌ها مستقل‌اند — کولیس قطعه قبلی را به خاطر نمی‌آورد — پس قاعده ضرب می‌گوید قابل‌قبول‌بودن کل دفترچه حاصل‌ضرب چگالی‌های منفرد است. آن حاصل‌ضرب درست‌نمایی θ\theta است.1 جهت را دقت کنید، چون همان جهتی است که قاعده بیز درباره آن حرف می‌زند: داده ثابت و معلوم است، و پارامتر است که تغییر می‌کند. این «احتمال شیب» نیست. احتمالی است که مدل به داده‌ای که واقعاً گرفته‌اید اختصاص می‌دهد، وقتی به‌صورت تابعی از شیب خوانده شود.

likelihood.pyPYTHON
SIGMA = 0.12

def gaussian(r, sigma):
    return np.exp(-r ** 2 / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi))

def likelihood(theta):
    return np.prod(gaussian(v - theta * u, SIGMA))          

for t in (0.25, 0.293, 0.35):
    print(f"theta={t}   likelihood = {likelihood(t):.6g}")
TEXT
theta=0.25   likelihood = 521.952
theta=0.293   likelihood = 2.42028e+07
theta=0.35   likelihood = 0.190312

شیب 0.293 این دفترچه را چهل‌وشش هزار برابر قابل‌قبول‌تر از 0.25 می‌کند، و صدوبیست‌وهفت میلیون برابر قابل‌قبول‌تر از 0.35. درست‌نمایی بیشینه اصلی است که می‌گوید پارامتری را انتخاب کنید که آنچه واقعاً مشاهده کرده‌اید تا حد ممکن نامتعجب‌کننده باشد. این قضیه نیست، بلکه پیشنهادی درباره این است که «بهترین» باید چه معنایی داشته باشد — پیشنهادی با محتوا، چون شما را مجبور می‌کند پیش از آنکه اجازه امتیازدهی به چیزی داشته باشید، فرضتان درباره نویز را بیان کنید.

همان سه خط کد را به‌جای یک شیفت، روی یک ماه شیفت اجرا کنید، و روش از پا می‌افتد.

likelihood.py (continued)PYTHON
rng = np.random.default_rng(7)
u_big = rng.uniform(-5.25, 5.25, 2000)                     # 2000 readings, not 20
v_big = 0.30 * u_big + 0.12 * rng.standard_normal(2000)

print("2000 readings, sigma = 0.12 mm :", np.prod(gaussian(v_big - 0.30 * u_big, 0.12)))
noisy = 0.30 * u_big + 2.0 * rng.standard_normal(2000)
print("2000 readings, sigma = 2.00 mm :", np.prod(gaussian(noisy - 0.30 * u_big, 2.0)))
print("largest float64 :", np.finfo(np.float64).max)
TEXT
RuntimeWarning: overflow encountered in reduce
2000 readings, sigma = 0.12 mm : inf
2000 readings, sigma = 2.00 mm : 0.0
largest float64 : 1.7976931348623157e+308

دو هزار ضرب و پاسخ inf است. یک ثابت را عوض کنید — کولیس شلخته‌تر، طوری که چگالی‌ها به‌جای بزرگ‌تر از 1 کوچک‌تر از 1 شوند — و همان کد 0.0 برمی‌گرداند. هر دو پاسخ غلط‌اند، در جهت‌های مخالف، هیچ‌کدام استثنایی بالا نمی‌آورند که بتوانید بگیرید، و دومی حتی هشدار هم چاپ نمی‌کند.

مشکلی در ریاضیات نیست. درست‌نمایی در آن تنظیمات عددی کاملاً خوش‌تعریف و متناهی است: لگاریتم طبیعی‌اش 1400.91 است، پس خود عدد حدود 1060810^{608} است. مشکل این است که کامپیوتر شما آن عدد را ندارد، و ارزش دارد دقیقاً بفهمید چه عددهایی را دارد، چون این آخرین باری نیست که نتیجه را تعیین خواهد کرد.

رفع مشکل حاصل‌ضرب منفجرشونده همان راه همیشگی است: لگاریتم بگیرید. لگاریتم حاصل‌ضرب‌ها را به جمع تبدیل می‌کند، اکیداً صعودی است پس نمی‌تواند جایگاه بیشینه را جابه‌جا کند، و جمع دو هزار عدد متوسط چیزی است که float64 بی‌اعتراض مدیریت می‌کند. طبق قرارداد منفی لگاریتم درست‌نمایی را می‌گیریم، تا بهتر یعنی کوچک‌تر. حالا چگالی Gaussian را جای‌گذاری کنید و ببینید چه می‌شود.

  1. از حاصل‌ضرب شروع کنید. درست‌نمایی L(θ)=i=1Np(yiθxi)\mathcal{L}(\theta) = \prod_{i=1}^{N} p(y_i - \theta x_i) است، که در آن pp همان چگالی Gaussian بالاست.

  2. منفی لگاریتم را بگیرید. حاصل‌ضرب به جمع تبدیل می‌شود، و نماییِ داخل چگالی مستقیماً با لگاریتم خنثی می‌شود:

logL(θ)=N2log ⁣(2πσ2)+12σ2i=1N(yiθxi)2-\log \mathcal{L}(\theta) = \frac{N}{2}\log\!\left(2\pi\sigma^2\right) + \frac{1}{2\sigma^2}\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2
  1. هر چیزی را که شامل θ\theta نیست دور بریزید. جمله اول ثابت است. 1/2σ21/2\sigma^2 جلوی جمع یک ثابت مثبت است، و ضرب‌کردن یک تابع در ثابت مثبت نمی‌تواند جای کمینه آن را عوض کند. چیزی که باقی می‌ماند این است
i=1N(yiθxi)2\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2

که جمع باقیمانده‌های مربعی است — همان چیزی که فصل را با آن شروع کردیم چون اولین چیزی بود که به ذهن هر کسی می‌رسد.

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

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

likelihood.py (continued)PYTHON
N = v.size

def nll(theta):
    r = v - theta * u
    return N * np.log(SIGMA * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * SIGMA ** 2)

nlls = np.array([nll(t) for t in grid])
mses = np.array([mse(t) for t in grid])
print(f"argmin of the negative log-likelihood : theta={grid[nlls.argmin()]:.3f}  nll={nlls.min():.6f}")
print(f"argmin of the mean squared error      : theta={grid[mses.argmin()]:.3f}  mse={mses.min():.6f}")
print("same index:", nlls.argmin() == mses.argmin())
TEXT
argmin of the negative log-likelihood : theta=0.293  nll=-17.001977
argmin of the mean squared error      : theta=0.293  mse=0.010115
same index: True

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

نمایش استنتاج کامل

دقیقاً کدام دورریختن‌ها امن‌اند؟ همین مانور در هر فصلی که زیانی را استخراج می‌کند ظاهر می‌شود، و همیشه بی‌خطر نیست.

حذف یک ثابت جمعی هر وقت به پارامتری که بهینه می‌کنید وابسته نباشد امن است، و حذف یک ثابت ضربی مثبت امن است چون argminθcf(θ)=argminθf(θ)\arg\min_\theta c\,f(\theta) = \arg\min_\theta f(\theta) برای هر c>0c > 0. هر دو در لحظه‌ای شکست می‌خورند که σ\sigma هم در حال برازش باشد: آن وقت N2log(2πσ2)\frac{N}{2}\log(2\pi\sigma^2) اصلاً ثابت نیست، همان جمله‌ای است که جلوی مدل را می‌گیرد تا ادعای σ=0\sigma = 0 و درست‌نمایی بی‌نهایت نکند. این دقیقاً بخش بعدی است.

در فصل 3 جور دیگری هم شکست می‌خورند: ثابت ضربی کمینه را جابه‌جا نمی‌کند، اما gradient را مقیاس می‌کند، و gradient در learning rate ضرب می‌شود. تقسیم بر NN برای گرفتن خطای مربعی میانگین به‌جای جمع برای پاسخ نامرئی است و برای اجرای آموزش بسیار مرئی — با جمع، دوبرابرکردن اندازه batch هر قدمی را که برمی‌دارید دوبرابر می‌کند.

ما σ\sigma را با حکم خودمان روی 0.12 ثابت کردیم، و هیچ‌کس در کارخانه پراکندگی خطای کولیسش را نمی‌داند. آن را به‌عنوان مجهول دوم در نظر بگیرید و بگذارید درست‌نمایی بیشینه آن را هم تعیین کند. اینجا جمله ثابتی که همین حالا دور انداختیم برمی‌گردد، چون تنها چیزی است که بین مدل و ادعای دقت کامل ایستاده است.

likelihood.py (continued)PYTHON
r = v - 0.293 * u
sigmas = np.arange(0.01, 1.0001, 0.0001)
nll_sigma = N * np.log(sigmas * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * sigmas ** 2)

print("best sigma on the grid       :", round(float(sigmas[nll_sigma.argmin()]), 4))
print("sqrt(mean squared residual)  :", round(float(np.sqrt(np.mean(r ** 2))), 4))
TEXT
best sigma on the grid       : 0.1006
sqrt(mean squared residual)  : 0.1006

این دو تا چهار رقم اعشار با هم موافق‌اند، و تصادفی نیست: مشتق‌گرفتن از آن عبارت و صفر قرار دادنش دقیقاً σ^2=1Nri2\hat{\sigma}^2 = \frac{1}{N}\sum r_i^2 می‌دهد. پس خطای مربعی میانگین صرفاً شبیه واریانس نیست. تحت این مدل، خودِ برآورد درست‌نمایی بیشینه واریانس نویز است — عددی که تمام این مدت کمینه می‌کردید برآوردی از میزان نویزی‌بودن حسگرتان بود.

یک نکته ظریف، ارزان برای گفتن و گران برای کشف دوباره در آینده: آن برآورد به سمت پایین سوگیر است، چون باقیمانده‌ها نسبت به برازش اندازه‌گیری شدند که خودش طوری انتخاب شده بود که آن‌ها را کوچک کند. شبیه‌سازی‌اش کنید — دویست هزار دفترچه، هرکدام با بیست قرائت، کشیده‌شده از توزیعی که واریانس واقعی‌اش دقیقاً 1 است، با یک پارامتر برازش که از خود قرائت‌ها برآورد می‌شود. تقسیم جمع مربع‌ها بر NN میانگین 0.9501 می‌دهد؛ تقسیم بر N1N-1 عدد 1.0001 می‌دهد؛ و (N1)/N(N-1)/N دقیقاً 0.95 است. هر پارامتری که برازش می‌کنید یک درجه آزادی هزینه دارد، و این کوچک‌ترین نمونه قابل‌مشاهده از مسئله‌ای بسیار بزرگ‌تر است: یک مدل همیشه روی داده‌ای که با آن برازش شده بهتر به نظر می‌رسد. فصل 4 این را به انضباط نگه‌داشتن داده کنار تبدیل می‌کند، و فصل 6 نامش را به آن می‌دهد.

زیان ادعایی درباره نویز است

لینک به بخش: زیان ادعایی درباره نویز است

اگر خطای مربعی ادعا کند نویز Gaussian است، پرسش بعدی این است که وقتی این ادعا غلط باشد چه می‌شود. نه کمی غلط — غلط به همان شکلی که اندازه‌گیری‌های واقعی غلط‌اند.

در کف کارگاه، بیشتر قرائت‌های کولیس تا یک‌دهم میلی‌متر خوب‌اند، و یکی دو بار در هر شیفت تراشه‌ای زیر فک گیر می‌کند و قرائت چند میلی‌متر خطا می‌رود. خطاهایی از این نوع دُم‌سنگین هستند: اغلب کوچک، گاهی عظیم، و عظیم بسیار بیشتر از آنچه منحنی زنگوله‌ای اجازه می‌دهد. توزیع Cauchy مدل تمیز استاندارد این رفتار است، و چگالی‌اش به سادگی چگالی Gaussian است:

p(ε)=1πs(1+(ε/s)2)p(\varepsilon) = \frac{1}{\pi s \left(1 + (\varepsilon/s)^2\right)}

تفاوت در دُم است: Gaussian مثل eε2e^{-\varepsilon^2} افت می‌کند، با خشونت و سرعت زیاد، و Cauchy مثل 1/ε21/\varepsilon^2، تقریباً اصلاً. پیامد را دیدن آسان‌تر از گفتن است:

PYTHON
rng = np.random.default_rng(3)
g = 0.12 * rng.standard_normal(10 ** 6)          # Gaussian noise
c = 0.12 * rng.standard_cauchy(10 ** 6)          # Cauchy noise, same scale
for k in (10 ** 2, 10 ** 3, 10 ** 4, 10 ** 5, 10 ** 6):
    print(f"{k:>9,} samples   gaussian var {g[:k].var():.4f}   cauchy var {c[:k].var():10.2f}")
TEXT
      100 samples   gaussian var 0.0164   cauchy var       0.26
    1,000 samples   gaussian var 0.0146   cauchy var      59.88
   10,000 samples   gaussian var 0.0145   cauchy var     358.17
  100,000 samples   gaussian var 0.0144   cauchy var    3097.98
1,000,000 samples   gaussian var 0.0144   cauchy var   32886.10

واریانس نمونه Gaussian روی 0.0144 می‌نشیند، که 0.1220.12^2 است، و همان‌جا می‌ماند. واریانس Cauchy بالا می‌رود، و تا هر وقت نمونه بگیرید همچنان بالا می‌رود، چون چیزی وجود ندارد که به آن همگرا شود: توزیع Cauchy نه واریانس دارد و نه حتی میانگین. از خطای مربعی، که کل کارش کمینه‌کردن میانگین مربع‌هاست، کمیتی خواسته شده که وجود ندارد.

پس این یک شیفت است که کولیس فریب خورده. همان بیست ساعت، همان تیغه، همان drift برابر 0.30 میلی‌متر در ساعت — فقط نویز حالا Cauchy است. دوبار برازش کنید: یک‌بار با کمینه‌کردن باقیمانده‌های مربعی، یک‌بار با کمینه‌کردن منفی لگاریتم درست‌نمایی نویزی که واقعاً داده را تولید کرده است. ترفند مرکزگذاری اینجا کمکی نمی‌کند — عرض از مبدأ را فقط برای خطای مربعی میخکوب می‌کند — پس هر دو برازش با brute force روی شبکه‌ای از عرض از مبدأها و شیب‌ها انجام می‌شوند، چون هنوز راهی نداریم که کف یک دره را جز با بازدید از آن پیدا کنیم.

swarf.pyPYTHON
SWARF = np.array([
    (0.5, 20.08), (1.0, 21.95), (1.5, 20.86), (2.0, 27.51), (2.5, 20.64),
    (3.0, 20.75), (3.5, 21.01), (4.0, 21.03), (4.5, 21.37), (5.0, 20.60),
    (5.5, 22.03), (6.0, 21.95), (6.5, 21.98), (7.0, 22.01), (7.5, 21.73),
    (8.0, 22.97), (8.5, 22.60), (9.0, 22.66), (9.5, 22.44), (10.0, 22.78),
])
hs, ys = SWARF[:, 0], SWARF[:, 1]

A = np.arange(18.0, 22.001, 0.005)      # 801 intercepts
B = np.arange(-0.20, 0.8001, 0.002)     # 501 slopes
R = ys - (A[:, None, None] + B[None, :, None] * hs)      # every line against every point

SCALE = 0.12
square = np.sum(R ** 2, axis=2)                          # least squares          
cauchy = np.sum(np.log(1 + (R / SCALE) ** 2), axis=2)    # Cauchy likelihood      

for name, surface in (("least squares", square), ("Cauchy likelihood", cauchy)):
    i, j = np.unravel_index(surface.argmin(), surface.shape)
    print(f"{name:>18}:  width = {A[i]:.3f} + {B[j]:.4f} * hours"
          f"   -> 23.5 mm at hour {(23.5 - A[i]) / B[j]:.2f}")
print(f"{'the truth':>18}:  width = 20.000 + 0.3000 * hours"
      f"   -> 23.5 mm at hour {(23.5 - 20.0) / 0.30:.2f}")
print(f"{A.size * B.size:,} candidate lines evaluated")

دو خط هایلایت‌شده کل تفاوت میان برازش‌ها هستند. لگاریتم چگالی Cauchy را بگیرید، ثابت‌ها را دقیقاً مثل قبل حذف کنید، و log(1+(r/s)2)\sum \log\left(1 + (r/s)^2\right) چیزی است که باقی می‌ماند. همان دستور پخت، ادعایی متفاوت درباره نویز.

TEXT
     least squares:  width = 21.380 + 0.1080 * hours   -> 23.5 mm at hour 19.63
 Cauchy likelihood:  width = 19.935 + 0.3020 * hours   -> 23.5 mm at hour 11.80
         the truth:  width = 20.000 + 0.3000 * hours   -> 23.5 mm at hour 11.67
401,301 candidate lines evaluated

کمترین مربعات drift را 0.108 میلی‌متر در ساعت گزارش می‌کند، تقریباً یک‌سوم نرخ واقعی، و نتیجه می‌گیرد که تیغه تا ساعت 19.6 خوب است. پاسخ واقعی ساعت 11.7 است. اگر کارخانه بر اساس آن برازش عمل کند، پرس را هشت ساعت اضافه اجرا می‌کند و قطعه‌های خارج از تلرانس می‌سازد، به اعتبار استانداردترین تابع زیان این حوزه. برازش Cauchy، با همان بیست قرائت، همان شبکه، و تفاوت فقط یک خط در کد، روی ساعت 11.8 می‌نشیند.

دو اعتراض سزاوار پاسخ‌اند، چون هر دو اولین چیزی‌اند که یک مهندس خوب می‌گوید.

داده پرت واضح است — فقط حذفش کنید. می‌توانید، و کمک می‌کند، و کافی نیست. حذف تنها بدترین قرائت شیب کمترین مربعات را از 0.108 به 0.239 می‌برد، که هنوز زمان تعویض تیغه را ساعت 13.1 می‌گذارد، یک ساعت و نیم دیر؛ حذف بدترین، برازش دوباره، و حذف هرچه حالا بدترین است شما را به 0.286 می‌رساند — و توجه کنید که این دیگر یک رویه است، نه مشاهده: اگر به‌جایش دو باقیمانده بزرگ برازش اولیه را حذف کنید، روی 0.223 فرود می‌آیید. اما حالا قضاوت‌هایی کرده‌اید که نمی‌توانید بنویسید یا از آن‌ها دفاع کنید، و خودکارکردن قاعده هم نجاتش نمی‌دهد: حذف بزرگ‌ترین باقیمانده و سپس برازش دوباره، اجراشده روی هزار شیفت شبیه‌سازی‌شده، خطای میانه شیب 0.0177 دارد در برابر 0.0100 برازش درست‌نمایی، و در 14.7% شیفت‌ها بیش از 0.05 خطا دارد در برابر 1.3%. حذف، وصله‌ای روی یک فرض غلط است. درست‌نمایی به وصله نیاز ندارد، چون هرگز فرض نکرده بود داده پرت ناممکن است.

شما یک دیتاست خوش‌شانس انتخاب کردید. این اعتراض دقیقاً درست است، و به همین دلیل آزمایش آخر هزار شیفت مستقل را شبیه‌سازی می‌کند و هر دو روش را روی هرکدام دوباره برازش می‌کند.

swarf.py (continued)PYTHON
A = np.arange(18.0, 22.001, 0.02)        # a coarser grid: a thousand fits to do
B = np.arange(-0.20, 0.8001, 0.005)
lines = A[:, None, None] + B[None, :, None] * hs
rng = np.random.default_rng(2026)
err_sq, err_ca = [], []

for _ in range(1000):                                        # 1000 independent shifts
    ys = 20.00 + 0.30 * hs + SCALE * rng.standard_cauchy(hs.size)
    R = ys - lines
    _, j = np.unravel_index(np.sum(R ** 2, axis=2).argmin(), (A.size, B.size))
    _, q = np.unravel_index(np.sum(np.log1p((R / SCALE) ** 2), axis=2).argmin(), (A.size, B.size))
    err_sq.append(abs(B[j] - 0.30))
    err_ca.append(abs(B[q] - 0.30))

err_sq, err_ca = np.array(err_sq), np.array(err_ca)
for name, e in (("least squares", err_sq), ("Cauchy likelihood", err_ca)):
    print(f"{name:>18}: median slope error {np.median(e):.4f} mm/h"
          f"   off by more than 0.05 in {100 * np.mean(e > 0.05):4.1f}% of shifts"
          f"   worst {e.max():.3f}")
print(f"the likelihood fit is the closer of the two in {100 * np.mean(err_ca < err_sq):.1f}% of shifts")
TEXT
     least squares: median slope error 0.0350 mm/h   off by more than 0.05 in 40.4% of shifts   worst 0.500
 Cauchy likelihood: median slope error 0.0100 mm/h   off by more than 0.05 in  1.3% of shifts   worst 0.090
the likelihood fit is the closer of the two in 75.6% of shifts

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

هیچ‌کدام از این‌ها خطای مربعی را بد نمی‌کند. آن را خاص می‌کند، و حساب دقیقاً می‌گوید چرا. یک باقیمانده 0.1 میلی‌متری و یکی 7 میلی‌متری را بگیرید. با مربع‌کردن، قرائت بد 4,900 برابر بیشتر از قرائت خوب به کل اضافه می‌کند، پس خط با تمام بدن به سمت آن کشیده می‌شود؛ تحت لگاریتم درست‌نمایی Cauchy همان دو باقیمانده 0.527 و 8.133 سهم دارند، نسبت 15.4. قرائت بد هنوز حساب می‌شود، فقط اجازه ندارد تصمیم بگیرد. این آغاز آمار مقاوم است، جایی که زیان Huber در 1964 با رفتار درجه‌دوم برای باقیمانده‌های کوچک و خطی برای بزرگ‌ها حد میانه را می‌گیرد،7 و جایی که Tukey از پیش نشان داده بود چه مقدار کمی آلودگی کافی است تا واریانس نمونه را به ابزاری بدتر از میانگین انحراف مطلق تبدیل کند.8

یک یادداشت تاریخی هم هست، آن‌قدر خوب که نمی‌شود حذفش کرد. کمترین مربعات نخستین بار توسط Legendre در 1805 منتشر شد، به‌عنوان وسیله‌ای جبری و راحت بدون توجیهی فراتر از اینکه کار می‌کرد.9 چهار سال بعد Gauss استدلال را برعکس اجرا کرد: او فرض گرفت که میانگین حسابی راه درست ترکیب اندازه‌گیری‌های تکراری است، پرسید کدام توزیع خطا میانگین را محتمل‌ترین مقدار می‌کند، و نشان داد اساساً فقط یکی چنین می‌کند — همان که حالا به نام اوست.10 استخراج این فصل از اوست، بیش از دو قرن قدمت دارد، و هنوز همان بخشی است که بیشتر دوره‌ها جا می‌اندازند.

حالا چه می‌توانید بگویید، و هنوز چه نمی‌توانید بکنید

لینک به بخش: حالا چه می‌توانید بگویید، و هنوز چه نمی‌توانید بکنید

به دست آمده. تابع زیان یک قاعده امتیازدهی است، و رتبه‌بندی‌ای که تولید می‌کند خاصیت قاعده است، نه کاندیداها. هر زیان در این دوره منفی لگاریتم درست‌نماییِ فرضی درباره نویز است، با حذف ثابت‌ها — Gaussian اینجا خطای مربعی می‌دهد، Bernoulli در فصل 4 cross-entropy می‌دهد، و یک توزیع categorical روی واژگان زیان next-token را در فصل 8 می‌دهد. دستور پخت هیچ‌وقت عوض نمی‌شود: نویز را بیان کنید، درست‌نمایی را بنویسید، منفی لگاریتم را بگیرید. و وقتی فرض غلط باشد مدل صرفاً نادقیق نیست، در جهتی غلط است که می‌توانید پیش‌بینی کنید.

هنوز کم داریم. کف دره را با بازدید از هر نقطه آن پیدا کردیم. این برای یک پارامتر و ششصد کاندیدا کار کرد، و برای دو پارامتر با 401,301 کاندیدا در یک‌پنجم ثانیه دوام آورد. سه پارامتر با همان تفکیک می‌شود 201,051,801 کاندیدا و دیگر در یک آرایه جا نمی‌گیرد؛ یک شبکه کوچک در فصل 5 هزاران پارامتر دارد، و مدل‌هایی که فصل 10 برایشان قیمت می‌گذارد میلیاردها پارامتر دارند. Brute force اینجا کند نیست، از نظر حسابی ناممکن است، و هیچ‌چیز در این فصل جایگزینی پیشنهاد نمی‌کند.

با این حال به دره نگاه کنید. وقتی در θ=0.20\theta = 0.20 با زیان 0.0822 ایستاده‌اید، جهت «سراشیبی» راز نیست — روی صفحه می‌بینید، منحنی به سمت راست پایین می‌رود. اگر می‌توانستید از تابع زیان بپرسید در نقطه‌ای که ایستاده‌اید به کدام سمت شیب دارد، بدون اینکه آن را جای دیگری ارزیابی کنید، می‌توانستید یک قدم به همان سمت بردارید، دوباره بپرسید، و آن‌قدر تکرار کنید تا زمین صاف شود.

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

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


خواندن این‌ها نیز در کنار این فصل ارزش دارد: Prince, Understanding Deep Learning §5.1–5.2 و Appendix C، که هر زیان کتاب را از درست‌نمایی بیشینه با همین ترتیبی که اینجا به کار رفته می‌سازد؛ Goodfellow, Bengio and Courville, Deep Learning §3.1–3.11 و §5.5، که بخش درست‌نمایی بیشینه‌اش واگرایی KL موردنیاز فصل 4 را هم استخراج می‌کند؛ Murphy, Probabilistic Machine Learning: An Introduction chapter 2 و §4.2، درباره اینکه درست‌نمایی بیشینه چه چیزی را تضمین می‌کند و چه چیزی را نه؛ Deisenroth, Faisal and Ong, Mathematics for Machine Learning §6.1–6.4 برای قاعده جمع، قاعده ضرب و قاعده بیز به‌صورت درست‌وحسابی؛ یادداشت کوتاه CMU از Tom Mitchell با عنوان Estimating Probabilities: MLE and MAP (2016)؛ و §22.7 از Dive into Deep Learning، که به همان نتیجه در کد قابل اجرا می‌رسد.

  1. Fisher, R. A. On the mathematical foundations of theoretical statistics. Philosophical Transactions of the Royal Society A 222, pp. 309–368 (1922). جایی که درست‌نمایی به‌عنوان روشی عمومی، همراه با «parameter»، «statistic»، کفایت و کارایی، صورت‌بندی می‌شود. خود نام‌گذاری، و جداسازی آن از احتمال، یک سال زودتر است: Fisher, R. A., On the «probable error» of a coefficient of correlation deduced from a small sample, Metron 1, pp. 3–32 (1921), pp. 24–25.

  2. IEEE Standard for Floating-Point Arithmetic, IEEE 754-2019. binary32 و binary16 را تعریف می‌کند، و قواعد گردکردنی را که باعث می‌شوند آزمایش جمع همان‌طور که دیدیم بیرون بیاید.

  3. Kalamkar, D. et al. A Study of BFLOAT16 for Deep Learning Training. arXiv:1905.12322 (2019). پارامترهای قالب، و استدلال برای معاوضه بیت‌های مانتیسا با بیت‌های نما.

  4. Micikevicius, P. et al. Mixed Precision Training. ICLR 2018, arXiv:1710.03740. Loss scaling، و اندازه‌های gradient اندازه‌گیری‌شده‌ای که آن را در float16 ضروری می‌کنند.

  5. Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1), pp. 5–48 (1991). هنوز بهترین توضیح واحد برای اینکه چرا دو ترتیب جمع با هم اختلاف دارند.

  6. Kahan, W. Pracniques: further remarks on reducing truncation errors. Communications of the ACM 8(1), p. 40 (1965). جمع جبرانی در نیم صفحه.

  7. Huber, P. J. Robust estimation of a location parameter. The Annals of Mathematical Statistics 35(1), pp. 73–101 (1964). زیانی که نزدیک صفر درجه‌دوم و در دُم‌ها خطی است، استخراج‌شده نه وصله‌خورده.

  8. Tukey, J. W. A survey of sampling from contaminated distributions, in Contributions to Probability and Statistics (Stanford University Press, 1960), pp. 448–485.

  9. Legendre, A. M. Nouvelles méthodes pour la détermination des orbites des comètes (Paris, 1805), appendix Sur la méthode des moindres quarrés. نخستین انتشار کمترین مربعات، به‌عنوان ابزار محاسباتی.

  10. Gauss, C. F. Theoria Motus Corporum Coelestium (Hamburg, 1809), Book II, §§175–179. استدلال از میانگین حسابی به قانون خطای نرمال، و از آنجا به کمترین مربعات.


تهیه‌شده توسط

David Vicente Campos

بنیان‌گذار NeuraLIA Labs و هم‌بنیان‌گذار MyRealFood

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

بیشتر درباره نویسنده

منتشرشده توسط NeuraLIA Labs.

پست‌های جدید را در ایمیل خود دریافت کنید

اخبار AI، راهنماها و به‌روزرسانی‌های محصول — هر وقت چیزی ارزشمند منتشر کنیم، یک ایمیل کوتاه می‌فرستیم.

فهرست دوره

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev12 دقیقه مطالعه

مدل هوش مصنوعی Jev برای تصمیم ساخته شده، نه نثر

Jev از TypeSafe AI توجه‌ها را جلب کرده چون هوشمندی نرم‌افزار را مسئله‌ای احتمالاتی می‌بیند: شاخه درست را انتخاب کنید، میزان اطمینان را کنار آن بگذارید، و وقتی کد به یک تصمیم نیاز دارد برای نوشتن متن به یک LLM پول ندهید.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering13 دقیقه مطالعه

مهندسی کانتکست برای عامل‌های AI بلندافق

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

آماده‌اید انتخاب مدل را به LIA بسپارید؟

با همه مدل‌های هوش مصنوعی در یک جا بسازید — همین امروز رایگان شروع کنید.