Занятие 8
Бинарная линейная классификация¶
Теоретическая часть¶
Предсказания в бинарной линейной классификации¶
Вспомнить из лекции:
- Как выполняются предсказания в бинарной линейной классификации?
- Как интерпретировать веса?
- Вспомните обозначения $\langle \cdot, \cdot \rangle$, $sign(z)$, $[z>a]$.
Задача 1.¶
Какое предсказание вернет бинарный линейный классификатор $a(x) = sign(\langle w, x \rangle+w_0)$ для объекта $x=(1, 0, 0, 1, 1)$ при использовании весов $w=(0.1, -0.2, 0.5, -1.1, 0)$ и $w_0=0.35$?
Решение. Запишем скалярное произведение: $\langle w, x \rangle = w_1 x_1 + \dots + w_d x_d$, где $d$ - размерность обоих векторов (число признаков). В нашей задаче $d=5$. Итак, в скобках получится значение $0.1\cdot1 - 0.2\cdot0 + 0.5\cdot 0 -1.1 \cdot 1 + 0 \cdot 1 + 0.35 = -0.65$. Его знак отрицательный, значит $a(x) = -1$.
Задача 2.¶
Визуализируйте разделящую поверхность классификатора $a(x) = sign(\langle w, x \rangle+w_0)$ для $w=(-1, 2)$, $w_0=0.5$, задача бинарной классификации с двумя признаками.
Решение.
Предсказания в линейной классификации выполняются по формуле $a(x) = sign(\langle w, x\rangle +w_0)$, то есть класс +1, если выражение в скобках больше 0, и -1, если выражение в скобках меньше 0. Если выражение в скобках равно 0, считаем, что отказываемся от классификации или выбираем случайный класс (на практике такая ситуация встречается очень редко). Соответственно, мы можем сделать такое предсказание в каждой точке признакового пространства, то есть для любого возможного объекта, и получить области классов +1 и -1. Разделяющей границей этих двух областей будет прямая, поэтому классификация линейная.
Разберемся, почему разделяющей границей будет прямая. Вспомним, что уравнение $w_1 x_1 + w_2 x_2 + w_0 = 0$ задает прямую на плоскости в координатах $x_1-x_2$ (мы обычно обозначаем эти координаты $d_1-d_2$, чтобы не путать $x_1$ - первый признак или первый объект в выборке; в данном контексте - первый признак). Все точки $x=(x_1, x_2)$, для которых $w_1 x_1 + w_2 x_2 + w_0 > 0$, находятся с одной стороны от прямой, а все точки, для которых $w_1 x_1 + w_2 x_2 + w_0 < 0$, с другой стороны от прямой. А эти два условия как раз и проверяются в бинарном линейной классификаторе.
В нашем случае прямая задается уравнением $-x_1 + 2 x_2 + 0.5 = 0$. Построим ее по точкам: при $x_1=0$ выполнено $2 x_2+0.5=0$, то есть $x_2=-1/4$; при $x_1=1$ выполнено $2 x_2 -0.5=0$, то есть $x_2=1/4$. Проводим прямую через точки $(0, -1/4)$ и $(1, 1/4)$. Теперь выбираем любую точку, не принадлежащую прямой, например $(0, 1/4)$. Проверяем, какой будет знак выражения $-x_1 + 2 x_2 + 0.5$: $0 + 0.5 + 0.5 = 1 > 0$. Значит, в полуплоскости, где находится эта точка, мы предсказываем класс +1, а в другой - класс -1.
Вспомнить из лекции¶
- Какие метрики бинарной классификации вы знаете?
- Какие проблемы есть у метрики accuracy? в каких случаях она нам не подходит?
- Что такое матрица ошибок?
Практическая часть¶
В практической части мы обучим линейный классификатор на данных кредитного скорринга, проанализируем веса модели, научимся работать с категориальными признаками в линейных моделях. Далее поработаем с метриками, сравних их между собой и найдем оптимиальные гиперпараметры в модели.
import pandas as pd
Мы будем работать с данными клиентов банка (задача кредитного скоринга). Для целей семинара данные были преобразованы в немного другой формат.
Значение признаков:
- account: банковский счет (-1: отриц. баланс, 0: нет счета, 1: до 200 ед., 2: более 200 ед.)
- duration: на какой период запрашивают кредит
- credit_history: рейтинг по кредитной истории (от 0 - отличная кр. история до 4 - критическая ситуация с кредитами)
- amount: на какую сумму запрашивают кредит
- savings: сберегательный счет (0: нет, 1: < 100, 2: 100 <= ... < 500, 3: 500 <= ... < 1000, 4: >= 1000)
- employment: срок работы на текущей позиции (0: не работает, 1: до 1 года, 2: от 1 до 4 лет, 3: от 4 до 7 лет, 4: более 7 лет)
- guarantors: 1 - есть поручители, 0 - нет
- residence: сколько лет резидент
- age: возраст, деленный на 100
- credits_number: число кредитов
- job: 0: не работает, 1: неквалифицированный, 2: квалифицированный специалист, 3: высокая должность или бизнес
- maintenance_people: число людей, которых обеспечивает заемщик
- telephone: указан ли телефон (1: да, 0: нет)
- foreign: 1: иностранец, 0: нет
- real_estate: 1: есть недвижимость, 0: нет недвижимости
- life_insurance: 1: оформлено страхование жизни, 0, нет страховки
- car: 1: есть автомобиль, 0: нет автомобиля
- housing_rent: 1: платит за съем жилья, 0: не платит за съем жилья
- sex: пол - 1: муж., 0: жен.
- purpose: на какую цель запрашивают кредит (из нескольких вариантов)
- target: 1: кредит выдан, 0: в кредите отказано
Требуется решить задачу предсказания значения в последнем столбце, то есть задачу бинарной классификации.
По описанию данных понятно, что все признаки числовые (включая вещественные, порядковые, бинарные), кроме предпоследнего, который является категориальным.
#!pip install xlrd
tab = pd.read_excel("https://github.com/nadiinchi/voronovo_seminar_materials/blob/master/base_track/seminars/scoring.xls?raw=true")
len(tab["purpose"].unique())
tab.head() # вывести первые строки
По строкам - объекты (клиенты), по столбцам - признаки, последний столбец - целевая переменная (1 - кредит выдан, 0 - в кредите отказано).
tab.dtypes # типы столбцов
Признаки в основном числовые.
tab["target"].value_counts()
Классы сбалансированы.
Создаем матрицу объекты-признаки и матрицу ответов. Удалим пока столбец с категориальной переменной, чтобы оставить только числовые признаки.
X = tab[tab.columns[:-2]]
y = tab["target"]
X.head()
X.shape, y.shape # атрибут shape показывает размерности матрицы
Разделение выборки¶
from sklearn.model_selection import train_test_split
# функция для разделения выборки на обучающую и тестовую
X_train, X_test, y_train, y_test = train_test_split(X, y, \
test_size=0.3,\
shuffle=True,
random_state=0)
y_train.value_counts()
y_test.value_counts()
X_train.shape, y_train.shape
Нормируем данные¶
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
Импортируем класс модели¶
from sklearn.linear_model import LinearRegression
from sklearn.metrics import accuracy_score # функция оценки качества
clf_lr = LinearRegression()
clf_lr.fit(X_train, y_train)
y_pred = clf_lr.predict(X_test)
accuracy_score(y_test, y_pred > 0.5)
Важности и веса признаков¶
Большинство алгоритмов умеют так или иначе оценивать важности признаков. В линейной модели в качестве важностей можно рассматривать веса признаков. Они хранятся в атрибуте coef_ и появляются, конечно, только после вызова процедуры обучения.
clf_lr.coef_
Задание. Оформить веса признаков в виде датафрейма: первый столбец - имя признака, второй столбец - вес, и отсортировать датафрейм по увеличению веса.
Решение:
Признаки отсортировались по логичным критериям: плата за съем жилья, число кредитов, заемщик-иностранец уменьшают шанс получить кредит; наличие собственности, машины, работы, счета в банке - увеличивают шансы.
Впрочем, некоторые признаки отсортировались менее логично: например, наличие поручителя тоже голосует в "минус", хотя и с маленьким весом.
Обратите внимание: интерпретировать величину весов можно, только если данные отнормированы. Иначе модуль веса будет зависеть от масштаба признака.
Работа с категориальным признаком¶
Применим метод one-hot-encoding к переменной "цель получения кредита", чобы включить ее в модель. Для этого воспользуемся функцией pd.get_dummies
tab_ohe = pd.get_dummies(tab, "purpose")
tab
tab_ohe.head()
# удаляем целевую переменную с помощью метода drop
X_ohe = tab_ohe.drop("target", axis=1)
# axis=1 показывает, что мы отим удалить столбец, а не строку (axis=0)
X_train_ohe, X_test_ohe, y_train, y_test = train_test_split(X_ohe, y, \
test_size=0.3,\
shuffle=True,
random_state=0)
scaler = StandardScaler()
X_train_ohe = scaler.fit_transform(X_train_ohe)
X_test_ohe = scaler.transform(X_test_ohe)
Благодаря фиксации random_state=0 мы получаем одно и то же разделение.
Оформим обучение классификатора и подсчет качества в виде функции:
def get_accuracy(clf):
clf.fit(X_train_ohe, y_train)
y_pred = clf.predict(X_test_ohe)
return accuracy_score(y_test, y_pred > 0.5)
print(get_accuracy(LinearRegression()))
Качество с новым признаком повысилось.
А что, если мы просто пронумеруем категории? Для этого воспользуемся классом LabelEncoder:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
tab["purpose"] = le.fit_transform(tab["purpose"])
X_le = tab[tab.columns[:-1]]
X_train_le, X_test_le, y_train, y_test = train_test_split(X_le, y, \
test_size=0.3,\
shuffle=True,
random_state=0)
scaler = StandardScaler()
X_train_le = scaler.fit_transform(X_train_le)
X_test_le = scaler.transform(X_test_le)
def get_accuracy(clf):
clf.fit(X_train_le, y_train)
y_pred = clf.predict(X_test_le)
return accuracy_score(y_test, y_pred > 0.5)
print(get_accuracy(LinearRegression()))
Посчитаем метрики нашей модели. Метрики разобранные на лекции уже реализованы в библиотеке scikit-learn.
from sklearn.metrics import precision_score, recall_score, confusion_matrix, f1_score
confusion_matrix(y_test, y_pred > 0.5)
precision_score(y_test, y_pred > 0.5)
recall_score(y_test, y_pred > 0.5)
f1_score(y_test, y_pred > 0.5)
Порог 0.5 мы выбрали самостоятельно, и мы можем его менять. Как увелечиение порога отразится на precision/recall?
На практике, перед нами всегда встает трейдофф - потерять часть сигнала взамен на большую точность или получить false positives взамен на больший recall.
Постройте на одном графике precision, recall и f1 score в зависимости от порога.
Какой порог соответствует максимизации f1 score?
Обучите линейную регрессию с регуляризацией и подберите параметр регуляризации исходя из максимального f1-score.