Перейти до вмісту
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}")               

Величина у виділених рядках — це залишок: що сказала лінія мінус що сказав штангенциркуль, по одному числу на показ. Кожне правило оцінювання в цьому розділі й кожна функція втрат у двадцяти восьми розділах після нього — це певний спосіб стиснути список залишків до одного числа. Вони різняться лише тим, як саме стискають.

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 ішов зліва направо:

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)

Тепер зробіть те, чого 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 справляється без скарг. За домовленістю ми беремо від’ємну лог-правдоподібність, щоб краще означало менше. Тепер підставте Gaussian density і подивіться, що станеться.

  1. Почніть із добутку. Правдоподібність — це L(θ)=i=1Np(yiθxi)\mathcal{L}(\theta) = \prod_{i=1}^{N} p(y_i - \theta x_i), де pp — Gaussian density вище.

  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. Те саме дно тієї самої долини, до останньої точки сітки.

Показати повне виведення

Які саме відкидання безпечні? Та сама операція з’являється в кожному розділі, де виводять loss, і вона не завжди невинна.

Відкинути додаткову константу безпечно тоді, коли вона не залежить від параметра, який ви оптимізуєте; відкинути додатний множник безпечно, бо 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, щоб отримати середню квадратичну помилку, а не суму, невидиме для відповіді й дуже помітне для training run — із сумою подвоєння розміру batch подвоює кожен ваш крок.

Ми зафіксували σ\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 оцінкою дисперсії шуму — число, яке ви весь час мінімізували, було оцінкою того, наскільки шумить ваш sensor.

Одна складність, яку дешево сформулювати й дорого перевідкривати пізніше: ця оцінка зміщена вниз, бо залишки вимірювалися відносно fit, який сам був вибраний так, щоб зробити їх малими. Просимулюйте це — двісті тисяч блокнотів по двадцять показів кожен, взятих із розподілу, істинна дисперсія якого рівно 1, з одним параметром fit, оціненим із самих показів. Ділення суми квадратів на NN дає середнє 0,9501; ділення на N1N-1 дає 1,0001; а (N1)/N(N-1)/N дорівнює 0,95 рівно. Кожен параметр, який ви fit, коштує один ступінь свободи, і це найменший видимий приклад значно більшої проблеми: модель завжди виглядає кращою на даних, до яких її fit. Розділ 4 перетворює це на дисципліну відкладання даних убік, а розділ 6 дає цьому ефекту назву.

Якщо квадратична помилка стверджує, що шум Gaussian, наступне питання — що відбувається, коли це твердження хибне. Не трохи хибне — хибне так, як хибними бувають реальні вимірювання.

У цеху більшість показів штангенциркуля точні до десятої частки міліметра, а раз чи двічі за зміну під губку потрапляє стружка, і показ помиляється на кілька міліметрів. Такі помилки важкохвості: здебільшого малі, іноді величезні, і величезні значно частіше, ніж дозволяє дзвоноподібна крива. Розподіл Коші — стандартна чиста модель такої поведінки, і його щільність така ж проста, як у 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. Fit двічі: один раз мінімізуючи квадрати залишків, інший — мінімізуючи від’ємну лог-правдоподібність шуму, який фактично згенерував дані. Прийом із центруванням тут не допомагає — він фіксує перетин лише для квадратичної помилки — тож обидва fits виконуються грубою силою по сітці перетинів і нахилів, бо ми досі не маємо способу знайти дно долини, окрім як відвідати його.

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")

Два виділені рядки — вся різниця між fits. Візьміть log щільності 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. Діючи за таким fit, завод запускає прес ще на вісім годин, виготовляючи деталі за межами допуску, на підставі найстандартнішої функції втрат у галузі. Cauchy fit, використовуючи ті самі двадцять показів, ту саму сітку й різницю в один рядок коду, потрапляє в годину 11,8.

Два заперечення заслуговують на відповіді, бо обидва — перше, що скаже хороший інженер.

Викид очевидний — просто видаліть його. Можете, це допомагає, і цього недостатньо. Видалення одного найгіршого показу зсуває нахил найменших квадратів із 0,108 до 0,239, що все ще ставить заміну леза на годину 13,1, на півтори години запізно; видалення найгіршого, повторний fit і видалення того, що найгірше тепер, приводить до 0,286 — і зверніть увагу, це вже процедура, а не спостереження: видаліть натомість два найбільші залишки початкового fit — і ви опинитеся на 0,223. Але ви вже зробили судження, які не можете записати чи захистити, і автоматизація правила не рятує: drop-the-largest-residual-then-refit, запущений на тисячі змодельованих змін, має медіанну помилку нахилу 0,0177 проти 0,0100 у likelihood fit, і відхиляється більш ніж на 0,05 у 14,7% змін проти 1,3%. Видалення — це латка поверх неправильного припущення. Likelihood не потребує латки, бо ніколи не припускав, що викид неможливий.

Ви вибрали вдалий набір даних. Це заперечення абсолютно правильне, саме тому останній експеримент симулює тисячу незалежних змін і 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

Медіана, не середнє, з тієї самої причини, що й усе інше в цьому розділі: помилки найменших квадратів керуються Cauchy, тож їхнє середнє не є стабільною величиною для звіту. Найменші квадрати сильно помиляються у двох змінах із п’яти; likelihood fit сильно помиляється в одній зміні із сімдесяти семи, а його найгірший провал серед тисячі змін менший ніж п’ята частина найгіршого провалу найменших квадратів.

Ніщо з цього не робить квадратичну помилку поганою. Це робить її специфічною, і арифметика точно каже чому. Візьміть залишок 0,1 мм і залишок 7 мм. У квадраті поганий показ дає внесок у 4 900 разів більший за хороший, тож лінію силоміць тягне до нього; за лог-правдоподібністю Cauchy ті самі два залишки дають 0,527 і 8,133, співвідношення 15,4. Поганий показ усе ще рахується, просто він не отримує права вирішувати. Це початок робастної статистики, де loss Г’юбера 1964 року розділяє різницю, поводячись квадратично для малих залишків і лінійно для великих,7 і де Тьюкі вже показав, як мало забруднення потрібно, щоб вибіркова дисперсія стала гіршим інструментом, ніж середнє абсолютне відхилення.8

Одна історична примітка, надто добра, щоб її пропустити. Найменші квадрати вперше опублікував Лежандр у 1805 році як зручний алгебраїчний прийом без жодного обґрунтування, крім того, що він працював.9 Чотири роки потому Гаусс пройшов аргумент у зворотному напрямку: він узяв як даність, що арифметичне середнє — правильний спосіб поєднувати повторні вимірювання, запитав, який розподіл помилок робить середнє найімовірнішим значенням, і показав, що по суті це робить лише один — той, який тепер названо на його честь.10 Виведення в цьому розділі — його, йому понад два століття, і це досі та частина, яку більшість курсів пропускає.

Що ви тепер можете сказати, а чого досі не можете зробити

Посилання на розділ: Що ви тепер можете сказати, а чого досі не можете зробити

Засвоєно. Функція втрат — це правило оцінювання, і ранжування, яке воно дає, є властивістю правила, а не кандидатів. Кожен loss у цьому курсі — від’ємна лог-правдоподібність певного припущення про шум, із викинутими константами: Gaussian дає тут квадратичну помилку, Bernoulli дає cross-entropy в розділі 4, а categorical distribution над словником дає next-token loss у розділі 8. Рецепт ніколи не змінюється: сформулюйте шум, запишіть правдоподібність, візьміть мінус log. І коли припущення хибне, модель не просто неточна — вона помиляється в напрямку, який можна передбачити.

Досі бракує. Ми знайшли дно долини, відвідавши кожну її точку. Це спрацювало для одного параметра й шістсот кандидатів, і пережило два параметри на 401 301 кандидаті за п’яту частку секунди. Три параметри з тією самою роздільністю — це 201 051 801 кандидат, і це вже не вміщується в один масив; маленька мережа в розділі 5 має тисячі параметрів, а моделі, яким розділ 10 виставляє ціну, мають мільярди. Груба сила тут не повільна — вона арифметично неможлива, і ніщо в цьому розділі не пропонує альтернативи.

Погляньте, однак, назад на долину. Стоячи в θ=0.20\theta = 0.20 із loss 0,0822, напрямок «вниз» не є загадкою — ви бачите його на сторінці, крива спадає вправо. Якби ви могли запитати функцію втрат, у який бік вона нахилена в точці, де ви стоїте, не обчислюючи її більше ніде, ви могли б зробити крок туди, запитати знову й повторювати, доки ґрунт не стане пласким.

Це питання має назву. Нахил функції в точці — її похідна, а для функції багатьох параметрів сукупність нахилів у всіх напрямках одразу — це gradient. Розділ 1 не міг ним скористатися, бо помилка perceptron була сходами без нахилу, про який можна спитати. Цей розділ збудував щось краще: loss, що всюди гладкий і походить із явно сформульованого припущення, а не з уподобання.

Тож питання для розділу 3 уже не в тому, чи існує нахил. Воно в тому, як його обчислити, чому рух проти нього веде вниз, а не вгору — знак, який майже кожен курс просить прийняти на віру, — і наскільки далеко ступати перед наступним запитом; це, як виявиться, одне число, яке вирішує, чи training run збігається, вічно коливається навколо відповіді, чи тікає в нескінченність.


Також варто читати поруч із цим розділом: 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, де sum rule, product rule і Bayes' rule зроблені як слід; коротку нотатку 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 і виміряні величини gradients, які роблять його потрібним у 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.
jev10 хв читання

AI-модель Jev створена для рішень, а не прози

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

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering12 хв читання

Context engineering for long-horizon AI agents

Long-running agents do not fail only because the window is small. They fail when files, tool outputs and stale history crowd out the task the agent was supposed to finish.

Готові довірити вибір моделі LIA?

Створюйте з усіма моделями ШІ в одному місці — почніть безкоштовно вже сьогодні.