Открытый курс по машинному обучению

Автор материала: Юрий Кашницкий, программист-исследователь Mail.Ru Group

Материал распространяется на условиях лицензии Creative Commons CC BY-NC-SA 4.0. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала.

Домашнее задание № 8 (демо)

Реализация алгоритмов онлайн-обучения

Вам предлагается реализовать два алгоритма – регрессор и классификатор, обучаемые стохастическим градиентным спуском (Stochastic Gradient Descent, SGD). Веб-форма для ответов.

План домашнего задания

1. Линейная регрессия и SGD
2. Логистическая регрессия и SGD
3. Логистическая регрессия и SGDClassifier в задаче классификации отзывов к фильмам

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

1. Линейная регрессия и SGD

In [1]:
import numpy as np
import pandas as pd
from tqdm import tqdm
from sklearn.base import BaseEstimator
from sklearn.metrics import mean_squared_error, log_loss, roc_auc_score
from sklearn.model_selection import train_test_split
%matplotlib inline
from matplotlib import pyplot as plt
import seaborn as sns
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_.
In [2]:
class SGDRegressor(BaseEstimator):
    ''' ВАШ КОД ЗДЕСЬ '''
                  

Проверим работу алгоритма на данных по росту и весу. Будем прогнозировать рост (в дюймах) по весу (в фунтах).

In [3]:
data_demo = pd.read_csv('../../data/weights_heights.csv')
In [4]:
plt.scatter(data_demo['Weight'], data_demo['Height']);
plt.xlabel('Вес (фунты)')
plt.ylabel('Рост (дюймы)');
In [5]:
X, y = data_demo['Weight'].values, data_demo['Height'].values

Выделим 70% под обучение, 30% – под проверку и масштабируем выборку.

In [6]:
X_train, X_valid, y_train, y_valid = train_test_split(X, y,
                                                     test_size=0.3,
                                                     random_state=17)
In [7]:
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). Параметры оставьте по умолчанию.

In [8]:
''' ВАШ КОД ЗДЕСЬ '''
Out[8]:
' ВАШ КОД ЗДЕСЬ '

Изобразите на графике процесс обучения – как среднеквадратичная ошибка зависит от номера итерации стохастического градиентного спуска.

In [9]:
''' ВАШ КОД ЗДЕСЬ '''
Out[9]:
' ВАШ КОД ЗДЕСЬ '

Выведите наименьшее значение среднеквадратичной ошибки и лучший вектор весов модели.

In [10]:
''' ВАШ КОД ЗДЕСЬ '''
Out[10]:
' ВАШ КОД ЗДЕСЬ '

Постройте график того, как менялись значения весов модели ($w_0$ и $w_1$) по мере обучения.

In [11]:
''' ВАШ КОД ЗДЕСЬ '''
Out[11]:
' ВАШ КОД ЗДЕСЬ '

Сделайте прогноз для отложенной выборки (X_valid_scaled, y_valid) и посмотрите на MSE.

In [12]:
''' ВАШ КОД ЗДЕСЬ '''
Out[12]:
' ВАШ КОД ЗДЕСЬ '

Теперь следайте то же самое, но с LinearRegression из sklearn.linear_model. Посчитайте MSE для отложенной выборки.

In [13]:
''' ВАШ КОД ЗДЕСЬ '''
Out[13]:
' ВАШ КОД ЗДЕСЬ '

Вопрос 1. В каком знаке после разделителя отличаются MSE линейной регрессии и SGDRegressor для отложенной выборки?

  • 2
  • 3
  • 4
  • 5

2. Логистическая регрессия и SGD

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

Задача классификации, $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. По какой формуле будут пересчитываться веса логистической регрессии при обучении стохастическим градиентным спуском?

  • $w_j^{(t+1)} = w_j^{(t)} + \eta (Cy_i x_{ij} \sigma(y_iw^Tx_i) + \delta_{j\neq0} w_j)$
  • $w_j^{(t+1)} = w_j^{(t)} - \eta (Cy_i x_{ij} \sigma(-y_iw^Tx_i) + \delta_{j\neq0}w_j)$
  • $w_j^{(t+1)} = w_j^{(t)} - \eta (Cy_i x_{ij} \sigma(-y_iw^Tx_i) - \delta_{j\neq0}w_j )$
  • $w_j^{(t+1)} = w_j^{(t)} + \eta (Cy_i x_{ij} \sigma(-y_iw^Tx_i) - \delta_{j\neq0}w_j)$

Здесь

  • $i \in {0,\ldots, \ell-1}, j \in {0,\ldots, d}$
  • C – коэффициент регуляризации
  • $x_{ij} $ – элемент матрицы X в строке $i$ и столбце $j$ (нумерация с 0),
  • $x_i$ – $i$-ая строка матрицы $X$ (нумерация с 0),
  • $w_j^{(t)}$ – значение $j$-ого элемента вектора весов $w$ на шаге $t$ стохастического градиентного спуска
  • $\eta$ – небольшая константа, шаг градиентного спуска
  • $\delta_{j\neq0}$ – символ Кронекера, то есть 1, когда $j\neq0$ и $0$ – в противном случае

Реализуйте класс 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 – в противном случае
  • И еще важный момент: во избежание вычислительных проблем из-за слишком больших или малых значений под экспонентной (overflow & underflow) используйте написанную функцию sigma
In [14]:
def sigma(z):
    z = z.flatten()
    z[z > 100] = 100
    z[z < -100] = -100
    return 1. / (1 + np.exp(-z))
In [15]:
class SGDClassifier(BaseEstimator):
    ''' ВАШ КОД ЗДЕСЬ '''
                  

Проверим SGDClassifier на данных UCI по раку молочной железы.

In [16]:
from sklearn.datasets import load_breast_cancer
In [17]:
cancer = load_breast_cancer()
# поменяем метки в y с 0 на -1
X, y = cancer.data, [-1 if i == 0 else 1 for i in cancer.target]

Выделим 70% под обучение, 30% – под проверку и масштабируем выборку.

In [18]:
X_train, X_valid, y_train, y_valid = train_test_split(X, y,
                                                     test_size=0.3,
                                                     random_state=17)
In [19]:
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.

In [20]:
''' ВАШ КОД ЗДЕСЬ '''
Out[20]:
' ВАШ КОД ЗДЕСЬ '

Постройте график изменения log_loss.

In [21]:
''' ВАШ КОД ЗДЕСЬ '''
Out[21]:
' ВАШ КОД ЗДЕСЬ '

Теперь обучите SGDClassifier с параметром C=1000, число проходов по выборке увеличьте до 10.

In [22]:
''' ВАШ КОД ЗДЕСЬ '''
Out[22]:
' ВАШ КОД ЗДЕСЬ '

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

Вопрос 3. Какой признак сильнее остальных влияет на вероятность того, что опухоль доброкачественна, согласно обученной модели SGDClassifier? (будьте внимательны – проверьте длину вектора весов, полученного после обучения, сравните с числом признаков в исходной задаче)

  • worst compactness
  • worst smoothness
  • worst concavity
  • concave points error
  • concavity error
  • compactness error
  • worst fractal dimension
In [23]:
''' ВАШ КОД ЗДЕСЬ '''
Out[23]:
' ВАШ КОД ЗДЕСЬ '

Посчитайте log_loss и ROC AUC на отложенной выборке, проделайте все то же с sklearn.linear_model.LogisticRegression (параметры по умолчанию, только random_state=17) и сравните результаты.

In [24]:
''' ВАШ КОД ЗДЕСЬ '''
Out[24]:
' ВАШ КОД ЗДЕСЬ '

3. Логистическая регрессия и SGDClassifier в задаче классификации отзывов к фильмам

Теперь посмотрим на логистическую регрессию и ее же версию, но обучаемую стохастическим градиентным спуском, в задаче классификации отзывов IMDB. С этой задачей мы знакомы по 4 и 8 темам курса. Данные можно скачать отсюда.

Импортируем файлы, и обучим на имеющихся данных CountVectorizer

In [25]:
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import SGDClassifier
In [26]:
# поменяйте путь к файлу
reviews_train = load_files("/Users/y.kashnitsky/Documents/Machine_learning/datasets/imdb_reviews/train")
text_train, y_train = reviews_train.data, reviews_train.target
In [27]:
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 млн.

In [28]:
%%time
cv = CountVectorizer(ngram_range=(1, 2))
X_train = cv.fit_transform(text_train)
X_test = cv.transform(text_test)
CPU times: user 35.6 s, sys: 977 ms, total: 36.6 s
Wall time: 36 s
In [29]:
X_train.shape, X_test.shape
Out[29]:
((25000, 1513832), (25000, 1513832))

Обучите на выборке (X_train, y_train) логистическую регрессию с параметрами по умолчанию (только укажите random_state=17) и посчитайте ROC AUC на тестовой выборке. Замерьте время обучения модели. Данные можно не масштабировать, так как признаки – по сути, счетчики, и они уже все измеряются примерно в одном диапазоне.

In [30]:
''' ВАШ КОД ЗДЕСЬ '''
Out[30]:
' ВАШ КОД ЗДЕСЬ '

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

Вопрос 4. Чем sklearn-реализация стохастического классификатора более продвинута, чем SGDClassifier, который мы реализовали? Отметьте все подходящие варианты.

  • Изменяемый шаг градиентного спуска
  • Реализован линейный SVM
  • Реализована ранняя остановка во избежание переобучения
  • Есть распараллеливание по процессорам
  • Можно обучать LASSO
  • Поддерживается онлайн-обучение деревьев решений
  • Поддерживается mini-batch подход (обновление весов по нескольким объектом сразу, а не по одному)

Проведите 100 итераций SGD-логрегрессии (опять random_state=17) на той же выборке. Опять замерьте время обучения модели и обратите внимание, насколько оно меньше, чем время обучения логистической регрессии.

In [31]:
''' ВАШ КОД ЗДЕСЬ '''
Out[31]:
' ВАШ КОД ЗДЕСЬ '

Вопрос 5. В каком знаке после разделителя отличаются ROC AUC на тестовой выборке логистической регрессии и SGD-классификатора Sklearn с логистической функцией потерь?

  • 2
  • 3
  • 4
  • 5