В космосе есть нечто завораживающее и прекрасное, в то же время человек устроен так, что если ему что то не известно, то он будет этого бояться (спасибо нашим мамам папам в n-ном поколении за столь широкий диапазон восприятия информации и реагирования на неё), тем не менее всегда находились безумцы исследователи, мечтатели и просто люди, которым в лом заниматься тем, что уже итак без них придумали и хорошо работает, поэтому они стремились придумать что то новое. Кто то занимается курсами по бесконечным саморазвитиям, открывает новые виды дыхания, а также наполняет свои чакры и чувствует прилив сил, а кто-то действительно пытается обнаружить то, что обычному человеку скорее всего в ближайшие лет 50 (а может и больше) не понадобится, ведь вряд-ли мы сможем покинуть нашу солнечную систему раньше этого срока. Однако в том чтобы смотреть в ночное небо и пытаться нарисовать у себя в голове линии, которые называют большой медведицей или тот же ковш, а может и повезет увидеть млечный путь во всей своей красе, есть нечто притягательное и необычное, то что заставляет одновременно почувствовать себя, как говорят некоторые маленькой точкой, но в то же время не забываем что у нас есть микромир, для которого человек, грубо говоря уже сам является целой вселенной. Как писала Лиза Рэндалл в 'достучаться до небес', человек, он где то посередине всего этого мира. Но мы тут про небо и звезды говорили, так что закончим отсылки на научпоп по квантовой физике и перейдем к делу.

Млечный путь пикча из интернета
Млечный путь пикча из интернета

В данной статье я хотел бы рассказать про задачу, непосредственно, связанную с тем, о чем говорилось выше, а именно про классификацию экзопланет, на основе данных с орбитального спутника Kepler.
(Датасет можно найти по этой ссылке на Kaggle: https://www.kaggle.com/datasets/nasa/kepler-exoplanet-search-results)

Экзопланета- это такая планета, которая вращается вокруг некоторой звезды (нет блин луны), вне солнечной системы.

На основе набора признаков из датасета, предлагается определить кандидата в экзопланету, или же не кандидата. То есть умные дяди из NASA уже разметили всех кандидатов и не кандидатов, также в данных есть подтвержденные планеты, однако их мы трогать не будем, поскольку:

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

b) Даже если мы решим что мы настолько хорошо знаем принцип принятия решений по классификации экзопланет, что можем построить модель которая предсказывает не просто кандидатов, но и потенциально подтвержденные планеты, мы столкнемся с дисбалансом классов, что не очень хорошо.

Стартовые параметры dataseta: 50 колонн и 9564 строки.
(Более подробно про эти параметры можно прочитать здесь: https://exoplanetarchive.ipac.caltech.edu/docs/API_kepcandidate_columns.html)

Импортируем библиотеки, если вас бесят предупреждения, то убираем их тоже:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import scipy as sc
from matplotlib.colors import ListedColormap
from matplotlib.colors import LinearSegmentedColormap
import warnings
warnings.filterwarnings('ignore')

Считываем датасет:

data = pd.read_csv('cumulative.csv')
data.head()

Далее посмотрим на целевую переменную и на баланс между классами

Candidate- кандидат в экзопланету

False Positive- точно не кандидат

data.groupby('koi_pdisposition').size().plot(kind='barh', color=sns.palettes.mpl_palette('Dark2'))
plt.gca().spines[['top', 'right',]].set_visible(False)
Целевая переменная
Целевая переменная

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

Почему?
Потому что там пропуски, сейчас увидим

Для начала выделим через X те данные, которые нам точно не понадобятся и оставим только нужное.

X=data.drop(['koi_teq_err1','koi_teq_err2', 'koi_disposition', 'koi_pdisposition','kepler_name', 'kepoi_name', 'rowid','koi_score', 'kepid'], axis=1)
X.head()

Почему мы их дропнули? Если коротко, то это данные, которые не помогут нам в обучении модели, такие как номер потенциальной планеты и прочие id, если интересно, советую открыть статью выше и ознакомиться с описанием параметров.

Далее прогоняем наш 'y' через pandas get_dummies

Y=data['koi_pdisposition']
y=pd.get_dummies (Y, drop_first= True, dtype=float )
y = y.rename(columns={'FALSE POSITIVE': 'koi_pdisposition'})
y.head()

Теперь у нас y в формате float, а был в формате object.

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

(X.isna().sum() / len(X)).round(4) * 100

Получим вывод всего признаков и рядом с ними процент отсутствующих данных.
Если вы пользуетесь форматом .py, возможно стоит ввести print((X.isna().sum() / len(X)).round(4) * 100), поскольку я пользуюсь ipynb, потому что он мне интуитивно удобен и не заставляет по многу раз переписывать выводы, чтобы проверить себя или построить графики.

Чтобы получить какое то визуально представление лучше построить тепловую карту по данным:

plt.figure(figsize=(10,12))
sns.heatmap(X.isna().transpose())
Белые линии- это пропуски
Белые линии- это пропуски

Заполним пропуски через интерполяцию методом ближних.
Зачем? Не будем создавать дисбаланса и искать средние и строить полиномы, поскольку далее мы увидим гистограммы, по которым данный выбор будет более ясным.

X=X.interpolate(method='nearest')
X.info()

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

Далее посмотрим на распределения в данных

X.hist(bins=25, figsize=(20,20));

Судя по гистограммам метод интерполяции по близлежащим точкам был неплохим выбором.

Далее сделаем feature selection:
1) Построим модель случайного леса и выведем важность признаков
2) F-test (автоотбор признаков)

Напомню что у нас осталось 42 признака, из которых было бы неплохо отбросить ненужные. Для этого нам собственно и нужен feature selection. На самом деле методов для отбора признаков (feature selection) намного больше чем 2, например матрица корреляций или variance treshold, Однако эти 2 мне нравятся больше и чаще всего именно они выдавали лучшие метрики.

Кстати о метриках. Напишем функцию, которая будет нам их считать, перед этим не забываем импортировать нужные библиотеки:

from sklearn.model_selection import train_test_split
from sklearn.metrics import  roc_auc_score, classification_report, accuracy_score, f1_score
from sklearn.ensemble import RandomForestClassifier
# пишем функцию для расчета метрик:
def metrics(y_true,y_pred):
    acc=accuracy_score(y_true, y_pred)
    f1=f1_score(y_true, y_pred)
    roc_auc=roc_auc_score(y_true, y_pred)
    print(f'accuracy: {acc}\nf1: {f1}\nroc auc: {roc_auc} ')

Теперь нам на вход надо подавать только значения y_true и pred, чтобы вывести метрики, что ускоряет процесс их вывода.

Разделим данные на тестовую и обучающую выборку

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

Обучим модель случайного леса

rfc = RandomForestClassifier(max_depth=2, random_state=42)
rfc.fit(X_train, y_train)
rfc_pred_train=rfc.predict(X_train)
rfc_pred_test=rfc.predict(X_test)
metrics(y_test,rfc_pred_test)

Вывод метрик:
accuracy: 0.9158389963408259
f1: 0.9182326053834434
roc auc: 0.9178450601875332

Теперь сделаем то, для чего это все затевалось:
FEATURE IMPORTANCE

feature_importances = rfc.feature_importances_
sorted_indices = np.argsort(feature_importances)
plt.figure(figsize=(12, 10))
plt.barh(np.arange(len(feature_importances)), feature_importances[sorted_indices], color='darkblue')
plt.yticks(np.arange(len(X.columns)), X.columns[sorted_indices])
plt.show()
selected_features = X.columns[feature_importances > 0.01]
print(selected_features)
Важность признаков
Важность признаков

В коде мы выводим только значения по важности больше чем 0.01, получится такой array:
Index(['koi_fpflag_nt', 'koi_fpflag_ss', 'koi_fpflag_co', 'koi_fpflag_ec', 'koi_depth', 'koi_prad', 'koi_prad_err1', 'koi_prad_err2', 'koi_teq', 'koi_insol_err1', 'koi_insol_err2', 'koi_model_snr', 'koi_steff_err1', 'koi_steff_err2'], dtype='object')

Отлично, теперь попробуем F-test, а именно для случая классификации:

from sklearn.feature_selection import f_classif,SelectKBest
f_statistic, p_values = f_classif(X, y)
selector = SelectKBest(f_classif, k=8)
X_f = selector.fit_transform(X,y)
mask = selector.get_support(indices=True)
best_features = [X.columns[i] for i in mask]
print(best_features)

Получаем такой array:
['koi_fpflag_nt', 'koi_fpflag_ss', 'koi_fpflag_co', 'koi_fpflag_ec', 'koi_depth', 'koi_teq', 'koi_steff_err1', 'koi_steff_err2']

Плюс метода f-test в том, что нам не надо строить модели и что то делать самим. За нас уже все реализовано, а мы просто получаем некоторые k-лучших параметров.
В нашем случае k=8, значение этого параметра естественно можно покрутить и поменять на ваше усмотрение.

Заключение

Мы поговорили о жизни, обработали данные, заполнили пропуски методом ближних, построили графики для того чтобы понимать что мы понатворили и отобрали признаки двумя методами. На этом можно закончить первую часть статьи. Вторая часть скоро также опубликуется, после публикации этой (первой), там мы построим модели по отобранным признакам и даже сделаем нейросеть, поскольку данные позволяют.

Комментарии (0)