В этой статье рассматривается содержание, обсуждаемое в модуле Алгоритмы оптимизации (часть 2) курса Глубокое обучение, и все изображения взяты из одного и того же модуля.
В этой статье мы попытаемся ответить на приведенный ниже вопрос, а также рассмотрим некоторые другие алгоритмы, помимо градиентного спуска, для правила обновления.
Идея стохастического и мини-пакетного градиентного спуска:
Код Python для алгоритма градиентного спуска, который мы использовали до сих пор в предыдущих статьях, выглядит следующим образом:
Как видно из подчеркнутой красным цветом части изображения выше, мы делаем несколько проходов по данным, и для каждой итерации/прохода мы смотрим на все точки данных (подчеркнутая синим) и накапливаем градиенты для все точки, и как только мы сделали один проход по данным (который также называется одной эпохой), мы обновляем веса.
Наше общее значение потерь будет суммой значений потерь по всем точкам данных, и, следовательно, производная общих потерь также может быть записана как сумма производных потерь по всем точкам данных:
Мы обновляем параметры один раз за один проход по всем данным, теперь скажем, если у нас есть один миллион точек данных, то сначала мы собираемся вычислить частную производную параметров для каждой из одного миллиона точек, аккумулировать все те, которые в градиент, а затем мы собираемся сделать одно обновление параметров. Это выглядит дорого в вычислительном отношении.
Здесь мы могли бы сделать приближение. Вместо того, чтобы смотреть на все точки данных, мы смотрим на несколько точек данных (мы будем случайным образом перемешивать данные), и мы могли бы посмотреть, скажем, первые точки данных 'B', и используя это, мы вычисляем частичное производная для каждой из этих точек данных «B», накапливает все в градиенте и обновляет параметры.
Таким образом, какой бы ни была полная производная, мы заменили ее меньшим количеством точек, т.е. суммой производных точек «B», которые мы рассматриваем за один раз:
Таким образом, за один проход по данным мы увидим много таких пакетов (каждый размером "B", последний может быть меньше "B"), например, для общего количества точек данных "M" и размер пакета «B», у нас будет почти M/B пакетов, и мы обновим веса столько раз всего за одну эпоху (один проход по всему набору данных). Таким образом, это называется мини-пакетным градиентным спуском, поскольку мы рассматриваем мини-пакеты данных.
Мы могли бы довести эту идею до крайности и вычислить производную для каждой из точек данных и обновить параметры для каждой из точек данных. Другими словами, размер пакета равен 1. Это называется стохастическим градиентным спуском.
Преимущество этого подхода в том, что мы можем делать много обновлений за одну эпоху, мы сможем двигаться/сойтись быстрее, чем ванильный градиентный спуск. Недостатком является то, что независимо от того, используем ли мы мини-пакет или стохастик, мы делаем приближение, мы фактически не вычисляем истинную производную, потому что истинная потеря - это общая потеря по всем точкам данных, а истинный градиент будет суммой частных производных по всем точкам данных.
Поверхность ошибок для Stochastic GD:
Код для ГД:
Код стохастического GD:
Для Vanilla GD поверхность ошибки выглядит так:
Как видно из приведенного выше изображения, кривая ошибки представляет собой гладкую кривую без каких-либо колебаний.
Для стохастического GD у нас есть поверхность ошибки как:
Теперь мы видим здесь много колебаний (и эти колебания отличаются от колебаний в случае Momentum, NAG GD).
Область ошибок для мини-пакетной GD:
Мы установили размер мини-пакета равным 10. Мы просматриваем данные, вычисляем частные производные и накапливаем их, а также ведем подсчет количества точек данных, которые мы видели, и как только мы увидели количество точек данных. точки данных, эквивалентные размеру мини-пакета, мы делаем обновление.
Здесь аппроксимация будет лучше, чем стохастический градиентный спуск, поскольку мы рассматриваем набор точек данных.
Еще раз мы можем видеть, что есть эти колебания, которые никогда не происходили в Ванильном GD или в полном пакетном GD, но происходят как в стохастическом, так и в мини-пакетном GD.
Когда мы увеличиваем размер партии, мы можем видеть некоторую стабильность в колебаниях.
На практике мы выбираем размер пакета, например 32, 64 или 128, в зависимости от объема данных, которые у нас есть. И это также гарантирует, что аппроксимации лучше, чем просто использование стохастического GD.
Эпохи и этапы:
Отличие Batch, Mini-batch, Stochastic GD заключается в количестве данных, которые учитываются при вычислении производных.
В случае полной партии GD мы смотрим на все данные, вычисляем частную производную функции потерь для каждой точки данных, затем берем сумму и делаем обновления.
В случае Stochastic GD мы смотрим на одну точку данных, вычисляем производную и обновляем параметры, а в случае мини-пакета мы смотрим на данные в пакетах размера «B».
Терминология:
Мы также могли бы иметь стохастик и мини-пакет для Momentum на основе и NAG GD.
Все в коде остается прежним, за исключением того, что мы делаем обновления для каждой точки данных, которую видим.
Поверхность ошибки для стохастической версии моментума и NAG выглядит так:
Колебания меньше в стохастической версии Momentum и NAG GD по сравнению с Vanilla GD, поскольку в этом случае есть этот исторический компонент, который помогает в определенной степени быть на треке.
Размер пакета — это гиперпараметр, и на практике мы используем мини-пакет GD с размером пакета 32, 64 или 128.
Зачем нам нужна адаптивная скорость обучения?
Допустим, у нас есть сигмовидный нейрон, который принимает множество входных данных.
Итак, у нас есть входные данные и соответствующие им веса, и уравнение сигмовидной формы выглядит так:
Правило обновления для каждого веса будет таким:
обновление пропорционально производной, и мы уже обсуждали, что если производные малы, то веса не изменятся так сильно.
Мы обсуждали это в более ранней статье, что производная функции потерь по весу пропорциональна входу:
Обновления пропорциональны производным, а производные зависят от входных данных.
В реальных данных многие функции разрежены, т. е. они принимают значение 0 для большинства обучающих входных данных, которые у нас есть, например, если у нас есть одна функция, скажем, «isActorMattDamon» для задачи классификации фильмов ( нравится нам это или нет), скажем, у нас есть данные примерно по 1000 фильмам, и из этих 1000 Мэтт Дэймон снялся бы максимум в 30–40 фильмах. Таким образом, это означает, что из 1000 записей значение признака isActorMattDamon будет равно 0 примерно для 960 точек данных. Смысл этого показан на изображении ниже:
Производная зависит от входных данных «x», и входные данные будут равны 0 для большинства точек данных в описанном выше сценарии. Это означает, что соответствующая производная также будет равна 0, и если производная будет равна 0, то обновление веса будет равно 0, и если веса не меняются, то у нас проблема с параметром, и, очевидно, значение потери будет остаются теми же.
Итак, мы хотим убедиться, что в случае, когда у нас есть разреженные функции, как в приведенном выше сценарии, когда вход «включен» (имеет значение 1) только 30–40 раз из 1000 раз, тогда нам нужно чтобы убедиться, что для этих 30–40 точек данных производная должна повышаться с большей скоростью обучения, чтобы веса изменялись соответствующим образом. И нам нужна более высокая скорость обучения, поскольку веса не обновляются для всех точек данных, и когда они обновляются, мы гарантируем, что они изменятся на большую величину.
Другими словами, мы хотим, чтобы скорость обучения была адаптивной к функциям, с которыми мы имеем дело, когда мы имеем дело с разреженными функциями, скорость обучения должна быть высокой, а с другой стороны, если мы имеем дело с плотными функциями (функции в большинстве случаев), тогда мы хотим, чтобы скорость обучения была небольшой.
Адаград:
Интуиция, лежащая в основе Adagrad, заключается в том, что мы должны попытаться уменьшить скорость обучения пропорционально истории обновления параметра. Итак, если у нас есть параметр, который обновляется очень часто (потому что входные данные 'on' в большинстве случаев, следовательно, производная в большинстве случаев отлична от нуля, следовательно, вес в большинстве случаев обновляется), тогда для таких параметров мы хотим иметь меньшую скорость обучения и в Напротив, для параметров, которые выключены в большинстве случаев, производная будет равна 0 в большинстве случаев и для таких функций, когда она включена. strong>, мы хотим, чтобы обновление было быстрее, поэтому мы хотим иметь более высокую скорость обучения для таких функций.
Adagrad помогает получить эту адаптивную скорость обучения, разделив скорость обучения на историю обновлений функции.
Если мы сосредоточимся на терминах, выделенных желтым цветом на изображении ниже, это просто обновление, что и для ванильного GD, мы делим эта скорости обучения на некоторое количество (выделено красным на изображении ниже)
И мы надеемся, что при этом нам нужно обеспечить две вещи: во-первых, для функций, которые получили много обновлений, член знаменателя (красный) на изображении выше должен быть высоким, чтобы эффективная скорость обучения становится маленькой, и мы хотим, чтобы этот знаменатель был маленьким, если мы получили очень мало обновлений в прошлом, чтобы повысить общую скорость обучения.
Член знаменателя «vt» – это текущая сумма квадратов производных:
Скажем, мы просматриваем точки данных по одной за раз, и для первых 10 точек данных значение признака равно 1, в этом случае производная не будет равна нулю ни для одной из точек данных, и, следовательно, у нас есть некоторое высокое значение. для 'vt', поскольку мы берем квадрат производной в уравнении для vt. Итак, для 11-й точки данных, поскольку знаменатель сейчас большой, эффективная скорость обучения (зеленая на изображении ниже) теперь станет маленькой, что именно то, что мы хотим для плотной функции.
В случае разреженного признака, большинство производных будут равны 0 и говорят, что для k-й точки данных производная отлична от нуля (и производная равна 0 для всех (k- 1)точки данных), поэтому здесь накопленный 'vt' будет равен 0, так как все производные были равны 0 до этой k-й точки данных и скорости обучения , если разделить на эту небольшую историю, станет очень большим по сравнению с тем, что было для плотных черт.
Визуализация Адаграда:
Черная кривая на изображении выше соответствует GD, красная — GD на основе Momentum, а синяя — NAG GD.
Мы начали с точки, отмеченной на изображении ниже, и должны были достичь минимума, который находится в центре долины.
Наше значение b должно увеличиться до точки, обведенной черным кружком на изображении ниже, а значение w должно увеличиться до точки, обведенной синим (в целом они оба представлены точкой, обведенной красным). на изображении ниже)
В идеале оба параметра должны были бы изменяться параллельно и следовать пути, выделенному желтым цветом на изображении ниже, для достижения минимума:
Вместо этого мы видим, что изначально 'w' вообще не меняется, а изменяется только 'b', и он изменился с -2 на 0, тогда как ' w' остается почти таким же.
Это для случая, когда 'w' связан с разреженным объектом, а 'b' всегда будет иметь некоторое значение, поэтому мы можем рассматривать его как плотный объект и вот почему он мог двигаться везде, где ему приходилось двигаться. Теперь, когда мы достигли хорошего значения для 'b', мы больше ничего не можем сделать, изменив 'b'. И единственный способ достичь минимума — изменить значение ‘w’.
И всякий раз, когда мы видим значение точки данных, соответствующее функции, связанной с 'w', будет происходить изменение, и если мы тренируемся в течение большого количества эпох, в конечном итоге 'w' strong> изменится со значения -2 на значение 0, при котором общая потеря будет минимальной.
Таким образом, для начальных 30–40 эпох здесь меняется только ‘b’ при почти неизменном значении ‘w’, а затем большое нет. эпох по-прежнему требуется для достижения оптимального значения w. Это проблема с другими алгоритмами, которые мы обсуждали в предыдущих статьях.
Теперь давайте посмотрим, как выглядит момент с Adagrad:
Зеленая кривая представляет момент для Адаграда. Теперь мы видим, что оба параметра изменяются одновременно, что является результатом адаптивной скорости обучения.
Ограничения Адаграда:
В случае GD и NAG на основе импульса после примерно 100 итераций мы смогли достичь минимума, хотя в течение первых 30–40 итераций они просто двигались в направлении 'b', они не вносили никаких изменений в 'w', но что происходит в случае с Adagrad, так это то, что, поскольку скорость обучения агрессивно снижается для частого параметра, как 'b' часто обновляется, его составляющая истории значительно увеличивается, из-за чего эффективная скорость обучения может быть близка к 0.
Итак, теперь происходит то, что через некоторое время, когда мы сделали много обновлений для 'b', мы больше не можем делать обновления для 'b'. , поэтому, даже если мы запустим этот алгоритм на 100-200 итераций, он просто достигает минимумов, но не может достичь точных минимумов, а для достижения минимумов он должен двигаться в направлении 'b' , он должен двигаться вверх и не может двигаться в направлении 'b', так как эффективная скорость обучения для 'b' стала очень близкой на 0 и, следовательно, не происходит никаких обновлений для 'b'.
Мы хотели увеличить скорость обучения для 'w', взорвать его скорость обучения, что и произошло, но в процессе мы также снизили скорость обучения для 'b' и через какое-то время она не сходится.
RMSProp:
RMSProp — это еще один алгоритм, который работает на основе следующей интуиции:
Таким образом, компонент истории в RMSProp отличается от Adagrad.
В Adagrad мы добавляем квадрат каждой производной, тогда как в RMSProp мы снова берем экспоненциально убывающую сумму квадрата производной.
'v4'в случае Adagrad будет выглядеть так:
тогда как в случае RMSProp это будет выглядеть так:
Мы видим, что 'v4' будет намного меньше в случае RMSProp, хотя мы накапливаем историю градиентов, мы не позволяем ей взорваться, поскольку мы умножаем ее на затухание. отношения.
Между значением «vt» для плотных и разреженных объектов по-прежнему будет существовать относительная разница.
Коричневая кривая представляет момент для RMSProp, и мы можем видеть, что он в конечном итоге достигает минимума, в отличие от Adagrad (зеленая кривая).
Алгоритм Адама:
Мы видели GD на основе Momentum и GD RMSProp, и оба они использовали компонент истории, в то время как GD на основе Momentum использует историю градиента, тогда как RMSProp использует историю квадрата градиентов, и мы видели в GD на основе импульса, что эта кумулятивная история фактически помогает быстро перемещаться с плоских поверхностей.
В GD на основе импульса компонент истории используется для вычисления текущего обновления, тогда как в RMSProp история используется для настройки скорости обучения. Итак, алгоритм Адама объединяет эти две идеи:
Адам объединяет идеи двух алгоритмов и обеспечивает адаптивность скорости обучения, а также гарантирует, что второй член в обновлении будет не текущей производной, а историей производных, чтобы мы могли быстро выйти из плоских областей.
На практике Адам также выполняет коррекцию смещения (чтобы обеспечить более стабильные градиенты в начале), где вместо использования «mt» и «vt» мы имеем:
Итак, сначала он вычисляет 'mt' и 'vt', используя приведенное ниже уравнение, а затем делит на количество (выполняет коррекцию смещения), как на изображении выше:
Кривая голубого цвета соответствует Адаму.
Поскольку этот алгоритм имеет некоторую часть/свойства импульса, он ведет себя точно так же в том смысле, что он выходит за пределы минимумов, совершает некоторые колебания и достигает долины.
Обзор:
Мы видели шесть различных алгоритмов:
последние три имеют адаптивную скорость обучения. И мы видели три разные стратегии, которые можно использовать в сочетании с любым из алгоритмов.
На практике алгоритм Adam с Mini-Batch является наиболее популярным. Преимущество этой комбинации заключается в том, что она не слишком сильно зависит от начальной скорости обучения, потому что в любом случае скорость обучения будет адаптироваться, и из-за этого другого свойства наличия импульса она как бы сочетает в себе преимущества GD на основе Momentum и RMSProp.