У попередній темі ми провели пряму крізь хмару точок. Провели чесно: записали суму квадратів залишків, узяли похідну, прирівняли до нуля й отримали готову формулу. Підставив дані — маєш відповідь. Один рядок коду, жодних налаштувань, точний результат.
Ця розкіш закінчується майже одразу. Варто замінити пряму на щось трохи складніше — і рівняння, яке треба розв'язати, перестає розв'язуватися. Не «важко розв'язується», а не має запису через звичні операції взагалі. Тоді лишається інший шлях: не шукати відповідь одним обчисленням, а підповзати до неї. Саме цей спосіб — градієнтний спуск — і навчає сьогодні майже все, що називають машинним навчанням.
Розбиратися будемо на дошці оголошень про продаж вживаних телефонів — тому самому наборі даних, що й у решті курсу. Сімсот оголошень, у кожного відомий рік випуску телефона й ціна, яку просить продавець. Серед оголошень трапляються й шахрайські, з навмисно заниженою ціною, — це частина шуму, з яким моделі доводиться жити.
01 / ПостановкаКоли формули просто немає
Модель, яку ми підбираємо, максимально проста — два числа:
Словами: ціна телефона складається з базової вартості плюс надбавка за кожен рік
свіжості. Символ w (вага, weight) — на скільки тисяч гривень
дорожчає телефон за один рік новизни. Символ b (зсув, bias) —
скільки коштує найстаріший телефон у вибірці, тобто випуску 2018 року.
Віднімаємо 2018, щоб b мав зрозумілий сенс, а не був ціною телефона нульового
року. Ціни всюди рахуємо в тисячах гривень.
Для такої моделі формула є — ми вивели її минулого разу. Тож навіщо тоді спуск? Бо формула — це виняток, а не правило. Ось три причини, з яких вона зникає:
- Функція втрат перестає бути квадратичною. Уже логістична регресія мінімізує логарифмічну втрату, і рівняння «похідна дорівнює нулю» не розв'язується в замкненому вигляді. Не тому, що ніхто не додумався, — розв'язку в елементарних функціях просто не існує.
- Параметрів забагато. Формула лінійної регресії вимагає обернути матрицю розміром «кількість ознак × кількість ознак». На десятку ознак це мить, на десятках тисяч — неприйнятно, а в нейромережах параметрів мільйони.
- Дані не влазять у пам'ять. Формула хоче бачити всю матрицю одночасно. Спуск дивиться на дані порціями і взагалі не потребує тримати їх усі поруч.
Тому далі ми свідомо забудемо, що формула існує, і знайдемо ті самі w
і b наосліп. Наприкінці порівняємо — і числа мають зійтися.
02 / ЛандшафтФункція втрат як ландшафт
Щоб рухатись до відповіді, спершу треба вміти сказати, наскільки поточна відповідь погана. Беремо середній квадрат помилки:
Читається так: для кожного оголошення візьми справжню ціну, відніми ту, яку
прогнозує модель, піднеси різницю до квадрата — і всі ці квадрати усередни.
Тут x_i — вік телефона в роках від 2018, y_i — справжня ціна
з оголошення, n — кількість оголошень (у нас 700), а Σ означає
«склади по всіх оголошеннях». Навіщо вона тут: це єдине число, яким ми міряємо якість
пари (w, b), — і саме його зменшуватимемо.
Тепер найважливіший зсув у голові. L(w, b) — це не одне число, а
ціле поле чисел. Кожній парі (w, b) відповідає своя
величина втрати. Якщо відкласти w по горизонталі, b —
по вертикалі, а втрату показати кольором, вийде карта місцевості: пляма в центрі —
дно долини, а замкнені криві навколо неї — лінії однакової втрати.
Ось ця карта для наших семисот оголошень. Точки на ній підписані реальними значеннями втрати:
Схема 1 · Ландшафт втрат
Кожна точка карти — своя пара (w, b). Колір — величина втрати L. Хрест позначає найнижчу точку.
Тепер задача навчання формулюється однією фразою: знайти найнижчу точку цього
ландшафту. І тут головна складність. Ми не бачимо карти. Карту намалював комп'ютер,
перебравши сотні тисяч пар (w, b), — для двох параметрів це можливо, для
мільйона ні. Уяви натомість, що тебе висадили десь на цьому схилі в густому тумані.
Видимість — метр. Ти не знаєш ні де дно, ні в який бік воно. Але одне ти знаєш точно:
у який бік нахилена земля просто під ногами. І цього виявляється досить.
03 / ПохіднаПохідна — це напрямок схилу
Похідна має репутацію страшної речі з підручника. Насправді вона відповідає на дуже побутове питання:
і наскільки різко?
Ось і все. Число, яке відповідає на це питання, і називається похідною. Знак каже напрямок: додатна — праворуч угору, там гірше; від'ємна — праворуч униз, там краще. Величина каже крутизну: похідна −12 означає значно крутіший схил, ніж похідна −0.5.
Перевіримо це буквально, на калькуляторі. Щоб числа можна було звірити руками, візьмемо
шість оголошень — по одному типовому на кожен рік випуску — і тимчасово зафіксуємо
b = 4.2. Тоді змінна лишиться одна: w.
| рік випуску | x = рік − 2018 | ціна y, тис. грн |
|---|---|---|
| 2018 | 0 | 4.2 |
| 2019 | 1 | 5.9 |
| 2020 | 2 | 7.1 |
| 2021 | 3 | 9.7 |
| 2022 | 4 | 11.0 |
| 2023 | 5 | 12.4 |
Рахуємо похідну чисельно
Візьмемо w = 1.0 — тобто припущення «телефон дорожчає на тисячу гривень
за рік». Втрата виходить
Тепер зробимо той самий «крихітний крок праворуч»: збільшимо w на 0.001
і перерахуємо втрату наново:
Втрата впала. Отже, рухатись праворуч вигідно. Наскільки різко — покаже
відношення зміни втрати до зміни w:
Це число і є похідною, порахованою чисельно. Ніякої магії: два обчислення функції й одне ділення. Мінус означає «праворуч униз».
Рахуємо похідну формулою
Тепер виведемо формулу й переконаємось, що вона дає те саме число. Виведення — чотири кроки, кожен з однією думкою.
Втрата — це середнє з окремих доданків. Візьмемо один. Позначимо помилку
(залишок) на i-му оголошенні:
Усередині — лінійна залежність від w, зовні — квадрат. Похідна такої
конструкції: похідна зовнішньої, помножена на похідну внутрішньої. Похідна квадрата —
подвоєний аргумент, а e_i при зростанні w зменшується
зі швидкістю x_i:
Похідна суми дорівнює сумі похідних, а сталий множник 1/n просто
виноситься. Отже:
Словами: помнож кожну помилку на вік телефона, склади все, поділи на кількість
оголошень і візьми з мінусом та подвій. Множник x_i — це вага голосу:
старий телефон (великий x) сильніше тягне w на себе, бо на
ньому нахил прямої проявляється помітніше. Об'єкт із x = 0 не впливає
на w взагалі.
Для b викладки ті самі, тільки внутрішня похідна дорівнює −1
замість −x_i, бо b входить у прогноз з коефіцієнтом одиниця:
Пара цих двох чисел разом і називається градієнтом (gradient). Градієнт — це не нове поняття, а просто список усіх похідних одразу: по одній на кожен параметр.
Підставимо числа. При w = 1.0 і b = 4.2 прогноз і помилки
такі:
| x | y | прогноз w·x + b | помилка e | x · e |
|---|---|---|---|---|
| 0 | 4.2 | 4.2 | 0.0 | 0.0 |
| 1 | 5.9 | 5.2 | +0.7 | +0.7 |
| 2 | 7.1 | 6.2 | +0.9 | +1.8 |
| 3 | 9.7 | 7.2 | +2.5 | +7.5 |
| 4 | 11.0 | 8.2 | +2.8 | +11.2 |
| 5 | 12.4 | 9.2 | +3.2 | +16.0 |
| сума | — | — | +10.1 | +37.2 |
Тепер за формулою:
Порівняй: чисельно вийшло −12.3908, за формулою — −12.4000. Різниця в третьому знаку, і вона повністю пояснюється тим, що крок 0.001 усе-таки не нульовий: чисельна оцінка міряє нахил січної, а формула — нахил дотичної. Зменш крок до 0.000001 — розбіжність зникне.
А ось як це виглядає. Крива нижче — втрата L(w) при фіксованому
b = 4.2. Пряма, що торкається кривої, — та сама дотична, нахил якої ми
щойно рахували. Постав початкову точку й тисни «зробити крок»:
Інтерактив 1 · Кулька котиться схилом
Шість оголошень, b = 4.2, швидкість навчання зафіксована на 0.02. Кожен клік — одна ітерація.
04 / ПравилоКрок за кроком, руками
Правило оновлення — один рядок, і в ньому вся суть методу:
Стрілка означає «стало новим значенням». Символ η (грецька «ета») —
швидкість навчання (learning rate): додатне число, яке ми обираємо самі
й яке каже, наскільки великий крок робити. Похідна дає лише напрямок і крутизну, а
довжину кроку задає η.
Чому мінус. Похідна показує напрямок зростання втрати. Нам
треба навпаки — тому йдемо в протилежний бік. Мінус розвертає напрямок. Перевір на нашому
числі: похідна дорівнює −12.4, отже w − η·(−12.4) = w + η·12.4 — тобто
w зростає. Саме те, що треба: втрата ж падала праворуч. Якби похідна була
додатною, той самий мінус потягнув би w ліворуч.
Прокрутімо чотири ітерації руками. Швидкість навчання η = 0.02, старт
w = 1.0, зсув усе ще зафіксований на 4.2:
| ітерація | w | втрата L(w) | похідна | крок −η·похідна | нове w |
|---|---|---|---|---|---|
| 0 | 1.0000 | 4.2717 | −12.4000 | +0.2480 | 1.2480 |
| 1 | 1.2480 | 1.7603 | −7.8533 | +0.1571 | 1.4051 |
| 2 | 1.4051 | 0.7529 | −4.9738 | +0.0995 | 1.5045 |
| 3 | 1.5045 | 0.3488 | −3.1501 | +0.0630 | 1.5675 |
| 4 | 1.5675 | 0.1868 | −1.9950 | +0.0399 | 1.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 · Три долі одного спуску
Ліворуч — траєкторія по кривій втрат. Праворуч — як падає (або росте) втрата з ітераціями; шкала логарифмічна.
η < 2 / c, де c — крутизна
чаші, тобто друга похідна. У нас c = 18.33, звідси
2 / 18.33 = 0.109. Для нейромереж цю крутизну ніхто не знає наперед, і
η підбирають експериментом: запускають на кілька сотень кроків із різними
значеннями й дивляться, яка крива втрат падає найшвидше й не вибухає. Звідси й практика
розкладу швидкості (learning rate schedule): починають із більшого кроку,
щоб швидко дістатися потрібної області, і поступово зменшують, щоб акуратно сісти на дно.η = 0.05
доходить до дна за три кроки, а η = 0.091 — аж за два десятки, бо починає
стрибати через дно туди-сюди. При η = 0.105 навіть сорока ітерацій замало.
Залежність не монотонна: спершу швидшає, потім різко гіршає, потім вибухає.06 / МежіЯми, сідла й чесна межа методу
Досі все виглядало надійно: є схил — котимося вниз — приходимо на дно. Так і буде, але тільки для нашої задачі. Функція втрат лінійної регресії опукла (convex): у неї форма чаші, дно рівно одне, і з якої точки не почни — прийдеш у ту саму відповідь. Це рідкісний подарунок, і ним варто скористатися, поки він є.
Для складніших моделей ландшафт зовсім інший. Там трапляються:
- Локальні мінімуми — ямки на схилі. Земля навколо йде вгору, похідна дорівнює нулю, спуск зупиняється. Але справжнє дно долини — далеко внизу, за перевалом, і потрапити туди зі своєї ямки метод не може.
- Сідла (saddle points) — місця, де вздовж одного напрямку схил іде вгору, а вздовж іншого вниз, як перевал між двома вершинами. Похідна теж нульова. Спуск біля сідла не зупиняється зовсім, але сповільнюється так, що це важко відрізнити від зупинки.
- Плато — довгі майже рівні ділянки. Похідна крихітна, кроки крихітні, навчання виглядає мертвим, хоча формально триває.
Це чесна межа методу: градієнтний спуск не гарантує глобального мінімуму. Він гарантує лише, що ти не підеш угору. Твердження «спуск завжди знаходить найкращу відповідь» правильне для опуклої задачі й неправильне для будь-якої іншої.
07 / СтохастикаСтохастичний спуск
Повернімось до всіх семисот оголошень і порахуємо ціну одного кроку. Щоб обчислити градієнт чесно, треба пройти всі 700 рядків: для кожного порахувати прогноз, помилку, домножити на вік, скласти. І все це — заради одного оновлення двох чисел. Такий варіант називають повним (batch gradient descent).
Для 700 рядків це дрібниця. Для датасету з мільярда прикладів — катастрофа: один крок триває годину, а кроків треба тисячі.
Ідея стохастичного градієнтного спуску (stochastic gradient descent, SGD) до образливого проста. Точний градієнт нам, власне, не потрібен — потрібен приблизний напрямок униз. То візьмімо не всі 700 оголошень, а одне випадкове, порахуймо градієнт по ньому й одразу зробімо крок. Одне оголошення — погана оцінка загального схилу, вона може вказати геть убік. Але вона у середньому правильна, а коштує в 700 разів дешевше. За той самий обчислювальний бюджет ми зробимо 700 кроків замість одного.
Компроміс між крайнощами — партія (mini-batch): беремо не одне оголошення й не всі, а 32, або 64, або 256. Оцінка градієнта стає точнішою (шум усереднюється), а крок усе одно набагато дешевший за повний. Практично весь сучасний машинний навчальний код працює саме так.
Схема 2 · Скільки прикладів іде на один крок
Дошка з 700 оголошень. Рожевим позначено ті, які алгоритм дивиться, щоб зробити одне оновлення параметрів.
Тепер найцікавіше: що з цього виходить на практиці. Обидві траєкторії нижче стартують
з точки (0, 0) з тією самою швидкістю навчання й витрачають однаковий
обчислювальний бюджет. Різниця лише в тому, на скількох прикладах рахується кожен крок:
Інтерактив 3 · Стохастичний проти повного
Бірюзова траєкторія — повний градієнт. Рожева — партіями. Однаковий бюджет переглядів, швидкість навчання 0.005 в обох.
Головний висновок звучить контрінтуїтивно: гірші кроки, зроблені частіше, перемагають кращі кроки, зроблені рідко. Точність напрямку окупається гірше, ніж кількість оновлень.
08 / МоментМомент: чому шар котиться швидше
Подивись ще раз на карту втрат: долина витягнута. Уздовж неї схил пологий, упоперек — крутий. Звичайний спуск у такій долині поводиться безглуздо: він бачить переважно крутий поперечний схил і майже не бачить пологого поздовжнього. Тому він скочується до осі долини, перескакує її, повертається — і повзе вздовж крихітними кроками. Це та сама проблема різних масштабів, про яку йшлося в темі про лінійну регресію.
Тепер уяви замість точки важкий шар. Він теж відчуває поперечний схил, але має інерцію: за кілька метрів руху вздовж долини він розганяється, і поперечні поштовхи вже не встигають його розвертати. Поперечні коливання гасяться самі — вони щоразу спрямовані в різні боки й взаємно віднімаються. А поздовжній рух завжди в один бік, тому накопичується.
Математично це два рядки замість одного:
Тут v — швидкість, накопичена величина: пам'ять про те,
куди ми рухались досі. Коефіцієнт β (бета) каже, яку частку минулої швидкості
зберігати; на практиці беруть від 0.8 до 0.95. Читається так: нова швидкість — це
більша частина старої плюс сьогоднішній градієнт; параметр зсуваємо не за градієнтом,
а за швидкістю.
Що саме накопичується — важливо зрозуміти точно. Якщо градієнт кілька кроків підряд
дивиться в один бік, доданки складаються, і v виростає приблизно
в 1/(1−β) разів: уп'ятеро при β = 0.8, удесятеро
при β = 0.9. Якщо ж градієнт
хитається туди-сюди, сусідні доданки гасять один одного, і v лишається малою.
Момент буквально підсилює стабільні напрямки й приглушує метушню.
Схема 3 · Із моментом і без
Та сама карта втрат, той самий старт, та сама швидкість навчання 0.005 і рівно 80 кроків у кожної траєкторії. Коефіцієнт моменту 0.8.
Момент — найпростіший із сімейства прийомів, які роблять спуск розумнішим. Далі в цьому сімействі йдуть методи, що підбирають окрему швидкість навчання для кожного параметра за історією його градієнтів, — найвідоміший з них Adam. Але всередині кожного з них досі та сама конструкція: порахуй градієнт, зроби крок проти нього.
09 / ДаліКуди це веде далі
Зберемо весь метод у чотири рядки. Це повний цикл навчання будь-якої моделі, від нашої прямої до мовної моделі на сто мільярдів параметрів:
- Візьми поточні параметри й порахуй на них втрату.
- Порахуй градієнт — по одній похідній на кожен параметр.
- Зсунь кожен параметр на
−η ·його похідну. - Повтори.
Змінюється в цьому циклі напрочуд мало:
| тема | що змінюється | що лишається |
|---|---|---|
| 07 · лінійна регресія | є ще й точна формула | та сама MSE, той самий цикл |
| 09 · логістична | втрата стає логарифмічною | той самий цикл, формули вже немає |
| 31 · нейромережі | параметрів мільйони | той самий цикл, градієнт рахує backprop |
У наступній темі ми візьмемо ту саму
дошку оголошень, але питатимемо не «яка ціна», а «чи це шахрайство». Відповідь стане
«так» або «ні», прямої вже не вистачить, і функція втрат зміниться на логарифмічну.
А от навчатись модель буде рівно тим самим спуском, який ти щойно прокрутив руками
на калькуляторі, — з тим самим правилом w ← w − η · ∂L/∂w.
Ще далі, у темі про нейромережі, параметрів стане мільйони, і з'явиться окремий алгоритм — зворотне поширення помилки — який рахує всі ці похідні за один прохід. Але це буде спосіб дістати градієнт. Що з ним робити далі, ти вже знаєш.
practice.ipynb ти напишеш
градієнтний спуск з нуля десятком рядків на NumPy, звіриш знайдені w
і b з аналітичною формулою й зі scikit-learn, прожени три
швидкості навчання й побачиш три долі, а потім додаси SGD і порахуєш, скільки кроків
він встигає зробити.Далі в темі
Теорію прочитано. Тепер закріпи її на практиці.