Сегодня пойдет речь о регрессии, как обучать и какие проблемы могут вам попасться при использовании таких моделей.
Для начала давайте введем удобные обозначения.
$ x = (x^1, ... x^n) - \text{признаковое описание объекта}$
$ X = (x_i,y_i)^l_{i=1} - \text{обучающая выборка} $
$ a(x) - \text{алгоритм, модель} $
$ Q(a,X) - \text{функция ошибки алгоритма a на выборке X} $
$ \text{Обучение} - a(x) = \text{argmin}_{a \in \mathbb{A}} Q(a,X)$
Но этого мало, давайте разберем, что такое функционал ошибки, семейство алгоритмов, метод обучения.
- Функционал ошибки Q: способ измерения того, хорошо или плохо работает алгоритм на конкретной выборке
-
Семейство алгоритмов
$\mathbb{A}$ : как выглядит множество алгоритмов, из которых выбирается лучший - Метод обучения: как именно выбирается лучший алгоритм из семейства алгоритмов.
Пусть известен один признак - расстояние кафе от метро, а предсказать необходимо количество заказов. Поскольку количество заказов - вещественное число
По графику можно сделать вывод о существование зависимости между расстоянием от метро и количеством заказов. Можно предположить, что зависимость линейна, ее можно представить в видео прямой на графике. По этой прямой и можно будет предсказывать количество заказов, если известно расстояние от метро.
В целом такая модель угадывает тенденцию, то есть описывает зависимость между ответом и признаком. При этом, разумеется ,она делает это не идеально, с некоторой ошибкой. Истинный ответ на каждом объекте несколько отклоняется от прогноза.
Один признак - это несерьезно. Гораздо сложнее и интереснее работать с многомерными выборками, которые описываются большим количеством признаков. В этом случае отобразить выборку и понять, подходит ли модель или нет, нельзя. Можно лишь оценить ее качество и по нему уже понять, подходит ли эта модель.
Давайте обсудим, как выглядит семейство алгоритмов в случае с линейными моделями. Линейный алгоритм в задачах регрессии выглядит следующим образом
где
Если у нашего набора данных есть только один признак, то алгоритм выглядит так
Ничего не напоминает? Да, действительно это обычная линейная функция, известная с 7 класса.
В качестве меры ошибки мы не может быть выбрано отклонение от прогноза
Но функция модуля не является гладкой функцией, и для оптимизации такого функционала неудобно использовать градиентные методы. Поэтому в качестве меры ошибки часто используют квадрат отклонения:
Функционал ошибки, именуемый среднеквадратичной ошибкой алгоритма, задается следующим образом:
В случае линейной модели его можно переписать в виде функции (поскольку теперь Q зависит от вектора, а не от функции) ошибок: $$ Q(\omega,x) = \frac{1}{l} \sum\limits_{i=1}^l ( \langle w_i,x_i\rangle- y_i) ^2 $$
Разберем том, как обучать модель линейной регрессии, то есть как настраивать ее параметры. $$ Q(w,x) = \frac{1}{l} \sum\limits_{i=1}^l ( \langle w_i,x_i\rangle- y_i) ^2 \to \min_{w}$$
То есть нам необходимо подобрать
Прежде, чем рассмотрим задачу о оптимизации этой функции, имеет смысл используемые соотношения в матричной форме. Матрица "объекты-признаки"
Таким образом, в
В этом случае среднеквадратичная шибка может быть переписана в матричном виде:
Самый лучший метод - это численный метод оптимизации.
Не сложно показать, что среднеквадратичная ошибка - это выпуклая и гладкая функция. Выпуклость гарантирует существование лишь одного минимумам, а гладкость - существование вектора градиента в каждой точке. Это позволяет использовать метод градиентного спуска.
При использовании метода градиентного спуска необходимо указать начальное приближение. Есть много подходов к тому, как это сделать, в том числе инициализировать случайными числами (не очень большими). Самый простой способ это сделать — инициализировать значения всех весов равными нулю:
На каждой следующей итерации,
Остановить итерации следует , когда наступает сходимость. Сходимость можно определять по-разному .В данном случае разумно определить сходимость следующим образом: итерации следует завершить, если разница двух последовательных приближений не слишком велика:
В случае парной регрессии признак всего один, а линейная модель выглядит следующим образом:
$$ a(x)=w_0 + w x $$
где
Для нахождения оптимальных параметров будет применяться метод градиентного спуска, про который уже было сказано ранее. Чтобы это сделать, необходимо сначала вычислить частные производные функции ошибки:
Следующие два графика демонстрируют применение метода градиентного спуска в случае парной регрессии. Справа изображены точки выборки, а слева — пространство параметров. Точка в этом пространстве обозначает конкретную модель (кафе)
График зависимости функции ошибки от числа произведенных операции выглядит следующим образом:
Очень важно при использовании метода градиентного спуска правильно подбирать шаг. Каких-либо концерт- иных правил подбора шага не существует, выбор шага — это искусство, но существует несколько полезных закономерностей.
Если длина шага слишком мала, то метод будет неспешна, но верно шагать в сторону минимума. Если же взять размер шага очень большим, появляется риск, что метод будет перепрыгивать через минимум. Более того, есть риск того, что градиентный спуск не сойдется.
Имеет смысл использовать переменный размер шага: сначала, когда точка минимума находится еще да- легко, двигаться быстро, а позже, спустя некоторое количество итерации — делать более аккуратные шаги. Один из способов задать размер шага следующий:
где
import matplotlib.pyplot as plt # библиотека для отрисовки графиков
import numpy as np # импортируем numpy для создания своего датасета
from sklearn import linear_model, model_selection # импортируем линейную модель для обучения и библиотеку для разделения нашей выборки
X = np.random.randint(100,size=(500, 1)) # создаем вектор признаков, вектора так как у нас один признак
y = np.random.normal(np.random.randint(300,360,size=(500, 1))-X) # создаем вектор ответом
plt.scatter(X, y) # рисуем график точек
plt.xlabel('Расстояние до кафе в метрах') # добавляем описание для оси x
plt.ylabel('Количество заказов')# добавляем описание для оси y
plt.show()
# Делим созданную нами выборку на тестовую и обучающую
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, y)
Давайте посмотрим какие параметры принимает LinearRegression.
fit_intercept - подбирать ли значения для свободного член True
или False
. Если ваши данные центрированны, то можете указать False. По умолчанию - True
normalize- нормализация данных перед обучением. Если fit_intercept=False то параметр будет проигнорирован. Если True
- то данные перед обучением будут нормализованы при помощи L^2-Norm нормализации. По умолчанию - False
copy_X - True
- копировать матрицу признаков. False
- не копировать. **По умолчанию - **True
n_jobs - количество ядер используемых для сборки. Скорость будет существенно выше n_targets>1. По умолчанию - None
Параметры которые можно у модели:
coef_ - коэффициенты
intercept_ - свободный член
Что бы предсказать значения необходимо вызвать функцию: predict и передать массив из признаков.
Example: regr.predict([[40]])
regr = linear_model.LinearRegression() # создаем линейную регрессию
#Обучаем модель
regr.fit(X_train, y_train)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
После выполнения кода, вам вернется ответ с установленными параметрами
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
# Посмотрим какие коэффициенты установила модель
print('Коэфициент: \n', regr.coef_)
# Средний квадрат ошибки
print("Средний квадрат ошибки: %.2f"
% np.mean((regr.predict(X_test) - y_test) ** 2))
# Оценка дисперсии: 1 - идеальное предсказание. Качество предсказания.
print('Оценка дисперсии:: %.2f' % regr.score(X_test, y_test))
Коэфициент:
[[-1.03950324]]
Средний квадрат ошибки: 325.63
Оценка дисперсии:: 0.70
# Посмотрим на получившуюся функцию
print ("y = {:.2f}*x + {:.2f}".format(regr.coef_[0][0], regr.intercept_[0]))
y = -1.00*x + 329.48
# Посмотрим, как предскажет наша модель тестовые данные.
plt.scatter(X_test, y_test, color='black')# рисуем график точек
plt.plot(X_test, regr.predict(X_test), color='blue') # рисуем график линейной регрессии
plt.show() # Покажем график
Давайте предскажем количество заказов, для нашего потенциального магазина. Скажем, что мы планируем построить магазин в 40 метрах от метро
regr.predict([[40]])
array([[288.13894078]])
290 заказов мы получим если построим магазин в 40 метрах от метро, но при этом качество предсказания всего лишь 71%, думаю не стоит доверять.
Для этого воспользуемся встроенным датасетом make_regression
из sklearn
.
n_features - отвечает за количество признаков один нормальный другой избыточный
n_informative - отвечает за количество информативных признаков
n_targets - отвечает за размер ответов
noise - шум накладываемый на признаки
соef - возвращать ли коэффициенты
random_state - определяет генерацию случайных чисел для создания набора данных.
# Импортируем библиотеки для валидация, создания датасетов, и метрик качества
from sklearn import cross_validation, datasets, metrics
# Создаем датасет с избыточной информацией
X, y, coef = datasets.make_regression(n_features = 2, n_informative = 1, n_targets = 1,
noise = 5., coef = True, random_state = 2)
# Поскольку у нас есть два признака,для отрисовки надо их разделить на две части
data_1, data_2 = [],[]
for x in X:
data_1.append(x[0])
data_2.append(x[1])
plt.scatter(data_1, y, color = 'r')
plt.scatter(data_2, y, color = 'b')
<matplotlib.collections.PathCollection at 0x1134a6e10>
Можно заметить, что признак отмеченный красным цветом имеет явную линейную зависимость, а признак отмеченный синим, никакой информативность нам не дает.
# Разделение выборку для обучения и тестирования
# test_size - отвечает за размер тестовой выборки
X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size = 0.3)
# Создаем линейную регрессию
linear_regressor = linear_model.LinearRegression()
# Тренируем ее
linear_regressor.fit(X_train, y_train)
# Делаем предсказания
predictions = linear_regressor.predict(X_test)
# Выводим массив для просмотра наших ответов (меток)
print (y_test)
[ 28.15553021 38.36241814 -24.77820218 -61.47026695 13.02656201
23.87701013 12.74038341 -70.11132234 27.83791274 -14.97110322
-80.80239408 24.82763821 58.26281761 -45.27502383 10.33267887
-48.28700118 -21.48288019 -32.71074998 -21.47606913 -15.01435792
78.24817537 19.66406455 5.86887774 -42.44469577 -12.0017312
14.76930132 -16.65927231 -13.99339669 4.45578287 22.13032804]
# Смотрим и сравниваем предсказания
print (predictions)
[ 22.32670386 40.26305989 -27.69502682 -56.5548183 18.47241345
31.86585663 6.08896992 -66.15105402 22.99937016 -12.65174022
-78.50894588 30.90311195 56.21877707 -47.81097232 8.98196478
-56.41188567 -24.46096716 -43.55445698 -17.99670898 -9.09225523
65.49657633 26.50900527 4.74114126 -39.28939851 -6.80665816
8.30372903 -15.27594937 -15.15598987 8.34469779 19.45159738]
# Средняя ошибка предсказания
metrics.mean_absolute_error(y_test, predictions)
4.568919119945587
Для валидации можем выполнить перекрестную проверкую cross_val_score
.
http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.cross_val_score.html
Передадим, нашу линейную модель, признаки, ответы, количество разделений кросс-валидация
linear_scoring = cross_validation.cross_val_score(linear_regressor, X, y, cv=10)
print ('Средняя ошибка: {}, Отклонение: {}'.format(linear_scoring.mean(), linear_scoring.std()))
Средняя ошибка: 0.9792410447209384, Отклонение: 0.020331171766276405
# Создаем свое тестирование на основе абсолютной средней ошибкой
scorer = metrics.make_scorer(metrics.mean_absolute_error)
linear_scoring = cross_validation.cross_val_score(linear_regressor, X, y, scoring=scorer, cv=10)
print ('Средняя ошибка: {}, Отклонение: {}'.format(linear_scoring.mean(), linear_scoring.std()))
Средняя ошибка: 4.0700714987797, Отклонение: 1.0737104492890193
# Коэффициенты который дал обучающий датасет
coef
array([38.07925837, 0. ])
# Коэффициент полученный при обучении
linear_regressor.coef_
array([38.18191713, 0.81751244])
# обученная модель так же дает свободный член
linear_regressor.intercept_
-0.6433147737798083
print ("y = {:.2f}*x1 + {:.2f}*x2".format(coef[0], coef[1]))
y = 38.08*x1 + 0.00*x2
print ("y = {:.2f}*x1 + {:.2f}*x2 + {:.2f}".format(linear_regressor.coef_[0],
linear_regressor.coef_[1],
linear_regressor.intercept_))
y = 37.76*x1 + 0.17*x2 + -0.86
# Посмотрим качество обучения
print('Оценка дисперсии:: %.2f' % linear_regressor.score(X_test, y_test))
Оценка дисперсии:: 0.98
Вы уже знаете как создать модель линейной регрессии, можете создать датасет. Давайте посмотрим как влияет количество признаков на качество обучения линейной модели.
Скачайте этот файл, можете редактировать этот ноутбук, да бы уменьшить/увеличить количество признаков.
Будут вопросы пишите нам в Slack канал #machine_learning