Материал распространяется на условиях лицензии Creative Commons CC BY-NC-SA 4.0. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала.
В этом задании мы разберемся с тем, как работают методы снижения размерности и кластеризации данных. Заодно еще раз попрактикуемся в задаче классификации.
Мы будем работать с набором данных Samsung Human Activity Recognition. Скачайте данные отсюда. Данные поступают с акселерометров и гироскопов мобильных телефонов Samsung Galaxy S3 (подробнее про признаки – по ссылке на UCI выше), также известен вид активности человека с телефоном в кармане – ходил ли он, стоял, лежал, сидел или шел вверх/вниз по лестнице.
Вначале мы представим, что вид активности нам неизвестнен, и попробуем кластеризовать людей чисто на основе имеющихся признаков. Затем решим задачу определения вида физической активности именно как задачу классификации.
Заполните код в клетках (где написано "Ваш код здесь") и ответьте на вопросы в веб-форме.
In [1]:
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 KMeans, AgglomerativeClustering, 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 [2]:
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 [3]:
# Проверим размерности
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 [4]:
X = np.vstack([X_train, X_test])
y = np.hstack([y_train, y_test])
Определим число уникальных значений меток целевого класса.
In [5]:
np.unique(y)
Out[5]:
In [6]:
n_classes = np.unique(y).size
Отмасштабируйте выборку с помощью StandardScaler
с параметрами по умолчанию.
In [7]:
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
Понижаем размерность с помощью PCA, оставляя столько компонент, сколько нужно для того, чтобы объяснить как минимум 90% дисперсии исходных (отмасштабированных) данных. Используйте отмасштабированную выборку и зафиксируйте random_state (константа RANDOM_STATE).
In [8]:
pca = PCA(0.90, random_state=RANDOM_STATE)
X_pca = pca.fit_transform(X_scaled)
Вопрос 1:
Какое минимальное число главных компонент нужно выделить, чтобы объяснить 90% дисперсии исходных (отмасштабированных) данных?
In [9]:
len(pca.explained_variance_ratio_)
Out[9]:
Варианты:
Вопрос 2:
Сколько процентов дисперсии приходится на первую главную компоненту? Округлите до целых процентов.
Варианты:
In [10]:
round(pca.explained_variance_ratio_[0] * 100)
Out[10]:
Визуализируйте данные в проекции на первые две главные компоненты.
In [11]:
plt.figure(figsize=(13,10))
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, s=20, cmap='viridis');
plt.colorbar()
Out[11]:
Вопрос 3:
Если все получилось правильно, Вы увидите сколько-то кластеров, почти идеально отделенных друг от друга. Какие виды активности входят в эти кластеры?
Ответ:
Сделайте кластеризацию данных методом KMeans
, обучив модель на данных со сниженной за счет PCA размерностью. В данном случае мы подскажем, что нужно искать именно 6 кластеров, но в общем случае мы не будем знать, сколько кластеров надо искать.
Параметры:
Остальные параметры со значениями по умолчанию.
In [12]:
k_means = KMeans(n_clusters=n_classes, n_init=100, random_state=RANDOM_STATE).fit(X_pca)
In [13]:
cluster_labels = k_means.predict(X_pca)
Визуализируйте данные в проекции на первые две главные компоненты. Раскрасьте точки в соответствии с полученными метками кластеров.
In [14]:
plt.figure(figsize=(13,10))
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=cluster_labels, s=20, cmap='viridis')
plt.colorbar()
Out[14]:
Посмотрите на соответствие между метками кластеров и исходными метками классов и на то, какие виды активностей алгоритм KMeans
путает.
In [15]:
tab = pd.crosstab(y, cluster_labels, margins=True)
tab.index = ['ходьба', 'подъем вверх по лестнице',
'спуск по лестнице', 'сидение', 'стояние', 'лежание', 'все']
tab.columns = ['cluster' + str(i + 1) for i in range(6)] + ['все']
tab
Out[15]:
Видим, что каждому классу (т.е. каждой активности) соответствуют несколько кластеров. Давайте посмотрим на максимальную долю объектов в классе, отнесенных к какому-то одному кластеру. Это будет простой метрикой, характеризующей, насколько легко класс отделяется от других при кластеризации.
Пример: если для класса "спуск по лестнице", в котором 1406 объектов, распределение кластеров такое:
то такая доля будет 900 / 1406 $\approx$ 0.64.
Вопрос 4:
Какой вид активности отделился от остальных лучше всего в терминах простой метрики, описанной выше?
Ответ:
Видно, что kMeans не очень хорошо отличает только активности друг от друга. Используйте метод локтя, чтобы выбрать оптимальное количество кластеров. Параметры алгоритма и данные используем те же, что раньше, меняем только n_clusters
.
In [18]:
# Ваш код здесь
inertia = []
for k in tqdm_notebook(range(1, n_classes + 1)):
kmeans = KMeans(n_clusters=k, n_init=100, random_state=RANDOM_STATE).fit(X_pca)
inertia.append(np.sqrt(kmeans.inertia_))
for i in range(1, len(inertia) - 1):
D = abs(inertia[i] - inertia[i + 1]) / abs(inertia[i - 1] - inertia[i])
print(D)
Вопрос 5:
Какое количество кластеров оптимально выбрать, согласно методу локтя?
Ответ:
Попробуем еще один метод кластеризации, который описывался в статье – агломеративную кластеризацию.
In [220]:
ag = AgglomerativeClustering(n_clusters=n_classes,
linkage='ward').fit_predict(X_pca)
Посчитайте Adjusted Rand Index (sklearn.metrics
) для получившегося разбиения на кластеры и для KMeans
с параметрами из задания к 4 вопросу.
In [221]:
metrics.adjusted_rand_score(y, ag)
Out[221]:
In [222]:
metrics.adjusted_rand_score(y, cluster_labels)
Out[222]:
Вопрос 6:
Отметьте все верные утверждения.
Варианты:
Можно заметить, что задача не очень хорошо решается именно как задача кластеризации, если выделять несколько кластеров (> 2). Давайте теперь решим задачу классификации, вспомнив, что данные у нас размечены.
Для классификации используйте метод опорных векторов – класс sklearn.svm.LinearSVC
. Мы в курсе отдельно не рассматривали этот алгоритм, но он очень известен, почитать про него можно, например, в материалах Евгения Соколова – тут.
Настройте для LinearSVC
гиперпараметр C
с помощью GridSearchCV
.
StandardScaler
на обучающей выборке (со всеми исходными признаками), примените масштабирование к тестовой выборкеGridSearchCV
укажите cv=3.
In [223]:
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
In [224]:
svc = LinearSVC(random_state=RANDOM_STATE)
svc_params = {'C': [0.001, 0.01, 0.1, 1, 10]}
In [225]:
grid = GridSearchCV(svc, param_grid=svc_params, cv=3).fit(X_train_scaled, y_train)
best_svc = grid.best_estimator_
In [231]:
best_svc.C
Out[231]:
Вопрос 7
Какое значение гиперпараметра C
было выбрано лучшим по итогам кросс-валидации?
Ответ:
In [227]:
y_predicted = best_svc.predict(X_test_scaled)
In [229]:
print(metrics.classification_report(y_test, y_predicted, target_names=tab.index[:6]))
Вопрос 8:
Какой вид активности SVM определяет хуже всего в терминах точности? Полноты?
Ответ:
Наконец, проделайте то же самое, что в 7 вопросе, только добавив PCA.
X_train_scaled
и X_test_scaled
C
на кросс-валидации по обучающей выборке с PCA-преобразованием. Вы заметите, насколько это проходит быстрее, чем раньше.Вопрос 9:
Какова разность между лучшим качеством (долей верных ответов) на кросс-валидации в случае всех 561 исходных признаков и во втором случае, когда применялся метод главных компонент? Округлите до целых процентов.
Варианты:
In [233]:
X_train_scaled_pca = pca.fit_transform(X_train_scaled)
X_test_scaled_pca = pca.transform(X_test_scaled)
In [234]:
svc = LinearSVC(random_state=RANDOM_STATE)
svc_params = {'C': [0.001, 0.01, 0.1, 1, 10]}
grid_2 = GridSearchCV(svc, param_grid=svc_params, cv=3).fit(X_train_scaled_pca, y_train)
In [235]:
round((grid.best_score_ - grid_2.best_score_) * 100)
Out[235]:
Вопрос 10:
Выберите все верные утверждения:
Варианты: