Методи оптимізації нейронних мереж

Методи оптимізації нейронних мереж

У переважній більшості джерел інформації про нейронні мережі під «а тепер давайте навчимо нашу мережу» розуміється «згодуємо цільову функцію оптимізатору» лише з мінімальним налаштуванням швидкості навчання. Іноді говориться, що оновлювати ваги мережі можна не тільки стохастичним градієнтним спуском, але без всякого пояснення, чим же примітні інші алгоритми і що означають загадкові і в їх параметрах. Навіть викладачі на курсах машинного навчання часто не загострюють на цьому увагу. Я б хотів виправити брак інформації в рунеті про різних оптимізаторів, які можуть зустрітися вам в сучасних пакетах машинного навчання. Сподіваюся, моя стаття буде корисна людям, які хочуть поглибити своє розуміння машинного навчання або навіть винайти щось своє.

Під палицею багато картинок, у тому числі анімованих gif.

Стаття орієнтована на знайомого з нейронними мережами читача. Передбачається, що ви вже розумієте суть backpropagation і SGD. Я не буду вдаватися в суворий доказ схожості представлених нижче алгоритмів, а навпаки, постараюся донести їх ідеї простою мовою і показати, що формули відкриті для подальших експериментів. У статті перераховані далеко не всі складнощі машинного навчання і далеко не всі способи їх долати.

Навіщо потрібні хитрощі

Нагадаю, як виглядають формули для звичайного градієнтного спуску:

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

Явно розписані формули для оновлення терезів де-нібдуь в середині мережі виглядають страшненько, адже кожен нейрон залежить від всіх нейронів, з якої він пов'язаний, а ті - від усіх нейронів, з якими пов'язані вони, і так далі. При цьому навіть в «іграшкових» нейронних мережах може бути близько 10 шарів, а серед мереж, що утримують олимп класифікування сучасних датасетів - набагато, набагато більше. Кожна вага - змінна в. Така неймовірна кількість ступенів свободи дозволяє будувати дуже складні відображення, але приносить дослідникам головний біль:

  • Застрелювання в локальних мінімумах або сивлових точках, яких для функції від змінних може бути дуже багато.
  • Складний ландшафт цільової функції: плато чергуються з регіонами сильної нелінійності. Похідна на плато практично дорівнює нулю, а раптовий обрив, навпаки, може відправити нас занадто далеко.
  • Деякі параметри оновлюються значно рідше за інші, особливо коли в даних зустрічаються інформативні, але рідкісні ознаки, що погано позначається на нюансах узагальнюючого правила мережі. З іншого боку, надання занадто великої значимості взагалі всім рідко зустрічаються ознаками може призвести до перенавчання.
  • Занадто маленька швидкість навчання змушує алгоритм сходитися дуже довго і застрягати в локальних мінімумах, занадто велика - «пролітати» вузькі глобальні мінімуми або зовсім розходитися

Обчислювальній математиці відомі просунуті алгоритми другого порядку, яким під силу знайти хороший мінімум і на складному ландшафті, але тут удар знову завдає кількість терезів. Щоб скористатися чесним методом другого порядку «в лоб», доведеться порахувати гесіан - матрицю похідних по кожній парі параметрів пари параметрів (вже погано) - а, скажімо, для методу Ньютона, ще й зворотну до неї. Доводиться винаходити всілякі хитрощі, щоб впоратися з проблемами, залишаючи завдання обчислювально підйомним. Робочі оптимізатори другого порядку існують, але поки що давайте сконцентруємося на тому, що ми можемо досягти, не розглядаючи другі похідні.

Nesterov Accelerated Gradient

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

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

Де зазвичай береться порядку. Зверніть увагу, що не пропало, а включилося в; іноді можна зустріти і варіант формули з очевидним множником. Чим менше, тим більше алгоритм поводиться як звичайний SGD. Щоб отримати популярну фізичну інтерпретацію рівнянь, уявіть, як кулька котиться по горбистій поверхні. Якщо в момент під кулькою був ненульовий ухил (), а потім він потрапив на плато, він все одно продовжить котитися по цьому плато. Більш того, кулька продовжить рухатися пару оновлень в той же бік, навіть якщо ухил змінився на протилежний. Тим не менш, на кульку діє в'язке тертя і кожну секунду він втрачає своєї швидкості. Ось як виглядає накопичений імпульс для різних (тут і далі по осі X відкладені епохи, а по Y - значення градієнта і накопичені значення):

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

Така зміна дозволяє швидше «котитися», якщо в стороні, куди ми прямуємо, похідна збільшується, і повільніше, якщо навпаки. Особливо цього добре видно для графіка з синусом.

Заглядання вперед може зіграти з нами злий жарт, якщо встановлені занадто великі і: ми заглядаємо настільки далеко, що промахуємося повз областей з протилежним знаком градієнта:

Втім, іноді така поведінка може виявитися бажаною. Ще раз зверну вашу увагу на ідею - заглядання вперед - а не на виконання. Метод Нестерова (6) - найочевидніший варіант, але не єдиний. Наприклад, можна скористатися ще одним прийомом з обчислювальної математики - стабілізацією градієнта усередненням по декількох точках уздовж прямої, по якій ми рухаємося. Скажімо, так:

Або так:

Такий прийом може допомогти в разі галасливих цільових функцій.

Ми не будемо маніпулювати аргументом цільової функції в наступних методах (хоча вам, зрозуміло, ніхто не заважає поекспериментувати). Далі для короткості

Adagrad

Як працюють методи з накопиченням імпульсу уявляють собі багато хто. Перейдемо до більш цікавих алгоритмів оптимізації. Почнемо з порівняно простого Adagrad - adaptive gradient.

Деякі ознаки можуть бути вкрай інформативними, але зустрічатися рідко. Екзотична високооплачувана професія, химерне слово в спам-базі - вони запросто потонуть в шумі всіх інших оновлень. Мова йде не тільки про рідко зустрічаються вхідні параметри. Скажімо, вам цілком можуть зустрітися рідкісні графічні візерунки, які і в ознаку-то перетворюються тільки після проходження через кілька шарів бурточної мережі. Добре б вміти оновлювати параметри з оглядкою на те, наскільки типова ознака вони фіксують. Досягти цього нескладно: давайте будемо зберігати для кожного параметра мережі суму квадратів його оновлень. Вона виступатиме як проксі для типовості: якщо параметр належить ланцюжку нейронів, що часто активуються, його постійно дергають туди-сюди, а значить сума швидко накопичується. Перепишемо формулу оновлення ось так:

Де - сума квадратів оновлень, а - згладжуючий параметр, необхідний, щоб уникнути поділу на 0. У часто оновлюваного в минулому параметра велика, значить великий знаменник в (12). Параметр, що змінився лише раз або два оновиться на повну силу. беруть порядку або для зовсім агресивного оновлення, але, як видно з графіків, це відіграє роль тільки на початку, ближче до середини навчання починає переважувати:

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

(Інша річ, що це вимагає експериментів. Якщо прибрати корінь, оновлення почнуть зменшуватися занадто швидко, і алгоритм погіршиться)

Ще одна гідність Adagrad - відсутність необхідності точно підбирати швидкість навчання. Досить виставити її в міру великий, щоб забезпечити хороший запас, але не такий величезний, щоб алгроритм розходився. По суті ми автоматично отримуємо загасання швидкості навчання (learning rate decay).

RMSProp и Adadelta

Брак Adagrad у тому, що в (12) може збільшуватися скільки завгодно, що через деякий час призводить до занадто маленьких оновлень і паралічу алгоритму. RMSProp і Adadelta покликані виправити цей недолік.

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

(4). Нехай - біжуче середнє в момент

тоді замість (12) отримаємо

Знаменник є корінь з середнього квадратів градієнтів, звідси RMSProp - root mean square propagation

Зверніть увагу, як відновлюється швидкість оновлення на графіку з довгими зубцями для різних. Також порівняйте графіки з меандром для Adagrad і RMSProp: у першому випадку оновлення зменшуються до нуля, а в другому - виходять на певний рівень.

Ось і весь RMSProp. Adadelta відрізняється тим, що ми додаємо до числівника (14) стабілізуючий член пропорційний від. На кроці ми ще не знаємо значення, тому оновлення параметрів відбувається в три етапи, а не в два: спочатку накопичуємо квадрат градієнта, потім оновлюємо, після чого оновлюємо.

Таку зміну зроблено з міркувань, що розмірності і повинні збігатися. Зауважте, що learning rate не має розмірності, а значить у всіх алгоритмах до цього ми складали розмірну величину з безрозмірною. Фізики в цьому місці жахнуться, а ми зниземо плечима: працює ж.

Зауважимо, що нам потрібен ненульовий для першого кроку, інакше всі наступні, а значить і будуть рівні нулю. Але цю проблему ми вирішили ще раніше, додавши в. Інша справа, що без явного великого ми отримаємо поведінку, протилежну Adagrad і RMSProp: ми будемо сильнішими (до деякої межі) оновлювати ваги, які використовуються частіше. Адже тепер щоб став значущим, параметр повинен накопичити більшу суму в числнику дробу.

Ось графіки для нульового початкового:

А ось для великого:

Втім, схоже, автори алгоритму і домагалися такого ефекту. Для RMSProp і Adadelta, як і для Adagrad не потрібно дуже точно підбирати швидкість навчання - досить прикидного значення. Зазвичай радять почати підгін c, а так і залишити. Чим ближче до, тим довше RMSProp і Adadelta з більшим будуть сильно оновлювати мало використовувані ваги. Якщо ж і, то Adadelta буде довго «з недовірою» ставитися до рідко використовуваних терезах. Останнє може призвести до паралічу алгоритму, а може викликати навмисно «жадібну» поведінку, коли алгоритм спочатку оновлює нейрони, які кодують найкращі ознаки.

Adam

Adam - adaptive moment estimation, ще один оптимізаційний алгоритм. Він поєднує в собі і ідею накопичення руху та ідею більш слабкого оновлення терезів для типових ознак. Знову згадаємо (4):

Від Нестерова Adam відрізняється тим, що ми накопичуємо не, а значення градієнта, хоча це чисто косметична зміна, див. (23). Крім того, ми хочемо знати, як часто градієнт змінюється. Автори алгоритму запропонували для цього оцінювати ще й середню нецентровану дисперсію:

Легко помітити, що це вже знайомий нам, так що по суті тут немає відмінностей від RMSProp.

Важлива відмінність полягає в початковому калібруванні і: вони страждають від тієї ж проблеми, що і в RMSProp: якщо вказати нульове початкове значення, то вони будуть довго накопичуватися, особливо при великому вікні накопичення (,), а якісь початкові значення - це ще два гіперпараметри. Ніхто не хоче ще два гіперпараметри, так що ми штучно збільшуємо і на перших кроках (приблизно для і для)

Image