Глубокое обучение кодированию для начинающих - линейная регрессия (часть 3): обучение с градиентным спуском

Это пятая статья из серии Глубокое обучение кодированию для начинающих. Здесь вы сможете найти ссылки на все статьи, повестку дня и общую информацию о предполагаемой дате выпуска следующих статей внизу первой статьи. Они также доступны в моем портфолио с открытым исходным кодом - MyRoadToAI вместе с некоторыми мини-проектами, презентациями, руководствами и ссылками.

Цель

В этой статье я объясню концепцию обучения алгоритмов машинного обучения с помощью градиентного спуска. Большинство контролируемых алгоритмов используют это преимущество, особенно все нейронные сети. Это важная тема, а также одно из самых серьезных препятствий, которые необходимо преодолеть людям, начинающим заниматься машинным обучением. Это связано с тем, что он основан на исчислении, и некоторые люди не освещали его в школе или их знания устарели.

Но не волнуйтесь, даже если вам не нравится математика или вы совсем не знаете математический анализ, вы все равно можете понять его, изучите его и используйте. Я покажу вам как!

Тренировка методом грубой силы

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

куда:

  • ŷ - прогнозируемая цена квартиры,
  • x - размер квартиры,
  • w - вес, присвоенный размеру квартиры,
  • b - предвзятость.

Нижний индекс «0» обозначает положение объекта в каждом векторе набора данных X и опускается для улучшения читаемости.

Целью процесса обучения будет поиск комбинации параметров w и b, для которой функция стоимости возвращает как можно меньшую ошибку.

Для улучшения характеристик модели будет сделана очень наивная вещь. Параметры будут изменены на небольшой фиксированный шаг во многих итерациях, и, поскольку неизвестно, следует ли вообще изменять значения параметров и в каком направлении . Следовательно, будет рассчитана стоимость для всех возможных комбинаций, чтобы найти лучшую модификацию, и поэтому это называется «обучение грубой силой». Три действия можно выполнить с параметром во время каждой итерации обучения: увеличить, уменьшить, не изменять вообще. Есть два параметра, которые означает, что на каждой итерации будет 3² = 9 наборов параметров для проверки, для которых будут сделаны прогнозы и будет рассчитана стоимость. Модель будет обновлена ​​с настройками, для которых стоимость имеет наименьшее значение.

Давайте реализуем это и посмотрим на результат. Большинство операций уже было закодировано в прошлых статьях, поэтому осталось реализовать только цикл обучения.

Здесь всего два параметра, а код уже достаточно сложен, чтобы опустить некоторые части для улучшения читаемости. Скрытые части могут быть реализованы очень похоже, как показано на рисунке, с той лишь разницей, что математический оператор изменяется с += на -= или иногда вообще не используется.

Фрагмент начинается с импорта функции deepcopy из модуля copy. Необходимо продублировать параметры модели, хранящиеся в словаре model_parameters, вместе с его содержимым. До того, как произойдет какое-либо обучение, model_parameters используются функциями predict и mse для измерения потенциала модели путем вычисления ее начальной ошибки.

Затем model_parameters копируются девять раз. Каждая копия была изменена на значение step по-разному (сложение, вычитание, без изменений), а затем использовалась для прогнозов. В зависимости от candidate_pred правильности копии присваивается значение стоимости как candidate_error переменная. Ошибка и параметры хранятся в списках candidates и errors. После того, как каждый кандидат рассчитан и измерена их производительность, для замены model_parameters используется набор параметров, стоимость которых была наименьшей. Это происходит iterations раз, что по умолчанию равно 100. Обратите внимание, что функция train не возвращает никакого результата и model_parameters изменяются на месте.

После всего этого функцию train можно использовать для загруженных данных и инициализированных параметров модели, чтобы найти значения, которые минимизируют ошибку:

В тело train функции можно добавлять простые print функции и отображать, как процесс обучения меняет параметры модели (полный код см. По этой ссылке, если вы не делали этого раньше):

Initial state:
 - error: [75870.4884482]
 - parameters: {'w': array([0.]), 'b': 0.0}

Iteration 0:
 - error: [73814.2609893]
 - parameters: {'w': array([0.1]), 'b': 0.1}

Iteration 20:
 - error: [38764.28631114]
 - parameters: {'w': array([2.1]), 'b': 2.1}

Iteration 40:
 - error: [15284.92972772]
 - parameters: {'w': array([4.1]), 'b': 4.1}

Iteration 60:
 - error: [3376.19123904]
 - parameters: {'w': array([6.1]), 'b': 6.1}

Iteration 80:
 - error: [1753.32046443]
 - parameters: {'w': array([7.1]), 'b': 8.1}

Final state:
 - error: [1741.85716443]
 - parameters: {'w': array([7.1]), 'b': 10.0}

Ошибка уменьшается с каждой итерацией. Это связано с тем, что все обучающие образцы используются во время каждой итерации - но об этом подробнее в следующих статьях. Еще одно важное наблюдение: значение ошибки не равно нулю после завершения обучения. Невозможно пройти по всем точкам с помощью только линейной функции.

Наконец, можно отобразить результаты обучения и увидеть, как модель может приблизительно определять цены на квартиры:

Выводы

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

  • Много шаблонного кода.
  • На каждой итерации необходимо проверять слишком много комбинаций. Эта модель использует только два параметра. Представьте, что произошло бы в таких моделях, как нейронные сети, где количество параметров иногда исчисляется миллионами - 3¹ ⁰⁰⁰ ⁰⁰⁰ - не очень хорошее число ...
  • Шаг, по которому изменяются параметры, фиксирован для каждого параметра. Диапазон его значений также неизвестен, иногда 0,1 может быть слишком большим, иногда слишком маленьким.
  • Направление, в котором следует изменять каждый параметр, неизвестно, поэтому необходимо опробовать все возможные изменения. Это требует больших вычислительных мощностей и работает очень медленно.

Роль производной

Как это ни парадоксально, когда речь идет о повторном использовании уже изобретенных концепций машинного обучения, связанных с градиентным спуском, более важно понимание, для чего можно использовать производные, нежели умение их вычислять. Эндрю Траск в своей книге Grokking Deep Learning дал очень хорошее представление о том, что такое производная и как она работает. В объяснении этой статьи будут использоваться аналогичные рассуждения.

Интуиция

Представьте себе два стержня, торчащие из стены. Назовем их стержнем A и стержнем B. Стержень A в два раза короче стержня B. Чтобы понять взаимосвязь между стержнями, проводятся два эксперимента:

  • Сначала в стену вдавливают стержень А. Что можно наблюдать, так это то, что длина ограждения B сокращается вместе со стержнем A, но в два раза больше.
  • Во-вторых, стержень А вытаскивается из стены и его длина увеличивается. В результате стержень B также удлиняется, но в два раза быстрее.

Теперь давайте попробуем описать взаимосвязь между двумя стержнями в форме математического уравнения. В обоих случаях можно заметить, что длина стержня B всегда вдвое превышает длину стержня A:

куда:

  • l_A - длина стержня A,
  • l_B - длина стержня B.

Подождите секунду и подумайте о роли числа 2 в этом уравнении. Это производное от l_B по отношению к l_A и может быть записано как:

Что из этого можно извлечь?

  • Производная описывает, как одна переменная изменяется при изменении другой. Итак, в этом случае, во сколько раз длина стержня B удлиняется или сжимается при изменении длины стержня A.
  • Производная рассчитывается между двумя переменными.
  • Когда производная имеет положительное значение, оба значения изменяются в одном направлении. Если стержень A расширяется, стержень B также удлиняется, потому что 2 - положительное число. И наоборот, если производная имеет отрицательное значение, значения изменяются в противоположном направлении.

Помните, что:

Для дифференцируемой функции можно найти взаимосвязь между двумя переменными - на сколько изменяется одна переменная и в каком направлении, когда другая изменяется с помощью производной.

Учитывая предыдущие проблемы с «обучением методом грубой силы», производные обладают некоторыми интересными свойствами, которые можно использовать для решения проблем.

Градиентный спуск

Итерационный оптимизационный алгоритм поиска минимума выпуклой функции. Он основан на исчислении - полагается на свойства первой производной, чтобы определить, в каком направлении и с какими коэффициентами величины функции должны быть изменено.

В предыдущей статье упоминалось, что функция стоимости, используемая для измерения производительности модели машинного обучения, должна быть дифференцируемой. Если это не так, тогда алгоритм градиентного спуска не может быть применен.

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

  • i - индекс выборки,
  • ŷ - прогнозируемое значение,
  • y - математическое ожидание,
  • m - количество выборок в наборе данных.

Прогнозируемое значение ŷ можно заменить формулой модели аппроксимации цены квартиры. Давайте также немного подправим используемую номенклатуру, поскольку очень популярно называть ошибку модели машинного обучения заглавной буквой J.

Теперь легко увидеть, что значение ошибки зависит от коэффициентов w и b. Параметры m, x, y можно рассматривать как константы, значения которых известны и определяются набором данных, на котором модель обучена.

Уже было сказано, что можно вычислить производную между любыми двумя параметрами функции, и она предоставит информацию о том, по какому фактору изменяется один при изменении другого. Следовательно, производное от J:

  • относительно параметра w предоставляет информацию о том, как настроить значение параметра w, чтобы минимизировать или максимизировать значение J,

  • относительно параметра b предоставляет информацию о том, как настроить значение параметра b, чтобы минимизировать или максимизировать значение J.

Если бы у модели было больше параметров, необходимо было бы вычислить больше производных. И именно здесь исчисление пригодится. В подавляющем большинстве случаев математика не требуется для использования алгоритма градиентного спуска. Производные различных функций, используемых в машинном обучении, подробных вычислениях или реализациях кода, глобально представлены в Интернете.

Для начала можно найти ответы в Интернете. Умение использовать Calculus обязательно приведет к лучшему пониманию того, как работают различные механизмы машинного обучения. Это также даст возможность проверять код других людей.

Если вам интересно, как были рассчитаны эти две производные, попробуйте этот пост в блоге или взгляните на 106-ю страницу книги Яна Гудфеллоу« Глубокое обучение ».

Геометрическая интерпретация

Производную можно рассматривать как наклон касательной к графику в данной точке. Синоним слова «градиент» - слово «наклон».

Значения параметров модели в начале обычно рандомизируются. Их значения определяют положение точки на кривой ошибки (для модели с одним параметром), поверхность ошибки (для модели с двумя параметрами ) или гиперплоскость ошибки (для более чем двух параметров). Цель алгоритма градиентного спуска - найти значения параметров, чтобы точка всегда находилась в самой нижней области, поскольку именно там значение ошибки является самым низким.

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

  • рандомизированное значение веса слишком мало и его необходимо увеличить,

  • значение рандомизированного веса слишком велико, и его необходимо уменьшить.

Есть несколько критических замечаний:

  • Обратите внимание, что значение веса слишком мало, тогда производная отрицательна, а значение веса слишком велико, тогда производная положительна. Следовательно, для минимизации функции ошибок необходимо вычесть производную из веса, чтобы приблизить точку к глобальному минимуму. Если бы была добавлена ​​производная, то вместо этого увеличилась бы ошибка.
  • Производное значение уменьшается по мере того, как обновления веса приближают значения ошибок к глобальному минимуму. Это также правильно с геометрической точки зрения, потому что «наклон» касательной также становится менее крутым с каждой итерацией.
  • Расстояние между точками называется шагом градиента. Обратите внимание, что производные значения намного больше, чем значение веса по оси x. Если бы первое утверждение было верным, вычитание производной из веса не изменило бы значение веса на такие маленькие числа. Это связано с тем, что при обновлении параметров с помощью градиентного спуска необходимо использовать только крошечную часть производной, чтобы сохранить числовую стабильность и не перепрыгнуть через глобальный минимум. Какую часть производной следует использовать или какой должен быть шаг градиента, определяется гиперпараметром скорость обучения.

Когда скорость обучения слишком велика, значения начинают отскакивать от кривой, и ошибка продолжает бесконечно увеличиваться:

Если модель зависит от двух параметров, а не от одного, то для отображения значения ошибки нужна дополнительная ось (для бонусного параметра). Это автоматически переведет визуализацию с 2D-плоскости на 3D-поверхность. Вот хороший пример, найденный в Интернете, как может выглядеть такая поверхность ошибки:

Визуализация более двух параметров очень сложна. Обычно важно полагаться на такие методы уменьшения размерности, как PCA и родственные им. К счастью, математика очень похожа и масштабируется по любому количеству параметров.

Правило обновления

Чтобы обновить параметры модели и добиться сходимости, необходимо итеративно применять следующую математику:

куда:

  • w ’- новое значение веса,
  • b ’- новое значение смещения,
  • w - текущее значение веса,
  • b - текущее значение смещения,
  • α - скорость обучения,
  • dJ (w_0, b) / dw_0 - производная J по w_0,
  • dJ (w_0, b) / db - производная J по b.

Параметры следует обновлять до тех пор, пока значение функции затрат не перестанет уменьшаться, и весь процесс можно будет остановить, если текущее состояние модели уже удовлетворительное.

Реализация

Напишем новую train функцию, которая использует алгоритм градиентного спуска:

Параметры изменяются на очень маленькие значения, так как используется только 0.0005 значения производной в определенной точке. Для достижения хороших результатов требуется 20000 итераций. Весь процесс можно было бы ускорить, если бы входные данные были предварительно нормализованы.

Во время каждой итерации частичный градиент вычисляется для каждой выборки данных и суммируется для всего набора данных. После этого накопленный градиент усредняется и используется для обновления параметров в конце шага градиента (итерация, в которой обновляются параметры).

Теперь новую функцию train можно использовать точно так же, как и раньше:

что дает следующий результат:

Initial state:
 - error: [75870.4884482]
 - parameters: {'w': array([0.]), 'b': 0.0}

Iteration 0:
 - error: [13536.3070032]
 - parameters: {'w': array([10.17501967]), 'b': array([0.17843399])}

Iteration 4000:
 - error: [1737.28457739]
 - parameters: {'w': array([7.09101188]), 'b': array([10.96966037])}

Iteration 8000:
 - error: [1707.33242182]
 - parameters: {'w': array([6.9583785]), 'b': array([18.67110985])}

Iteration 12000:
 - error: [1692.21685452]
 - parameters: {'w': array([6.86415678]), 'b': array([24.14215949])}

Iteration 16000:
 - error: [1684.5886765]
 - parameters: {'w': array([6.79722241]), 'b': array([28.02875048])}

Final state:
 - error: [1680.73973307]
 - parameters: {'w': array([6.74968272]), 'b': array([30.78917543])}

Сравнивать оба метода обучения нет смысла. Основная проблема тренировок методом грубой силы - это неэффективность. В случае алгоритма градиентного спуска модель итеративно приближается к сходимости , и нет необходимости проверять все возможные модификации, которые можно внести. Это будет иметь огромное значение при работе с сложные задачи - данные с большой размерностью.

Наконец, давайте посмотрим на проекцию модели:

Резюме

Мы очень близки к созданию нашей первой модели машинного обучения - линейной регрессии, написанной с нуля на Python, которая позволяет прогнозировать цены на квартиры в Кракове. Обладая уже предоставленными знаниями, вы можете создать окончательное решение самостоятельно!

В этой статье я показал вам как работает цикл обучения и как можно использовать функцию стоимости для поиска лучших параметров модели. Затем я указал на множество недостатков представленного метода. В следующей части мы объяснили, что такое производная и для чего она используется. Я рассказал вам, что такое алгоритм градиентного спуска с теоретической, числовой и геометрической точек зрения, используя пример функции среднеквадратичной ошибки в качестве помощи в объяснении. Наконец, я превратил объясненную концепцию в код и использовал его для обучения нашей модели по одному элементу - размеру квартиры.

Сейчас нам не хватает… обобщения. Все концепции, фрагменты кода представлены для простой задачи, когда данные одномерные. Это упрощение, конечно, сделано намеренно. В следующей статье я покажу вам, как реорганизовать все эти концепции, чтобы они работали для любого количества функций и больше никогда не приходилось редактировать! Этого можно добиться благодаря умножению матриц, а весь процесс называется векторизацией.

Следующая статья

Ожидайте следующую статью примерно 30.08.2018.