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

Мы начнем с простого класса LinearRegression, а затем на его основе создадим целый модуль линейных моделей в простом стиле, похожем на Scikit-Learn. Мои реализации никоим образом не являются оптимальными решениями и предназначены только для улучшения нашего понимания машинного обучения.

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

Чтобы просмотреть репозиторий Github, посетите здесь.

Установка

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

    $ pip install mlscratch==0.0.1

Чтобы загрузить весь исходный код в локальном репозитории из Github, создайте виртуальную среду и выполните следующие команды в своем терминале.

    $ git clone https://github.com/lukenew2/mlscratch
    $ cd mlscratch
    $ python setup.py install

Линейная регрессия

Часто одной из первых моделей, которую узнает каждый в своем путешествии по науке о данных, является линейная регрессия. Проще говоря, модель линейной регрессии представляет взаимосвязь между зависимой скалярной переменной y и независимыми переменными X путем вычисления весов параметров для каждой независимой переменной плюс константа, называемая термин смещения (также называемый термином перехвата).

Мы делаем прогнозы, умножая вектор весов признаков, Θ, на независимые переменные X.

Вот и все! Вот и все, что касается модели линейной регрессии. Давайте посмотрим, как мы его тренируем.

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

Обычные наименьшие квадраты

Первый метод, который мы собираемся написать с нуля, называется Обычный метод наименьших квадратов (OLS). OLS вычисляет псевдообратную величину X и умножает ее на целевые значения y. В python этот метод довольно легко реализовать с помощью scipy.linalg.lstsq (), который является той же функцией, что и класс LinearRegression () в Scikit-Learn.

Мы постараемся сделать нашу библиотеку похожей на библиотеку Scikit-Learn, используя методы fit () и pred (). Мы также дадим ему атрибут coef_, который представляет собой параметры, вычисляемые во время обучения.

Чтобы закодировать метод fit (), мы просто добавляем термин смещения в наш массив функций и выполняем OLS с функцией scipy.linalg.lstsq (). Мы сохраняем рассчитанные коэффициенты параметров в нашем атрибуте coef_, а затем возвращаем экземпляр self. Метод pred () еще проще. Все, что мы делаем, это добавляем по единице к каждому экземпляру для члена смещения, а затем берем скалярное произведение матрицы признаков и наших вычисленных параметров.

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

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

Градиентный спуск - это общий алгоритм оптимизации, который ищет оптимальное решение, внося небольшие изменения в параметры. Для начала вы заполняете Θ случайными значениями (этот подход называется случайной инициализацией). Затем изменяйте параметры до тех пор, пока алгоритм не сойдется к минимальному решению, двигаясь в направлении, которое уменьшает функцию стоимости. В нашем случае это означает уменьшение MSE.

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

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

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

Давайте отредактируем наш текущий модуль, включив в него возможность использования пакетного градиентного спуска для обучения. Поскольку мы также будем создавать классы Ridge, Lasso и ElasticNet, мы создадим базовый класс Regression (), от которого могут наследовать все наши регрессоры.

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

Полиномиальная регрессия

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

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

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

Мы будем кодировать его в том же стиле, что и классы предварительной обработки Scikit-Learn. У него будут методы fit (), transform () и fit_transform ().

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

Нормализация данных

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

Давайте добавим класс StandardScaler () в наш модуль preprocessing.py. Мы включим сюда метод inverse_transform () на тот случай, если нам когда-нибудь понадобится вернуть данные в исходное состояние после их стандартизации.

С помощью нашего кода полиномиальная регрессия может быть реализована в три этапа. Сначала добавьте в набор данных полиномиальные функции. Затем стандартизируйте наши функции с помощью StandardScaler (). Наконец, обучите нашу модель LinearRegression с помощью OLS или пакетного градиентного спуска.

Регуляризованные линейные модели

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

Для линейных моделей один из способов регуляризации - это ограничение весов параметров. Давайте рассмотрим три разных способа добиться этого, написав с нуля регрессию Ridge, Lasso и Elastic Net.

Хребет

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

где α контролирует, сколько регуляризации вы хотите добавить в модель. Чтобы реализовать это с нуля с помощью градиентного спуска, нам также потребуются частные производные члена регуляризации. Достаточно просто это просто умножить α на веса параметров.

Важно отметить, что сумма члена регуляризации начинается с i = 1, а не с 0. Это связано с тем, что мы не регуляризуем член смещения.

Хорошо, мы знаем все, что нам нужно, чтобы добавить Ridge в наш модуль regression.py. Сначала мы создадим класс, который вычисляет норму L2 и вектор градиента.

Когда мы вызываем этот класс, он будет вести себя как функция и вычислять член регуляризации для нас, а когда мы вызываем его метод grad (), он будет вычислять член регуляризации вектора градиента. Теперь мы просто используем наш вспомогательный класс для вычисления наших условий регуляризации во время градиентного спуска в нашем базовом классе регрессии.

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

Затем мы обновили наш класс LinearRegression, включив в него атрибуты self.regularization и self.regularization.grad, которые оцениваются как 0 благодаря быстрому использованию лямбда-функций. Это означает, что при использовании LinearRegression не будет регуляризации.

Наконец, мы создаем наш класс регрессии Ridge, который наследуется от базового класса регрессии, предоставляя ему те же атрибуты и методы. Единственное, что нам нужно изменить, - это метод __init __ ().

Мы даем ему два атрибута: alpha и self.regularization. Альфа используется для управления степенью регуляризации и саморегуляции. Регуляризация равна нашему классу l2_regularization, который вычисляет наши штрафные термины, используемые при градиентном спуске.

Лассо

Регрессия оператора наименьшего абсолютного сжатия и выбора (лассо) реализована точно так же, как и Ridge, за исключением того, что она добавляет штраф за регуляризацию, равный норме L1.

Поскольку норма L1 не дифференцируема в 0, мы вычисляем штраф за регуляризацию градиентов с вектором субградиента, равным функции знака.

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

Сначала мы добавим класс регуляризации L1 в наш модуль regularization.py.

Во-вторых, мы создадим новый класс Lasso, который также будет унаследован от нашего базового класса Regression в нашем модуле regression.py.

Регрессия лассо реализует форму выбора функций, потому что она имеет тенденцию устранять наименее важные функции (т. Е. Обнулять их).

Эластичная сетка

Elastic Net реализует простое сочетание условий регуляризации Ridge и Lasso для функции стоимости и вектора градиента. Соотношение смешивания r определяет, сколько из каждого термина включено. Когда r = 0, Elastic Net эквивалентен Ridge, а когда r = 1, он эквивалентен Lasso.

Так же, как мы это делали при написании кода Lasso Regression с нуля, мы создаем класс регуляризации для вычисления штрафов и наследования класса ElasticNet от базового класса Regression.

Теперь давайте запрограммируем класс регрессии ElasticNet.

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

Ridge - хороший вариант по умолчанию, однако, если вы ожидаете, что будут полезны только некоторые функции, вам следует использовать Lasso или Elastic Net из-за их свойств выбора функций. Лассо может вести себя беспорядочно, когда количество функций превышает количество обучающих примеров, или когда несколько функций сильно коррелированы. Следовательно, обычно предпочтительнее использовать эластичную сетку.

Заворачивать

На этом мы завершаем эту часть серии статей о машинном обучении с нуля. Мы реализовали наиболее распространенные линейные модели, используемые для регрессии.

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

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

Как всегда, спасибо за чтение и очень благодарны за все отзывы!

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

Подпишитесь, если вам нравится контент, который вы видите здесь! :-)