Автор материала: Юрий Кашницкий (@yorko в Slack ODS). Материал распространяется на условиях лицензии Creative Commons CC BY-NC-SA 4.0. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала.
Вам предлагается реализовать два алгоритма – регрессор и классификатор, обучаемые стохастическим градиентным спуском (Stochastic Gradient Descent, SGD). Веб-форма для ответов.
1. Линейная регрессия и SGD
2. Логистическая регрессия и SGD
3. Логистическая регрессия и SGDClassifier в задаче классификации отзывов к фильмам
В статье было описано, как таким образом обучать регрессор, т.е. минимизировать квадратичную функцию потерь. Реализуем этот алгоритм.
import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator
from sklearn.metrics import log_loss, mean_squared_error, roc_auc_score
from sklearn.model_selection import train_test_split
from tqdm import tqdm
%matplotlib inline
import seaborn as sns
from matplotlib import pyplot as plt
from sklearn.preprocessing import StandardScaler
Реализуйте класс SGDRegressor
. Спецификация:
sklearn.base.BaseEstimator
eta
– шаг градиентного спуска (по умолчанию $10^{-3}$) и n_iter
– число проходов по выборке (по умолчанию 10)mse_
и weights_
для отслеживания значений среднеквадратичной ошибки и вектора весов по итерациям градиентного спускаfit
и predict
fit
принимает матрицу X
и вектор y
(объекты numpy.array
), добавляет к матрице X
слева столбец из единиц, инициализирует вектор весов w
нулями и в цикле с числом итераций n_iter
обновляет веса (см. статью), а также записывает получившиеся на данной итерации значения среднеквадратичной ошибки (именно MSE, SE слишком большими будут) и вектор весов w
в предназначенные для этого списки.fit
создает переменную w_
, в которой хранится тот вектор весов, при котором ошибка минимальнаfit
должен возвращать текущий экземпляр класса SGDRegressor
, т.е. self
predict
принимает матрицу X
, добавляет к ней слева столбец из единиц и возвращает вектор прогнозов модели, используя созданный методом fit
вектор весов w_
.class SGDRegressor(BaseEstimator):
""" ВАШ КОД ЗДЕСЬ """
Проверим работу алгоритма на данных по росту и весу. Будем прогнозировать рост (в дюймах) по весу (в фунтах).
data_demo = pd.read_csv("../../data/weights_heights.csv")
plt.scatter(data_demo["Weight"], data_demo["Height"])
plt.xlabel("Вес (фунты)")
plt.ylabel("Рост (дюймы)");
X, y = data_demo["Weight"].values, data_demo["Height"].values
Выделим 70% под обучение, 30% – под проверку и масштабируем выборку.
X_train, X_valid, y_train, y_valid = train_test_split(
X, y, test_size=0.3, random_state=17
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train.reshape([X_train.shape[0], 1]))
X_valid_scaled = scaler.transform(X_valid.reshape([X_valid.shape[0], 1]))
Обучите созданный вами SGDRegressor
на выборке (X_train_scaled, y_train)
. Параметры оставьте по умолчанию.
""" ВАШ КОД ЗДЕСЬ """
Изобразите на графике процесс обучения – как среднеквадратичная ошибка зависит от номера итерации стохастического градиентного спуска.
""" ВАШ КОД ЗДЕСЬ """
Выведите наименьшее значение среднеквадратичной ошибки и лучший вектор весов модели.
""" ВАШ КОД ЗДЕСЬ """
Постройте график того, как менялись значения весов модели ($w_0$ и $w_1$) по мере обучения.
""" ВАШ КОД ЗДЕСЬ """
Сделайте прогноз для отложенной выборки (X_valid_scaled, y_valid)
и посмотрите на MSE.
""" ВАШ КОД ЗДЕСЬ """
Теперь следайте то же самое, но с LinearRegression
из sklearn.linear_model
. Посчитайте MSE для отложенной выборки.
""" ВАШ КОД ЗДЕСЬ """
Вопрос 1. В каком знаке после разделителя отличаются MSE линейной регрессии и SGDRegressor
для отложенной выборки?
Теперь давайте разберемся, как при таком же стохастическом подходе обучать логистическую регрессию.
Задача классификации, $X$ – обучающая выборка размеров $\ell \times (d+1)$ (первый столбец – вектор из единиц), $y$ – вектор ответов, $y_i \in \{-1, 1\}$. В 4 статье серии мы подробно разбирали, как логистическая регрессия с $L_2$-регуляризацией сводится к задаче минимизации: $$ C\sum_{i=1}^\ell \log{(1 + e^{-y_iw^Tx_i})} + \frac{1}{2}\sum_{j=1}^d w_j^2 \rightarrow min_w$$
Вопрос 2. По какой формуле будут пересчитываться веса логистической регрессии при обучении стохастическим градиентным спуском?
Здесь
Реализуйте класс SGDClassifier
. Спецификация:
sklearn.base.BaseEstimator
eta
– шаг градиентного спуска (по умолчанию $10^{-3}$), n_iter
– число проходов по выборке (по умолчанию 10) и C – коэффициент регуляризацииloss_
и weights_
для отслеживания значений логистических потерь и вектора весов по итерациям градиентного спускаfit
, predict
и predict_proba
fit
принимает матрицу X
и вектор y
(объекты numpy.array
, рассматриваем только случай бинарной классификации, и значения в векторе y
могут быть -1 и 1), добавляет к матрице X
слева столбец из единиц, инициализирует вектор весов w
нулями и в цикле с числом итераций n_iter
обновляет веса по выведенной вами формуле, а также записывает получившиеся на данной итерации значения log_loss и вектор весов w
в предназначенные для этого списки.fit
создает переменную w_
, в которой хранится тот вектор весов, при котором ошибка минимальнаfit
должен возвращать текущий экземпляр класса SGDClassifier
, т.е. self
predict_proba
принимает матрицу X
, добавляет к ней слева столбец из единиц и возвращает матрицу прогнозов модели (такую же, какую возвращают методы predict_proba
моделей sklearn
), используя созданный методом fit
вектор весов w_
predict
вызывает метод predict_proba
и возвращает вектор ответов: -1, если предсказанная вероятность класса 1 меньше 0.5 и 1 – в противном случаеsigma
def sigma(z):
z = z.flatten()
z[z > 100] = 100
z[z < -100] = -100
return 1.0 / (1 + np.exp(-z))
class SGDClassifier(BaseEstimator):
""" ВАШ КОД ЗДЕСЬ """
Проверим SGDClassifier
на данных UCI по раку молочной железы.
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
# поменяем метки в y с 0 на -1
X, y = cancer.data, [-1 if i == 0 else 1 for i in cancer.target]
Выделим 70% под обучение, 30% – под проверку и масштабируем выборку.
X_train, X_valid, y_train, y_valid = train_test_split(
X, y, test_size=0.3, random_state=17
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_valid_scaled = scaler.transform(X_valid)
Обучите на масштибированной выборке SGDClassifier
с параметрами C
=1, eta
=$10^{-3}$ и n_iter
=3.
""" ВАШ КОД ЗДЕСЬ """
Постройте график изменения log_loss.
""" ВАШ КОД ЗДЕСЬ """
Теперь обучите SGDClassifier
с параметром C
=1000, число проходов по выборке увеличьте до 10.
""" ВАШ КОД ЗДЕСЬ """
Посмотрите на веса модели, при которых ошибка на обучении была минимальна.
Вопрос 3. Какой признак сильнее остальных влияет на вероятность того, что опухоль доброкачественна, согласно обученной модели SGDClassifier
? (будьте внимательны – проверьте длину вектора весов, полученного после обучения, сравните с числом признаков в исходной задаче)
""" ВАШ КОД ЗДЕСЬ """
Посчитайте log_loss и ROC AUC на отложенной выборке, проделайте все то же с sklearn.linear_model.LogisticRegression
(параметры по умолчанию, только random_state=17) и сравните результаты.
""" ВАШ КОД ЗДЕСЬ """
Теперь посмотрим на логистическую регрессию и ее же версию, но обучаемую стохастическим градиентным спуском, в задаче классификации отзывов IMDB. С этой задачей мы знакомы по 4 и 8 темам курса. Данные можно скачать отсюда.
Импортируем файлы, и обучим на имеющихся данных CountVectorizer
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import SGDClassifier
# поменяйте путь к файлу
reviews_train = load_files(
"/Users/y.kashnitsky/Documents/Machine_learning/datasets/imdb_reviews/train"
)
text_train, y_train = reviews_train.data, reviews_train.target
reviews_test = load_files(
"/Users/y.kashnitsky/Documents/Machine_learning/datasets/imdb_reviews/test"
)
text_test, y_test = reviews_test.data, reviews_test.target
Обучим на имеющихся данных CountVectorizer
, считая биграммы, то есть перейдем к разреженному представлению данных, где каждому уникальному слову и паре подряд идущих слов в обучающей выборке соответсвует признак. Всего таких признаков получается более 1.5 млн.
%%time
cv = CountVectorizer(ngram_range=(1, 2))
X_train = cv.fit_transform(text_train)
X_test = cv.transform(text_test)
X_train.shape, X_test.shape
Обучите на выборке (X_train, y_train)
логистическую регрессию с параметрами по умолчанию (только укажите random_state
=17) и посчитайте ROC AUC на тестовой выборке. Замерьте время обучения модели. Данные можно не масштабировать, так как признаки – по сути, счетчики, и они уже все измеряются примерно в одном диапазоне.
""" ВАШ КОД ЗДЕСЬ """
Теперь перейдем к онлайн-алгоритму. Мы написали свой SGDClassifier
и принцип его работы поняли, надо еще немного постараться, чтобы сделать его эффективным, например, сделать поддержку разреженных данных. Но мы теперь перейдем к sklearn
-реализации SGD-алгоритма. Прочитайте документацию SGDClassifier, сделайте выводы, чем SGDClassifier
из Sklearn
более продвинут, чем наша реализация SGD-классификатора.
Вопрос 4. Чем sklearn
-реализация стохастического классификатора более продвинута, чем SGDClassifier
, который мы реализовали? Отметьте все подходящие варианты.
Проведите 100 итераций SGD-логрегрессии (опять random_state
=17) на той же выборке. Опять замерьте время обучения модели и обратите внимание, насколько оно меньше, чем время обучения логистической регрессии.
""" ВАШ КОД ЗДЕСЬ """
Вопрос 5. В каком знаке после разделителя отличаются ROC AUC на тестовой выборке логистической регрессии и SGD-классификатора Sklearn
с логистической функцией потерь?