Теперь мы наконец подойдем к обучению моделей классификации, сравним на кросс-валидации несколько алгоритмов, разберемся, какие параметры длины сессии (session_length и window_size) лучше использовать. Также для выбранного алгоритма построим кривые валидации (как качество классификации зависит от одного из гиперпараметров алгоритма) и кривые обучения (как качество классификации зависит от объема выборки).
План 4 недели:
В этой части проекта Вам могут быть полезны видеозаписи следующих лекций курса "Обучение на размеченных данных":
# pip install watermark
%load_ext watermark
%watermark -v -m -p numpy,scipy,pandas,matplotlib,statsmodels,sklearn -g
from __future__ import division, print_function
# отключим всякие предупреждения Anaconda
import warnings
warnings.filterwarnings('ignore')
from time import time
import itertools
import os
import numpy as np
import pandas as pd
import seaborn as sns
%matplotlib inline
from matplotlib import pyplot as plt
import pickle
from scipy.sparse import csr_matrix
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold, GridSearchCV
from sklearn.metrics import accuracy_score, f1_score
# Поменяйте на свой путь к данным
PATH_TO_DATA = 'capstone_user_identification'
Загрузим сериализованные ранее объекты X_sparse_10users и y_10users, соответствующие обучающей выборке для 10 пользователей.
with open(os.path.join(PATH_TO_DATA,
'X_sparse_10users.pkl'), 'rb') as X_sparse_10users_pkl:
X_sparse_10users = pickle.load(X_sparse_10users_pkl)
with open(os.path.join(PATH_TO_DATA,
'y_10users.pkl'), 'rb') as y_10users_pkl:
y_10users = pickle.load(y_10users_pkl)
Здесь более 14 тысяч сессий и почти 5 тысяч уникальных посещенных сайтов.
X_sparse_10users.shape
Разобьем выборку на 2 части. На одной будем проводить кросс-валидацию, на второй – оценивать модель, обученную после кросс-валидации.
X_train, X_valid, y_train, y_valid = train_test_split(X_sparse_10users, y_10users,
test_size=0.3,
random_state=17, stratify=y_10users)
Зададим заранее тип кросс-валидации: 3-кратная, с перемешиванием, параметр random_state=17 – для воспроизводимости.
skf = StratifiedKFold(n_splits=3, shuffle=True, random_state=17)
Вспомогательная функция для отрисовки кривых валидации после запуска GridSearchCV (или RandomizedCV).
def plot_validation_curves(param_values, grid_cv_results_):
train_mu, train_std = grid_cv_results_['mean_train_score'], grid_cv_results_['std_train_score']
valid_mu, valid_std = grid_cv_results_['mean_test_score'], grid_cv_results_['std_test_score']
train_line = plt.plot(param_values, train_mu, '-', label='train', color='green')
valid_line = plt.plot(param_values, valid_mu, '-', label='test', color='red')
plt.fill_between(param_values, train_mu - train_std, train_mu + train_std, edgecolor='none',
facecolor=train_line[0].get_color(), alpha=0.2)
plt.fill_between(param_values, valid_mu - valid_std, valid_mu + valid_std, edgecolor='none',
facecolor=valid_line[0].get_color(), alpha=0.2)
plt.legend()
1. Обучите KNeighborsClassifier
со 100 ближайшими соседями (остальные параметры оставьте по умолчанию, только n_jobs
=-1 для распараллеливания) и посмотрите на долю правильных ответов на 3-кратной кросс-валидации (ради воспроизводимости используйте для этого объект StratifiedKFold
skf
) по выборке (X_train, y_train)
и отдельно на выборке (X_valid, y_valid)
.
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier ''' ВАШ КОД ЗДЕСЬ '''
Вопрос 1. Посчитайте доли правильных ответов для KNeighborsClassifier на кросс-валидации и отложенной выборке. Округлите каждое до 3 знаков после запятой и введите через пробел.
''' ВАШ КОД ЗДЕСЬ '''
2. Обучите случайный лес (RandomForestClassifier
) из 100 деревьев (для воспроизводимости random_state
=17). Посмотрите на OOB-оценку (для этого надо сразу установить oob_score
=True) и на долю правильных ответов на выборке (X_valid, y_valid)
. Для распараллеливания задайте n_jobs
=-1.
from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier ''' ВАШ КОД ЗДЕСЬ '''
Вопрос 2. Посчитайте доли правильных ответов для RandomForestClassifier
при Out-of-Bag оценке и на отложенной выборке. Округлите каждое до 3 знаков после запятой и введите через пробел.
write_answer_to_file(''' ВАШ КОД ЗДЕСЬ ''',
'answer4_2.txt')
!cat answer4_2.txt
3. Обучите логистическую регрессию (LogisticRegression
) с параметром C
по умолчанию и random_state
=17 (для воспроизводимости). Посмотрите на долю правильных ответов на кросс-валидации (используйте объект skf
, созданный ранее) и на выборке (X_valid, y_valid)
. Для распараллеливания задайте n_jobs=-1
.
from sklearn.linear_model import LogisticRegression, LogisticRegressionCV
logit = LogisticRegression ''' ВАШ КОД ЗДЕСЬ '''
Почитайте документацию к LogisticRegressionCV. Логистическая регрессия хорошо изучена, и для нее существуют алгоритмы быстрого подбора параметра регуляризации C
(быстрее, чем с GridSearchCV
).
С помощью LogisticRegressionCV
подберите параметр C
для LogisticRegression
сначала в широком диапазоне: 10 значений от 1e-4 до 1e2, используйте logspace
из NumPy
. Укажите у LogisticRegressionCV
параметры multi_class
='multinomial' и random_state
=17. Для кросс-валидации используйте объект skf
, созданный ранее. Для распараллеливания задайте n_jobs=-1
.
Нарисуйте кривые валидации по параметру C
.
%%time
logit_c_values1 = np.logspace(-4, 2, 10)
logit_grid_searcher1 = LogisticRegressionCV ''' ВАШ КОД ЗДЕСЬ '''
logit_grid_searcher1.fit(X_train, y_train)
Средние значения доли правильных ответов на кросс-валидации по каждому из 10 параметров C
.
logit_mean_cv_scores1 = ''' ВАШ КОД ЗДЕСЬ '''
Выведите лучшее значение доли правильных ответов на кросс-валидации и соответствующее значение C
.
''' ВАШ КОД ЗДЕСЬ '''
Нарисуйте график зависимости доли правильных ответов на кросс-валидации от C
.
plt.plot(logit_c_values1, logit_mean_cv_scores1);
Теперь то же самое, только значения параметра C
перебирайте в диапазоне np.linspace
(0.1, 7, 20). Опять нарисуйте кривые валидации, определите максимальное значение доли правильных ответов на кросс-валидации.
%%time
logit_c_values2 = np.linspace(0.1, 7, 20)
logit_grid_searcher2 = LogisticRegressionCV ''' ВАШ КОД ЗДЕСЬ '''
logit_grid_searcher2.fit(X_train, y_train)
Средние значения доли правильных ответов на кросс-валидации по каждому из 10 параметров C
.
''' ВАШ КОД ЗДЕСЬ '''
Выведите лучшее значение доли правильных ответов на кросс-валидации и соответствующее значение C
.
''' ВАШ КОД ЗДЕСЬ '''
Нарисуйте график зависимости доли правильных ответов на кросс-валидации от C
.
plt.plot(logit_c_values2, logit_mean_cv_scores2);
Выведите долю правильных ответов на выборке (X_valid, y_valid)
для логистической регрессии с лучшим найденным значением C
.
logit_cv_acc = accuracy_score ''' ВАШ КОД ЗДЕСЬ '''
Вопрос 3. Посчитайте доли правильных ответов для logit_grid_searcher2
на кросс-валидации для лучшего значения параметра C
и на отложенной выборке. Округлите каждое до 3 знаков после запятой и выведите через пробел.
''' ВАШ КОД ЗДЕСЬ '''
4. Обучите линейный SVM (LinearSVC
) с параметром C
=1 и random_state
=17 (для воспроизводимости). Посмотрите на долю правильных ответов на кросс-валидации (используйте объект skf
, созданный ранее) и на выборке (X_valid, y_valid)
.
from sklearn.svm import LinearSVC
svm = LinearSVC ''' ВАШ КОД ЗДЕСЬ '''
С помощью GridSearchCV
подберите параметр C
для SVM сначала в широком диапазоне: 10 значений от 1e-4 до 1e4, используйте linspace
из NumPy. Нарисуйте кривые валидации.
%%time
svm_params1 = {'C': np.linspace(1e-4, 1e4, 10)}
svm_grid_searcher1 = GridSearchCV ''' ВАШ КОД ЗДЕСЬ '''
svm_grid_searcher1.fit(X_train, y_train)
Выведите лучшее значение доли правильных ответов на кросс-валидации и соответствующее значение C
.
''' ВАШ КОД ЗДЕСЬ '''
Нарисуйте график зависимости доли правильных ответов на кросс-валидации от C
.
plot_validation_curves(svm_params1['C'], svm_grid_searcher1.cv_results_)
Но мы помним, что с параметром регуляризации по умолчанию (С=1) на кросс-валидации доля правильных ответов выше. Это тот случай (не редкий), когда можно ошибиться и перебирать параметры не в том диапазоне (причина в том, что мы взяли равномерную сетку на большом интервале и упустили действительно хороший интервал значений C
). Здесь намного осмысленней подбирать C
в районе 1, к тому же, так модель быстрее обучается, чем при больших C
.
С помощью GridSearchCV
подберите параметр C
для SVM в диапазоне (1e-3, 1), 30 значений, используйте linspace
из NumPy. Нарисуйте кривые валидации.
%%time
svm_params2 = {'C': np.linspace(1e-3, 1, 30)}
svm_grid_searcher2 = GridSearchCV ''' ВАШ КОД ЗДЕСЬ '''
svm_grid_searcher2.fit(X_train, y_train)
Выведите лучшее значение доли правильных ответов на кросс-валидации и соответствующее значение C
.
''' ВАШ КОД ЗДЕСЬ '''
Нарисуйте график зависимости доли правильных ответов на кросс-валидации от С.
plot_validation_curves(svm_params2['C'], svm_grid_searcher2.cv_results_)
Выведите долю правильных ответов на выборке (X_valid, y_valid)
для LinearSVC
с лучшим найденным значением C
.
svm_cv_acc = accuracy_score ''' ВАШ КОД ЗДЕСЬ '''
Вопрос 4. Посчитайте доли правильных ответов для svm_grid_searcher2
на кросс-валидации для лучшего значения параметра C
и на отложенной выборке. Округлите каждое до 3 знаков после запятой и выведите через пробел.
''' ВАШ КОД ЗДЕСЬ '''
Возьмем LinearSVC
, показавший лучшее качество на кросс-валидации в 1 части, и проверим его работу еще на 8 выборках для 10 пользователей (с разными сочетаниями параметров session_length и window_size). Поскольку тут уже вычислений побольше, мы не будем каждый раз заново подбирать параметр регуляризации C
.
Определите функцию model_assessment
, ее документация описана ниже. Обратите внимание на все детали. Например, на то, что разбиение выборки с train_test_split
должно быть стратифицированным. Не теряйте нигде random_state
.
def model_assessment(estimator, path_to_X_pickle, path_to_y_pickle, cv, random_state=17, test_size=0.3):
'''
Estimates CV-accuracy for (1 - test_size) share of (X_sparse, y)
loaded from path_to_X_pickle and path_to_y_pickle and holdout accuracy for (test_size) share of (X_sparse, y).
The split is made with stratified train_test_split with params random_state and test_size.
:param estimator – Scikit-learn estimator (classifier or regressor)
:param path_to_X_pickle – path to pickled sparse X (instances and their features)
:param path_to_y_pickle – path to pickled y (responses)
:param cv – cross-validation as in cross_val_score (use StratifiedKFold here)
:param random_state – for train_test_split
:param test_size – for train_test_split
:returns mean CV-accuracy for (X_train, y_train) and accuracy for (X_valid, y_valid) where (X_train, y_train)
and (X_valid, y_valid) are (1 - test_size) and (testsize) shares of (X_sparse, y).
'''
''' ВАШ КОД ЗДЕСЬ '''
Убедитесь, что функция работает.
model_assessment(svm_grid_searcher2.best_estimator_,
os.path.join(PATH_TO_DATA, 'X_sparse_10users.pkl'),
os.path.join(PATH_TO_DATA, 'y_10users.pkl'), skf, random_state=17, test_size=0.3)
Примените функцию model_assessment для лучшего алгоритма из предыдущей части (а именно, svm_grid_searcher2.best_estimator_
) и 9 выборок вида с разными сочетаниями параметров session_length и window_size для 10 пользователей. Выведите в цикле параметры session_length и window_size, а также результат вывода функции model_assessment.
Удобно сделать так, чтоб model_assessment возвращала 3-им элементом время, за которое она выполнилась. На моем ноуте этот участок кода выполнился за 20 секунд. Но со 150 пользователями каждая итерация занимает уже несколько минут.
Здесь для удобства стоит создать копии ранее созданных pickle-файлов X_sparse_10users.pkl, X_sparse_150users.pkl, y_10users.pkl и y_150users.pkl, добавив к их названиям s10_w10, что означает длину сессии 10 и ширину окна 10.
!cp $PATH_TO_DATA/X_sparse_10users.pkl $PATH_TO_DATA/X_sparse_10users_s10_w10.pkl
!cp $PATH_TO_DATA/X_sparse_150users.pkl $PATH_TO_DATA/X_sparse_150users_s10_w10.pkl
!cp $PATH_TO_DATA/y_10users.pkl $PATH_TO_DATA/y_10users_s10_w10.pkl
!cp $PATH_TO_DATA/y_150users.pkl $PATH_TO_DATA/y_150users_s10_w10.pkl
%%time
estimator = svm_grid_searcher2.best_estimator_
for window_size, session_length in itertools.product([10, 7, 5], [15, 10, 7, 5]):
if window_size <= session_length:
path_to_X_pkl = ''' ВАШ КОД ЗДЕСЬ '''
path_to_y_pkl = ''' ВАШ КОД ЗДЕСЬ '''
print ''' ВАШ КОД ЗДЕСЬ '''
Вопрос 5. Посчитайте доли правильных ответов для LinearSVC
с настроенным параметром C
и выборки X_sparse_10users_s15_w5
. Укажите доли правильных ответов на кросс-валидации и на отложенной выборке. Округлите каждое до 3 знаков после запятой и выведите через пробел.
''' ВАШ КОД ЗДЕСЬ '''
Прокомментируйте полученные результаты. Сравните для 150 пользователей доли правильных ответов на кросс-валидации и оставленной выборке для сочетаний параметров (session_length, window_size): (5,5), (7,7) и (10,10). На среднем ноуте это может занять до часа – запаситесь терпением, это Data Science :)
Сделайте вывод о том, как качество классификации зависит от длины сессии и ширины окна.
%%time
estimator = svm_grid_searcher2.best_estimator_
for window_size, session_length in [(5,5), (7,7), (10,10)]:
path_to_X_pkl = ''' ВАШ КОД ЗДЕСЬ '''
path_to_y_pkl = ''' ВАШ КОД ЗДЕСЬ '''
print ''' ВАШ КОД ЗДЕСЬ '''
Вопрос 6. Посчитайте доли правильных ответов для LinearSVC
с настроенным параметром C
и выборки X_sparse_150users
. Укажите доли правильных ответов на кросс-валидации и на отложенной выборке. Округлите каждое до 3 знаков после запятой и выведите через пробел.
''' ВАШ КОД ЗДЕСЬ '''
Поскольку может разочаровать, что многоклассовая доля правильных ответов на выборке из 150 пользовалей невелика, порадуемся тому, что конкретного пользователя можно идентифицировать достаточно хорошо.
Загрузим сериализованные ранее объекты X_sparse_150users и y_150users, соответствующие обучающей выборке для 150 пользователей с параметрами (session_length, window_size) = (10,10). Так же точно разобьем их на 70% и 30%.
with open(os.path.join(PATH_TO_DATA, 'X_sparse_150users.pkl'), 'rb') as X_sparse_150users_pkl:
X_sparse_150users = pickle.load(X_sparse_150users_pkl)
with open(os.path.join(PATH_TO_DATA, 'y_150users.pkl'), 'rb') as y_150users_pkl:
y_150users = pickle.load(y_150users_pkl)
X_train_150, X_valid_150, y_train_150, y_valid_150 = train_test_split(X_sparse_150users,
y_150users, test_size=0.3,
random_state=17, stratify=y_150users)
Обучите LogisticRegressionCV
для одного значения параметра C
(лучшего на кросс-валидации в 1 части, используйте точное значение, не на глаз). Теперь будем решать 150 задач "Один-против-Всех", поэтому укажите аргумент multi_class
='ovr'. Как всегда, где возможно, указывайте n_jobs=-1
и random_state
=17.
%%time
logit_cv_150users = LogisticRegressionCV ''' ВАШ КОД ЗДЕСЬ '''
logit_cv_150users.fit(X_train_150, y_train_150)
Посмотрите на средние доли правильных ответов на кросс-валидации в задаче идентификации каждого пользователя по отдельности.
cv_scores_by_user = {}
for user_id in logit_cv_150users.scores_:
print('User {}, CV score: {}'.format ''' ВАШ КОД ЗДЕСЬ '''
Результаты кажутся впечатляющими, но возможно, мы забываем про дисбаланс классов, и высокую долю правильных ответов можно получить константным прогнозом. Посчитайте для каждого пользователя разницу между долей правильных ответов на кросс-валидации (только что посчитанную с помощью LogisticRegressionCV
) и долей меток в y_train_150, отличных от ID
этого пользователя (именно такую долю правильных ответов можно получить, если классификатор всегда "говорит", что это не пользователь с номером $i$ в задаче классификации $i$-vs-All).
class_distr = np.bincount(y_train_150.astype('int'))
for user_id in np.unique(y_train_150):
''' ВАШ КОД ЗДЕСЬ '''
num_better_than_default = (np.array(list(acc_diff_vs_constant.values())) > 0).sum()
Вопрос 7. Посчитайте долю пользователей, для которых логистическая регрессия на кросс-валидации дает прогноз лучше константного. Округлите до 3 знаков после запятой.
''' ВАШ КОД ЗДЕСЬ '''
Дальше будем строить кривые обучения для конкретного пользователя, допустим, для 128-го. Составьте новый бинарный вектор на основе y_150users, его значения будут 1 или 0 в зависимости от того, равен ли ID-шник пользователя 128.
y_binary_128 = ''' ВАШ КОД ЗДЕСЬ '''
from sklearn.model_selection import learning_curve
def plot_learning_curve(val_train, val_test, train_sizes,
xlabel='Training Set Size', ylabel='score'):
def plot_with_err(x, data, **kwargs):
mu, std = data.mean(1), data.std(1)
lines = plt.plot(x, mu, '-', **kwargs)
plt.fill_between(x, mu - std, mu + std, edgecolor='none',
facecolor=lines[0].get_color(), alpha=0.2)
plot_with_err(train_sizes, val_train, label='train')
plot_with_err(train_sizes, val_test, label='valid')
plt.xlabel(xlabel); plt.ylabel(ylabel)
plt.legend(loc='lower right');
Посчитайте доли правильных ответов на кросс-валидации в задаче классификации "user128-vs-All" в зависимости от размера выборки. Не помешает посмотреть встроенную документацию для learning_curve.
%%time
train_sizes = np.linspace(0.25, 1, 20)
estimator = svm_grid_searcher2.best_estimator_
n_train, val_train, val_test = learning_curve ''' ВАШ КОД ЗДЕСЬ '''
plot_learning_curve(val_train, val_test, n_train,
xlabel='train_size', ylabel='accuracy')
Сделайте выводы о том, помогут ли алгоритму новые размеченные данные при той же постановке задачи.
На следующей неделе мы вспомним про линейные модели, обучаемые стохастическим градиентным спуском, и порадуемся тому, насколько быстрее они работают. Также сделаем первые (или не первые) посылки в соревновании Kaggle Inclass.