В этой статье я проведу вас через все шаги, необходимые для того, чтобы вы уверенно шагнули вперед в мир машинного обучения, начиная с самых основ науки о данных и проектирования данных и заканчивая полноценным машинным обучением.
В основе любого проекта машинного обучения лежат 6 очень широких и простых шагов:
1. Предварительная обработка данных: обработка данных и EDA.
2. Тренируйтесь и тестируйте сплит
3. Настройка алгоритма
4. Примерка модели
5. Предсказания модели
6. Оценка модели
Я буду использовать набор данных Titanic Survival Prediction от Kaggle, чтобы по ходу приводить примеры. Основная цель состоит в том, чтобы настроить классификационную модель, чтобы предсказать, выживет ли конкретный человек или нет, на основе определенных заданных атрибутов.
Итак, давайте начнем, не так ли?
Самое первое, что вам нужно сделать, это импортировать все библиотеки, которые вам потребуются в вашем проекте. Теперь вполне возможно, что вы не сможете идентифицировать их все в начале, но я бы посоветовал сохранить эту блочную переменную и продолжать добавлять операторы импорта по мере необходимости.
import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from typing import Text, List, Dict, Set, Tuple, Optional, Union from sklearn.compose import make_column_transformer from sklearn.preprocessing import (StandardScaler, OneHotEncoder) from sklearn.base import BaseEstimator from sklearn.model_selection import KFold from sklearn.pipeline import Pipeline from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier from sklearn.svm import SVC from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, f1_score
Давайте теперь загрузим наши тестовые и обучающие наборы данных.
train_data = pd.read_csv("/kaggle/input/titanic/train.csv") test_data = pd.read_csv("/kaggle/input/titanic/test.csv")
Шаг 1. Обработка данных, исследовательский анализ данных и обработка данных
Давайте сначала посмотрим на наш набор данных
train_data.head()
test_data.head()
Разработка функций
1.) Давайте сначала обработаем все пропущенные значения во всем наборе данных.
for i in train_data.columns: print(i) print(train_data[i].isnull().value_counts())
Мы замечаем, что в полях «Возраст», «Кабина» и «Посадка» отсутствуют значения.
#store top 10 ages and their frequencies top_10_ages= train_data['Age'].value_counts().head(10).index.values freq_of_top_10_ages= train_data['Age'].value_counts().head(10).values age_probabilities= list(map(lambda value: (1/sum(freq_of_top_10_ages))*value ,freq_of_top_10_ages)) # np.random.choice(a, size=None, replace=True, p=None) #p is the probabilities associated with each entry in an array #Generates a random sample from a given 1-D array train_data['Age']= train_data['Age'].apply( lambda value: value if not np.isnan(value) else np.random.choice(top_10_ages, p= age_probabilities))
Здесь мы нашли 10 наиболее частых возрастов и соответствующие им частоты. Затем мы выяснили вероятности, связанные с каждым возрастом, и обновили недостающие значения столбца «Возраст» случайным заполнением этих 10 наиболее часто встречающихся значений с учетом их вероятностей.
Обратите внимание, что мы не использовали метод вменения наиболее частых значений, поскольку в столбце «Возраст» имеется значительный процент пропущенных значений, и мы хотели бы максимально сохранить случайность набора данных.
Напротив, мы можем использовать импутацию режима для «Embarked», поскольку в нем всего 2 пропущенных значения.
mode = train_data['Embarked'].value_counts().values[0] train_data['Embarked'].fillna(mode, inplace=True)
2.) Далее мы проверяем все уникальные категории всех атрибутов, чтобы обнаружить любые нежелательные типы данных. Мы находим некоторые из них в колонке «Принято» и приступаем к их своевременному устранению (простите за неудачную шутку :D)
for i in train_data.columns[0:10]: print(i) print(train_data[i].unique()) train_data['Embarked'].replace(to_replace= 644, value= train_data['Embarked'].value_counts().index.values[0], inplace= True)
Теперь мы немного отклонимся, чтобы понять набор данных, прежде чем углубиться в разработку функций.
train_corr= train_data.corr() plt.figure(figsize=(18,10)) sns.heatmap(train_corr, annot=True) plt.show()
Вау! Требуется много работы, вернемся к инженерам-разработчикам :)
3. ) Рекатегоризация
а. Давайте теперь используем столбец «Имя», чтобы переклассифицировать и извлечь заголовки в другой столбец.
TITLES = ('Mrs', 'Mr', 'Master', 'Miss', 'Major', 'Rev', 'Dr', 'Ms', 'Mlle','Col', 'Capt', 'Mme', 'Countess', 'Don', 'Jonkheer') def patterns(name: Text, titles: Set) -> Optional[Text]: for title in titles: if title in name: return title return "Untitled" train_data['Title']= train_data['Name'].apply(lambda name: patterns(name, TITLES))
Впоследствии разделите «названия» на три широкие категории, используя также столбец «Пол».
def squeeze_title( dataframe:pd.DataFrame )-> Text: title= dataframe['Title'] if title in ('Don', 'Major', 'Capt', 'Jonkheer', 'Rev', 'Col'): return 'Mr' elif title in ('Countess', 'Mme'): return 'Mrs' elif title in ('Mlle', 'Ms'): return 'Miss' elif title == 'Dr': if dataframe['Sex'] == 'male': return 'Mr' else: return 'Mrs' else: return title train_data['Title']= train_data.apply(squeeze_title, axis=1)
б. Разделите столбец «Каюта» на более широкие категории.
cabin_list = ['A', 'B', 'C', 'D', 'E', 'F', 'T', 'G', 'Unknown'] train_data['Deck'] = train_data['Cabin'].apply(lambda letter: patterns(str(letter), cabin_list)) train_data.drop(columns='Cabin', inplace=True)
Теперь, когда мы завершили перекатегоризацию, давайте посмотрим на наш набор данных.
До:
После:
4.) Корреляция
(i) Между супругами/братьями и сестрами/родителями/детями и коэффициент выживаемости
fig= plt.figure(figsize=(15,21)) ss= fig.add_subplot(221) pc= fig.add_subplot(222) ss_pc= fig.add_subplot(2,2,(3,4)) sns.barplot(x='Survived', y='SibSp', data=train_data, ax= ss) sns.barplot(x='Survived', y='Parch', data=train_data, ax= pc) ss_pc.title.set_text("Correlation between Sibsp and Parch") sns.regplot(x='SibSp', y='Parch', data=train_data, ax= ss_pc) plt.show()
Существует положительная корреляция между Sibsp и Parch. То есть люди с родственными связями, скорее всего, сами не выжили, но в первую очередь спасли свои семьи. Даже из них, особенно те, у которых есть брат/сестра/супруг, а не ребенок/родитель.
Кроме того, было бы логично объединить оба столбца, поскольку они представляют одну и ту же идею.
train_data['Family_size']= train_data['SibSp']+ train_data['Parch']
(ii) Между «Возрастом» и «P-классом»
plt.figure(figsize=(18,10)) sns.regplot(x='Pclass', y='Age', data= train_data) plt.show()
Слабая корреляция, поэтому бесполезна
(iii) Между «Возрастом» и «Коэффициентом выживаемости»
tester= train_data.copy() tester[['Survived','Died']]= pd.get_dummies(tester['Survived']) new= tester[['Age','Survived','Died']].groupby('Age').sum() new.reset_index(inplace=True) #sorting values by two columns means that if the first column has same values, then sort according to the second column new.sort_values(by=['Died','Survived'], ascending=False, inplace=True) new
Большинство молодых людей в возрасте 24 лет умерло
(iv) Между «Возрастом» и «Тарифом»
plt.figure(figsize=(18,10)) sns.regplot(x='Age',y='Fare',data=train_data) plt.show()
Между ними существует очень слабая корреляция, поэтому гипотеза неверна.
(v) Между «Pclass» и «Fare»
train_data[['Pclass','Fare']].loc[train_data['Fare']==0,:]
Мы находим значения Pclass, для которых тариф становится равным нулю, поэтому мы не можем связать Pclass с тарифом. Опять неверная гипотеза :/
Давайте теперь отбросим ненужные столбцы, проверим, хорошо ли выглядит карта корреляции, а затем перейдем к следующим шагам после обработки данных.
train_data.drop(columns=['Name','Ticket','PassengerId'], inplace=True) COLUMNS = ['Title', 'Sex', 'Age', 'SibSp', 'Parch', 'Family_size', 'Pclass', 'Fare', 'Deck', 'Embarked', 'Survived'] train_data = train_data[COLUMNS] plt.figure(figsize=(18,10)) sns.heatmap(train_data.corr(), annot=True) plt.show()
Шаг 2. Тренировка тестового сплита
Прежде чем перейти к разделению набора данных, просто помните о двух вещах: во-первых, не забудьте убедиться, что все ваши категориальные переменные закодированы в числовые значения. Во-вторых, перед разделением укажите свои X и Y
train_data = pd.get_dummies(train_data, columns = ['Title','Sex','Deck','Embarked']) y_label= train_data['Survived'] X_features= train_data.iloc[:,:-1] folds=4 #75, 25 split # random state- shuffling applied to the data before the split # stratify- When it splits the label, it will make sure that the proportion of the training label is similar to the proportion of the testing label, for good accuracy X_train, X_test, Y_train, Y_test= train_test_split(X_features, y_label, test_size=(1/folds), random_state= 42, stratify= y_label)
Шаг 3. Настройка алгоритма
Перед подгонкой модели мы хотим убедиться, что наши наборы данных правильно преобразованы и нормализованы. Итак, мы собираемся построить преобразованный конвейер, чтобы добавить в него наши функции.
Мы построим два конвейера: один конвейер преобразования для масштабирования числовых столбцов и кодирования категориальных столбцов.
Трубопровод трансформаторов-
NUMERICAL_COLUMNS = ["Age", "SibSp", "Parch", "Family_size", "Fare"] CATEGORICAL_COLUMNS = ["Title", "Sex", "Pclass", "Deck", "Embarked"] transformers = make_column_transformer( (StandardScaler(), NUMERICAL_COLUMNS), (OneHotEncoder(handle_unknown='ignore'), CATEGORICAL_COLUMNS))
Конвейер моделей-
Сначала мы создадим собственный оценщик
class ClfSwitcher(BaseEstimator): def __init__(self, estimator = DecisionTreeClassifier()): """ Custom Estimator is a custom class that helps you to switch between classifiers. Args: estimator: sklearn object – classifier """ self.estimator = estimator def fit(self, X, y=None, **kwargs): self.estimator.fit(X, y) return self def predict(self, X, y=None): return self.estimator.predict(X) def predict_proba(self, X): return self.estimator.predict_proba(X) def score(self, X, y): return self.estimator.score(X, y)
Наконец мы компилируем весь пайплайн
pipeline = Pipeline([ ('transformer', transformers), ('clf', ClfSwitcher()) ]) parameters = [ { 'clf__estimator': [DecisionTreeClassifier()], 'clf__estimator__criterion': ['gini', 'entropy'], }, { 'clf__estimator': [ExtraTreesClassifier()], 'clf__estimator__n_estimators': [100, 250], 'clf__estimator__criterion': ['gini', 'entropy'], }, { 'clf__estimator': [RandomForestClassifier()], 'clf__estimator__n_estimators': [100, 250], 'clf__estimator__criterion': ['gini', 'entropy'], }, { 'clf__estimator': [SVC()], 'clf__estimator__kernel': ['rbf', 'sigmoid'], 'clf__estimator__C': [1e-3, 1e-2, 1e-1, 1.0, 10., 1e+2, 1e+3], 'clf__estimator__degree': [3, 4, 5, 6] }, { 'clf__estimator': [LogisticRegression()], 'clf__estimator__penalty': ['l1', 'l2'], 'clf__estimator__tol': [1e-4, 1e-3, 1e-2], 'clf__estimator__C': [1e-3, 1e-2, 1e-1, 1.0, 10., 1e+2, 1e+3], 'clf__estimator__solver': ['lbfgs', 'liblinear'] } ] cv = KFold(n_splits=(folds - 1))
Шаг 4. Подгонка модели
Мы точно настраиваем модель, чтобы определить наилучшую модель и наиболее оптимальные гиперпараметры
gscv = GridSearchCV(pipeline, parameters, cv=cv, scoring='r2', n_jobs=12, verbose=3) gscv.fit(X_train, y_train) gscv.best_params_ model = pipeline.set_params(**gscv.best_params_) model.fit(X_train, y_train)
Шаг 5. Прогнозы моделей
Predictions = model.predict(X_test)
Шаг 6. Оценка модели
Давайте оценим нашу модель, используя одиночные и множественные метрики оценки.
а) Единая оценочная метрика
model.score(X_test, y_test) f1_score(y_test, Predictions)
б) Метрика множественной оценки
Матрица путаницы дает нам количественную метрику количества истинных/ложных положительных или отрицательных результатов.
cm= confusion_matrix(Y_test, Predictions, labels=[1,0]) confusion_plot = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Survived', 'Died']) fig, ax = plt.subplots(1,1,figsize=(12, 8)) ax.grid(False) confusion_plot.plot(cmap='Blues', ax=ax) ax.set_title('Confusion Matrix Between True Label & Predicted Label') plt.show()
Матрица классификации помогает нам анализировать точность, отзыв и оценку f1.
classification_report(Y_test, Predictions)
Фу!
Если вы дожили до этого места, похлопайте себя по плечу, вы проделали действительно хорошую работу. Мы рассмотрели обработку данных, начиная с самых основ вменения пропущенных значений и заканчивая корреляциями между переменными. Затем мы перешли к моделям машинного обучения, и здесь я снова немного растянул вас, не пойдя по проторенному пути, пытаясь попробовать несколько моделей одну за другой, вместо этого выбрав конвейер. Я был бы честен, идти в ногу с этим, должно быть, не было легкой прогулкой, и я искренне надеюсь, что вы узнали что-то ценное.
До скорого!