Полная разбивка процесса машинного обучения

Линейная регрессия служит краеугольным камнем алгоритма работы с данными. Он широко используется и является одним из наиболее применимых существующих алгоритмов контролируемого машинного обучения. Выполняя линейную регрессию, мы пытаемся выявить наилучшую линейную связь между предсказанием независимых переменных (X1, X2 и т. Д.…) и предсказанной зависимой переменной (Y). Вместе давайте рассмотрим процесс модели линейной регрессии в Python. Репозиторий со всем кодом и данными можно найти здесь.

Установка

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

  • Продажи: штучные продажи в каждом месте
  • CompPrice: цена, взимаемая ближайшим конкурентом в каждом месте.
  • Доход: уровень дохода сообщества
  • Реклама: местный рекламный бюджет компании в каждом месте
  • Население: численность населения в регионе (в тысячах).
  • Цена: цена, взимаемая за автокресло на каждой площадке.
  • ShelveLoc: качество размещения стеллажей на площадке (хорошее | плохое | среднее)
  • Возраст: средний возраст местного населения
  • Образование: уровень образования в каждом месте
  • Городской: находится ли магазин в городе или в сельской местности.
  • США: находится ли магазин в США или нет

Продажи будут нашей зависимой переменной (или целью), а оставшиеся 10 переменных (функций) будут обрабатываться и управляться, чтобы помочь в нашей регрессии. Здесь важно отметить, из чего состоят наши переменные. У нас есть определенные переменные, такие как доход, реклама или численность населения, которые являются целочисленными переменными. Однако у нас также есть такие переменные, как Urban, USA и ShelveLoc. Значения этих переменных представляют собой категориальные значения (Да / Нет; Хорошо / Плохо / Средне и т. Д.). Мы увидим, как учитывать эти различия по мере нашего продвижения, просто важно отметить на этом этапе, что они разные. (Использование команды ‘df.info ()’ в Python поможет определить состав ваших значений переменных.

Вот снимок нашего DataFrame:

Предварительная обработка

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

Первое, что нам нужно сделать, это разделить наш DataFrame на целевую серию и серию предикторов. X будет нашим DataFrame независимых переменных, а y будет нашей целевой функцией.

X = df.drop('Sales', axis = 1)
y = df.Sales

Теперь, когда у нас есть каждое разбиение, мы можем выполнить разбиение поезд-тест. Этот шаг жизненно важен для нашей модели машинного обучения. Мы разделили наши данные на «обучающую» и «тестовую» группы. Набор поездов будет использоваться для нашей модели для фактического обучения, а наш набор тестов будет использоваться в качестве проверки для вывода, поскольку мы уже знаем эти значения. Чтобы получить доступ к этой функции, мы должны импортировать ее из Sklearn. У нас есть 400 записей данных, что является прилично достаточным размером данных, поэтому мы будем использовать соотношение 70/30 процентов для размера нашего поезда и размера теста соответственно (для нашего аргумента test_size в функции).

from sklearn.preprocessing import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = .30, random_state = 33)

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

X_train.drop_duplicates(inplace = True)
X_train.dropna(inplace = True)

Работа с разными типами данных

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

X_train.info()

Мы видим типы данных в третьем столбце, где все функции, кроме трех, имеют тип данных «int64», что означает, что наш DataFrame интерпретирует эти данные как целые числа. Они будут рассматриваться как наши числовые данные. Остальные три, категориальные переменные, будут временно исключены.

Числовые данные

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

X_train_numerics = X_train.select_dtypes(exclude = 'object')
X_train_cols = X_train.columns
X_train_numerics.columns = X_train_cols

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

Несмотря на то, что теперь у нас собраны все числовые данные, наша предварительная обработка еще не завершена. Теперь мы должны понять, что содержит каждая числовая серия. Например, «CompPrice» представляет собой сумму в долларах от другого конкурента, а «Education» представляет собой уровень образования в каждом месте, но у нас нет никакой дополнительной информации. Население представляет (в тысячах), сколько человек проживает в соответствующем районе. Несмотря на то, что это все формы числовых данных, мы не можем сравнивать их, потому что все единицы измерения разные! Как нам справиться с этой загадкой? Масштабирование!

Масштабирование наших данных

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

from sklearn.preprocessing import StandardScaler
from scipy import stats
ss = StandardScaler()
X_train_numeric = pd.DataFrame(ss.fit_transform(X_train_numeric))
X_train_numeric.set_index(X_train.index, inplace = True)
X_train_numeric.columns = X_numeric_cols
X_train_numeric.head()

Сначала мы импортируем и создаем экземпляр объекта StandardScaler. Затем мы подгоняем и трансформируем наш числовой DataFrame и устанавливаем индексную функцию, равную таковой у нашего исходного X_train. Это гарантирует, что мы работаем с правильными данными. Мы должны установить имена столбцов еще раз, потому что они были удалены при прохождении через объект StandardScaler. Наши результирующие данные выглядят так:

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

Удаление выбросов

Наш последний шаг при работе с числовыми данными - удалить все выбросы. Выбросы могут привести к неточному представлению данных объекта, поскольку небольшое количество вводимых значений может неточно влиять на другую переменную. Мы сделаем это с помощью пакета «stats» и фильтрации любых значений со значением z-score, превышающим 2,5 стандартных отклонения от среднего.

X_train_numeric = X_train_numeric[(np.abs(stats.zscore(X_train_numeric)) < 2.5).all(axis = 1)]

Категориальные данные

Теперь, когда мы позаботились о подготовке числовых данных, мы хотим обратиться к нашим категориальным данным. Давайте выделим наши категориальные данные в отдельный DataFrame и посмотрим, с чем мы работаем. Это тот же первый шаг, что и при работе с числовыми значениями, мы просто меняем аргумент внутри нашего метода «select_dtypes» с «exclude» на «include».

X_train_cat = X_train.select_dtypes(include = 'object')

Правый рисунок показывает нам, на что на самом деле смотрит наш категориальный DataFrame. Было бы здорово, если бы наши компьютеры могли понять, как интерпретировать «хорошо», «средний» или «да», но, к сожалению, это еще не так умно - мы оставим модели НЛП на потом;). На этом этапе нам нужно преобразовать эти значения в то, что наш компьютер сможет понять, с помощью процесса, называемого кодированием.

Кодирование категориальных данных

Начнем с переменных городов и США. Эти переменные являются двоичными («да» или «нет»), поэтому мы хотим выразить их как двоичные значения нашему компьютеру, а не как «да» или «нет». Для этого мы воспользуемся функцией LabelBinarizer из sklearn. Чтобы использовать функцию, мы сначала создаем экземпляр объекта LabelBinarizer () для каждой соответствующей функции, а затем выполняем fit_transform для каждой из них.

from sklearn.preprocessing import LabelBinarizer
urban_bin = LabelBinarizer()
us_bin = LabelBinarizer()
X_train_cat.Urban = urban_bin.fit_transform(X_train_cat.Urban)
X_train_cat.US = us_bin.fit_transform(X_train_cat.US)

Получившийся в результате DataFrame теперь представляет «Да» как 1 и «Нет» как 0. Теперь давайте обратим наше внимание на функцию «ShelveLoc». Поскольку для этой функции больше нет двоичного набора значений, мы должны использовать другой тип кодирования. Вместо этого мы создадим новую двоичную переменную для каждого значения и отбросим исходную функцию. Это позволит компьютеру интерпретировать, какая переменная имеет «истинное» двоичное значение, а затем присвоить ей целое число. По сути, мы создаем фиктивные переменные для каждого соответствующего значения. Это звучит немного запутанно, но давайте посмотрим на это в действии. Мы будем использовать функцию pandas .get_dummies (), чтобы присвоить каждое значение фиктивной переменной. Примечание. Важно, чтобы мы передали аргумент drop_first = True, чтобы избежать автокорреляции в нашем DataFrame.

X_cat_prepped = X_train_cat.merge(pd.get_dummies(X_train_cat.ShelveLoc, drop_first=True), left_index=True, right_index=True)
X_cat_prepped.drop('ShelveLoc', axis = 1, inplace=True)

Теперь мы можем легко увидеть, как варианты категориальных значений теперь преобразованы в новые двоичные переменные, которые наш компьютер может интерпретировать.

Теперь, когда мы закончили предварительную обработку как числовых, так и категориальных типов данных, мы объединяем их обратно в один полный и подготовленный DataFrame.

X_train_prep = pd.merge(X_cat_prepped, X_train_numeric, left_index = True, right_index = True)

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

y_train = y_train.loc[X_train_prep.index]

Построение модели линейной регрессии

Мы позаботились о мельчайших деталях нашей модели, о предварительной обработке. Когда у нас есть подготовленные данные, создание реальной модели линейной регрессии становится довольно простым делом. Сначала мы импортируем модель LinearRegression и создадим ее экземпляр из Sklearn. Затем мы подгоняем X_train_prep и y_train к нашему объекту линейной регрессии. Наконец, мы будем использовать метод .predict () для предсказания наших будущих значений.

from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(X_train_prep, y_train)
y_hat_train = lr.predict(X_train_prep)

Наша модель теперь полностью построена и предсказана. Чтобы увидеть, как работает наша модель, мы будем использовать два показателя: r-квадратное значение и среднеквадратичное значение (rmse).

  • R-квадрат: статистическая мера того, насколько данные близки к нашей подобранной линии регрессии (также известной как «степень согласия»). Диапазон составляет [0, 1], где r2 = 1,00 означает, что наша модель объясняет всю изменчивость данных ответа относительно среднего значения, в то время как r2 = 0 означает, что наша модель не объясняет никакой изменчивости. Чем выше r2, тем лучше модель.
  • RMSE: нормализованное относительное расстояние между вектором прогнозируемых значений и вектором наблюдаемых значений. «Маленькое» значение будет означать, что члены ошибки ближе к прогнозируемой линии регрессии, что доказывает лучшую модель. Чем ниже RMSE, тем лучше модель.
from sklearn.metrics import r2_score, mean_squared_error
print(f"r^2: {r2_score(y_train, y_hat_train)}")
print(f"rmse: {np.sqrt(mean_squared_error(y_train, y_hat_train))}")

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

Обе наши метрики выглядят неплохо! Наше значение r2 0,88 говорит нам, что наша модель уловила 88,6% изменчивости вокруг нашего среднего прогноза. Для нашего RMSE нам, вероятно, потребуется несколько моделей для сравнения, потому что они являются относительной мерой. Если бы мы запустили другую модель и вернули бы RMSE = 1,5, наша первая модель была бы лучшей из двух.

Допущения линейной регрессии

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

1. Линейность

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

2. Нормальность

Предположение о нормальности требует, чтобы остатки модели следовали нормальному распределению. Самый простой способ проверить это предположение - с помощью гистограммы или графика квантиль-квантиль (Q-Q-Plot).

  • График QQ: график вероятности, на котором сравниваются два распределения вероятностей путем сопоставления их квантилей друг с другом. Этот график помогает нам увидеть, нормально ли распределяется наша выборка. Этот график должен показывать прямую положительную линейную линию с вашими остаточными точками, входящими в линию. Если есть искажение нашей остаточной линии, то наши данные предполагают ненормальное распределение.
from statsmodels.graphics.gofplots import qqplot
qqplot(residuals, line = 'q')

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

3. Гомоскедастичность.

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

residuals = y_hat_train - y_train
plt.scatter(y_hat_train, residuals)

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

Заключение

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