{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "\n", "##
[mlcourse.ai](https://mlcourse.ai) – открытый курс OpenDataScience по машинному обучению \n", " \n", "Авторы материала: Ольга Дайховская (@aiho в Slack ODS), Юрий Кашницкий (@yorko в Slack ODS). Материал распространяется на условиях лицензии [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#
Домашнее задание № 7 (демо)\n", "##
Обучение без учителя: метод главных компонент и кластеризация" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В этом задании мы разберемся с тем, как работают методы снижения размерности и кластеризации данных. Заодно еще раз попрактикуемся в задаче классификации.\n", "\n", "Мы будем работать с набором данных [Samsung Human Activity Recognition](https://archive.ics.uci.edu/ml/datasets/Human+Activity+Recognition+Using+Smartphones). Скачайте данные [отсюда](https://drive.google.com/file/d/14RukQ0ylM2GCdViUHBBjZ2imCaYcjlux/view?usp=sharing). Данные поступают с акселерометров и гироскопов мобильных телефонов Samsung Galaxy S3 (подробнее про признаки – по ссылке на UCI выше), также известен вид активности человека с телефоном в кармане – ходил ли он, стоял, лежал, сидел или шел вверх/вниз по лестнице. \n", "\n", "Вначале мы представим, что вид активности нам неизвестнен, и попробуем кластеризовать людей чисто на основе имеющихся признаков. Затем решим задачу определения вида физической активности именно как задачу классификации. \n", "\n", "Заполните код в клетках (где написано \"Ваш код здесь\") и ответьте на вопросы в [веб-форме](https://docs.google.com/forms/d/1qzcrfsNFy-e4TW59v2fqMj_OTom2SIOxtq4MWlI92p0)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "from tqdm import tqdm_notebook\n", "\n", "%matplotlib inline\n", "from matplotlib import pyplot as plt\n", "\n", "plt.style.use(['seaborn-darkgrid'])\n", "plt.rcParams['figure.figsize'] = (12, 9)\n", "plt.rcParams['font.family'] = 'DejaVu Sans'\n", "\n", "from sklearn import metrics\n", "from sklearn.cluster import AgglomerativeClustering, KMeans, SpectralClustering\n", "from sklearn.decomposition import PCA\n", "from sklearn.model_selection import GridSearchCV\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.svm import LinearSVC\n", "\n", "RANDOM_STATE = 17" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X_train = np.loadtxt(\"../../data/samsung_HAR/samsung_train.txt\")\n", "y_train = np.loadtxt(\"../../data/samsung_HAR/samsung_train_labels.txt\").astype(int)\n", "\n", "X_test = np.loadtxt(\"../../data/samsung_HAR/samsung_test.txt\")\n", "y_test = np.loadtxt(\"../../data/samsung_HAR/samsung_test_labels.txt\").astype(int)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Проверим размерности\n", "assert(X_train.shape == (7352, 561) and y_train.shape == (7352,))\n", "assert(X_test.shape == (2947, 561) and y_test.shape == (2947,))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для кластеризации нам не нужен вектор ответов, поэтому будем работать с объединением обучающей и тестовой выборок. Объедините *X_train* с *X_test*, а *y_train* – с *y_test*. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь\n", "X = \n", "y = " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Определим число уникальных значений меток целевого класса." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.unique(y)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_classes = np.unique(y).size" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Эти метки соответствуют:](https://archive.ics.uci.edu/ml/machine-learning-databases/00240/UCI%20HAR%20Dataset.names)\n", "- 1 - ходьбе\n", "- 2 - подъему вверх по лестнице\n", "- 3 - спуску по лестнице\n", "- 4 - сидению\n", "- 5 - стоянию\n", "- 6 - лежанию\n", "\n", "*уж простите, если звучание этих существительных кажется корявым :)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Отмасштабируйте выборку с помощью `StandardScaler` с параметрами по умолчанию." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь\n", "scaler = \n", "X_scaled = " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Понижаем размерность с помощью PCA, оставляя столько компонент, сколько нужно для того, чтобы объяснить как минимум 90% дисперсии исходных (отмасштабированных) данных. Используйте отмасштабированную выборку и зафиксируйте random_state (константа RANDOM_STATE)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь\n", "pca = \n", "X_pca = " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Вопрос 1:**
\n", "Какое минимальное число главных компонент нужно выделить, чтобы объяснить 90% дисперсии исходных (отмасштабированных) данных?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Варианты:**\n", "- 56 \n", "- 65\n", "- 66\n", "- 193" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Вопрос 2:**
\n", "Сколько процентов дисперсии приходится на первую главную компоненту? Округлите до целых процентов. \n", "\n", "**Варианты:**\n", "- 45\n", "- 51\n", "- 56\n", "- 61" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Визуализируйте данные в проекции на первые две главные компоненты." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь\n", "plt.scatter(, , c=y, s=20, cmap='viridis');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Вопрос 3:**
\n", "Если все получилось правильно, Вы увидите сколько-то кластеров, почти идеально отделенных друг от друга. Какие виды активности входят в эти кластеры?
\n", "\n", "**Ответ:**\n", "- 1 кластер: все 6 активностей\n", "- 2 кластера: (ходьба, подъем вверх по лестнице, спуск по лестнице) и (сидение, стояние, лежание)\n", "- 3 кластера: (ходьба), (подъем вверх по лестнице, спуск по лестнице) и (сидение, стояние, лежание)\n", "- 6 кластеров" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "------------------------------" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Сделайте кластеризацию данных методом `KMeans`, обучив модель на данных со сниженной за счет PCA размерностью. В данном случае мы подскажем, что нужно искать именно 6 кластеров, но в общем случае мы не будем знать, сколько кластеров надо искать.\n", "\n", "Параметры:\n", "\n", "- **n_clusters** = n_classes (число уникальных меток целевого класса)\n", "- **n_init** = 100\n", "- **random_state** = RANDOM_STATE (для воспроизводимости результата)\n", "\n", "Остальные параметры со значениями по умолчанию." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Визуализируйте данные в проекции на первые две главные компоненты. Раскрасьте точки в соответствии с полученными метками кластеров." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь\n", "plt.scatter(, , c=cluster_labels, s=20, cmap='viridis');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посмотрите на соответствие между метками кластеров и исходными метками классов и на то, какие виды активностей алгоритм `KMeans` путает." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tab = pd.crosstab(y, cluster_labels, margins=True)\n", "tab.index = ['ходьба', 'подъем вверх по лестнице', \n", " 'спуск по лестнице', 'сидение', 'стояние', 'лежание', 'все']\n", "tab.columns = ['cluster' + str(i + 1) for i in range(6)] + ['все']\n", "tab" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Видим, что каждому классу (т.е. каждой активности) соответствуют несколько кластеров. Давайте посмотрим на максимальную долю объектов в классе, отнесенных к какому-то одному кластеру. Это будет простой метрикой, характеризующей, насколько легко класс отделяется от других при кластеризации. \n", "\n", "Пример: если для класса \"спуск по лестнице\", в котором 1406 объектов, распределение кластеров такое:\n", " - кластер 1 – 900\n", " - кластер 3 – 500\n", " - кластер 6 – 6,\n", " \n", "то такая доля будет 900 / 1406 $\\approx$ 0.64.\n", " \n", "\n", "**Вопрос 4:**
\n", "Какой вид активности отделился от остальных лучше всего в терминах простой метрики, описанной выше?
\n", "\n", "**Ответ:**\n", "- ходьба\n", "- стояние\n", "- спуск по лестнице\n", "- перечисленные варианты не подходят" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Видно, что kMeans не очень хорошо отличает только активности друг от друга. Используйте метод локтя, чтобы выбрать оптимальное количество кластеров. Параметры алгоритма и данные используем те же, что раньше, меняем только `n_clusters`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь\n", "inertia = []\n", "for k in tqdm_notebook(range(1, n_classes + 1)):\n", " #\n", " #" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Вопрос 5:**
\n", "Какое количество кластеров оптимально выбрать, согласно методу локтя?
\n", "\n", "**Ответ:**\n", "- 1\n", "- 2\n", "- 3\n", "- 4" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "------------------------" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Попробуем еще один метод кластеризации, который описывался в статье – агломеративную кластеризацию." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ag = AgglomerativeClustering(n_clusters=n_classes, \n", " linkage='ward').fit(X_pca)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Посчитайте Adjusted Rand Index (`sklearn.metrics`) для получившегося разбиения на кластеры и для `KMeans` с параметрами из задания к 4 вопросу." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Вопрос 6:**
\n", "Отметьте все верные утверждения.
\n", "\n", "**Варианты:**\n", "- Согласно ARI, KMeans справился с кластеризацией хуже, чем Agglomerative Clustering\n", "- Для ARI не имеет значения какие именно метки присвоены кластерам, имеет значение только разбиение объектов на кластеры\n", "- В случае случайного разбиения на кластеры ARI будет близок к нулю" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "-------------------------------" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Можно заметить, что задача не очень хорошо решается именно как задача кластеризации, если выделять несколько кластеров (> 2). Давайте теперь решим задачу классификации, вспомнив, что данные у нас размечены. \n", "\n", "Для классификации используйте метод опорных векторов – класс `sklearn.svm.LinearSVC`. Мы в курсе отдельно не рассматривали этот алгоритм, но он очень известен, почитать про него можно, например, в материалах Евгения Соколова – [тут](https://github.com/esokolov/ml-course-msu/blob/master/ML16/lecture-notes/Sem11_linear.pdf). \n", "\n", "Настройте для `LinearSVC` гиперпараметр `C` с помощью `GridSearchCV`. \n", "\n", "- Обучите новый `StandardScaler` на обучающей выборке (со всеми исходными признаками), прмиените масштабирование к тестовой выборке\n", "- В `GridSearchCV` укажите cv=3." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь\n", "#\n", "X_train_scaled = \n", "X_test_scaled = " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "svc = LinearSVC(random_state=RANDOM_STATE)\n", "svc_params = {'C': [0.001, 0.01, 0.1, 1, 10]}" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь\n", "best_svc = " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Ваш код здесь" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Вопрос 7**
\n", "Какое значение гиперпараметра `C` было выбрано лучшим по итогам кросс-валидации?
\n", "\n", "**Ответ:**\n", "- 0.001\n", "- 0.01\n", "- 0.1\n", "- 1\n", "- 10" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y_predicted = best_svc.predict(X_test_scaled)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tab = pd.crosstab(y_test, y_predicted, margins=True)\n", "tab.index = ['ходьба', 'подъем вверх по лестнице', 'спуск по лестнице', \n", " 'сидение', 'стояние', 'лежание', 'все']\n", "tab.columns = tab.index\n", "tab" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Вопрос 8:**
\n", "Какой вид активности SVM определяет хуже всего в терминах точности? Полноты?
\n", "\n", "**Ответ:**\n", "- по точности – подъем вверх по лестнице, по полноте – лежание\n", "- по точности – лежание, по полноте – сидение\n", "- по точности – ходьба, по полноте – ходьба\n", "- по точности – сидение, по полноте – стояние" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Наконец, проделайте то же самое, что в 7 вопросе, только добавив PCA.\n", "\n", "- Используйте выборки `X_train_scaled` и `X_test_scaled`\n", "- Обучите тот же PCA, что раньше, на отмасшабированной обучающей выборке, примените преобразование к тестовой\n", "- Настройте гиперпараметр `C` на кросс-валидации по обучающей выборке с PCA-преобразованием. Вы заметите, насколько это проходит быстрее, чем раньше.\n", "\n", "**Вопрос 9:**
\n", "Какова разность между лучшим качеством (долей верных ответов) на кросс-валидации в случае всех 561 исходных признаков и во втором случае, когда применялся метод главных компонент? Округлите до целых процентов.
\n", "\n", "**Варианты:**\n", "- Качество одинаковое\n", "- 2%\n", "- 4% \n", "- 10%\n", "- 20%\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Вопрос 10:**
\n", "Выберите все верные утверждения:\n", "\n", "**Варианты:**\n", "- Метод главных компонент в данном случае позволил уменьшить время обучения модели, при этом качество (доля верных ответов на кросс-валидации) очень пострадало, более чем на 10%\n", "- PCA можно использовать для визуализации данных, однако для этой задачи есть и лучше подходящие методы, например, tSNE. Зато PCA имеет меньшую вычислительную сложность\n", "- PCA строит линейные комбинации исходных признаков, и в некоторых задачах они могут плохо интерпретироваться человеком" ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" } }, "nbformat": 4, "nbformat_minor": 2 }