Машинне навчання · Блок 4 · Тема 16

Спуск у долину

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

У попередній темі ми провели пряму крізь хмару точок. Провели чесно: записали суму квадратів залишків, узяли похідну, прирівняли до нуля й отримали готову формулу. Підставив дані — маєш відповідь. Один рядок коду, жодних налаштувань, точний результат.

Ця розкіш закінчується майже одразу. Варто замінити пряму на щось трохи складніше — і рівняння, яке треба розв'язати, перестає розв'язуватися. Не «важко розв'язується», а не має запису через звичні операції взагалі. Тоді лишається інший шлях: не шукати відповідь одним обчисленням, а підповзати до неї. Саме цей спосіб — градієнтний спуск — і навчає сьогодні майже все, що називають машинним навчанням.

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

01 / ПостановкаКоли формули просто немає

Модель, яку ми підбираємо, максимально проста — два числа:

ціна = w · (рік − 2018) + b

Словами: ціна телефона складається з базової вартості плюс надбавка за кожен рік свіжості. Символ w (вага, weight) — на скільки тисяч гривень дорожчає телефон за один рік новизни. Символ b (зсув, bias) — скільки коштує найстаріший телефон у вибірці, тобто випуску 2018 року. Віднімаємо 2018, щоб b мав зрозумілий сенс, а не був ціною телефона нульового року. Ціни всюди рахуємо в тисячах гривень.

Для такої моделі формула є — ми вивели її минулого разу. Тож навіщо тоді спуск? Бо формула — це виняток, а не правило. Ось три причини, з яких вона зникає:

Тому далі ми свідомо забудемо, що формула існує, і знайдемо ті самі w і b наосліп. Наприкінці порівняємо — і числа мають зійтися.

02 / ЛандшафтФункція втрат як ландшафт

Щоб рухатись до відповіді, спершу треба вміти сказати, наскільки поточна відповідь погана. Беремо середній квадрат помилки:

L(w, b) = ⅟n Σ (yi − (w·xi + b))2

Читається так: для кожного оголошення візьми справжню ціну, відніми ту, яку прогнозує модель, піднеси різницю до квадрата — і всі ці квадрати усередни. Тут x_i — вік телефона в роках від 2018, y_i — справжня ціна з оголошення, n — кількість оголошень (у нас 700), а Σ означає «склади по всіх оголошеннях». Навіщо вона тут: це єдине число, яким ми міряємо якість пари (w, b), — і саме його зменшуватимемо.

Метрика чи функція втрат. Той самий MSE ти вже бачив у темі про метрики регресії — там він був метрикою: числом для звіту, яке рахують один раз наприкінці, щоб сказати людині, наскільки модель добра. Тут той самий MSE працює функцією втрат: величиною, яку алгоритм рахує тисячі разів під час навчання і від якої бере похідну. Різниця не у формулі, а в ролі. Метрику обирають за зрозумілістю людині — тому в звіті частіше пишуть RMSE або MAE. Функцію втрат обирають за зручністю для оптимізації — щоб вона була гладкою й мала похідну скрізь. Іноді це те саме число, іноді ні: частку правильних відповідей зручно показати замовнику, але оптимізувати її неможливо, бо вона стрибками змінюється.

Тепер найважливіший зсув у голові. L(w, b) — це не одне число, а ціле поле чисел. Кожній парі (w, b) відповідає своя величина втрати. Якщо відкласти w по горизонталі, b — по вертикалі, а втрату показати кольором, вийде карта місцевості: пляма в центрі — дно долини, а замкнені криві навколо неї — лінії однакової втрати.

Ось ця карта для наших семисот оголошень. Точки на ній підписані реальними значеннями втрати:

Схема 1 · Ландшафт втрат

Кожна точка карти — своя пара (w, b). Колір — величина втрати L. Хрест позначає найнижчу точку.

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

Тепер задача навчання формулюється однією фразою: знайти найнижчу точку цього ландшафту. І тут головна складність. Ми не бачимо карти. Карту намалював комп'ютер, перебравши сотні тисяч пар (w, b), — для двох параметрів це можливо, для мільйона ні. Уяви натомість, що тебе висадили десь на цьому схилі в густому тумані. Видимість — метр. Ти не знаєш ні де дно, ні в який бік воно. Але одне ти знаєш точно: у який бік нахилена земля просто під ногами. І цього виявляється досить.

03 / ПохіднаПохідна — це напрямок схилу

Похідна має репутацію страшної речі з підручника. Насправді вона відповідає на дуже побутове питання:

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

Ось і все. Число, яке відповідає на це питання, і називається похідною. Знак каже напрямок: додатна — праворуч угору, там гірше; від'ємна — праворуч униз, там краще. Величина каже крутизну: похідна −12 означає значно крутіший схил, ніж похідна −0.5.

Перевіримо це буквально, на калькуляторі. Щоб числа можна було звірити руками, візьмемо шість оголошень — по одному типовому на кожен рік випуску — і тимчасово зафіксуємо b = 4.2. Тоді змінна лишиться одна: w.

рік випускуx = рік − 2018ціна y, тис. грн
201804.2
201915.9
202027.1
202139.7
2022411.0
2023512.4

Рахуємо похідну чисельно

Візьмемо w = 1.0 — тобто припущення «телефон дорожчає на тисячу гривень за рік». Втрата виходить

L(1.000) = 4.271667

Тепер зробимо той самий «крихітний крок праворуч»: збільшимо w на 0.001 і перерахуємо втрату наново:

L(1.001) = 4.259276

Втрата впала. Отже, рухатись праворуч вигідно. Наскільки різко — покаже відношення зміни втрати до зміни w:

(4.259276 − 4.271667) / 0.001 = −12.3908

Це число і є похідною, порахованою чисельно. Ніякої магії: два обчислення функції й одне ділення. Мінус означає «праворуч униз».

Рахуємо похідну формулою

Тепер виведемо формулу й переконаємось, що вона дає те саме число. Виведення — чотири кроки, кожен з однією думкою.

крок 1 · дивимось на одне оголошення

Втрата — це середнє з окремих доданків. Візьмемо один. Позначимо помилку (залишок) на i-му оголошенні:

ei = yi − (w·xi + b)     доданок = ei2
крок 2 · це функція у функції

Усередині — лінійна залежність від w, зовні — квадрат. Похідна такої конструкції: похідна зовнішньої, помножена на похідну внутрішньої. Похідна квадрата — подвоєний аргумент, а e_i при зростанні w зменшується зі швидкістю x_i:

∂ei2 / ∂w = 2·ei · (−xi) = −2·xi·ei
крок 3 · збираємо назад по всіх

Похідна суми дорівнює сумі похідних, а сталий множник 1/n просто виноситься. Отже:

∂L / ∂w = −⅟n · 2 · Σ xi·ei

Словами: помнож кожну помилку на вік телефона, склади все, поділи на кількість оголошень і візьми з мінусом та подвій. Множник x_i — це вага голосу: старий телефон (великий x) сильніше тягне w на себе, бо на ньому нахил прямої проявляється помітніше. Об'єкт із x = 0 не впливає на w взагалі.

крок 4 · те саме для зсуву

Для b викладки ті самі, тільки внутрішня похідна дорівнює −1 замість −x_i, бо b входить у прогноз з коефіцієнтом одиниця:

∂L / ∂b = −⅟n · 2 · Σ ei

Пара цих двох чисел разом і називається градієнтом (gradient). Градієнт — це не нове поняття, а просто список усіх похідних одразу: по одній на кожен параметр.

Підставимо числа. При w = 1.0 і b = 4.2 прогноз і помилки такі:

xyпрогноз w·x + bпомилка ex · e
04.24.20.00.0
15.95.2+0.7+0.7
27.16.2+0.9+1.8
39.77.2+2.5+7.5
411.08.2+2.8+11.2
512.49.2+3.2+16.0
сума+10.1+37.2

Тепер за формулою:

∂L / ∂w = −(2 / 6) · 37.2 = −12.4000      ∂L / ∂b = −(2 / 6) · 10.1 = −3.3667

Порівняй: чисельно вийшло −12.3908, за формулою — −12.4000. Різниця в третьому знаку, і вона повністю пояснюється тим, що крок 0.001 усе-таки не нульовий: чисельна оцінка міряє нахил січної, а формула — нахил дотичної. Зменш крок до 0.000001 — розбіжність зникне.

Що з цього забрати. Похідна — це виміряна величина, а не заклинання. Її можна отримати двома незалежними способами, і вони дають те саме число. Коли пізніше бібліотека сама рахуватиме градієнти для мільйона параметрів, за цим стоятиме рівно та сама операція, тільки повторена мільйон разів.

А ось як це виглядає. Крива нижче — втрата L(w) при фіксованому b = 4.2. Пряма, що торкається кривої, — та сама дотична, нахил якої ми щойно рахували. Постав початкову точку й тисни «зробити крок»:

Інтерактив 1 · Кулька котиться схилом

Шість оголошень, b = 4.2, швидкість навчання зафіксована на 0.02. Кожен клік — одна ітерація.

поточне w
втрата L
похідна
крок
Що видно: нахил дотичної підписаний числом просто на картинці. Що далі від дна, то крутіша дотична й то довший крок. Біля дна дотична стає майже горизонтальною, похідна прямує до нуля — і кроки самі собою вкорочуються. Зупиняти спуск руками не треба.

04 / ПравилоКрок за кроком, руками

Правило оновлення — один рядок, і в ньому вся суть методу:

w ← w − η · ∂L/∂w      b ← b − η · ∂L/∂b

Стрілка означає «стало новим значенням». Символ η (грецька «ета») — швидкість навчання (learning rate): додатне число, яке ми обираємо самі й яке каже, наскільки великий крок робити. Похідна дає лише напрямок і крутизну, а довжину кроку задає η.

Чому мінус. Похідна показує напрямок зростання втрати. Нам треба навпаки — тому йдемо в протилежний бік. Мінус розвертає напрямок. Перевір на нашому числі: похідна дорівнює −12.4, отже w − η·(−12.4) = w + η·12.4 — тобто w зростає. Саме те, що треба: втрата ж падала праворуч. Якби похідна була додатною, той самий мінус потягнув би w ліворуч.

Прокрутімо чотири ітерації руками. Швидкість навчання η = 0.02, старт w = 1.0, зсув усе ще зафіксований на 4.2:

ітераціяwвтрата L(w)похіднакрок −η·похіднанове w
01.00004.2717−12.4000+0.24801.2480
11.24801.7603−7.8533+0.15711.4051
21.40510.7529−4.9738+0.09951.5045
31.50450.3488−3.1501+0.06301.5675
41.56750.1868−1.9950+0.03991.6074

Прочитай цю таблицю по стовпцях — вона показує всю поведінку методу одразу. Втрата падає: 4.27 → 1.76 → 0.75 → 0.35 → 0.19. Похідна слабшає за модулем: −12.4 → −7.85 → −4.97 → −3.15 → −2.00. І тому крок сам собою коротшає: 0.248 → 0.157 → 0.100 → 0.063 → 0.040. Ніхто цього не програмував — так влаштований схил: біля дна він пологіший.

Якщо продовжити, w зупиниться на 1.6764 із втратою 0.0782. Точнісінько те саме значення дає формула найменших квадратів для цих шести оголошень при тому самому зафіксованому b = 4.2: w = Σx·(y − b) / Σx² = 92.2 / 55 = 1.6764. Спуск не наблизився до відповіді — він у неї прийшов.

Головна пастка новачка. Обидва параметри оновлюються з тих самих старих значень. Спершу порахуй обидві похідні, і лише потім змінюй w і b. Якщо оновити w, а похідну по b рахувати вже з новим w, це буде інший алгоритм — іноді він працює, іноді розходиться, і зловити таку помилку в коді дуже важко, бо помилки вона не викидає.

05 / КрокШвидкість навчання

Швидкість навчання — найважливіший параметр, який ти задаєш руками. Похідна не має одиниць вимірювання, зручних для кроку: вона каже «схил такий-то», а на скільки саме зсунутись — вирішуєш ти. І тут рівно дві катастрофи, між якими треба протиснутись.

Замала. Крок мікроскопічний, кожна ітерація зсуває w на волосину. Метод працює правильно, просто повзе. При η = 0.005 наші шість оголошень навіть за 30 ітерацій дають w = 1.639 — до відповіді 1.676 ще далеко. На реальній задачі це означає години обчислень замість хвилин.

Завелика. Крок перестрибує дно й опиняється на протилежному схилі — причому вище, ніж був. Наступна похідна там більша, крок довший, і система вибухає. При η = 0.12 той самий спуск за 30 ітерацій відлітає до w = −159 із втратою в сотні тисяч. Класична ознака в логах — втрата, що росте, а потім перетворюється на NaN.

Між ними є вікно. Для наших шести оголошень воно закінчується рівно на η = 0.109: до цього значення спуск збігається, після — розлітається. Число не випадкове — воно визначається крутизною чаші. Прожени повзунок від краю до краю й подивись на всі три режими:

Інтерактив 2 · Три долі одного спуску

Ліворуч — траєкторія по кривій втрат. Праворуч — як падає (або росте) втрата з ітераціями; шкала логарифмічна.

кінцеве w
втрата
мінімум
перестрибів
Спробуй три положення: 0.005 — крапки ледь повзуть праворуч, за 40 ітерацій до дна не доходить. 0.05 — три кроки, і все. 0.12 — траєкторія розкидається все ширше, а крива праворуч летить угору. Робоче вікно вужче, ніж здається на око.
Звідки береться межа. Для квадратичної втрати поріг рахується точно: спуск збігається, поки η < 2 / c, де c — крутизна чаші, тобто друга похідна. У нас c = 18.33, звідси 2 / 18.33 = 0.109. Для нейромереж цю крутизну ніхто не знає наперед, і η підбирають експериментом: запускають на кілька сотень кроків із різними значеннями й дивляться, яка крива втрат падає найшвидше й не вибухає. Звідси й практика розкладу швидкості (learning rate schedule): починають із більшого кроку, щоб швидко дістатися потрібної області, і поступово зменшують, щоб акуратно сісти на дно.
Поширена помилка. «Більша швидкість навчання — швидше навчання» неправильно вже на середині діапазону. У нашому інтерактиві η = 0.05 доходить до дна за три кроки, а η = 0.091 — аж за два десятки, бо починає стрибати через дно туди-сюди. При η = 0.105 навіть сорока ітерацій замало. Залежність не монотонна: спершу швидшає, потім різко гіршає, потім вибухає.

06 / МежіЯми, сідла й чесна межа методу

Досі все виглядало надійно: є схил — котимося вниз — приходимо на дно. Так і буде, але тільки для нашої задачі. Функція втрат лінійної регресії опукла (convex): у неї форма чаші, дно рівно одне, і з якої точки не почни — прийдеш у ту саму відповідь. Це рідкісний подарунок, і ним варто скористатися, поки він є.

Для складніших моделей ландшафт зовсім інший. Там трапляються:

Це чесна межа методу: градієнтний спуск не гарантує глобального мінімуму. Він гарантує лише, що ти не підеш угору. Твердження «спуск завжди знаходить найкращу відповідь» правильне для опуклої задачі й неправильне для будь-якої іншої.

Але панікувати не варто. Практика великих моделей показала дивну річ: у просторі з мільйонами вимірів справжні локальні ями майже не трапляються. Щоб точка була локальним мінімумом, схил має йти вгору в усіх мільйонах напрямків одразу — достатньо одного напрямку вниз, і це вже сідло. Тому головна перешкода у великих моделях — не ями, а сідла й плато. Саме проти них працюють прийоми з наступних розділів: шум стохастичного спуску й накопичений момент виштовхують точку з плоских ділянок.

07 / СтохастикаСтохастичний спуск

Повернімось до всіх семисот оголошень і порахуємо ціну одного кроку. Щоб обчислити градієнт чесно, треба пройти всі 700 рядків: для кожного порахувати прогноз, помилку, домножити на вік, скласти. І все це — заради одного оновлення двох чисел. Такий варіант називають повним (batch gradient descent).

Для 700 рядків це дрібниця. Для датасету з мільярда прикладів — катастрофа: один крок триває годину, а кроків треба тисячі.

Ідея стохастичного градієнтного спуску (stochastic gradient descent, SGD) до образливого проста. Точний градієнт нам, власне, не потрібен — потрібен приблизний напрямок униз. То візьмімо не всі 700 оголошень, а одне випадкове, порахуймо градієнт по ньому й одразу зробімо крок. Одне оголошення — погана оцінка загального схилу, вона може вказати геть убік. Але вона у середньому правильна, а коштує в 700 разів дешевше. За той самий обчислювальний бюджет ми зробимо 700 кроків замість одного.

Компроміс між крайнощами — партія (mini-batch): беремо не одне оголошення й не всі, а 32, або 64, або 256. Оцінка градієнта стає точнішою (шум усереднюється), а крок усе одно набагато дешевший за повний. Практично весь сучасний машинний навчальний код працює саме так.

Схема 2 · Скільки прикладів іде на один крок

Дошка з 700 оголошень. Рожевим позначено ті, які алгоритм дивиться, щоб зробити одне оновлення параметрів.

Термін «епоха» (epoch) означає один повний перегляд усіх даних. Повний спуск встигає за епоху зробити 1 крок, партія по 32 — 21 крок, стохастичний — 700 кроків. Обчислень витрачено однаково.

Тепер найцікавіше: що з цього виходить на практиці. Обидві траєкторії нижче стартують з точки (0, 0) з тією самою швидкістю навчання й витрачають однаковий обчислювальний бюджет. Різниця лише в тому, на скількох прикладах рахується кожен крок:

Інтерактив 3 · Стохастичний проти повного

Бірюзова траєкторія — повний градієнт. Рожева — партіями. Однаковий бюджет переглядів, швидкість навчання 0.005 в обох.

кроків повний
кроків партії
втрата повний
втрата партії
Що читати: бірюзова лінія гладка й коротка — кроків мало. При партії 1 рожева смикається, петляє, іноді йде вгору — і все одно опиняється набагато ближче до хреста. Що більша партія, то менше шуму, але й менше кроків: на 256 обидві траєкторії майже зливаються.

Головний висновок звучить контрінтуїтивно: гірші кроки, зроблені частіше, перемагають кращі кроки, зроблені рідко. Точність напрямку окупається гірше, ніж кількість оновлень.

Чого стохастичний спуск не робить. Він не точніший за повний — навпаки, кожен його крок гірший. І в саме дно він не сідає: біля мінімуму шум не зникає, точка вічно тремтить у невеликій області навколо відповіді. Постав розмір партії 1 і повільно потягни повзунок епох: втрата перестає падати й починає гуляти — 7.04 на шостій епосі, 8.80 на десятій, 7.10 на двадцятій. Перемкни на партію 16 — і числа підуть рівно вниз, без стрибків. Лікують це двома способами: збільшують партію під кінець навчання або зменшують швидкість навчання за розкладом. Перевага SGD не в якості кроку, а винятково в їхній кількості за одиницю часу.
Бонус, який не планували. Шум SGD виявився корисним сам по собі. Випадкове смикання не дає точці застрягти на плато й у дрібних ямках — вона просто вискакує звідти. Тому в глибокому навчанні стохастичність вважають не платою за швидкість, а частиною методу.

08 / МоментМомент: чому шар котиться швидше

Подивись ще раз на карту втрат: долина витягнута. Уздовж неї схил пологий, упоперек — крутий. Звичайний спуск у такій долині поводиться безглуздо: він бачить переважно крутий поперечний схил і майже не бачить пологого поздовжнього. Тому він скочується до осі долини, перескакує її, повертається — і повзе вздовж крихітними кроками. Це та сама проблема різних масштабів, про яку йшлося в темі про лінійну регресію.

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

Математично це два рядки замість одного:

v ← β · v + ∂L/∂w      w ← w − η · v

Тут vшвидкість, накопичена величина: пам'ять про те, куди ми рухались досі. Коефіцієнт β (бета) каже, яку частку минулої швидкості зберігати; на практиці беруть від 0.8 до 0.95. Читається так: нова швидкість — це більша частина старої плюс сьогоднішній градієнт; параметр зсуваємо не за градієнтом, а за швидкістю.

Що саме накопичується — важливо зрозуміти точно. Якщо градієнт кілька кроків підряд дивиться в один бік, доданки складаються, і v виростає приблизно в 1/(1−β) разів: уп'ятеро при β = 0.8, удесятеро при β = 0.9. Якщо ж градієнт хитається туди-сюди, сусідні доданки гасять один одного, і v лишається малою. Момент буквально підсилює стабільні напрямки й приглушує метушню.

Схема 3 · Із моментом і без

Та сама карта втрат, той самий старт, та сама швидкість навчання 0.005 і рівно 80 кроків у кожної траєкторії. Коефіцієнт моменту 0.8.

Що видно: обидві траєкторії спершу біжать управо по дну яру. Звичайний спуск (бірюзовий) там і залишається — за 80 кроків він відстає від мінімуму на 2.84. Момент (рожевий) розганяється, перелітає далі, але накопичена швидкість розвертає його вгору в долину: відставання 0.45, ушестеро менше. Чому це не інтерактив: крутити тут нема чого — висновок той самий при будь-яких розумних значеннях коефіцієнта.

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

09 / ДаліКуди це веде далі

Зберемо весь метод у чотири рядки. Це повний цикл навчання будь-якої моделі, від нашої прямої до мовної моделі на сто мільярдів параметрів:

  1. Візьми поточні параметри й порахуй на них втрату.
  2. Порахуй градієнт — по одній похідній на кожен параметр.
  3. Зсунь кожен параметр на −η · його похідну.
  4. Повтори.

Змінюється в цьому циклі напрочуд мало:

темащо змінюєтьсящо лишається
07 · лінійна регресіяє ще й точна формулата сама MSE, той самий цикл
09 · логістичнавтрата стає логарифмічноютой самий цикл, формули вже немає
31 · нейромережіпараметрів мільйонитой самий цикл, градієнт рахує backprop

У наступній темі ми візьмемо ту саму дошку оголошень, але питатимемо не «яка ціна», а «чи це шахрайство». Відповідь стане «так» або «ні», прямої вже не вистачить, і функція втрат зміниться на логарифмічну. А от навчатись модель буде рівно тим самим спуском, який ти щойно прокрутив руками на калькуляторі, — з тим самим правилом w ← w − η · ∂L/∂w.

Ще далі, у темі про нейромережі, параметрів стане мільйони, і з'явиться окремий алгоритм — зворотне поширення помилки — який рахує всі ці похідні за один прохід. Але це буде спосіб дістати градієнт. Що з ним робити далі, ти вже знаєш.

Далі в практиці. У practice.ipynb ти напишеш градієнтний спуск з нуля десятком рядків на NumPy, звіриш знайдені w і b з аналітичною формулою й зі scikit-learn, прожени три швидкості навчання й побачиш три долі, а потім додаси SGD і порахуєш, скільки кроків він встигає зробити.

Далі в темі

Теорію прочитано. Тепер закріпи її на практиці.