Към съдържанието
2/30Глава 2 от 30

Откъде идва една функция на загуба: от правдоподобие, не от конвенция

Три линии през едни и същи 20 измервания и три правила за оценяване с различни победители. Квадратичната грешка е избор.

На тази страница

Острието, което реже детайлите, се износва. За десетчасова смяна то губи достатъчно ръб, така че детайлите слизат от лентата с частица от милиметъра по-широки, отколкото са били в началото, а щом минат 23,5 милиметра, контролът ги отхвърля. Никой в завода не знае кога се случва това. Разполагат само с шублер, тетрадка и двадесет показания от миналия вторник: часовете от смяната на острието и ширината на детайла, измерена в този момент.

Някой прокарва линия през точките. Друг прокарва малко по-различна. Трети чертае трета. И трите изглеждат разумни на хартия и се разминават с няколко часа за момента на смяна на острието — в този завод това е разликата между спокойна седмица и бракувана партида.

Коя линия е по-добра?

Така зададен, този въпрос няма отговор. Не труден отговор — никакъв отговор. „По-добра“ не е свойство на линията така, както е наклонът ѝ; то е свойство на линия заедно с правило за оценяване на линии, и докато някой не запише правилото, няма какво да се изчислява. Тази глава приема това изречение сериозно и завършва с откритието, че най-често срещаното правило в machine learning не е конвенция, а следствие от твърдение за света — твърдение, което можете да проверите и което понякога е невярно.

Едно признание преди първия ред код. Тези двадесет показания не са от истински завод: генерирах ги от линия, която избрах, width=20.00+0.30h\text{width} = 20.00 + 0.30 \cdot h, плюс случаен шум с разсейване около една десета от милиметъра. Това има значение, защото всичко по-долу е за това дали даден метод възстановява истината, а единственият начин да проверим е да знаем истината предварително. И така: 0,30 милиметра на час е отговорът в края на учебника. Нямате право да го използвате, само да сверявате с него.

Ето показанията и трите линии, оценени по три начина: квадратична грешка, към която всички посягат; абсолютна грешка, която би избрал статистик; и най-голяма грешка, която би избрал машинистът, защото инспекторът не се интересува от средната стойност — той отхвърля единичния детайл извън толеранс.

NumPy се появява тук, една глава след чисто Python perceptron, по една причина: до края на тази глава ще оценим четиристотин хиляди кандидат-линии спрямо по двадесет показания всяка, а 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}")               

Величината в подчертаните редове е остатъкът: това, което линията казва, минус това, което шублерът казва, по едно число за всяко показание. Всяко правило за оценяване в тази глава и всяка loss function в двадесет и осемте глави след нея е някакъв начин да се смачка списък от остатъци до едно число. Различават се само по това как го правят.

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: три правила, трима победители, върху едни и същи двадесет точки.

Избрах тези три линии така, че да не са съгласни, и трябва да го кажа ясно. Важното е колко лесно беше — няколко минути търсене сред разумно изглеждащи свободни членове и наклони намира стотици такива тройки. Подредбата е свойство на правилото, което сте избрали, не факт за линиите, така че правилото не е детайл от имплементацията: то е дефиницията на проблема. Това повдига въпроса, заради който съществува тази глава: на какво основание го избирате?

Първо по-малък въпрос, защото няма три линии, а безкрайно много. Засега вземете квадратичната грешка, понеже всички я вземат, и свийте проблема до едно число с трика, който спести на perceptron единадесет хиляди епохи в Глава 1: извадете средната стойност от двете колони. Щом облакът от точки е центриран около началото, най-добрата линия при квадратична грешка минава точно през началото — така свободният член е уреден и остава да се избере само наклонът.

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. Едната е правилна за този завод, а другата е грешна, и нищо казано досега не може да ви каже коя. За да изберете правилото, трябва да знаете нещо за това как показанията са се разминавали с линията, а това е въпрос за света, не за математиката. За да му отговорим, ни трябва малко механика.

Ето твърдението, което превръща „коя линия е по-добра“ във въпрос с отговор.

Да приемем, че ширината на детайла е линията плюс случайна грешка, и че тази грешка е изтеглена от Гаусово разпределение — камбановидната крива — със средна стойност нула и стандартно отклонение σ\sigma:

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

Плътността на Гаусовото разпределение е

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

Сега направете нещо, което perceptron не можеше. За даден кандидат-наклон θ\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 вместо по-големи — и същият код връща 0.0. И двата отговора са грешни, в противоположни посоки, нито един не вдига изключение, което можете да хванете, а вторият дори не отпечатва предупреждение.

Няма нищо грешно в математиката. Правдоподобието при тези настройки е напълно добре дефинирано крайно число: естественият му логаритъм е 1400,91, така че самото число е около 1060810^{608}. Проблемът е, че компютърът ви няма това число, и си струва да разберете точно кои числа има, защото това няма да е последният път, когато той ще реши изхода.

Поправката за експлодиращото произведение е обичайната: вземете логаритми. Логаритъмът превръща произведенията в суми, строго нарастващ е, така че не може да премести мястото на максимума, а сума от две хиляди умерени числа е нещо, с което float64 се справя без оплакване. По конвенция вземаме отрицателното log-likelihood, така че по-добро да означава по-малко. Сега заместете Гаусовата плътност и вижте какво се случва.

  1. Започнете от произведението. Правдоподобието е L(θ)=i=1Np(yiθxi)\mathcal{L}(\theta) = \prod_{i=1}^{N} p(y_i - \theta x_i), където pp е Гаусовата плътност по-горе.

  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

което е сумата от квадрати на остатъците — нещото, с което започнахме главата, защото беше първото, за което всеки се сеща.

Това е резултатът, заради който съществува главата, и той заслужава да бъде казан без уговорки: квадратичната грешка не е конвенция. Тя е отрицателното log-likelihood на Гаусово разпределение, с премахнати константи. Минимизирането на квадратичната грешка е точно същото действие като да заявите, че грешките ви са Гаусови, и да попитате кой параметър прави данните ви най-малко изненадващи. Правили сте това твърдение през цялото време; просто не са ви го казвали.

Еквивалентността може да се провери, така че я проверете: сканирайте същите шестстотин и един наклона с пълното отрицателно log-likelihood, с всички константи, и с обикновена квадратична грешка.

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

Различни числа по вертикалната ос, а едното от тях е отрицателно, каквото сума от квадрати никога не е: отрицателно log-likelihood може да падне под нула, защото плътност може да надвиши 1. Същото дъно на същата долина, до последната точка от решетката.

Покажи пълното извеждане

Кои изхвърляния са безопасни точно? Същият ход се появява във всяка глава, която извежда loss, и невинаги е невинен.

Премахването на адитивна константа е безопасно винаги когато тя не зависи от параметъра, който оптимизирате, а премахването на положителна мултипликативна константа е безопасно, защото argminθcf(θ)=argminθf(θ)\arg\min_\theta c\,f(\theta) = \arg\min_\theta f(\theta) за всяко c>0c > 0. И двете се провалят в момента, в който σ\sigma също се fitted: тогава N2log(2πσ2)\frac{N}{2}\log(2\pi\sigma^2) изобщо не е константа, а членът, който спира модела да твърди σ=0\sigma = 0 и безкрайно правдоподобие. Това е точно следващият раздел.

Те се провалят по различен начин и в Глава 3: мултипликативната константа не мести минимума, но мащабира gradient, а gradient се умножава по learning rate. Делението на NN, за да получите средна квадратична грешка вместо сума, е невидимо за отговора и силно видимо за training run — със сумата удвояването на batch size удвоява всяка стъпка, която правите.

Фиксирахме σ\sigma на 0,12 по указ, а никой в завода не знае разсейването на грешката на шублера. Третирайте го като второ неизвестно и оставете maximum likelihood да го реши също. Тук постоянният член, който току-що изхвърлихме, се връща, защото е единственото нещо между модела и твърдение за съвършена точност.

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. Така че средната квадратична грешка не е просто като дисперсия. При този модел тя е maximum-likelihood оценката на дисперсията на шума — числото, което сте минимизирали през цялото време, е било оценка на това колко шумен е сензорът ви.

Една особеност, евтина за казване и скъпа за преоткриване по-късно: тази оценка е изместена надолу, защото остатъците са измерени спрямо fit, който сам е избран така, че да ги направи малки. Симулирайте го — двеста хиляди тетрадки с по двадесет показания всяка, изтеглени от разпределение, чиято истинска дисперсия е точно 1, като единият параметър на fit е оценен от самите показания. Делението на сумата от квадрати на NN дава средно 0,9501; делението на N1N-1 дава 1,0001; а (N1)/N(N-1)/N е точно 0,95. Всеки параметър, който fit-вате, струва една степен на свобода, и това е най-малкият видим пример за много по-голям проблем: моделът винаги изглежда по-добре върху данните, към които е fitted. Глава 4 превръща това в дисциплината да задържаме данни настрана, а Глава 6 дава име на ефекта.

Ако квадратичната грешка твърди, че шумът е Гаусов, следващият въпрос е какво се случва, когато твърдението е невярно. Не леко невярно — невярно по начина, по който реалните измервания са невярни.

В цеха повечето показания на шублера са точни до една десета от милиметъра, а веднъж-два пъти на смяна стружка попада под челюстта и показанието се отклонява с няколко милиметра. Такива грешки са с тежки опашки: малки през повечето време, понякога огромни, и огромни много по-често, отколкото позволява камбановидната крива. Разпределението на Коши е стандартният чист модел на това поведение, а плътността му е толкова проста, колкото и Гаусовата:

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

Разликата е в опашката: Гаусовото разпределение спада като eε2e^{-\varepsilon^2}, брутално бързо, а Коши като 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

Извадковата дисперсия на Гаусовото се установява на 0,0144, което е 0.1220.12^2, и остава там. Тази на Коши се покачва и продължава да се покачва, докато вземате извадки, защото няма към какво да сходим: разпределението на Коши няма дисперсия, нито средна стойност. Квадратичната грешка, чиято работа е да минимизира средно от квадрати, е помолена за величина, която не съществува.

Ето една смяна, в която шублерът е бил подведен. Същите двадесет часа, същото острие, същият drift от 0,30 милиметра на час — само шумът сега е Коши. Fit-нете го два пъти: веднъж чрез минимизиране на квадратичните остатъци, веднъж чрез минимизиране на отрицателното log-likelihood на шума, който реално е генерирал данните. Трикът с центрирането тук не помага — той фиксира свободния член само за квадратична грешка — така че и двата fit-а стават 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")

Двата подчертани реда са цялата разлика между fit-овете. Вземете логаритъма на плътността на Коши, изхвърлете константите точно както преди, и 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. Ако действа според този fit, заводът пуска пресата още осем часа и произвежда детайли извън толеранс, с авторитета на най-стандартната loss function в областта. Fit-ът на Коши, използвайки същите двадесет показания, същата решетка и разлика от един ред в кода, попада на час 11,8.

Две възражения заслужават отговори, защото и двете са първото нещо, което добър инженер казва.

Outlier-ът е очевиден — просто го изтрийте. Можете, помага, и не е достатъчно. Изтриването на единичното най-лошо показание премества наклона на най-малките квадрати от 0,108 на 0,239, което пак поставя смяната на острието в час 13,1, час и половина късно; изтриването на най-лошото, refit, и изтриването на онова, което е най-лошо сега, ви довежда до 0,286 — и забележете, че това вече е процедура, не наблюдение: изтрийте вместо това двата най-големи остатъка от първоначалния fit и стигате до 0,223. Но вече сте направили преценки, които не можете да запишете или защитите, а автоматизирането на правилото не го спасява: „премахни най-големия остатък и после refit“, пуснато върху хиляда симулирани смени, има медианна грешка на наклона 0,0177 срещу 0,0100 за likelihood fit-а, и е далеч с повече от 0,05 в 14,7% от смените срещу 1,3%. Изтриването е кръпка върху грешно допускане. Правдоподобието не се нуждае от кръпка, защото никога не е приемало, че outlier-ът е невъзможен.

Избрали сте късметлийски dataset. Това възражение е точно правилно, затова последният експеримент симулира хиляда независими смени и refit-ва по двата начина върху всяка.

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

Медиана, не средна стойност, по същата причина като всичко останало в този раздел: грешките на най-малките квадрати се движат от Коши, така че средната им стойност не е стабилно нещо за докладване. Най-малките квадрати са сериозно грешни в две смени от пет; likelihood fit-ът е сериозно грешен в една смяна от седемдесет и седем, а най-лошият му провал в хиляда смени е по-малко от една пета от най-лошия на най-малките квадрати.

Нищо от това не прави квадратичната грешка лоша. Прави я специфична, а аритметиката казва точно защо. Вземете остатък от 0,1 mm и един от 7 mm. Повдигнато на квадрат, лошото показание допринася 4 900 пъти повече към общото от доброто, така че линията се влачи цялата към него; при log-likelihood на Коши същите два остатъка допринасят 0,527 и 8,133, отношение 15,4. Лошото показание все още се брои, просто не получава право да решава. Това е началото на robust statistics, където loss на Хубер от 1964 г. разделя разликата, като се държи квадратично за малки остатъци и линейно за големи,7 и където Тюки вече е показал колко малко замърсяване е нужно, за да направи извадковата дисперсия по-лош инструмент от средното абсолютно отклонение.8

И една историческа бележка, твърде добра, за да се пропусне. Най-малките квадрати са публикувани първи от Лежандр през 1805 г. като удобно алгебрично средство без оправдание отвъд това, че работи.9 Четири години по-късно Гаус обръща аргумента: приема за дадено, че аритметичната средна е правилният начин да се комбинират повторни измервания, пита кое разпределение на грешките прави средната най-вероятната стойност и показва, че по същество само едно го прави — това, което сега носи неговото име.10 Изводът в тази глава е негов, на повече от два века е, и все още е частта, която повечето курсове пропускат.

Какво вече можете да кажете и какво още не можете да правите

Връзка към раздела: Какво вече можете да кажете и какво още не можете да правите

Заслужено. Loss function е правило за оценяване, а подредбата, която произвежда, е свойство на правилото, не на кандидатите. Всеки loss в този курс е отрицателното log-likelihood на някакво допускане за шума, с изхвърлени константи — Гаусовото дава квадратична грешка тук, Бернули дава cross-entropy в Глава 4, а категориално разпределение върху речник дава next-token loss в Глава 8. Рецептата никога не се променя: заявете шума, напишете правдоподобието, вземете минус логаритъма. И когато допускането е грешно, моделът не е просто неточен, а грешен в посока, която можете да предвидите.

Все още липсва. Намерихме дъното на долината, като посетихме всяка точка в нея. Това работеше за един параметър и шестстотин кандидата и оцеля при два параметъра с 401 301 кандидата за една пета от секундата. Три параметъра със същата резолюция са 201 051 801 кандидата и вече не се побират в един масив; малка мрежа в Глава 5 има хиляди параметри, а моделите, на които Глава 10 слага цена, имат милиарди. Brute force тук не е бавен, той е аритметично невъзможен, и нищо в тази глава не подсказва алтернатива.

Но погледнете отново долината. Ако стоите при θ=0.20\theta = 0.20 със loss 0,0822, посоката „надолу“ не е загадка — вижда се на страницата, кривата се спуска надясно. Ако можехте да попитате loss function накъде се накланя в точката, в която стоите, без да я оценявате никъде другаде, бихте могли да направите стъпка натам, да попитате отново и да повтаряте, докато земята стане равна.

Този въпрос има име. Наклонът на функция в точка е нейната производна, а за функция на много параметри съвкупността от наклони във всички посоки наведнъж е gradient. Глава 1 не можеше да използва такъв, защото грешката на perceptron беше стълбище без наклон, за който да попитаме. Тази глава изгради нещо по-добро: loss, който е гладък навсякъде и който идва от заявено допускане, а не от предпочитание.

Така въпросът за Глава 3 вече не е дали съществува наклон. Той е как да го изчислим, защо движението срещу него слиза надолу, а не нагоре — знак, който почти всеки курс ви кара да приемете на доверие — и колко далеч да стъпим, преди да попитаме отново, което се оказва единственото число, решаващо дали training run ще converges, ще осцилира около отговора завинаги или ще избяга към безкрайност.


Също си струва да четете успоредно с тази глава: Prince, Understanding Deep Learning §5.1–5.2 and Appendix C, където всеки loss в книгата се изгражда от maximum likelihood в използвания тук ред; Goodfellow, Bengio and Courville, Deep Learning §3.1–3.11 and §5.5, чийто раздел за maximum-likelihood извежда и KL divergence, нужен на Глава 4; Murphy, Probabilistic Machine Learning: An Introduction chapter 2 and §4.2, за това какво maximum likelihood гарантира и какво не; Deisenroth, Faisal and Ong, Mathematics for Machine Learning §6.1–6.4 за правилото за сума, правилото за произведение и правилото на Бейс, направени както трябва; кратката бележка на Tom Mitchell от CMU 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). Където likelihood е изложено като общ метод, заедно с „parameter“, „statistic“, достатъчност и ефективност. Самото именуване и отделянето от probability е една година по-рано: 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). Loss, който е квадратичен близо до нула и линеен в опашките, изведен, а не закърпен.

  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, където като CTO създадох приложението, което милиони хора са използвали, за да се хранят по-здравословно, и основах NeuraLIA Labs, където изграждам AI продукти. Тук пиша за това, което трябваше да разбера по пътя, така, както ми се иска някой да ми го беше обяснил.

Още за автора

Публикувано от NeuraLIA Labs.

Получавайте нови публикации във входящата си поща

Новини за AI, ръководства и продуктови обновления — кратък имейл, когато публикуваме нещо, което си заслужава.

Индекс на курса

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev12 мин четене

AI моделът Jev е създаден за решения, не за проза

Jev на TypeSafe AI привлича внимание, защото разглежда софтуерната интелигентност като проблем на вероятностите: изберете правилния клон, добавете увереност и не плащайте на LLM да пише текст, когато кодът има нужда от решение.

Abstract legal research workspace with documents, search nodes and governance controls.
openai11 мин четене

Astra for Law на OpenAI е правна AI система, не нов модел

Правният старт на OpenAI е не толкова за нов базов модел, колкото за системата около него: домейн извличане, надеждни инструменти, права, бенчмаркове и пътища за преглед.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering12 мин четене

Инженеринг на контекста за AI агенти с дълъг хоризонт

Дълго работещите агенти не се провалят само защото прозорецът е малък. Те се провалят, когато файлове, изходи от инструменти и остаряла история изтласкат задачата, която агентът е трябвало да завърши.

Готови ли сте LIA да избира вместо вас?

Създавайте с всички AI модели на едно място — започнете безплатно още днес.