{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Линейные методы классификации + оценка качества\n",
"Шестаков А.В. Майнор по анализу данных 22/03/2016"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Сегодня мы рассмотрим следующие темы\n",
"\n",
"1. Задача классификации\n",
"2. Методы линейной классификации\n",
"3. Регуляризация линейной регрессии и градиентный спуск"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Задача классификации"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"На прошлом семинаре мы рассматривали модели регрессии - случай, в котором необходимо было предсказать вещественную переменную $y \\in \\mathbb{R}^n$ (Стоимость автомобиля, стоимость жилья, размер мозга, объемы продаж и тп.)\n",
"\n",
"В задаче классификации переменная $y$ - содержит метку принадлежности к классу, как, например, это было в задаче с наивным байесом - категорию текстов. Частный случай задачи классификации - бинарная классификация $y = \\{-1, 1\\}$. Например: является ли клиент банка кредитоспособным, доброкачественная ли опухоль, сообщение - SPAM или HAM?\n",
"\n",
"Спрашивается, почему бы нам не взять, да и построить обычную регрессию на метки класса $y$?
\n",
"Загрузите [данные](https://www.dropbox.com/s/g3s1drtaxqwthw1/crx.data?dl=0) о кредитовании. Они достаточно сильно анонимизированны и еще не до конца подходят для применения, но сейчас это нам не помешает. Постройте график наблюдений в координатах `y` и `a15`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"plt.style.use('ggplot')\n",
"\n",
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"df = pd.read_csv('crx.data',index_col=None) \n",
"df.head()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"df.plot(x='a15', y='y', kind='scatter')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Почему бы не обучить по этим данным регрессию, предстказывающую значение $y$? Да потому что это ~~бред~~ не очень корректно!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Методы линейной классификации"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Обратимся к слегка идеализированному варианту, линейно разделимой выборке:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Нам надо найти уравнение прямой (гиперплоскости), которая бы могла разделить два класса ($H_2$ и $H_3$ подходят). В данном случае, уравнение прямой задаётся как: $$g(x) = w_0 + w_1x_1 + w_2x_2 = \\langle w, x \\rangle = w^\\top x$$\n",
"\n",
"* Если $g(x^*) > 0$, то $y^* = \\text{'черный'}$\n",
"* Если $g(x^*) < 0$, то $y^* = \\text{'белый'}$\n",
"* Если $g(x^*) = 0$, то мы находимся на линии\n",
"* т.е. решающее правило: $y^* = sign(g(x^*))$\n",
"\n",
"Некоторые геометрические особенности\n",
"* $\\frac{w_0}{||w||}$ - расстояние от начала координат то прямой\n",
"* $\\frac{|g(x)|}{||w||}$ - степень \"уверенности\" в классификациий\n",
"* Величину $M = y\\langle w, x \\rangle = y \\cdot g(x)$ называют **отступом**(margin)\n",
"\n",
"Если для какого-то объекта $M \\geq 0$, то его классификация выполнена успешно."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Отлично! Значит нам надо просто минимизировать ошибки классификации для всех объектов:\n",
"\n",
"$$L(w) = \\sum_i [y^{(i)} \\langle w, x^{(i)} \\rangle < 0] \\rightarrow \\min_w$$\n",
"Проблема в том, что это будет комбинаторная оптимизация. Существуют различные аппроксимации этой функции ошибок:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Знакомьтесь - Перцептрон!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Это самая простая модель человеческой нейронной сети. В ней есть входы, которые взвешиваются и суммируются. Затем взвешенная сумма проходит через некую функцию активации (в данном случае $sign(\\cdot)$)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Перцептрон можно использовать для классификации.
\n",
"Существует итерационный алгоритм, который корректирует веса $w_0 \\cdots w_n$ до тех пор, пока ошибки имею место быть:\n",
" \n",
"```python\n",
"Randomly initialize weights: w=(w_0, \\dots, w_d)\n",
"Until no errors on train set:\n",
" for i in xrange(N):\n",
" if y_i * w.T * x_i < 0:\n",
" w = w + alpha * y_i * x_i\n",
"```\n",
"Этот алгоритм гарантированно сходится для линейно разделимой выборки.\n",
"А если это не наш случай?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Знакомьтесь - Логистическая регрессия!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Перед тем как мы начнем, рассмотрим функцию $$\\sigma(z) = \\frac{1}{1 + exp{(-z)}},$$она называется **сигмойда**. Постройте данную фукнцию."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Your code here\n",
"def sigmoid(z):\n",
" return 1./(1+np.exp(-z))\n",
"\n",
"z = np.arange(-10, 10)\n",
"s = sigmoid(z)\n",
"\n",
"plt.plot(z, s)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Можно несколькими способами представить линейную регрессию. Один из самых простых - вот какой.\n",
"\n",
"Рассмотрим принадлежность к классу $y=\\pm1$ некого объекта $x$: $p(y=\\pm1 | x,w)$ и выразим её через **сигмойду** от **отступа**:\n",
"$$p(y=\\pm1|x,w) = \\sigma(y \\langle w, x \\rangle) $$\n",
"\n",
"А ошибка, которую мы будем минимизировать - логарифмическая:\n",
"\n",
"$$L(w) = -\\sum_i \\log(\\sigma(y^{(i)} \\langle w, x^{(i)} \\rangle)) \\rightarrow \\min_w$$\n",
"\n",
"**История с регуляризацией, мультиколлинеарностью и шкалированием признаков здесь полностью повторяется!**"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Сгенерируем выборку и применим к ней линейную регрессию"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"np.random.seed(0)\n",
"X = np.r_[np.random.randn(20, 2) + [2, 2],\n",
" np.random.randn(20, 2) + [-2, -2]]\n",
"y = [-1] * 20 + [1] * 20"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"fig, ax = plt.subplots(figsize=(7, 7))\n",
"ax.scatter(X[:, 0],\n",
" X[:, 1],\n",
" c=y,\n",
" cmap=plt.cm.Paired)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from sklearn.linear_model import LogisticRegression"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Обучите LogisticRegression() на данных `X` и `y`, изобразите разделяющую прямую"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"model = LogisticRegression()\n",
"## Your code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Как сделать нелинейную границу?"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Рассмотрим набор данных, который в простонародье называют \"Бублик\"."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from sklearn.datasets import make_circles"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"X, y = make_circles(n_samples=100, shuffle=True, noise = 0.1, factor=0.1)\n",
"X = X\n",
"\n",
"plt.scatter(X[:, 0],\n",
" X[:, 1],\n",
" c=y,\n",
" cmap=plt.cm.Paired)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Очевидно, что классы нельзя разделить линией. Но можно сделать это окружностью! \n",
"Т.е. разделяющся линия теперь будет задаваться не уравнением прямой $g(x) = w_0 + w_1x_1 + w_2x_2$, а уравнением окружности $c(x) = (x_1 - a)^2 + (x_2 - b)^2 - R^2$. \n",
"\n",
"Выполните преобразование матрицы X, чтобы в ней были столбцы для $x_1$, $x^2_1$, $x_2$, $x^2_1$, "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Your code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Обучите логистическую регрессию по полученным признакам. Проверьте, что полученные предсказания совпадают с ответами"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"model = LogisticRegression(C=1000000, fit_intercept=True)\n",
"# Параметр выставлен таким, чтобы отказаться от регуляризации\n",
"\n",
"# Your code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Нарисуйте разделяющую окружность и исходные данные. \n",
"\n",
"Вам поможет функция `LogisticRegression.decision_function(X)`, которая возвращает значения $c(X)$ для всех строк из матрицы X.
\n",
"\n",
"Нам нужны только точки, соответствующие $c(x)=0$. поэтому для того, чтобы нарисовать окружность используйте функцию `plt.contour` c параметром `levels=[0]`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"x0, x1 = np.meshgrid(np.arange(-1.5, 1.5, 0.1),\n",
" np.arange(-1.5, 1.5, 0.1))\n",
"xx0, xx1 = x0.ravel(), x1.ravel()\n",
"\n",
"# Your code here"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"## Реальные данные + оценка качества"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Предобработка данных"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Вновь [данные](https://www.dropbox.com/s/g3s1drtaxqwthw1/crx.data?dl=0) по кредитованию.\n",
"\n",
"Столбец с классом называется `y`.
Значение $1$ соответствует классу клиентов банка, которым выдали кредит и они его успешно вернули.
Значение $-1$ соответствует клиентам, невыполнившим свои кредитные обязанности. \n",
"\n",
"В банке хотят уметь определять по признакам `a1-a15`, сможет ли новый клиент вернуть кредит или нет? То есть нам надо обучить классификатор! *Обычно, в банках используют скор-карты, но процесс их построения тесно связан с логистической регрессией*"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Загрузите данные и преобразуйте признаки `a1`, `a9`, `a10` и `a12` из строковых в числовые. В них только 2 возможных значения. Для этого можно использовать функцию DataFrame.replace() в `pandas` или самое обычное присваивание на соответствующих строках."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"df = pd.read_csv('crx.data').dropna()\n",
"\n",
"# Your code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"В признаках `a6`, `a7` присутствуют \"редкие\" значение. Найдите их с помощью фунцкии `.value_counts()` и объедините, присвоив им одно и то же значение, например `'Other'`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Your code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Выделите бинарные признаки `a1`, `a9`, `a10` и `a12` в матрицу `X_binary`\n",
"\n",
"Преобразуйте категориальные признаки `a5`, `a6`, `a7`, `a13` с помощью `DictVectorizer`. Вы должны получить матрицу `X_cat`.\n",
"\n",
"Нормализуйте количественные признаки `a2`, `a3`, `a8`, `a11`, `a14` и `a15` с помощью `StandartScaler` или вручную. Вы должны получить матрицу `X_real`.\n",
"\n",
"Матрица `X_cat` будет sparse-матрицой (разреженной). Преобразуте её в полную матрицу с помощью команд `X_cat = X_cat.toarray()` или `X_cat = X_cat.todence()`\n",
"\n",
"Используйте функцию np.concatinate(..) или np.c[..] чтобы сцепить матрицы `X_binary`, `X_cat` и `X_real`\n",
"\n",
"В результате вы должны получить матрицу с преобразованными призанками `X` и вектор ответов `y`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from sklearn.preprocessing import StandardScaler\n",
"from sklearn.feature_extraction import DictVectorizer\n",
"\n",
"# Your code here"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": true
},
"source": [
"### Применение модели, расчет мер качества\n",
"\n",
"Разделите ваши даннные на обучающую и контрольную выборку в пропорции 70/30 соответственно.\n",
"\n",
"Обучите логистическую регрессию с параметром `penalty='l2'` и `C=1`.\n",
"\n",
"Для контрольной и обучающей выборке:\n",
"* Посчитайте матрицу сопряженности\n",
"* Посчитайте меры accuracy, precision, recall и f1_score\n",
"* Постройте ROC-кривые и посчитайте AUC"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from sklearn.cross_validation import train_test_split\n",
"from sklearn.metrics import accuracy_score\n",
"from sklearn.metrics import precision_score\n",
"from sklearn.metrics import recall_score\n",
"from sklearn.metrics import f1_score\n",
"from sklearn.metrics import confusion_matrix\n",
"from sklearn.metrics import roc_curve\n",
"from sklearn.metrics import roc_auc_score"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Your code here"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Скользящий контроль. Cross-validation."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Уверены ли мы, что нам не просто повезло, а построенная модель действительно \"ловит\" классы?
\n",
"Какие признаки использовать в конечном счете?
\n",
"Как оценивать гипер-параметры моделей (например параметр регуляризации в регрессии)?
\n",
"\n",
"Методами скользящего контроля!\n",
"\n",
"Это называют K-Fold Cross-Validation. Как это проходит:\n",
"* Выборку разделяют на K одинаковых частей\n",
"* Одну часть данных используют для валидации, а остальные K-1 для обучения модели\n",
"* Усредняют меру качества по результатам экспериментов\n",
"* Принимают решение (по выбору модели, параметров модели и тп..)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from sklearn.cross_validation import KFold"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"n = y.size\n",
"kfold = KFold(n,\n",
" n_folds=3, \n",
" shuffle=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"score = np.empty((3,))\n",
"i = 0\n",
"\n",
"# Нагруженная версия, чтобы было явно видно, что происходит\n",
"for train_idx, test_idx in kfold:\n",
" X_train, y_train = X[train_idx], y[train_idx]\n",
" X_test, y_test = X[test_idx], y[test_idx]\n",
" \n",
" model = LogisticRegression(C=1)\n",
" model.fit(X_train, y_train)\n",
" \n",
" y_hat = model.predict(X_test)\n",
" \n",
" score[i] = recall_score(y_test, y_hat)\n",
" i+=1\n",
" \n",
"print score"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Легкая версия\n",
"from sklearn.cross_validation import cross_val_score\n",
"\n",
"model = LogisticRegression(C=1)\n",
"score = cross_val_score(model, X, y, \n",
" cv=kfold,\n",
" scoring='recall')\n",
"print score"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Теперь должно быть очевидно, как с помощью кросс-валидации подбирать параметры моделей.\n",
"Возьмем, например, параметр `С` для регуляризации:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"C_vals = [0.1, 0.2, 0.5, 1, 2, 20000]\n",
"\n",
"for c in C_vals:\n",
" model = LogisticRegression(C=c, penalty='l1')\n",
" score = cross_val_score(model, X, y, \n",
" cv=kfold,\n",
" scoring='recall')\n",
" \n",
" print 'C = %.1f, mean = %.3f, std = %.3f'%(c, score.mean(), score.std())"
]
}
],
"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.11"
}
},
"nbformat": 4,
"nbformat_minor": 0
}