Описание проекта
Вам нужно защитить данные клиентов страховой компании «Хоть потоп». Разработайте такой метод преобразования данных, чтобы по ним было сложно восстановить персональную информацию. Обоснуйте корректность его работы.
Нужно защитить данные, чтобы при преобразовании качество моделей машинного обучения не ухудшилось. Подбирать наилучшую модель не требуется.
Инструкция по выполнению проекта
- Загрузите и изучите данные.
- Ответьте на вопрос и обоснуйте решение.
- Признаки умножают на обратимую матрицу. Изменится ли качество линейной регрессии? (Её можно обучить заново.)
- Изменится. Приведите примеры матриц.
- Не изменится. Укажите, как связаны параметры линейной регрессии в исходной задаче и в преобразованной.
- Предложите алгоритм преобразования данных для решения задачи. Обоснуйте, почему качество линейной регрессии не поменяется.
- Запрограммируйте этот алгоритм, применив матричные операции. Проверьте, что качество линейной регрессии из sklearn не отличается до и после преобразования. Примените метрику R2.
Описание данных
Набор данных находится в файле /datasets/insurance.csv
.
- Признаки: пол, возраст и зарплата застрахованного, количество членов его семьи.
- Целевой признак: количество страховых выплат клиенту за последние 5 лет.
Загрузка данных¶
# Базовые библиотеки
import pandas as pd # Датафреймы
import numpy as np # Математика для массивов
# Автоматизация раздельного декодирования признаков
from sklearn.compose import(
make_column_selector,
make_column_transformer,
ColumnTransformer
)
# Обработка данных для машинного обучения
# Стандартизация данных
import re
from sklearn.preprocessing import(
StandardScaler
)
from sklearn.model_selection import(
train_test_split, # Разделение выборок с целевыми и нецелевыми признаками на обучающую и тестовую
)
# Метрики (Показатели качества моделей)
from sklearn.metrics import(
r2_score, # R^2, Коэффициент детерминации (универсальная в %, чувствительная к выбросам, может быть отрицательной и возвращать NaN)
)
# Визуализация графиков
import seaborn as sns
import matplotlib
%matplotlib inline
from matplotlib import pyplot as plt
from matplotlib import rcParams, rcParamsDefault
from pandas.plotting import scatter_matrix
# Отображение всех столбцов таблицы
pd.set_option('display.max_columns', None)
# Обязательно для нормального отображения графиков plt
rcParams['figure.figsize'] = 10, 6
%config InlineBackend.figure_format = 'svg'
# Дополнительно и не обязательно для декорирования графиков
factor = .8
default_dpi = rcParamsDefault['figure.dpi']
rcParams['figure.dpi'] = default_dpi * factor
# Чтение данных из файла
try:
data = pd.read_csv('/datasets/insurance.csv')
except:
data = pd.read_csv('datasets/insurance.csv')
# Изучение датафрейма
print(data.info())
data.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 5000 entries, 0 to 4999 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Пол 5000 non-null int64 1 Возраст 5000 non-null float64 2 Зарплата 5000 non-null float64 3 Члены семьи 5000 non-null int64 4 Страховые выплаты 5000 non-null int64 dtypes: float64(2), int64(3) memory usage: 195.4 KB None
Пол | Возраст | Зарплата | Члены семьи | Страховые выплаты | |
---|---|---|---|---|---|
0 | 1 | 41.0 | 49600.0 | 1 | 0 |
1 | 0 | 46.0 | 38000.0 | 1 | 1 |
2 | 0 | 29.0 | 21000.0 | 0 | 0 |
3 | 0 | 21.0 | 41700.0 | 2 | 0 |
4 | 1 | 28.0 | 26100.0 | 0 | 0 |
# Изучение распределений данных
data.hist();
data.describe()
Пол | Возраст | Зарплата | Члены семьи | Страховые выплаты | |
---|---|---|---|---|---|
count | 5000.000000 | 5000.000000 | 5000.000000 | 5000.000000 | 5000.000000 |
mean | 0.499000 | 30.952800 | 39916.360000 | 1.194200 | 0.148000 |
std | 0.500049 | 8.440807 | 9900.083569 | 1.091387 | 0.463183 |
min | 0.000000 | 18.000000 | 5300.000000 | 0.000000 | 0.000000 |
25% | 0.000000 | 24.000000 | 33300.000000 | 0.000000 | 0.000000 |
50% | 0.000000 | 30.000000 | 40200.000000 | 1.000000 | 0.000000 |
75% | 1.000000 | 37.000000 | 46600.000000 | 2.000000 | 0.000000 |
max | 1.000000 | 65.000000 | 79000.000000 | 6.000000 | 5.000000 |
# Количество значений
# разных страховых выплат
#data['Страховые выплаты'].unique()
data.groupby(by='Страховые выплаты', axis=0)['Страховые выплаты'].count()
Страховые выплаты 0 4436 1 423 2 115 3 18 4 7 5 1 Name: Страховые выплаты, dtype: int64
# Средние значения показателей
# для разного количества страховых выплат
data.groupby(by='Страховые выплаты', axis=0).mean()
Пол | Возраст | Зарплата | Члены семьи | |
---|---|---|---|---|
Страховые выплаты | ||||
0 | 0.499324 | 28.970694 | 39924.752029 | 1.205816 |
1 | 0.468085 | 44.706856 | 40260.520095 | 1.139480 |
2 | 0.600000 | 50.860870 | 39035.652174 | 1.017391 |
3 | 0.444444 | 55.833333 | 37677.777778 | 0.944444 |
4 | 0.571429 | 60.000000 | 34057.142857 | 0.714286 |
5 | 1.000000 | 65.000000 | 39700.000000 | 1.000000 |
# Корреляция значения показателей
data.corr()
Пол | Возраст | Зарплата | Члены семьи | Страховые выплаты | |
---|---|---|---|---|---|
Пол | 1.000000 | 0.002074 | 0.014910 | -0.008991 | 0.010140 |
Возраст | 0.002074 | 1.000000 | -0.019093 | -0.006692 | 0.651030 |
Зарплата | 0.014910 | -0.019093 | 1.000000 | -0.030296 | -0.014963 |
Члены семьи | -0.008991 | -0.006692 | -0.030296 | 1.000000 | -0.036290 |
Страховые выплаты | 0.010140 | 0.651030 | -0.014963 | -0.036290 | 1.000000 |
Итоги анализа загруженных данных
- Датафрейм содержит 5000 объектов и 5 числовых признаков.
- В данных нет пропусков.
- Целевой признак содержит 6 категорий от
0
до5
. Вероятнее всего, категории обозначают количество страховых выплат. По мере увеличения значения категории уменьшается количество объектов в ней. Так, в категории0
содержится 4436 объектов, в категории1
только 423 объекта, а в категории5
всего 1 объект. - По мере увеличения количества страховых выплат увеличивается возраст застрахован лица. Эти два показателя имеют сильную положительную корреляцию. При этом, количество страховых выплат почти не зависит от пола и других имеющихся признаков застрахованного лица.
- Признаки имеют значительное различие в разбросе числовых значений. Для эффективного использования в обучении их следует стандартизировать.
Умножение матриц¶
В этом задании вы можете записывать формулы в Jupyter Notebook.
Чтобы записать формулу внутри текста, окружите её символами доллара \$; если снаружи — двойными символами \\$\$. Эти формулы записываются на языке вёрстки LaTeX.
Для примера мы записали формулы линейной регрессии. Можете их скопировать и отредактировать, чтобы решить задачу.
Работать в LaTeX необязательно.
Обозначения:
$X$ — матрица признаков (нулевой столбец состоит из единиц)
$y$ — вектор целевого признака
$P$ — матрица, на которую умножаются признаки
$w$ — вектор весов линейной регрессии (нулевой элемент равен сдвигу)
Предсказания:
$$ a = Xw $$Задача обучения:
$$ w = \arg\min_w MSE(Xw, y) $$Формула обучения:
$$ w = (X^T X)^{-1} X^T y $$Вопрос:
Признаки умножают на обратимую матрицу. Изменится ли качество линейной регрессии? (Её можно обучить заново.)
Ответ:
Не изменится.
Обоснование:
Если использовать формулу линейной регрессии (https://academy.yandex.ru/handbook/ml/article/linear-models):
$$ a = Xw $$$$ w = (X^T X)^{-1} X^T y $$то, при умножжении признаков на обратимую матрицу, формула примет вид:
$$ a’ = X’w’ = (XP)w’ $$$$ w’ = ((XP)^T (XP))^{-1} (XP)^T y $$где $P$ — случайная обратимая матрица. Преобразуем форумлу:
$$ w’ = (X’^T X’)^{-1} X’^T y $$$$ w’ = ((X P)^T (X P))^{-1} (X P)^T y $$$$ w’ = (P^T X^T X P)^{-1} P^T X^T y $$$$ w’ = P^{-1} (X^T X)^{-1} (P^T)^{-1} P^T X^T y $$$$ w’ = P^{-1} (X^T X)^{-1} E X^T y $$$$ w’ = P^{-1} (X^T X)^{-1} X^T y $$$$ w’ = P^{-1} w $$Т.к. $w’ \not= w$, то и $a’ \not= a$, однако, если $w’$ можно выразить через $P$ и $w$, то и $a’$ можно выразить через $X$, $P$ и $w$:
$$ a’ = X’ w’ $$$$ a’ = (X P) P^{-1} w $$$$ a’ = X P P^{-1} w $$Алгоритм преобразования¶
Алгоритм
- При вызове метода
fit()
пользовательского классаLinearRegression()
внутри метода:- Создать внутриклассовую переменную
self.P
со случайной обратимой матрицей. - Проверить случайную матрицу на обратимость. Для этого проверить определитель матрицы на равенство нулю. В случае равенства нулю, создать матрицу заново.
- Создать переменную
X
со скалярным произведением обучающей выборкиtrain_features
, содержащей все аттрибуты, кроме целевого, наself.P
. - Добавить единичный столбец в
X
. - Создать переменную
y
, в которую передать данные целевого признака обучающей выборкиtrain_target
. - Создать переменную
w
и записать в нее результаты выполнения формулы $w = (X^T X)^{-1} X^T y$ с использованием одноименных переменных методаfit()
. - Создать внутриклассовые переменные
self.w
иself.w0
, в которые передать все значения из переменнойw
, кроме нулевого, и нулевое, соответственно.
- Создать внутриклассовую переменную
- При вызове метода
predict()
пользовательского классаLinearRegression()
:- Создать переменную
X
со скалярным произведением обучающей выборкиtest_features
, содержащей все аттрибуты, кроме целевого, наself.P
. - Вернуть в качестве предсказания, результаты скалярного произведения переменной
X
на внутриклассовую переменнуюself.w
, сложенного с внутриклассовой переменнойself.w0
.
- Создать переменную
Обоснование
На основании выводов, полученных в пункте 2 данного проекта предполагается, что умножение треннировочной выборки с нецелевыми аттрибутами и аналогичной тестовой выборки на случайную обратимую матрицу даст такой же результат, что и без умножения на подобную матрицу.
Проверка алгоритма¶
# Создание класса "LinearRegression" и его методов
# для предсказания результатов методом линейной регрессии
class LinearRegression:
# Метод, включающий использование
# случайной обратимой матрицы для защиты данных
def keep(self, keeping):
if keeping == True:
self.keep = True
else:
self.keep = False
# Метод обучения
def fit(self, train_features, train_target):
n = 0
while n == 0:
self.P = np.random.normal(size=(len(train_features.columns), len(train_features.columns)))
n = np.linalg.det(self.P)
if self.keep == True:
X = train_features @ self.P
X = np.concatenate((np.ones((X.shape[0], 1)), X), axis=1)
else:
X = np.concatenate((np.ones((train_features.shape[0], 1)), train_features), axis=1)
y = train_target
w = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(y)
self.w = w[1:]
self.w0 = w[0]
# Метод предсказания
def predict(self, test_features):
if self.keep == True:
X = test_features @ self.P
else:
X = test_features
return X.dot(self.w) + self.w0
# Подготовка данных для использования в модели
# Разделение датафрейма на выбоки с целевыми и не целевыми аттрибутами
features = data.drop('Страховые выплаты', axis=1)
target = data['Страховые выплаты']
# Разделение выборок на обучающие и треннировочные
features_train, features_test, target_train, target_test = train_test_split(
features,
target,
test_size=.25,
random_state=12345
)
# Стандартизация данных
scaler = StandardScaler()
scaler.fit(features_train)
features_train = scaler.transform(features_train)
features_test = scaler.transform(features_test)
# Реализаця модели без защиты данных
model = LinearRegression()
model.keep(False)
model.fit(features, target)
predictions = model.predict(features)
pd.DataFrame(
data={
'predictions': predictions.values,
'target': target.values,
}
).plot(
kind='hist',
alpha=.5,
rot=20,
bins=20
)
r2 = []
r2.append(r2_score(target, predictions))
print('R2:', r2[0])
R2: 0.42494550286668
# Реализаця модели с защитой данных
model = LinearRegression()
model.keep(True)
model.fit(features, target)
predictions = model.predict(features)
pd.DataFrame(
data={
'predictions': predictions.values,
'target': target.values,
}
).plot(
kind='hist',
alpha=.5,
rot=20,
bins=20
)
r2.append(r2_score(target, predictions))
print('R2:', r2[1])
R2: 0.4249455028639104
# Разница показателя "R2" между моделями
# с защищенными и незащищенными данными
print('Показатель R2 моделей:')
print('с защищенными данными :', r2[1])
print('с незащищенными данными:', r2[0])
print()
print('Разница в показателях :', r2[1] - r2[0])
Показатель R2 моделей: с защищенными данными : 0.4249455028639104 с незащищенными данными: 0.42494550286668 Разница в показателях : -2.7695623572299155e-12
Вывод из проверки алгоритма
Разница показателя R2
между моделями с защищенными и не защищенными данными стремиться к нулю. Можно сделать вывод о том, что защиту данных с помощью случайной обратимой матрицы допустимо использовать.
Выводы проекта¶
Цель проекта достигнута: обоснован, создан и проверен класс LinearRegression()
для предсказания количества страховых случаев с защитой использованных в классе данных путем их умножения на случайную обратимую матрицу. В рамках проекта решены следующие задачи:
- Загружены и проанализированы данные.
- Найден и обоснован ответ на вопрос о зависимости
w
иw'
с использованием случайной обратимой матрицы. - Предложен и обоснован алгоритм решения данных, используемых в классе
LinearRegression()
, путем их умножения на случайную обратимую матрицу. - Эксперементально доказана возможнгость использования случайной обратимой матрицы для защиты данных в классе
LinearRegression()
.
Чек-лист проверки¶
Поставьте ‘x’ в выполненных пунктах. Далее нажмите Shift+Enter.
- [x] Jupyter Notebook открыт
- [x] Весь код выполняется без ошибок
- [x] Ячейки с кодом расположены в порядке исполнения
- [x] Выполнен шаг 1: данные загружены
- [x] Выполнен шаг 2: получен ответ на вопрос об умножении матриц
- [x] Указан правильный вариант ответа
- [x] Вариант обоснован
- [x] Выполнен шаг 3: предложен алгоритм преобразования
- [x] Алгоритм описан
- [x] Алгоритм обоснован
- [x] Выполнен шаг 4: алгоритм проверен
- [x] Алгоритм реализован
- [x] Проведено сравнение качества моделей до и после преобразования