Phystech@DataScience¶

Домашнее задание 2¶

Правила, прочитайте внимательно:

  • Выполненную работу нужно отправить телеграм-боту @thetahat_ds25_bot. Для начала работы с ботом каждый раз отправляйте /start. Дождитесь подтверждения от бота, что он принял файл. Если подтверждения нет, то что-то не так. Работы, присланные иным способом, не принимаются.
  • Дедлайн см. в боте. После дедлайна работы не принимаются кроме случаев наличия уважительной причины.
  • Прислать нужно ноутбук в формате ipynb. Если вы строите интерактивные графики, их стоит прислать в формате html.
  • Следите за размером файлов. Бот не может принимать файлы весом более 20 Мб. Если файл получается больше, заранее разделите его на несколько.
  • Выполнять задание необходимо полностью самостоятельно. При обнаружении списывания всем участникам списывания дается штраф -2 балла к итоговой оценке за семестр.
  • Решения, размещенные на каких-либо интернет-ресурсах, не принимаются. Кроме того, публикация решения в открытом доступе может быть приравнена к предоставлении возможности списать.
  • Обратите внимание на правила использования ИИ-инструментов при решении домашнего задания.
  • Код из рассказанных на занятиях ноутбуков можно использовать без ограничений.
  • Для выполнения задания используйте этот ноутбук в качестве основы, ничего не удаляя из него. Можно добавлять необходимое количество ячеек.
  • Комментарии к решению пишите в markdown-ячейках.
  • Выполнение задания (ход решения, выводы и пр.) должно быть осуществлено на русском языке.
  • Решение проверяется системой ИИ-проверки No description has been provided for this image ThetaGrader. Результат проверки валидируется и исправляется человеком, после чего комментарии отправляются студентам.
  • Если код будет не понятен проверяющему, оценка может быть снижена.
  • Никакой код из данного задания при проверке запускаться не будет. Если код студента не выполнен, недописан и т.д., то он не оценивается.

Важно!!! Правила заполнения ноутбука:

  • Запрещается удалять имеющиеся в ноутбуке ячейки, менять местами положения задач.
  • Сохраняйте естественный линейный порядок повествования в ноутбуке сверху-вниз.
  • Отвечайте на вопросы, а также добавляйте новые ячейки в предложенных местах, которые обозначены <...>.
  • В markdown-ячейка, содержащих описание задачи, находятся специальные отметки, которые запрещается модифицировать.
  • При нарушении данных правил работа может получить 0 баллов.

Перед выполнением задания посмотрите презентацию по выполнению и оформлению домашних заданий с занятия 2.

Баллы за задание:

  • Задача 1 — 50 баллов
  • Задача 2 — 50 баллов
  • Задача 3 — 30 баллов

In [ ]:
# Bot check

# HW_ID: phds_hw2
# Бот проверит этот ID и предупредит, если случайно сдать что-то не то

# Status: not final
# Перед отправкой в финальном решении удали "not" в строчке выше
# Так бот проверит, что ты отправляешь финальную версию, а не промежуточную
In [ ]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, mean_squared_error, mean_absolute_error, mean_absolute_percentage_error, r2_score
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import StandardScaler


import warnings
warnings.filterwarnings("ignore")
import seaborn as sns
sns.set_theme(palette='Set2')

Легкая часть¶


Задача 1¶

1. Загрузка данных и предобработка¶

Профиль биология¶

Загрузите данные по предсказанию рака груди.

In [ ]:
data = pd.read_csv('breast_cancer_disbalances.csv')
data.head()

Проверьте, имеются ли в ваших данных пропуски. Если да, то удалите их.

In [ ]:
...

Библиотека pandas позволяет строить графики matplotlib для своих объектов DataFrame (подробнее). Посмотрим, как распределены значения признака Bare Nuclei для разных классов:

In [ ]:
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 или другим методом, который вам нравится. Не забывайте подписывать, к чему относится каждый график.

In [ ]:
 

Какие выводы вы можете сделать из полученных графиков?

Вывод: ...

Это не конец задачи! Переходите к пункту 2!

Профиль физика¶

Загрузите данные по бинарной классификации астероидов в зависимости от различных параметров с сайта.

Вашей целевой переменной будет являться столбец pha. Более подробно ознакомить с датасетом вы можете также здесь. Можно заметить, что наш датасет сильно меньше по размерам, чем оригинал. Это сделано намеренно.

In [ ]:
data = pd.read_csv('asteroid_cut.csv')
data.info()

Библиотека pandas позволяет строить графики matplotlib для своих объектов DataFrame (подробнее). Посмотрим, как распределены значения признака rms для разных классов:

In [ ]:
fig, axs = plt.subplots(1, 2, figsize=(10, 5))
data.groupby("pha")['rms'].hist(ax=axs[0], density=True)
data.groupby("pha")['rms'].plot(kind='kde', ax=axs[1])
axs[0].set_title('Гистограмма для H', fontsize=20)
axs[1].set_title('KDE для H', fontsize=20);

Чем отличаются способы построения ЯОП и гистограммы? Какую информацию о наших данных можно извлечь из каждого графика?

Ответ: ...

Постройте гистограммы и ядерные оценки плотности для указанных ниже признаков отдельно для каждого класса. pha — целевая переменная. Можно это сделать, опираясь на код выше, а можно воспользоваться параметром hue у функции sns.histplot или другим методом, который вам нравится. Не забывайте подписывать, к чему относится каждый график.

In [ ]:
features = ['epoch', 'ma', 'tp', 'rms']

...

Какие выводы вы можете сделать из полученный графиков?

Вывод: ...

2. Обучение модели¶

Продолжайте использовать выбранные вами данные.

Создайте массив признаков и массив таргета. Разбейте ваши данные на обучающую и тестовую выборки в отношении 7:3.

In [ ]:
X = ...
y = ...
X_train, X_test, y_train, y_test = ...

Примените стандартизацию к обучающей и тестовой выборкам, используя класс StandardScaler, натренированный на обучающей выборке.

In [ ]:
...

Объясните, что делает StanderdScaler и почему нельзя его нельзя обучать на тестовой выборке?

Ответ:

...

Обучите модель логистической регрессии.

In [ ]:
...

Сделайте предсказание для тестовой выборки и оцените качества полученного предсказания, используя метрику accuracy_score

Если названия ваших классов отличаются от 0 и 1, то надо использовать pos_label.

In [ ]:
...

Можем ли порадоваться таким результатам? Вернемся к гистограммам и сделаем вывод, почему метрика оказалась такой большой.

Давайте посмотрим на распределение наших данных по целевой переменной по всему датасету, тренировочной и тестовой выборках.

In [ ]:
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, ax=axes[0], palette=['blue'])
axes[0].set_title('Распределение классов в data')
axes[0].set_ylabel('Количество')

sns.barplot(x=train.index, y=train, ax=axes[1], palette=['green'])
axes[1].set_title('Распределение классов в train')

sns.barplot(x=test.index, y=test, ax=axes[2], palette=['orange'])
axes[2].set_title('Распределение классов в test')

plt.show()

Видно, что в данных есть сильный перекос — классы представлены неравномерно. Как и почему это может повлиять на наши результаты?

Ответ: ...

Есть много способов борьбы с этим. Можно искусственно сгенерировать данные нужного класса или урезать другой класс. Однако сегодня мы воспользуемся взвешенной логистической регрессией. Суть метода в том, что мы вручную поставим веса для классов, исходя из их предполагаемой природы: важность разных классов, цена ошибки в реальной жизни(например, что лучше, предсказать наличие рака, если он есть или нет?) и представленность данных.

Кросс-энтропия для взвешенной логистической регресси будет записана как:

$$ F(\theta) = \sum_{i=1}^{n} H(y_i, \widehat{y}_i) = -\sum_{i=1}^{n} w_{y_i} \left[ y_i \log(\widehat{y}_i) + (1-y_i) \log(1-\widehat{y}_i) \right] $$

где:

  • $ y_i $ - истинный класс для образца $i$
  • $ \widehat{y}_i = \sigma(\theta^Tx_i)$ - предсказанный класс для образца $i$
  • $ w_{y_i} $ - вес класса

Давайте применим этот подход на практике. Воспользуемся параметром class_weight. Для начала определим количество примеров каждого класса в обучающей выборке и вычислим их соотношение.

In [ ]:
...

Применим рассчитанное соотношение классов в качестве весов:

In [ ]:
threshold = ...
class_weights = {<класс_1>: threshold, <класс_2>: 1 - threshold}
# если использовать class_weights = 'balanced' модель сама подсчитает веса

weighted_model = LogisticRegression(class_weight=class_weights, random_state=0)
...

Посчитаем метрику accuracy_score:

In [ ]:
accuracy = ...
print(f"accuracy = {accuracy}")

Как изменилось качество нашей модели?

Сделайте общий вывод по задаче.

Вывод:


Задача 2¶

Датасет penguins¶

В первой части задания мы познакомимся с новым для нас датасетом

In [ ]:
penguins = pd.read_csv('penguins.csv')
penguins.head()

⚠️ Warning: Лучше не нажимать на пингвинов

Здесь содержатся данные о морфололгии пингвинов трех различных видов: Adelie, Chinstrap, и Gentoo.

No description has been provided for this image

Оставшеися колонки: bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g описывают морфологию каждого пингвина.

No description has been provided for this image

Как мы видим, в данном датасете присутствуют категориальные переменные: species, island и sex, которые не могут быть оценены моделью линейной регресии. Закодируйте их: к переменным, где присутствует более трёх категорий, примените преобразование OneHotEncoder. Обратите внимание на аргумент drop.

In [ ]:
cat_features = [...]

onehotencoder = ...
encoded = pd.DataFrame(onehotencoder.fit_transform(...), dtype=int)

penguins_encoded = penguins.drop(cat_features, axis=1).join(...)
penguins_encoded = penguins_encoded.rename(str, axis="columns")

penguins_encoded.head()

Выберем в качестве таргета вес пингвинов (body_mass_g), и разделим нашу выборку на тренировочную и тестовую:

In [ ]:
X = penguins_encoded.drop('body_mass_g', axis=1)
y = penguins_encoded['body_mass_g']
X_train, X_test, y_train, y_test = ...

Реализуйте линейную регрессию самостоятельно, используя формулы с лекции. Вам нужно только заполнить прочерки в методах fit и predict.

In [ ]:
class MyLinearRegression:
    """
    Класс, реализующий линейную регрессию c помощью МНК.
    """

    def __init__(self):
        pass

    def fit(self, X, Y):
        """
        Функция обучения модели.

        Предполагается модель Y = X * theta + epsilon.
        где X --- регрессор (матрица размера n x d),
        Y --- отклик (матрица размера n x 1),
        epsilon-ы имеют нормальное распределение

        Обратите внимание, здесь нет intercept_
        """

        self.n, self.d = X.shape[0], X.shape[1]

        self.theta = МНК-оценка

        return ...


    def predict(self, X):
        """
        Возвращает предсказание отклика на новых объектах X.

        X --- матрица объектов размера n x d
        """

        y_pred =  ...
        return ...

Обучите вашу модель на датасете о пингвинах без добавления свободного члена. Распечатайте коэффициенты и сравните их с коэффициентами модели из sklearn.

In [ ]:
MyModel = ...

...

Как соотносятся коэффициенты вашей модели и реализации из sklearn?

Ответ: ...

Теперь обучите собственную модель c добавлением свободного члена.

In [ ]:
...

Помимо рассмотренных на семенаре метрик, довольно часто рассматривают коэфициент детерминации $R^2$, который может быть более интуитивным.

Определяется он так: $$ R^2 = 1 - \frac{SS_{reg}}{SS_{tot}}, $$ где $SS_{res} = \sum\limits_{i=1}^n (y_i - f_i)^2$ отражает точность нашей модели,
a $SS_{tot} = \sum\limits_{i=1}^n (y_i - \overline{y})^2$ отражает дисперсию исходных данных.
Соответсвенно чем результат ближе к еденице тем более точная у нас модель и тем лучше она предсказывает. Отрицательный же результат можем получить в случае, если наша модель предсказывает хуже, чем предсказание средним.

В sklearn есть готовая реализация под названием r2_score

Сравните качество моделей со свободным членом и без него на тестовой выборке, используя метрики RMSE, MAE, MAPE и $R^2$.

In [ ]:
...

Сделайте выводы

Вывод: ...


Задача 3¶

Во взвешенном методе наименьших квадратов каждому наблюдению задается некоторый известный вес $w_i$. Задача имеет вид: $$\sum\limits_{i=1}^n w_i\left(Y_i - x_i^T \theta\right)^2 \to \min\limits_\theta$$ Найдите решение задачи в матричном виде.