{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Лабораторная работа 4. Линейные методы.\n", "\n", "## Общая информация\n", "\n", "Дата выдачи: 20.04.2016\n", "\n", "Срок сдачи: 10.05.2016 09:00MSK\n", "\n", "### О задании\n", "Лабораторная работа №4 направлена на применение линейных методов из библиотеки scikit-learn.\n", "\n", "### Оценивание и штрафы\n", "Каждая из задач имеет определенную «стоимость» (указана в скобках). Максимально допустимая оценка за работу — 15 баллов. Обратите внимание, что только за реализацию функций без подтверждения их корректной работы оценка выставляться не будет.\n", "\n", "Сдавать задание после указанного срока сдачи нельзя. При выставлении неполного балла за задание в связи с наличием ошибок на усмотрение проверяющего предусмотрена возможность исправить задание на указанных в ответном письме условиях.\n", "\n", "Задание выполняется САМОСТОЯТЕЛЬНО. «Похожие» решения считаются плагиатом и все задействованные студенты (в том числе те, у кого списали) не могут получить за него больше 0 баллов. Если вы нашли решение какого-то из заданий в открытом источнике, необходимо прислать ссылку на этот источник (скорее всего вы будете не единственным, кто это нашел, поэтому чтобы исключить подозрение в плагиате, необходима ссылка на источник).\n", "\n", "Если вы будете решать задание на виртуальной машине, учтите, что его могут видеть все. К тому же недоступность виртуальной машины не является уважительной причиной для продления дедлайна.\n", "\n", "### Формат сдачи\n", "Для сдачи задания переименуйте получившийся файл *.ipynb в соответствии со следующим форматом: Username_(group)_Lab4.ipynb, где Username — ваша фамилия на латинице, group — название группы (например, Kozlova_IAD-11_Lab4.ipynb). Далее отправьте этот файл на используемую в Вашей группе почту курса (hse.minor.dm@gmail.com) c темой письма [ИАД-NN] - Лабораторная работа 4 - Фамилия Имя Отчество." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Линейная регрессия" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В рамках данного задания мы будем работать с датасетом *bikes_rent.csv*, в котором для каждого дня имеется календарная информация и погодные условия, характеризующие автоматизированные пункты проката велосипедов, а также число прокатов велосипедов в этот день в автоматизированных пунктах . Последнее мы будем предсказывать; таким образом, мы будем решать задачу регрессии." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Загрузите данные при помощи функции *pandas.read_csv*. Выведите первые 5 строк, чтобы убедиться в корректном считывании данных." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для каждого дня проката известны значения следующих признаков:\n", "* _season_: 1 - весна, 2 - лето, 3 - осень, 4 - зима\n", "* _yr_: 0 - 2011, 1 - 2012\n", "* _mnth_: от 1 до 12 (соответственно январь - декабрь)\n", "* _holiday_: 0 - нет праздника, 1 - есть праздник\n", "* _weekday_: от 0 до 6 (соответственно понедельник - воскресенье)\n", "* _workingday_: 0 - нерабочий день, 1 - рабочий день\n", "* _workthersit_: оценка благоприятности погоды от 1 (чистый, ясный день) до 4 (ливень, туман)\n", "* _temp_: температура в Цельсиях\n", "* _atemp_: температура по ощущениям в Цельсиях\n", "* _hum_: влажность\n", "* _windspeed(mph)_: скорость ветра в милях в час\n", "* _windspeed(ms)_: скорость ветра в метрах в секунду\n", "* _cnt_: количество арендованных велосипедов (это целевой признак, его мы будем предсказывать)\n", "\n", "Итак, у нас есть вещественные, бинарные и номинальные (порядковые) признаки, и со всеми из них можно работать как с вещественными. \n", "\n", "Сохраните в отдельную переменную матрицу объект-признак и в другую — столбец с целевым признаком." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(0.5 балла)** В начале попробуем посмотреть на признаки. Постройте матрицу коэффициентов корреляции Пирсона между признаками. Для этого можно воспользоваться методом датафрейма *corr*. Для удобства можно визуализировать ее как *hot-map* (пример, как это можно сделать, есть в семинаре про визуализацию). Можно ли сказать, что среди признаков есть линейно зависимые? Укажите какие?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(0.5 балла)** Выведите средние значения всех признаков. Можно ли сказать, что признаки имеют разный масштаб? В случае положительного ответа стандартизуйте признаки (из признака вычитается среднее и делится на стандартное отклонение), применив функцию [scale](http://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.scale.html). " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(1 балл)** Теперь попробуем обучить модель регрессии. Линейные модели реализованы в модуле *sklearn.linear_model*. Создайте объект [LinearRegression](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html) с параметрами по умолчанию, обучите его на всех данных X и ответах y, а затем для каждого признака выведите его название (хранятся в после *columns* исходного датафрейма) и вес (веса хранятся в переменной *coef_* класса регрессора)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Обратите внимание, что веса при линейно-зависимых признаках по модулю значительно больше, чем при других признаках.\n", "\n", "Чтобы понять, почему так произошло, вспомним аналитическую формулу, по которой вычисляются веса линейной модели в методе наименьших квадратов:\n", "\n", "$w = (X^TX)^{-1} X^T y$.\n", "\n", "Если в X есть линейно-зависимые столбцы, матрица $X^TX$ становится вырожденной, и формула перестает быть корректной. Чем более зависимы признаки, тем меньше определитель этой матрицы и тем хуже аппроксимация $Xw \\approx y$. Такую ситуацию называют _проблемой мультиколлинеарности_.\n", "\n", "__Решение__ проблемы мультиколлинеарности состоит в _регуляризации_ линейной модели. К оптимизируемому функционалу $||Xw-y||^2$ прибавляют L1 $\\left(\\sum_{j=1}^d |w_j| \\right)$ или L2 норму весов $\\left(\\sum_{j=1}^d w_j^2 \\right)$, умноженную на коэффициент регуляризации $\\alpha$. В первом случае метод называется Lasso, а во втором — Ridge." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(2 балла)** Обучите регрессоры [Lasso](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Lasso.html) и [Ridge](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.Ridge.html) с параметрами по умолчанию, выведите значения весов аналогично тому как это делалось ранее и убедитесь, что описанная проблема решена. " ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В отличие от L2-регуляризации, L1 производит отбор признаков путем обнуления весов при некоторых признаках. Объяснение последнему факту было дано на лекции.\n", "\n", "Давайте пронаблюдаем, как меняются веса при увеличении коэффициента регуляризации $\\alpha$. \n", "\n", "**(1 балл)** Для каждого значения коэффициента регуляризации из *alphas* создайте регрессор Lasso, указав в качестве параметра соответствующее значение alpha, обучите его на объектах X и ответах y и запишите полученные веса в соответствующую строку матрицы coefs_lasso, а затем проделайте то же самое для регрессора Ridge и переменной coefs_ridge." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "alphas = np.arange(1, 500, 50)\n", "coefs_lasso = np.zeros((alphas.shape[0], X.shape[1])) # матрица весов размера (число регрессоров) x (число признаков)\n", "coefs_ridge = np.zeros((alphas.shape[0], X.shape[1]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Визуализируйте динамику весов при увеличении параметра регуляризации, вызвав следующий код:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "plt.figure(figsize=(8, 5))\n", "for coef, feature in zip(coefs_lasso.T, df.columns):\n", " plt.plot(alphas, coef, label=feature, color=np.random.rand(3))\n", "plt.legend(loc=\"upper right\", bbox_to_anchor=(1.4, 0.95))\n", "plt.xlabel(\"alpha\")\n", "plt.ylabel(\"feature weight\")\n", "plt.title(\"Lasso\")\n", "\n", "plt.figure(figsize=(8, 5))\n", "for coef, feature in zip(coefs_ridge.T, df.columns):\n", " plt.plot(alphas, coef, label=feature, color=np.random.rand(3))\n", "plt.legend(loc=\"upper right\", bbox_to_anchor=(1.4, 0.95))\n", "plt.xlabel(\"alpha\")\n", "plt.ylabel(\"feature weight\")\n", "plt.title(\"Ridge\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(2 балла)** Ответьте на вопросы ниже.\n", "\n", "1. Какой регрессор (Ridge или Lasso) агрессивнее уменьшает веса?\n", "1. Что произойдет с весами Lasso, если alpha сделать очень большим? Поясните, почему так происходит.\n", "1. Можно ли утверждать, что Lasso исключает один из признаков windspeed при любом значении alpha? А Ridge?\n", "1. Какой из регрессоров подойдет для отбора неинформативных признаков?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Линейная классификация\n", "\n", "В этой части работы мы познакомимся с датасетом MNIST, объектами которого являются рукописные изображени цифр. Каждый объект представляет из себя матрицу размером 8х8 (в данных это выглядит как вектор с 64 признаками). Каждый признак — оттенок серого (в шкале от 0 до 16). Например, при визуализации это будет выглядеть примерно следующим образом (каждый квадратик — отдельный объект):\n", "\n", "![](https://www.tensorflow.org/versions/r0.8/images/mnist_digits.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Загрузите данные с помощью функции [load_digits](http://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_digits.html)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "У загруженного датасета целевая переменная находится в поле *target*, а сами данные в поле *data*. \n", "\n", "**(0.2 балла)** Посчитайте сколько различных классов цифр есть в данных, а также сколько в каждом классе содержится объектов." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для оценивания качества алгоритма будем использовать отложенную выборку. Воспользуйтесь функцией [train_test_split](http://scikit-learn.org/stable/modules/generated/sklearn.cross_validation.train_test_split.html) чтобы разделить данные на обучающие и тестовые. Важно, чтобы от перезапуска ваше разбиение не менялось, кроме того соотношение классов между собой соранялось. Для этого вам понадобится установить следующие дополнительные значения у данной функции:\n", " - *test_size* или *train_size* (любой на выбор) — установите параметр таким образом, чтобы в тестовую выборку попала половина всех примеров\n", " - *random_state* — необходимо задать некоторое число (любое на ваш выбор), чтобы разбиение оставалось фиксированным не зависимо от запуска\n", " - *stratify* — необходимо задать, чтобы соотношение классов в исходной выборке и новых совпадало; для этого нужно передать значение поля *target*" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для начала будем решать задачу бинарной классификации, а именно попробуем отделить цифру 8 от всех остальных. Создайте новую переменную, которая будет аналогично переменной *target* (своя для обучающей и тестовой выборки), в которой будет стоять 1, если соответствующая метка \"8\" и -1 в остальных случаях." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(0.8 балла)** Сколько теперь получилось объектов положительного класса, а сколько — отрицательного? Какая метрика качества из изученных ранее подойдет для данной задачи и почему? Чем плохо использовать, например, *Accuracy*?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Рассмотрим линейный классификатор: $$ a(x) = sign \\langle w, x\\rangle $$\n", "\n", "Обычно вводят обозначение $M_i = y_i\\langle w, x_i \\rangle$ — отступ на объекте $x_i$\n", "\n", " - $M_i < 0$ — классификатор ошибся на объекте $x_i$\n", " - $M_i > 0$ — классификатор не ошибся на объекте $x_i$\n", " \n", "Целью классификации является минимизиция количества ошибок классификатора, то есть\n", "\n", "$Q(w) = \\sum_{i=1}^l[M_i(w) < 0] \\to \\min_{w}$\n", "\n", "Это выражение достаточно сложно оптимизировать, поэтому вводять *аппроксимацию*:\n", "\n", "$Q(w) = \\sum_{i=1}^l[M_i(w) < 0] \\le \\tilde{Q}(w)$\n", "\n", "В качестве $\\tilde{Q}$ можно рассмотреть следующие функции:\n", "![](http://i.imgur.com/z12WF2C.png)\n", " - *Zero-one loss* — исходная функция \n", " - *Log loss* — экспоненциальная (используется в логистической регрессии)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Логистическая регрессия реализована в sklearn в классе [LogisticRegression](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html). Одним из ее гиперпараметров является коэффициент регуляризации *C*. Как вы помните, гиперпараметры подбираются с помощью кросс-валидации. \n", "\n", "Для начала зафиксируем схему кросс-валидации: будем использовать кросс-валидацию по 5 блокам. Для этого создайте объект [KFold](http://scikit-learn.org/stable/modules/generated/sklearn.cross_validation.KFold.html), которому необходимо задать размер выборки (обучающей), а также задать следующие параметры:\n", " - *n_folds* — число фолдов (5 в данном случае)\n", " - *random_state* — аналогично как было выше" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "После этого можно приступить к подбору значения *C*. Будем перебирать значения по логарифмической шкале, а именно из множества [0.001, 0.01, 0.1, 1.0, 10]. В качестве оптимизируемой метрики выберем *AUC-ROC*.\n", "\n", "В sklearn для логистической регрессии это можно сделать, например, следующим способом: воспользоваться классом [LogisticRegressionCV](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegressionCV.html). В этом случае вам необходимо задать следующие параметры:\n", " - *Cs* — массив со значениями перебираемых параметров\n", " - *cv* — схему кросс-валидации, с помощью которой будет осуществляться выбор наилучшего\n", " - *scoring* — название способа оценивания качества; в данном случае это *roc_auc*\n", " - *random_state*\n", " \n", "Чтобы подобрать оптимальное *C*, вызовите метод *fit*. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(2 балла)** Создайте объект класса [LogisticRegressionCV](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegressionCV.html), передав в качестве значения параметра *Cs* список [0.001, 0.01, 0.1, 1.0, 10], а в качестве *cv* — созданный ранее объект *KFold*. Обучите данный классификатор. Каким получилось оптимальное значение *C*?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь создайте объект класса [LogisticRegression](http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html) со значением *C* равным оптимальному, найденному выше. Обучите его (используя метод *fit*), на всей обучающей выборке." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь пришло время посмотреть как полученный классификатор работает на тестовой выборке. \n", "\n", "**(2 балла)** Для оценивания качества построим *ROC* и *Precision-Recall* кривые, а так же посчитаем площадь под ними. Для этого вам нужно сделать следующее:\n", " - получите предсказание вероятности принадлежности положительному классу, используя метод *predict_proba* для тестовой выборки (обратите внимание, что в данном случае этот метод вернет 2 колонки — вероятности принадлежностей каждому классу, вам необходима первая, если считать с нуля).\n", " - постройте график, на котором изобразите *Precision-Recall* кривую, используя функцию [precision_recall_curve](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.precision_recall_curve.html). Более подробно как построить такой график было рассказано на одном из семинаров. Вычислите площадь под этим графиком.\n", " - проделайте аналогичные действия для *ROC*-кривой (воспользовавшись функцией [roc_curve](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_curve.html#sklearn.metrics.roc_curve))." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(1 балл)** Теперь найдите, какую точность может иметь данный классификатор, чтобы полнота была не менее 0.8. Аналогично найдите какую максимальную полноту будет иметь классификатор при точности не менее 0.9." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А теперь вернемся к исходной многоклассовой задаче. Как известно, удобство sklearn заключается в том, что вам не нужно делать каких-либо модификаций алгоритмов в многоклассовом случае, так как все уже реализовано внутри библиотеки." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(0.5 балла)** Обучите на исходной обучающей выборке логистическую регрессию. Постройте предсказания для тестовой выборки, используя метод *predict*. Посчитайте [accuracy](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html) на тестовой выборке." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**(1.5 балла)** Самое интересное — посмотреть на ошибки классификации. Для этого постройте [confusion_matrix](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html). Какие цифры путаются чаще всего между собой? Есть ли цифра, на которой на тестовой выборке классификатор ни разу не ошибся?" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.10" } }, "nbformat": 4, "nbformat_minor": 0 }