mlcourse.ai – открытый курс OpenDataScience по машинному обучению

Авторы материала: Ольга Дайховская (@aiho в Slack ODS), Юрий Кашницкий (@yorko в Slack ODS). Материал распространяется на условиях лицензии Creative Commons CC BY-NC-SA 4.0. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала.

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

Обучение без учителя: метод главных компонент и кластеризация

В этом задании мы разберемся с тем, как работают методы снижения размерности и кластеризации данных. Заодно еще раз попрактикуемся в задаче классификации.

Мы будем работать с набором данных Samsung Human Activity Recognition. Скачайте данные отсюда. Данные поступают с акселерометров и гироскопов мобильных телефонов Samsung Galaxy S3 (подробнее про признаки – по ссылке на UCI выше), также известен вид активности человека с телефоном в кармане – ходил ли он, стоял, лежал, сидел или шел вверх/вниз по лестнице.

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

Заполните код в клетках (где написано "Ваш код здесь") и ответьте на вопросы в веб-форме.

In [ ]:
import numpy as np
import pandas as pd
import seaborn as sns
from tqdm import tqdm_notebook

%matplotlib inline
from matplotlib import pyplot as plt

plt.style.use(['seaborn-darkgrid'])
plt.rcParams['figure.figsize'] = (12, 9)
plt.rcParams['font.family'] = 'DejaVu Sans'

from sklearn import metrics
from sklearn.cluster import AgglomerativeClustering, KMeans, SpectralClustering
from sklearn.decomposition import PCA
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC

RANDOM_STATE = 17
In [ ]:
X_train = np.loadtxt("../../data/samsung_HAR/samsung_train.txt")
y_train = np.loadtxt("../../data/samsung_HAR/samsung_train_labels.txt").astype(int)

X_test = np.loadtxt("../../data/samsung_HAR/samsung_test.txt")
y_test = np.loadtxt("../../data/samsung_HAR/samsung_test_labels.txt").astype(int)
In [ ]:
# Проверим размерности
assert(X_train.shape == (7352, 561) and y_train.shape == (7352,))
assert(X_test.shape == (2947, 561) and y_test.shape == (2947,))

Для кластеризации нам не нужен вектор ответов, поэтому будем работать с объединением обучающей и тестовой выборок. Объедините X_train с X_test, а y_train – с y_test.

In [ ]:
# Ваш код здесь
X = 
y = 

Определим число уникальных значений меток целевого класса.

In [ ]:
np.unique(y)
In [ ]:
n_classes = np.unique(y).size

Эти метки соответствуют:

  • 1 - ходьбе
  • 2 - подъему вверх по лестнице
  • 3 - спуску по лестнице
  • 4 - сидению
  • 5 - стоянию
  • 6 - лежанию

уж простите, если звучание этих существительных кажется корявым :)

Отмасштабируйте выборку с помощью StandardScaler с параметрами по умолчанию.

In [ ]:
# Ваш код здесь
scaler = 
X_scaled = 

Понижаем размерность с помощью PCA, оставляя столько компонент, сколько нужно для того, чтобы объяснить как минимум 90% дисперсии исходных (отмасштабированных) данных. Используйте отмасштабированную выборку и зафиксируйте random_state (константа RANDOM_STATE).

In [ ]:
# Ваш код здесь
pca = 
X_pca = 

Вопрос 1:
Какое минимальное число главных компонент нужно выделить, чтобы объяснить 90% дисперсии исходных (отмасштабированных) данных?

In [ ]:
# Ваш код здесь

Варианты:

  • 56
  • 65
  • 66
  • 193

Вопрос 2:
Сколько процентов дисперсии приходится на первую главную компоненту? Округлите до целых процентов.

Варианты:

  • 45
  • 51
  • 56
  • 61
In [ ]:
# Ваш код здесь

Визуализируйте данные в проекции на первые две главные компоненты.

In [ ]:
# Ваш код здесь
plt.scatter(, , c=y, s=20, cmap='viridis');

Вопрос 3:
Если все получилось правильно, Вы увидите сколько-то кластеров, почти идеально отделенных друг от друга. Какие виды активности входят в эти кластеры?

Ответ:

  • 1 кластер: все 6 активностей
  • 2 кластера: (ходьба, подъем вверх по лестнице, спуск по лестнице) и (сидение, стояние, лежание)
  • 3 кластера: (ходьба), (подъем вверх по лестнице, спуск по лестнице) и (сидение, стояние, лежание)
  • 6 кластеров

Сделайте кластеризацию данных методом KMeans, обучив модель на данных со сниженной за счет PCA размерностью. В данном случае мы подскажем, что нужно искать именно 6 кластеров, но в общем случае мы не будем знать, сколько кластеров надо искать.

Параметры:

  • n_clusters = n_classes (число уникальных меток целевого класса)
  • n_init = 100
  • random_state = RANDOM_STATE (для воспроизводимости результата)

Остальные параметры со значениями по умолчанию.

In [ ]:
# Ваш код здесь

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

In [ ]:
# Ваш код здесь
plt.scatter(, , c=cluster_labels, s=20,  cmap='viridis');

Посмотрите на соответствие между метками кластеров и исходными метками классов и на то, какие виды активностей алгоритм KMeans путает.

In [ ]:
tab = pd.crosstab(y, cluster_labels, margins=True)
tab.index = ['ходьба', 'подъем вверх по лестнице', 
             'спуск по лестнице', 'сидение', 'стояние', 'лежание', 'все']
tab.columns = ['cluster' + str(i + 1) for i in range(6)] + ['все']
tab

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

Пример: если для класса "спуск по лестнице", в котором 1406 объектов, распределение кластеров такое:

  • кластер 1 – 900
  • кластер 3 – 500
  • кластер 6 – 6,

то такая доля будет 900 / 1406 $\approx$ 0.64.

Вопрос 4:
Какой вид активности отделился от остальных лучше всего в терминах простой метрики, описанной выше?

Ответ:

  • ходьба
  • стояние
  • спуск по лестнице
  • перечисленные варианты не подходят

Видно, что kMeans не очень хорошо отличает только активности друг от друга. Используйте метод локтя, чтобы выбрать оптимальное количество кластеров. Параметры алгоритма и данные используем те же, что раньше, меняем только n_clusters.

In [ ]:
# Ваш код здесь
inertia = []
for k in tqdm_notebook(range(1, n_classes + 1)):
    #
    #

Вопрос 5:
Какое количество кластеров оптимально выбрать, согласно методу локтя?

Ответ:

  • 1
  • 2
  • 3
  • 4

Попробуем еще один метод кластеризации, который описывался в статье – агломеративную кластеризацию.

In [ ]:
ag = AgglomerativeClustering(n_clusters=n_classes, 
                             linkage='ward').fit(X_pca)

Посчитайте Adjusted Rand Index (sklearn.metrics) для получившегося разбиения на кластеры и для KMeans с параметрами из задания к 4 вопросу.

In [ ]:
# Ваш код здесь

Вопрос 6:
Отметьте все верные утверждения.

Варианты:

  • Согласно ARI, KMeans справился с кластеризацией хуже, чем Agglomerative Clustering
  • Для ARI не имеет значения какие именно метки присвоены кластерам, имеет значение только разбиение объектов на кластеры
  • В случае случайного разбиения на кластеры ARI будет близок к нулю

Можно заметить, что задача не очень хорошо решается именно как задача кластеризации, если выделять несколько кластеров (> 2). Давайте теперь решим задачу классификации, вспомнив, что данные у нас размечены.

Для классификации используйте метод опорных векторов – класс sklearn.svm.LinearSVC. Мы в курсе отдельно не рассматривали этот алгоритм, но он очень известен, почитать про него можно, например, в материалах Евгения Соколова – тут.

Настройте для LinearSVC гиперпараметр C с помощью GridSearchCV.

  • Обучите новый StandardScaler на обучающей выборке (со всеми исходными признаками), прмиените масштабирование к тестовой выборке
  • В GridSearchCV укажите cv=3.
In [ ]:
# Ваш код здесь
#
X_train_scaled = 
X_test_scaled = 
In [ ]:
svc = LinearSVC(random_state=RANDOM_STATE)
svc_params = {'C': [0.001, 0.01, 0.1, 1, 10]}
In [ ]:
# Ваш код здесь
best_svc = 
In [ ]:
# Ваш код здесь

Вопрос 7
Какое значение гиперпараметра C было выбрано лучшим по итогам кросс-валидации?

Ответ:

  • 0.001
  • 0.01
  • 0.1
  • 1
  • 10
In [ ]:
y_predicted = best_svc.predict(X_test_scaled)
In [ ]:
tab = pd.crosstab(y_test, y_predicted, margins=True)
tab.index = ['ходьба', 'подъем вверх по лестнице', 'спуск по лестнице', 
             'сидение', 'стояние', 'лежание', 'все']
tab.columns = tab.index
tab

Вопрос 8:
Какой вид активности SVM определяет хуже всего в терминах точности? Полноты?

Ответ:

  • по точности – подъем вверх по лестнице, по полноте – лежание
  • по точности – лежание, по полноте – сидение
  • по точности – ходьба, по полноте – ходьба
  • по точности – сидение, по полноте – стояние

Наконец, проделайте то же самое, что в 7 вопросе, только добавив PCA.

  • Используйте выборки X_train_scaled и X_test_scaled
  • Обучите тот же PCA, что раньше, на отмасшабированной обучающей выборке, примените преобразование к тестовой
  • Настройте гиперпараметр C на кросс-валидации по обучающей выборке с PCA-преобразованием. Вы заметите, насколько это проходит быстрее, чем раньше.

Вопрос 9:
Какова разность между лучшим качеством (долей верных ответов) на кросс-валидации в случае всех 561 исходных признаков и во втором случае, когда применялся метод главных компонент? Округлите до целых процентов.

Варианты:

  • Качество одинаковое
  • 2%
  • 4%
  • 10%
  • 20%

Вопрос 10:
Выберите все верные утверждения:

Варианты:

  • Метод главных компонент в данном случае позволил уменьшить время обучения модели, при этом качество (доля верных ответов на кросс-валидации) очень пострадало, более чем на 10%
  • PCA можно использовать для визуализации данных, однако для этой задачи есть и лучше подходящие методы, например, tSNE. Зато PCA имеет меньшую вычислительную сложность
  • PCA строит линейные комбинации исходных признаков, и в некоторых задачах они могут плохо интерпретироваться человеком