Физтех.Статистика
Скачать ipynb
Phystech@DataScience¶
Домашнее задание 5¶
Правила, прочитайте внимательно:
- Выполненную работу нужно отправить телеграм-боту
@miptstats_pds_bot
. Для начала работы с ботом каждый раз отправляйте/start
. Работы, присланные иным способом, не принимаются. - Дедлайн см. в боте. После дедлайна работы не принимаются кроме случаев наличия уважительной причины.
- Прислать нужно ноутбук в формате
ipynb
. - Выполнять задание необходимо полностью самостоятельно. При обнаружении списывания все участники списывания будут сдавать устный зачет.
- Решения, размещенные на каких-либо интернет-ресурсах, не принимаются. Кроме того, публикация решения в открытом доступе может быть приравнена к предоставлении возможности списать.
- Для выполнения задания используйте этот ноутбук в качестве основы, ничего не удаляя из него. Можно добавлять необходимое количество ячеек.
- Комментарии к решению пишите в markdown-ячейках.
- Выполнение задания (ход решения, выводы и пр.) должно быть осуществлено на русском языке.
- Если код будет не понятен проверяющему, оценка может быть снижена.
- Никакой код из данного задания при проверке запускаться не будет. Если код студента не выполнен, недописан и т.д., то он не оценивается.
- Код из рассказанных на занятиях ноутбуков можно использовать без ограничений.
Правила оформления теоретических задач:
- Решения необходимо прислать одним из следующих способов:
- фотографией в правильной ориентации, где все четко видно, а почерк разборчив,
- отправив ее как файл боту вместе с ноутбуком или
- вставив ее в ноутбук посредством
Edit -> Insert Image
(фото, вставленные ссылкой, не принимаются);
- в виде $\LaTeX$ в markdown-ячейках.
- фотографией в правильной ориентации, где все четко видно, а почерк разборчив,
- Решения не проверяются, если какое-то требование не выполнено. Особенно внимательно все проверьте в случае выбора второго пункта (вставки фото в ноутбук). Неправильно вставленные фотографии могут не передаться при отправке. Для проверки попробуйте переместить
ipynb
в другую папку и открыть его там. - В решениях поясняйте, чем вы пользуетесь, хотя бы кратко. Например, если пользуетесь независимостью, то достаточно подписи вида "X и Y незав."
- Решение, в котором есть только ответ, и отсутствуют вычисления, оценивается в 0 баллов.
Баллы за задание:
Легкая часть (достаточно на "хор"):
- Задача 1 — 60 баллов
- Задача 2 — 60 баллов
Сложная часть (необходимо на "отл"):
- Задача 3 — 30 баллов
- Задача 4 — 30 баллов
# Bot check
# HW_ID: phds_hw5
# Бот проверит этот ID и предупредит, если случайно сдать что-то не то.
# Status: not final
# Перед отправкой в финальном решении удали "not" в строчке выше.
# Так бот проверит, что ты отправляешь финальную версию, а не промежуточную.
# Никакие значения в этой ячейке не влияют на факт сдачи работы.
import numpy as np
import pandas as pd
import seaborn as sns
import torch
from torch import nn, optim
import matplotlib.pyplot as plt
from IPython.display import clear_output
from sklearn.metrics import mean_absolute_percentage_error, r2_score
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.preprocessing import LabelBinarizer, StandardScaler
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')
Профиль биология¶
Мы будем исследовать датасет по экспрессиям различных генов (RNA-seq), используемых для предсказания возраста пациентов.
df = pd.read_csv("Rnaseq_age_reg.csv")
df.head()
Разбейте датасет на признаки и таргет, где в качестве таргета будет использоваться столбец Age
, а признаки - все остальные. В том числе разбейте на подвыборки для обучения и теста.
Переходите к общей части.
Профиль физика¶
df = pd.read_csv("physics_data.csv", index_col=0)
df.head()
Разбейте датасет на признаки и таргет, где в качестве таргета будет использоваться столбец Eat, а признаки - все остальные. В том числе разбейте на подвыборки для обучения и теста.
Общая часть¶
Отмасштабируйте данные:
Обучите модель линейной регрессии и посмотрите на значения метрик на тесте. Что вы можете сказать про результат обучения?
Обучите линейные модели с регуляризациями, которые мы проходили ранее. Для каждой из моделей постройте графики зависимости метрик r2 и MAPE от коэфициента регуляризации. Можно пользоваться кодом из домашнего задания по регуляризации. Сильно ли улучшился результат?
Выберите оптимальный, на вашь взгляд, параметр для L1-регуляризации, обучите модель Lasso-регрессии, выведите ещё раз метрики r2 и MAPE и проведите отбор признаков: уберите из датасета все те, для которых коэффициент регуляризации оказался нулевым.
Теперь обучите простейшую нейронную сеть на уменьшенном датасете, сравните результат с результатами обучения других моделей.
model =
optim_func = <...>
optimizer = <...>
with torch.no_grad():
y_pred = model(torch.FloatTensor(<Тестовая выборка>)).numpy()
print(f'R2: {round(r2_score(y_test, y_pred), 2)} \nMAPE: {round(mean_absolute_percentage_error(y_test, y_pred), 2)}')
Вопрос: объясните полученный результат.
<...>
Сравните все модели, поясняя полученные результаты и значения метрик.
Вывод:
data = pd.read_csv('Z_boson.csv')
data.head()
Удалите столбцы Unnamed: 0
, Run
и Event
, так как это не физические величины. Удалите строки, где есть пропуски, если таковые имеются.
Также можно как-нибудь взглянуть на признаки. Возможно, не все они вносят вклад в разделение классов. Не забудьте преобразовать таргет (столбец class
) к формату 0 и 1. Вам может пригодиться sklearn.preprocessing.LabelBinarizer
.
plt.figure(figsize=(20, 10))
sns.set_theme(font_scale=2.0)
sns.pairplot(data, hue="class", palette="deep")
Какие признаки вы бы использовали для разделения людей по классам? Выберите эти столбцы и создайте наборы train и test с помощью функции train_test_split, а также выделите набор данных для валидации при обучении.
# исходя из графиков, отберём признаки для обучения
selected_features = [<...>]
X = data[selected_features]
# таргет преобразуем из строк "Zee", "Zmumu" к 0 и 1
<...>
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7)
X_val, X_test, y_val, y_test = train_test_split(X_test, y_test, train_size=0.3)
Переходите к общей части.
df = pd.read_csv("smoking_driking_dataset_Ver01.csv")
df.describe()
df.columns, df.shape
column_names = df.columns[1:-2]# нам не нужны гендер и таргеты сейчас
column_names
Попробуем классифицировать людей с плохими привычками и без них, чтобы облегчить нам задачу. Для этого создадим дополнительный столбец в таблице, который будет содержать информацию о том, имеет ли человек вредные привычки или нет.
def smoking_preprocessing(x):
if x == 3 or x == 2:
return 1
else:
return 0
def drinking_preprocessing(x):
if x == 'Y':
return 1
else:
return 0
df['SMK_stat_type_cd'] = df['SMK_stat_type_cd'].apply(func = smoking_preprocessing)
df['DRK_YN'] = df['DRK_YN'].apply(func = drinking_preprocessing)
df['bad_habits'] = df['DRK_YN']+df['SMK_stat_type_cd']-df['DRK_YN']*df['SMK_stat_type_cd']
Теперь смотрим на разделение по привычкам
graph = sns.PairGrid(df.iloc[:1000], hue='bad_habits', vars = column_names[:7])
graph.map_upper(sns.scatterplot)
graph.map_lower(sns.kdeplot)
graph.map_diag(sns.kdeplot)
graph.add_legend()
graph = sns.PairGrid(df.iloc[:1000], hue='bad_habits', vars = column_names[7:15])
graph.map_upper(sns.scatterplot)
graph.map_lower(sns.kdeplot)
graph.map_diag(sns.kdeplot)
graph.add_legend()
graph = sns.PairGrid(df.iloc[:1000], hue='bad_habits', vars = column_names[15:])
graph.map_upper(sns.scatterplot)
graph.map_lower(sns.kdeplot)
graph.map_diag(sns.kdeplot)
graph.add_legend()
target_1 = df.pop('SMK_stat_type_cd')
target_2 = df.pop('DRK_YN')
target_3 = df.pop('bad_habits')
Какие признаки вы бы использовали для разделения людей по классам? Выберите эти столбцы и создайте наборы train и test с помощью функции train_test_split, а также выделите набор данных для валидации при обучении.
# исходя из графиков, отберём признаки для обучения (нас интересуют вредные привычки)
selected_features = [<...>]
X = data[selected_features]
# данных очень много, поэтому для экономии времени автор ноутбука отводит на обучение всего треть датасета
X_train, X_test, y_train, y_test = train_test_split(X, target_3, train_size=0.3)
X_val, X_test, y_val, y_test = train_test_split(X_test, y_test, train_size=0.3)
Общая часть¶
Как вы помните, в задаче классификации предсказывается вероятность. На основании этой вероятности можно делать вывод о принадлежности объекта к тому или иному классу. Причём не всегда используется порог $P=0.5$. Например, если классы несбалансированы, это значение можно варьировать на интервале (0, 1). Предоставляем вам возможность самим выбрать этот порог и поэкспериментировать.
class_lim_proba = <...> # критерий принадлежности к тому или иному классу
Стандартизируйте данные
Напишем функцию для отрисовки кривых обучения. На одном графике расположим значение функции потерь на трейне и валидации, а на другом — значение метрики качества на ваш выбор, также для трейна и валидации.
def plot_learning_curves(history):
'''
Функция для отображения лосса и метрики во время обучения.
'''
clear_output(wait=True)
fig = plt.figure(figsize=(20, 7))
fontsize = 15 # размер шрифта
plt.subplot(1,2,1)
plt.title('Лосс', fontsize=fontsize)
plt.plot(history['loss_train'], label='train')
plt.plot(history['loss_val'], label='val')
plt.ylabel('лосс', fontsize=fontsize)
plt.xlabel('эпоха', fontsize=fontsize)
plt.legend()
plt.subplot(1,2,2)
plt.title('Метрика', fontsize=fontsize)
plt.plot(history['metric_train'], label='train')
plt.plot(history['metric_val'], label='val')
plt.ylabel('Значение метрики', fontsize=fontsize)
plt.xlabel('эпоха', fontsize=fontsize)
plt.legend()
plt.show()
Напишите функцию метрики, которую вы будете использовать, например accuracy.
def metric(y_true, y_pred):
<...>
return metric
Создание модели.¶
В семинаре вы у промежуточных слоёв задавали in_features = out_features = 1
, а в данном случае вам надо будет создать нейросеть из нескольких слоёв, поставив только у последнего из них out_features = 1
.
Какой должна быть размерность входа первого слоя?
model = <...>
model
Обучение¶
В качетсве функции потерь возьмите бинарную кросс-энтропию, а шаг градиентного спуска установите равным 0.5. Можете взять и другие loss и lr
, если хотите поэкспериментировать.
optim_func = <...>
optimizer = <...>
batch_size = 2000 # этот
num_iter = 200 # и этот параметры можете также поварьировать
history = {
'loss_train': [],
'loss_val': [],
'metric_train': [],
'metric_val': [],
}
for i in range(num_iter):
# Так как размер выборки слишком велик, то будем обучать лишь на части данных
indexes_train = np.random.choice(np.arange(len(X_train)), batch_size, replace=False)
local_X_train = X_train[indexes_train]
local_y_train = y_train[indexes_train]
indexes_val = np.random.choice(np.arange(len(X_val)), batch_size//10, replace=False)
local_X_val = <...>
local_y_val = <...>
# Forward pass: предсказание модели по данным X_train
y_pred_train = <...>
with torch.no_grad():
y_pred_val = <...>
# Вычисление оптимизируемой функции (MSE) по предсказаниям
loss_train = <...>
with torch.no_grad():
loss_val = <...>
# Backward pass: вычисление градиентов оптимизируемой функции
# по всем параметрам модели
<...>
# Оптимизация: обновление параметров по формулам соответствующего
# метода оптимизации, используются вычисленные ранее градиенты
<...>
# Зануление градиентов
<...>
# Считаем метрику на эпохе (здесь посчитана acuracy, можете реализовать любую другую за доп. баллы)
metric_train = np.sum((y_pred_train.detach().numpy() >= class_lim_proba).reshape(-1) == local_y_train) / len(local_y_train)
metric_val = np.sum((y_pred_val.detach().numpy() >= class_lim_proba).reshape(-1) == local_y_val) / len(local_y_val)
# Сохраняем результаты эпохи
history['loss_train'].append(loss_train.item())
history['loss_val'].append(loss_val)
history['metric_train'].append(metric_train)
history['metric_val'].append(metric_val)
# График Метрики + Лосса для трейна и валидации каждую итерацию
plot_learning_curves(history)
Тестирование
with torch.no_grad():
y_pred_test = <...>
loss_test = <...>
metric_test = <считается по аналогии с тем, как на обучении>
print(f" Test Loss: {loss_test} \n Test metric: {metric_test}")
Выводы:
В этой задаче мы будем вручную реализовывать усложнение для линейной регрессии, которое вы рассматривали на семинаре.
Запрещено использовать torch.nn (саму библиотеку torch использовать можно и нужно). Чтобы иметь перед глазами оставим здесь формулы:
$$\widehat{y}(x) = w_1u(x) + b_1,$$
$$u(x) = \sigma(w_0x + b_0),$$
$$\sigma(x) = \text{ReLU}(x) = \begin{equation*}\begin{cases}x, \; x \ge 0, \\ 0, \; \text{иначе,} \end{cases} \end{equation*}$$
$w_0, b_0 \in \mathbb{R}$ — обучаемые параметры первого слоя, $w_1, b_1 \in \mathbb{R}$ — обучаемые параметры второго слоя, $\sigma(x)$ — функция активации, в данном случае мы выбрали ReLU
.
Реализуйте функцию активации:
def act_func(x):
return <...>
Задайте оптимизируемую функцию / функцию ошибки / лосс — MSE:
$$ MSE(\widehat{y}, y) = \frac{1}{n}\sum_{i=1}^n\left(\widehat y_i - y_i\right)^2 $$
def optim_func(y_pred, y_true):
return <...>
Обучите вашу модель на датасете с семинарского задания. Сравните полученный результат с результатом семинара.
# Инициализация параметров
w0 = <...>
b0 = <...>
w1 = <...>
b1 = <...>
# Количество итераций
num_iter = 1000
# Скорость обучения для параметров
lr_w = 0.01
lr_b = 0.05
for i in range(num_iter):
# Forward pass: предсказание модели
y_pred = <...>
# Вычисление оптимизируемой функции (MSE)
loss = <...>
# Bakcward pass: вычисление градиентов
loss.backward()
# Оптимизация: обновление параметров
<...>
# Зануление градиентов
<...>
Вывод:
Задача 4¶
Рассмотрим двуслойную нейронную сеть, которая принимает на вход $x\in\mathbb{R}$ и возвращает $y\in\mathbb{R}$. Выход первого слоя возвращает $u \in\mathbb{R}^2$. После первого слоя используется функция активации $\sigma(x) = \frac{1}{1 + \exp(-x)}$, после второго слоя функция активации не используется (или используется тождественная). Тем самым нашу нейронку можно представить в виде
$$\widehat{y}(x) = \sum_{h=1}^2 w_{2h}u_h(x) + b_2,$$
$$u_h(x) = \sigma(w_{1h}x + b_{1h}),$$
$$\text{где} \; h \in \{1, 2\}.$$
1. Нарисуйте схематически данную нейронную сеть. Сколько у нее обучаемых параметров?
...
2. Пусть нам дана обучающая выборка $(X_1, Y_1), ..., (X_n, Y_n)$, где $X_i \in \mathbb{R}$ и $Y_i \in \mathbb{R}$. Нейронная сеть обучается по этой выборке, минимизируя заданную функцию $L$ — функцию ошибки. Положим, что $L$ — это MSE: $$\text{MSE} = L(X, Y) = \frac{1}{n}\sum_{i=1}^n \big(Y_i - \widehat{y}(X_i)\big)^2.$$
Наша задача — найти оптимальные параметры нашей модели для минимизации $L(X, Y)$ на заданном наборе данных. Мы будем решать эту задачу с помощью градиентного спуска. Для этого нам понадобится выписать производные по всем параметрам сети. Конечно, в данном случае довольно просто выписать все производные напрямую. Однако мы воспользуемся следующей хитростью: мы будем считать производные поэтапно, причем начнем с конца вычислительной цепочки и, используя формулу производной сложной функции, последовательно посчитаем все необходимые производные. Этот процесс называется методом обратного распространения ошибки (backpropagation).
2.1. Начнем с производной MSE по выходам сети: $$\frac{\partial\:\text{MSE}}{\partial \widehat{y}(X_i)} = \; ...$$
2.2 Возьмем производные выходов сети по параметрам последнего слоя
$$\frac{\partial \widehat{y}(X_i)}{\partial w_{2h}} = \; ...$$
$$\frac{\partial \widehat{y}(X_i)}{\partial b_2} = \; ...$$
Также выпишем производные выходов сети по входам последнего слоя:
$$\frac{\partial \widehat{y}(X_i)}{\partial u_h(X_i)} = \; ...$$
Теперь выпишем производные MSE по параметрам и входам последнего слоя. Для этого вспомните правило производной сложной функции из математического анализа. Обратите внимание на то, что нам не нужно прописывать все производные до конца, достаточно заполнить пропуски в записи ниже:
$$\frac{\partial\:\text{MSE}}{\partial w_{2h}} = \sum_{i=1}^n \frac{\partial\:\text{MSE}}{\partial ...} \frac{\partial ...}{\partial w_{2h}}$$
$$\frac{\partial\:\text{MSE}}{\partial b_2} = \sum_{i=1}^n \frac{\partial\:\text{MSE}}{\partial ...} \frac{\partial ...}{\partial b_2}$$
$$\frac{\partial\:\text{MSE}}{\partial u_h} = \sum_{i=1}^n \frac{\partial\:\text{MSE}}{\partial ...} \frac{\partial ...}{\partial u_h}$$
2.3. Теперь будем разбираться с производными по параметрам первого слоя.
Для начала нам пригодится производная функции активации, запишите ее так, чтобы ответе осталась функция от $\sigma(x)$:
$$\frac{\partial\:\sigma(x)}{\partial x} = \; ...$$
Теперь возьмем производные выходов первого слоя по его параметрам:
$$\frac{\partial u_h(X_i)}{\partial w_{1h}} = \; ...$$
$$\frac{\partial u_h(X_i)}{\partial b_{1h}} = \; ...$$
Наконец, выпишем производные MSE по параметрам первого слоя. Так же как и раньше достаточно заполнить пропуски в записи ниже:
$$\frac{\partial\:\text{MSE}}{\partial w_{1h}} = \; \sum_{i=1}^n \frac{\partial\:\text{MSE}}{\partial ...} \frac{\partial ...}{\partial w_{1h}}$$
$$\frac{\partial\:\text{MSE}}{\partial b_{1h}} = \; \sum_{i=1}^n \frac{\partial\:\text{MSE}}{\partial ...} \frac{\partial ...}{\partial b_{1h}}$$
3. Пусть обучающая выборка очень большая. Что нужно делать в таком случае? Запишите, как нужно поменять правило обновления параметров.
...
Вывод:
...