Физтех.Статистика
Скачать ipynb
Phystech@DataScience¶
Домашнее задание 4¶
Правила, прочитайте внимательно:
- Выполненную работу нужно отправить телеграм-боту
@miptstats_pds_bot
. Для начала работы с ботом каждый раз отправляйте/start
. Работы, присланные иным способом, не принимаются. - Дедлайн см. в боте. После дедлайна работы не принимаются кроме случаев наличия уважительной причины.
- Прислать нужно ноутбук в формате
ipynb
. - Выполнять задание необходимо полностью самостоятельно. При обнаружении списывания все участники списывания будут сдавать устный зачет.
- Решения, размещенные на каких-либо интернет-ресурсах, не принимаются. Кроме того, публикация решения в открытом доступе может быть приравнена к предоставлении возможности списать.
- Для выполнения задания используйте этот ноутбук в качестве основы, ничего не удаляя из него. Можно добавлять необходимое количество ячеек.
- Комментарии к решению пишите в markdown-ячейках.
- Выполнение задания (ход решения, выводы и пр.) должно быть осуществлено на русском языке.
- Если код будет не понятен проверяющему, оценка может быть снижена.
- Никакой код из данного задания при проверке запускаться не будет. Если код студента не выполнен, недописан и т.д., то он не оценивается.
- Код из рассказанных на занятиях ноутбуков можно использовать без ограничений.
Правила оформления теоретических задач:
- Решения необходимо прислать одним из следующих способов:
- фотографией в правильной ориентации, где все четко видно, а почерк разборчив,
- отправив ее как файл боту вместе с ноутбуком или
- вставив ее в ноутбук посредством
Edit -> Insert Image
(фото, вставленные ссылкой, не принимаются);
- в виде $\LaTeX$ в markdown-ячейках.
- фотографией в правильной ориентации, где все четко видно, а почерк разборчив,
- Решения не проверяются, если какое-то требование не выполнено. Особенно внимательно все проверьте в случае выбора второго пункта (вставки фото в ноутбук). Неправильно вставленные фотографии могут не передаться при отправке. Для проверки попробуйте переместить
ipynb
в другую папку и открыть его там. - В решениях поясняйте, чем вы пользуетесь, хотя бы кратко. Например, если пользуетесь независимостью, то достаточно подписи вида "X и Y незав."
- Решение, в котором есть только ответ, и отсутствуют вычисления, оценивается в 0 баллов.
Баллы за задание:
Легкая часть (достаточно на "хор"):
- Задача 1 — 50 баллов
- Задача 2 — 50 баллов
Сложная часть (необходимо на "отл"):
- Задача 3 — 30 баллов
- Задача 4 — 20 баллов
# Bot check
# HW_ID: phds_hw4
# Бот проверит этот ID и предупредит, если случайно сдать что-то не то.
# Status: not final
# Перед отправкой в финальном решении удали "not" в строчке выше.
# Так бот проверит, что ты отправляешь финальную версию, а не промежуточную.
# Никакие значения в этой ячейке не влияют на факт сдачи работы.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, mean_squared_error, mean_absolute_error, mean_absolute_percentage_error
from sklearn.linear_model import Ridge, Lasso, ElasticNet
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import GroupShuffleSplit
import warnings
import seaborn as sns
sns.set_theme(palette='Set2')
warnings.filterwarnings("ignore")
data = pd.read_csv('breast_cancer.csv')
data.head()
Проверьте, имеются ли в ваших данных пропуски. Если да, то удалите их.
<...>
Библиотека pandas
позволяет строить графики matplotlib
для своих объектов DataFrame
(подробнее). Посмотрим, как распределены значения признака Bare Nuclei
для разных классов:
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
data.groupby("Class")['Bare Nuclei'].hist(ax=axs[0], alpha=0.5)
data.groupby("Class")['Bare Nuclei'].plot(kind='kde', ax=axs[1])
axs[0].set_title('Гистограмма для Bare Nuclei', fontsize=20)
axs[1].set_title('KDE для Bare Nuclei', fontsize=20);
Чем отличаются способы построения ЯОП и гистограммы? Какую информацию о наших данных можно извлечь из каждого графика?
Ответ:
Постройте гистограммы и ядерные оценки плотности для всех признаков из датасета отдельно для каждого класса. Class
— целевая переменная. Можно это сделать, опираясь на код выше, а можно воспользоваться параметром hue
у функции sns.histplot
или другим методом, который вам нравится. Не забывайте подписывать, к чему относится каждый график.
Какие выводы вы можете сделать из полученных графиков?
Вывод: <...>
Это не конец задачи! Переходите к пункту 2!
Профиль физика¶
Загрузите данные по бинарной классификации астероидов в зависимости от различных параметров с сайта.
Вашей целевой переменной будет являться столбец pha
. Более подробно ознакомить с датасетом вы можете также здесь. Можно заметить, что наш датасет сильно меньше по размерам, чем оригинал. Это сделано намеренно.
data = pd.read_csv('dataset_savaged.csv')
data.info()
Библиотека pandas
позволяет строить графики matplotlib
для своих объектов DataFrame
(подробнее). Посмотрим, как распределены значения признака rms
для разных классов:
fig, axs = plt.subplots(1, 2, figsize=(15, 7))
data.groupby("pha")['rms'].hist(ax=axs[0], alpha=0.5)
data.groupby("pha")['rms'].plot(kind='kde', ax=axs[1])
axs[0].set_title('Гистограмма для rms', fontsize=20)
axs[1].set_title('KDE для rms', fontsize=20);
Чем отличаются способы построения ЯОП и гистограммы? Какую информацию о наших данных можно извлечь из каждого графика?
Ответ: <...>
Постройте гистограммы и ядерные оценки плотности для указанных ниже признаков отдельно для каждого класса. Class
— целевая переменная. Можно это сделать, опираясь на код выше, а можно воспользоваться параметром hue
у функции sns.histplot
или другим методом, который вам нравится. Не забывайте подписывать, к чему относится каждый график.
features = ['epoch', 'ma', 'tp', 'rms']
<...>
Какие выводы вы можете сделать из полученных графиков?
Вывод:<...>
2. Обучение модели¶
Продолжайте использовать выбранные вами данные.
Создайте массив признаков и массив таргета. Разбейте ваши данные на обучающую и тестовую выборки в отношении 7:3.
X = <...>
y = <...>
X_train, X_test, y_train, y_test = <...>
Примените стандартизацию к обучающей и тестовой выборкам, используя класс StandardScaler
.
<...>
Объясните, что делает StandardScaler
и почему его нельзя обучать на тестовой выборке?
Ответ:
Обучите модель логистической регрессии.
<...>
Сделайте предсказание для тестовой выборки и оцените качества полученного предсказания, рассмотрите метрики: accuracy_score
, precision
и recall
.
Если названия ваших классов отличаются от 0 и 1, то надо использовать аргумент pos_label
.
<...>
Можем ли порадоваться таким результатам? Вернемся к гистограммам и сделаем вывод, почему метрики оказались такими большими.
3. Учтём дисбаланс классов¶
Давайте посмотрим на распределение наших данных по целевой переменной по всему датасету, тренировочной и тестовой выборках:
original = <...>.value_counts() # Колонка таргета из изначального датасета
train = <...>.value_counts() # Колонка таргета из тренировочного датасета
test = <...>.value_counts() # Колонка таргета из тестового датасета
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
sns.barplot(x=original.index, y=original.values, ax=axes[0], palette=['blue'])
axes[0].set_title('Распределение классов в data')
axes[0].set_ylabel('Количество')
sns.barplot(x=train.index, y=train.values, ax=axes[1], palette=['green'])
axes[1].set_title('Распределение классов в train')
sns.barplot(x=test.index, y=test.values, ax=axes[2], palette=['orange'])
axes[2].set_title('Распределение классов в test')
plt.show()
Видно, что в данных есть сильный перекос — классы представлены неравномерно. Как и почему это повлияло на наши результаты?
Ответ: <...>
Есть много способов борьбы с этим. Можно искусственно сгенерировать данные нужного класса или урезать другой класс. Однако сегодня мы воспользуемся взвешенной логистической регрессией. Суть метода заключается в том, чтобы вручную поставить веса для классов, исходя из их предполагаемой природы: важность разных классов, цена ошибки в реальной жизни (например, что хуже: предсказать наличие рака, если он есть или нет?) и представленность данных.
Функция потерь — в нашем случае логарифм функции правдоводобия — для взвешенной логистической регресси будет записана как:
$$ L(y, \widehat{y}) = \sum_{i=1}^{N} w_{y_i} \cdot \left[ y_i \cdot \log(\sigma(\widehat{y}_i)) + (1 - y_i) \cdot \log(1 - \sigma(\widehat{y}_i)) \right] $$
где:
- $ y_i $ - истинный класс для образца $i$
- $ \widehat{y}_i $ - предсказанный класс для образца $i$
- $ w_{y_i} $ - вес класса
Давайте реализуем этот метод. Допишите код и в качестве весов класса поставьте соотношение их представленности. Выведите подсчет количества экземпляров каждого класса и посчитайте их соотношение.
threshold = <...>
class_weights = {<класс_1>: threshold, <класс_2>: 1 - threshold}
# если использовать class_weights = 'balanced', модель сама подсчитает веса
weighted_model = LogisticRegression(class_weight=class_weights)
Посчитайте метрики качества. Accuracy
посчитайте двумя способами: без учёта и с учётом весов.
<...>
print(f"accuracy = {accuracy} \nprecision = {precision} \nrecall = {recall}")
Как изменилось качество нашей модели? Почему надо учитывать несбалансированность данных?
Сделайте общий вывод по задаче.
Вывод:
Задача 2¶
Вам предлагается изучить и сравнить свойства линейных регрессионных моделей: обычной и с регуляризациями — Lasso
, Ridge
, Elastic Net
.
При выполнении задания воспользуйтесь готовыми реализациями методов в sklearn
. Функции, описанные ниже, пригодятся вам во втором пункте этого задания.
def calculate_coef(model, X, y, log_min, log_max,
num):
"""
Данная функция считает коэффициенты для признаков
при различных значениях параметра регуляризации.
:param model: регрессионная модель
:param X: матрица регрессоров
:param y: вектор целевой переменной
:param log_min, log_max: логарифмы левой и правой границ диапазона для коэффициента регуляризации
:param num: число точек из диапазона
:return coefs: коэффициенты модели
"""
alphas = np.logspace(log_min, log_max, num) # сетка параметров
coefs = [] # коэффициенты моделей
for a in alphas:
if 'l1_ratio' in model.get_params(): # для ElasticNet
# равномерно распределим alpha по обоим коэффициентам
a *= 3/2
model.set_params(alpha=a) # переопределяем параметры модели
else:
model.set_params(alpha=a)
model.fit(X, y)
# отбираем только первые 20 признаков для ускорения работы кода
coefs.append(model.coef_[:20])
return coefs
def draw_track(coefs, log_min, log_max,
num, title='', figsize=(10, 5)):
"""
Данная функция строит график зависимости значений
коэффициентов модели от параметра регуляризации.
:param coefs: коэффициенты модели
:param log_min, log_max: логарифмы левой и правой границ диапазона для коэффициента регуляризации
:param num: число точек из диапазона
:param title: название графика
:param figsize: размеры рисунка
:return coefs: коэффициенты модели
"""
alphas = np.logspace(log_min, log_max, num) # сетка параметров
plt.figure(figsize=figsize)
ax = plt.gca() # используется для получения текущего экземпляра axes
ax.hlines(0, 10 ** log_min, 10 ** log_max, linewidth=15, alpha=0.15)
ind = 1
for coef in np.array(coefs).T:
label = r'$\theta_{' + str(ind) + '}$'
ax.plot(alphas, coef, linewidth=2, label=label) # рисуем коэффициенты в зависимости от alpha
ind += 1
ax.set_xscale('log') # логарифмическая шкала
ax.set_xlim(ax.get_xlim()[::-1]) # обратить ось
plt.xlabel('Параметр регуляризации', fontsize=19)
plt.ylabel('Значения коэффициентов', fontsize=19)
plt.title(title, fontsize=22)
plt.legend(loc='upper left', fontsize=8)
plt.axis('tight')
plt.show()
1. Загрузка данных¶
Профиль биология¶
Скачайте данные с сайта. Оригинал вместе с описанием можно найти здесь. Сами данные лежат в Data Folder
. Файл .data
можно читать с помощью read_csv
. В этой задаче мы хотим предсказать уровень выраженности болезни Паркинсона в зависимости от параметров речи пациента. В датасете есть записи о 42 пациентах, для каждого некотрое количество записей.
data = pd.read_csv('parkinsons_updrs.data', sep=',')
data.info()
Нас интересует предсказание total_UPDRS
— степени заболевания. Для корректной постановки задачи удалите из данных столбец motor_UPDRS
, так как это тоже мера тяжести заболевания, но лишь в аспекте моторных нарушений. Будем предсказывать значение total_UPDRS
в зависимости от остальных признаков.
Также обратите внимане, что в данных есть группы (пациенты). Колонку subject#
следует использовать не в качестве признака, а в качестве группы. Разделите данные на признаки $X$, таргет $y$ и массив номеров групп.
<...>
Разбейте данные на обучающую и тестовую выборки в соотношении 7:3. Здесь не подойдет стандартный метод test_train_split
, так как в данных есть группы. Нельзя допускать, чтобы разные записи для одного пациента попали в разные подвыборки.
Также, выведите что-либо, подтверждающее данное свойство.
groups = data["subject#"]
gss = GroupShuffleSplit(n_splits=1, test_size=0.3, random_state=42)
train_index, test_index = next(gss.split(X, y, groups=groups))
<...>
Далее везде, вплоть до сравнения моделей в задаче 3, используйте обучающую выборку.
Переходите к пункту 2.
Профиль физика¶
Загрузите данные с сайта. Данные были предобработаны и сокращены для более быстрой работы алгоритмов предсказания, так как в этом задании их будет большое кол-во. С исходными данными вы можете ознакомиться здесь.
В таблице находятся записи в кулоновской матрице в сжатом виде, которые действуют как молекулярные признаки. 0-я колонка — это Pubchem Id, по этому числу вы можете понять, для какой молекулы приведены числа. Этот столбец возьмем в качестве индекса строк. Последний столбец Eat
— это энергия распыления, рассчитанная путем моделирования с использованием пакета Quantum Espresso. Этот столбец и является целевой переменной.
Для интересующихся: cнижение размерности пространства признаков проводилось с помощью метода главных компонент.
data = pd.read_csv('physics_data.csv', index_col=0)
data.head()
Разделите данные на признаки $X$ и целевые переменные $y$. Для дальнейших заданий оставьте 20 признаков.
<...>
Разделите выборку в отношении 7:3. Далее везде, вплоть до сравнения моделей, используйте обучающую выборку.
X_train, X_test, y_train, y_test = <...>
Далее везде, вплоть до сравнения моделей в задаче 3, используйте обучающую выборку.
2. Влияние регуляризации на коэффициенты моделей¶
Примените стандартизацию к обучающей и тестовой выборкам, используя класс StandardScaler
.
<...>
Исследуйте зависимость значений коэффициентов от параметра регуляризации alpha
для Ridge, Lasso, Elastic регрессий. Используйте функции calculate_coefs
и draw_track
, реализованные в самом начале этой задачи.
Нарисуйте графики. Предложите диапазоны значений, где стоило бы искать оптимальные параметры регуляризации.
# Ridge регрессия
# инициализация и обучение моддели
ridge_model = <...>
# коэффициенты регрессии
ridge_coefs = <...>
# отрисовка
<...>
# Lasso регрессия
lasso_model = <...>
lasso_coefs = <...>
<...>
# Elastic регрессия
elastic_model = <...> # установите какое-то значение l1_ratio
elastic_coefs = <...>
<...>
Ответ:
Посмотрите, как выглядят графики без стандартизации. Почему так происходит?
Ответ:
Сложная часть¶
Задача 3¶
Эта задание является продолжением предыдущего. Здесь не нужно загружать новые данные, продолжайте работать с выбранными вами данными.
1. Для Elastic исследуйте зависимость от параметра l1_ratio
. Постройте график изменения весов признаков в зависимости от l1_ratio
для первых 20 признаков из датасета.
grid = <...>
coefs = <...>
model = <...>
for l1_ratio in grid:
<...> # Задайте новый параметр модели
<...> # Обучите
<...># Добавьте в список
# Для визуализации можно использовать код из функции draw_track
Предложите диапазоны значений, где стоило бы искать оптимальные параметры регуляризации.
Вывод:
2. Проиллюстрируйте, как меняется качество предсказания моделей при изменении параметра alpha
. Возьмите Ridge
, Lasso
и 3 ElasticNet
с разными фиксированными значениями l1_ratio
— вы будете исследовать 5 моделей с регуляризацией и 1 без нее.
Физика: Для этого задания возьмите полный датасет — все 300 признаков.
<...>
Сначала посчитайте ошибки для линейной регрессии без регуляризации.
linreg = {}
linreg['MSE'] = mean_squared_error(<...>)
linreg['MAE'] = mean_absolute_error(<...>)
linreg['MAPE'] = mean_absolute_percentage_error(<...>)
Допишите функцию для отрисовки изменения величины ошибки от параметра регуляризации.
def draw_errors(error, error_name, alphas):
"""
Функция строит график зависимости величины ошибки от параметра alpha для разных моделей
:param error: функция, вычисляющая ошибку
:param error_name: имя функции, вычисляющей ошибку (одно из 'MSE', 'MAE', 'MAPE')
:param alphas: массив величин alpha
"""
arr = [] # массив ошибок
for a in alphas:
tmp = [] # массив ошибок
models = [<Ваши модели с заданными парметрами>]
for model in models:
# обучение модели и предсказание
y_pred = <...>
tmp.append(error(y_test, y_pred))
arr.append(tmp)
arr = np.array(arr)
plt.figure(figsize=(10, 6), dpi=100)
names = <Имена моделей с заданными параметрами>
for i in range(5):
plt.plot(alphas, arr[:, i], label=names[i]) # рисуем ошибки в зависимости от alpha
# прерывистой линией рисуем ошибки логрега без регуляризации
plt.hlines(linreg[error_name], alphas[0], alphas[-1], color='black', label = 'No regularization', linestyles='dashed')
plt.xlabel('Параметр регуляризации')
plt.ylabel(error_name)
plt.xscale('log')
plt.legend()
Постройте графики для MSE, MAE и MAPE. Возьмите предложенный массив alphas
.
alphas = np.logspace(-2, 8, 20)
# вложите в функции метрики
draw_errors(<...>)
draw_errors(<...>)
draw_errors(<...>)
Оцените по графикам, в каких диапазонах достигается наилучшее качетсво предсказания моделей. Постройте графики для более узкого диапазона, чтобы сравнить модели более детально.
<...>
3. Сделайте общий вывод по задаче.
Укажите: в чем разница между L1
и L2
регуляризациями, как реализуется регуляризация в ElasticNet
, что такое l1-ratio
и зачем нужен, как это видно в наших графиках. (Своими словами)
Вывод: <...>
Задача 4¶
Регуляризацию успользуют не только в задачах регрессии, но и в задачах классификации.
Пусть дана выборка $(x_1, Y_1), ..., (x_n, Y_n)$, где $x_i = (x_{i1}, ..., x_{id}) \in \mathscr{X}$ и случайный класс $Y_i \sim Bern\left(\sigma (\theta^T x_i)\right)$. В задаче логистической регрессии максимизируется функция правдоподобия, а точнее - ее логарифм. $$L_Y (\theta)= \prod\limits_{i=1}^n \sigma (\theta^T x_i)^{Y_i} \left(1 - \sigma (\theta^T x_i)\right)^{1-Y_i}$$
$$\ell_Y(\theta) = \log L_Y(\theta)$$
$$\ell_Y(\theta) \longrightarrow \max_\theta.$$ Регуляризация заключается в искусственном усечении значений вектора оценок коэффициентов путем добавления его нормы к оптимизируемому функционалу. В случае логистической регрессии мы максимизируем функцию правдоподобия, поэтому норма добавляется со знаком минус. Тем самым решается задача $$\ell_Y(\theta) - \lambda \|\theta\|^2\longrightarrow \max_\theta,$$ где $\lambda > 0$ — гиперпараметр модели, то есть число, которое задается пользователем. Мы получили логистическую регрессию c $l_2$-регуляризацией.
Замечание. Такая модель дает некоторое другое приближение неизвестной зависимости. Но неправильно думать, что она не может дать "правильный" ответ, потому такого понятия как "правильный ответ" в подобных задачах не существует. Можно получить только более качественное приближение,согласно выбранной метрике.
Выведите формулу поиска оценки коэффициентов методом градиентного подъёма и стохастического градиентного подъёма для
- модели логистической регрессии без регуляризации
- модели логистической регрессии c ridge-регуляризацией