Рубрики
Без рубрики

ML с Python: Часть 2

В этой статье мы подробно рассмотрим, что мы делаем на различных этапах создания модели машинного обучения (ML). Я искал какой-то МЛ-проект, который не очень сложен, но охватывает все…

Автор оригинала: DhananjayKumar.

В этой статье мы подробно рассмотрим, что мы делаем на различных этапах создания модели машинного обучения (ML). Я искал какой-то МЛ-проект, который не очень сложен, но охватывает все концепции создания МЛ-модели. Я нашел один хороший проект в kaggle, который я использую здесь в качестве примера, и полный проект можно найти здесь . Файл Jupyter Notebook и обучающие/тестовые файлы также можно загрузить из моего репозитория git .

Начнем с постановки задачи:

“Титаник” – британский пассажирский лайнер, затонувший в Северной Атлантике ранним утром 15 апреля 1912 года после столкновения с айсбергом во время своего первого рейса из Саутгемптона в Нью-Йорк. По оценкам, на борту судна находилось 2224 пассажира и члена экипажа, и более 1500 человек погибло, что сделало его одной из самых смертоносных коммерческих морских катастроф мирного времени в современной истории.

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

# linear algebra
import numpy as np 

# data processing
import pandas as pd 

# data visualization
import seaborn as sns
%matplotlib inline
from matplotlib import pyplot as plt
from matplotlib import style

# Algorithms
from sklearn import linear_model
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC, LinearSVC
from sklearn.naive_bayes import GaussianNB

В нашем примере у нас уже есть обучающие/тестовые данные, доступные в виде csv-файла, который мы можем загрузить с помощью библиотеки Panda. В сложных проектах ML вы можете получить данные для различных источников, которые вы будете загружать и агрегировать в один обучающий фрейм данных.

test_df = pd.read_csv("test.csv")
train_df = pd.read_csv("train.csv")

Следующий шаг-анализ данных, и для этого мы используем некоторые очень полезные методы, доступные в Pandas.

train_df.info()
train_df.describe()
train_df.head(10)

Информация говорит, что у нас есть 891 запись и 11 функций с целевым результатом (выжили) Мы также получаем тип функций. Описание говорит, что выжили 38%, возраст от .4 до 80. Кроме того, у нас есть некоторые недостающие значения в некоторых функциях, таких как “Возраст”.

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

Давайте более подробно рассмотрим, какие данные на самом деле отсутствуют:

total = train_df.isnull().sum()
percent = train_df.isnull().sum()/train_df.isnull().count()*100
missing_data = pd.concat([total, percent], axis=1, keys=['Total', '%'])
missing_data.sort_values(by=['Total'], ascending=False)

Функция ‘Embarked’ имеет всего 2 пропущенных значения, которые мы легко заполняем. “Кабина” имеет 77% неизвестных, что приводит к тому, что мы должны отказаться от этой функции. Точно так же функция “Возраст” имеет 177 лет, и нам нужно иметь с этим дело. Кроме того, похоже, что функции “Пассажир”, “Билет” и ” Имя ” могут быть отброшены, поскольку они не связаны с результатом.

1. Возраст и пол:

survived = 'survived'
not_survived = 'not survived'
fig, axes = plt.subplots(nrows=1, ncols=2,figsize=(20, 4))
women = train_df[train_df['Sex']=='female']
men = train_df[train_df['Sex']=='male']
ax = sns.distplot(women[women['Survived']==1].Age.dropna(), bins=18, label = survived, ax = axes[0], kde =False)
ax = sns.distplot(women[women['Survived']==0].Age.dropna(), bins=40, label = not_survived, ax = axes[0], kde =False)
ax.legend()
ax.set_title('Female')
ax = sns.distplot(men[men['Survived']==1].Age.dropna(), bins=18, label = survived, ax = axes[1], kde = False)
ax = sns.distplot(men[men['Survived']==0].Age.dropna(), bins=40, label = not_survived, ax = axes[1], kde = False)
ax.legend()
_ = ax.set_title('Male')
1. Возраст и пол:

Из сюжета мы можем предположить, что вероятность выживания больше от 15 до 40 как для мужчин, так и для женщин. Выживаемость младенцев также высока. Поэтому лучше создать новую функцию в качестве возрастной группы.

2. Класс, Класс и Секс:

FacetGrid = sns.FacetGrid(train_df, row='Embarked', size=4.5, aspect=1.6)
FacetGrid.map(sns.pointplot, 'Pclass', 'Survived', 'Sex', palette=None,  order=None, hue_order=None )
FacetGrid.add_legend()
2. Класс, Класс и Секс:

Это, по-видимому, коррелирует с выживаемостью, в зависимости от пола. Женщины на портах Q и S имеют более высокие шансы на выживание. Мужчины имеют высокую вероятность выживания, если они находятся в порту С.

3. Класс:

sns.barplot(x='Pclass', y='Survived', data=train_df)
3. Класс:

Очевидно, что класс 1 имеет высокую вероятность выживания.

grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', size=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend();
Очевидно, что класс 1 имеет высокую вероятность выживания.

Приведенный выше график подтверждает наше предположение о классе 1, но мы также можем определить высокую вероятность того, что человек в классе 3 не выживет.

5. СибСп и Пергамент:

СибСп и Парч имели бы больше смысла как объединенная характеристика, показывающая общее количество родственников, которых человек имеет на “Титанике”. Я создам его ниже, а также функцию, которая показывает, если кто-то не один.

data = [train_df, test_df]
for dataset in data:
    dataset['relatives'] = dataset['SibSp'] + dataset['Parch']
    dataset.loc[dataset['relatives'] > 0, 'not_alone'] = 0
    dataset.loc[dataset['relatives'] == 0, 'not_alone'] = 1
    dataset['not_alone'] = dataset['not_alone'].astype(int)
train_df['not_alone'].value_counts()
axes = sns.factorplot('relatives','Survived', 
                      data=train_df, aspect = 2.5, )
СибСп и Парч имели бы больше смысла как объединенная характеристика, показывающая общее количество родственников, которых человек имеет на

Здесь мы видим, что у вас была высокая вероятность выживания с 1-3 родственниками, но более низкая, если у вас было меньше 1 или больше 3 (за исключением некоторых случаев с 6 родственниками).

Хорошо, давайте отбросим функции, которые не связаны между собой, как пассажиры.

train_df = train_df.drop(['PassengerId'], axis=1)

Недостающие Данные:

Сначала мы решили удалить каюту, но позже поняли, что 1-я буква каюты представляет собой палубу и важную особенность, которую следует учитывать. И поэтому мы собираемся извлечь их и создать новую функцию, которая содержит колоду людей. После этого мы преобразуем объект в числовую переменную. Пропущенные значения будут преобразованы в ноль.

import re
deck = {"A": 1, "B": 2, "C": 3, "D": 4, "E": 5, "F": 6, "G": 7, "U": 8}
data = [train_df, test_df]

for dataset in data:
    dataset['Cabin'] = dataset['Cabin'].fillna("U0")
    dataset['Deck'] = dataset['Cabin'].map(lambda x: re.compile("([a-zA-Z]+)").search(x).group())
    dataset['Deck'] = dataset['Deck'].map(deck)
    dataset['Deck'] = dataset['Deck'].fillna(0)
    dataset['Deck'] = dataset['Deck'].astype(int) 
# we can now drop the cabin feature
train_df = train_df.drop(['Cabin'], axis=1)
test_df = test_df.drop(['Cabin'], axis=1)

Теперь мы можем решить проблему с пропущенными значениями возрастных признаков. Я создам массив, содержащий случайные числа, которые вычисляются на основе среднего значения возраста в отношении стандартного отклонения и is_null.

data = [train_df, test_df]

for dataset in data:
    mean = train_df["Age"].mean()
    std = test_df["Age"].std()
    is_null = dataset["Age"].isnull().sum()
    # compute random numbers between the mean, std and is_null
    rand_age = np.random.randint(mean - std, mean + std, size = is_null)
    # fill NaN values in Age column with random values generated
    age_slice = dataset["Age"].copy()
    age_slice[np.isnan(age_slice)] = rand_age
    dataset["Age"] = age_slice
    dataset["Age"] = train_df["Age"].astype(int)

Поскольку функция Embarked имеет только 2 пропущенных значения, мы просто заполним их наиболее распространенным значением S.

train_df['Embarked'].describe()
data = [train_df, test_df]

for dataset in data:
    dataset['Embarked'] = dataset['Embarked'].fillna('S')

Преобразование Функций:

train_df.info()

Выше вы можете видеть, что “Тариф” – это поплавок, и мы имеем дело с 4 категориальными признаками: Имя, Пол, Билет и Посадка. Давайте исследовать и преобразовывать одно за другим.

Преобразование “Fare” из float в int64 с помощью функции ” astype ()”, которую предоставляет pandas:

data = [train_df, test_df]

for dataset in data:
    dataset['Fare'] = dataset['Fare'].fillna(0)
    dataset['Fare'] = dataset['Fare'].astype(int)

Мы будем использовать функцию Имени, чтобы извлечь заголовки из Имени, чтобы мы могли построить новую функцию из этого.

data = [train_df, test_df]
titles = {"Mr": 1, "Miss": 2, "Mrs": 3, "Master": 4, "Rare": 5}

for dataset in data:
    # extract titles
    dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False)
    # replace titles with a more common title or as Rare
    dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col','Don', 'Dr',\
                                            'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Rare')
    dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Ms', 'Miss')
    dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs')
    # convert titles into numbers
    dataset['Title'] = dataset['Title'].map(titles)
    # filling NaN with 0, to get safe
    dataset['Title'] = dataset['Title'].fillna(0)
train_df = train_df.drop(['Name'], axis=1)
test_df = test_df.drop(['Name'], axis=1)

Преобразуйте функцию “Секс” в числовую.

genders = {"male": 0, "female": 1}
data = [train_df, test_df]

for dataset in data:
    dataset['Sex'] = dataset['Sex'].map(genders)

Поскольку атрибут Ticket содержит 681 уникальный билет, будет немного сложно преобразовать их в полезные категории. Поэтому мы отбросим его из набора данных.

train_df = train_df.drop(['Ticket'], axis=1)
test_df = test_df.drop(['Ticket'], axis=1)

Преобразуйте функцию ‘Embarked’ в числовую.

ports = {"S": 0, "C": 1, "Q": 2}
data = [train_df, test_df]

for dataset in data:
    dataset['Embarked'] = dataset['Embarked'].map(ports)

Создание Категорий:

Теперь мы создадим категории в следующих функциях:

Возраст:

Теперь нам нужно преобразовать функцию “возраст”. Сначала мы преобразуем его из float в integer. Затем мы создадим классификацию возраста по группам таким образом, чтобы числа были равномерно распределены.

data = [train_df, test_df]
for dataset in data:
    dataset['Age'] = dataset['Age'].astype(int)
    dataset.loc[ dataset['Age'] <= 11, 'Age'] = 0
    dataset.loc[(dataset['Age'] > 11) & (dataset['Age'] <= 18), 'Age'] = 1
    dataset.loc[(dataset['Age'] > 18) & (dataset['Age'] <= 22), 'Age'] = 2
    dataset.loc[(dataset['Age'] > 22) & (dataset['Age'] <= 27), 'Age'] = 3
    dataset.loc[(dataset['Age'] > 27) & (dataset['Age'] <= 33), 'Age'] = 4
    dataset.loc[(dataset['Age'] > 33) & (dataset['Age'] <= 40), 'Age'] = 5
    dataset.loc[(dataset['Age'] > 40) & (dataset['Age'] <= 66), 'Age'] = 6
    dataset.loc[ dataset['Age'] > 66, 'Age'] = 6
# let's see how it's distributed
train_df['Age'].value_counts()

Плата за проезд:

Для функции “Тариф” нам нужно сделать то же самое, что и для функции “Возраст”. Но это не так просто, потому что если мы разделим диапазон справедливой стоимости на несколько одинаково больших категорий, 80% стоимости попадет в первую категорию. К счастью, мы можем использовать функцию sklearn “cut ()”, с помощью которой мы можем видеть, как мы можем формировать категории.

data = [train_df, test_df]

for dataset in data:
    dataset.loc[ dataset['Fare'] <= 7.91, 'Fare'] = 0
    dataset.loc[(dataset['Fare'] > 7.91) & (dataset['Fare'] <= 14.454), 'Fare'] = 1
    dataset.loc[(dataset['Fare'] > 14.454) & (dataset['Fare'] <= 31), 'Fare']   = 2
    dataset.loc[(dataset['Fare'] > 31) & (dataset['Fare'] <= 99), 'Fare']   = 3
    dataset.loc[(dataset['Fare'] > 99) & (dataset['Fare'] <= 250), 'Fare']   = 4
    dataset.loc[ dataset['Fare'] > 250, 'Fare'] = 5
    dataset['Fare'] = dataset['Fare'].astype(int)

Я добавлю в набор данных два новых объекта, которые я вычисляю из других объектов.

1. Возраст раз Класс

data = [train_df, test_df]
for dataset in data:
    dataset['Age_Class']= dataset['Age']* dataset['Pclass']

2. Стоимость проезда на человека

for dataset in data:
    dataset['Fare_Per_Person'] = dataset['Fare']/(dataset['relatives']+1)
    dataset['Fare_Per_Person'] = dataset['Fare_Per_Person'].astype(int)
X_train = train_df.drop("Survived", axis=1)
Y_train = train_df["Survived"]
X_test  = test_df.drop("PassengerId", axis=1).copy()
# stochastic gradient descent (SGD) learning
sgd = linear_model.SGDClassifier(max_iter=5, tol=None)
sgd.fit(X_train, Y_train)
Y_pred = sgd.predict(X_test)

sgd.score(X_train, Y_train)

acc_sgd = round(sgd.score(X_train, Y_train) * 100, 2)


print(round(acc_sgd,2,), "%")
69.02 %
# Random Forest
random_forest = RandomForestClassifier(n_estimators=100)
random_forest.fit(X_train, Y_train)

Y_prediction = random_forest.predict(X_test)

random_forest.score(X_train, Y_train)
acc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)
print(round(acc_random_forest,2,), "%")
92.7 %
# Logistic Regression
logreg = LogisticRegression()
logreg.fit(X_train, Y_train)

Y_pred = logreg.predict(X_test)

acc_log = round(logreg.score(X_train, Y_train) * 100, 2)
print(round(acc_log,2,), "%")
81.37 %
# KNN
knn = KNeighborsClassifier(n_neighbors = 3)
knn.fit(X_train, Y_train)

Y_pred = knn.predict(X_test)

acc_knn = round(knn.score(X_train, Y_train) * 100, 2)
print(round(acc_knn,2,), "%")
87.21 %
# Gaussian Naive Bayes
gaussian = GaussianNB()
gaussian.fit(X_train, Y_train)

Y_pred = gaussian.predict(X_test)

acc_gaussian = round(gaussian.score(X_train, Y_train) * 100, 2)
print(round(acc_gaussian,2,), "%")
76.88 %
# Perceptron
perceptron = Perceptron(max_iter=5)
perceptron.fit(X_train, Y_train)

Y_pred = perceptron.predict(X_test)

acc_perceptron = round(perceptron.score(X_train, Y_train) * 100, 2)
print(round(acc_perceptron,2,), "%")
81.48 %
# Linear SVC
linear_svc = LinearSVC()
linear_svc.fit(X_train, Y_train)

Y_pred = linear_svc.predict(X_test)

acc_linear_svc = round(linear_svc.score(X_train, Y_train) * 100, 2)
print(round(acc_linear_svc,2,), "%")
81.03 %
# Decision Tree
decision_tree = DecisionTreeClassifier()
decision_tree.fit(X_train, Y_train)

Y_pred = decision_tree.predict(X_test)

acc_decision_tree = round(decision_tree.score(X_train, Y_train) * 100, 2)
print(round(acc_decision_tree,2,), "%")
92.7 %

Какая модель самая лучшая ?

results = pd.DataFrame({
    'Model': ['Support Vector Machines', 'KNN', 'Logistic Regression', 
              'Random Forest', 'Naive Bayes', 'Perceptron', 
              'Stochastic Gradient Decent', 
              'Decision Tree'],
    'Score': [acc_linear_svc, acc_knn, acc_log, 
              acc_random_forest, acc_gaussian, acc_perceptron, 
              acc_sgd, acc_decision_tree]})
result_df = results.sort_values(by='Score', ascending=False)
result_df = result_df.set_index('Score')

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

K-Кратная Перекрестная валидация:

K-Fold Cross Validation случайным образом разбивает обучающие данные на K подмножеств, называемых foldes . Давайте представим, что мы разделили бы наши данные на 4 сгиба). Наша модель случайного леса будет обучаться и оцениваться 4 раза, каждый раз используя для оценки другую складку, в то время как она будет обучаться на оставшихся 3 складках.

На рисунке ниже показан процесс, используя 4 сгиба). Каждая строка представляет собой один процесс обучения + оценки. В первом ряду модель обучается на первом, втором и третьем подмножестве и оценивается на четвертом. Во втором ряду модель обучается на втором, третьем и четвертом подмножествах и оценивается на первом. Кросс-валидация K-Fold повторяет этот процесс до тех пор, пока каждая складка не будет действовать один раз как оценочная складка.

Результатом нашего примера K-кратной перекрестной проверки будет массив, содержащий 4 различных балла. Затем нам нужно вычислить среднее значение и стандартное отклонение для этих оценок.

Приведенный ниже код выполняет K-кратную перекрестную проверку на нашей модели случайного леса, используя 10 сгибов). Поэтому он выводит массив с 10 различными оценками.

from sklearn.model_selection import cross_val_score
rf = RandomForestClassifier(n_estimators=100)
scores = cross_val_score(rf, X_train, Y_train, cv=10, scoring = "accuracy")
print("Scores:", scores)
print("Mean:", scores.mean())
print("Standard Deviation:", scores.std())
Scores: [0.76666667 0.83333333 0.76404494 0.80898876 0.88764045 0.84269663
 0.79775281 0.7752809  0.84269663 0.84090909]
Mean: 0.8160010214504597
Standard Deviation: 0.0382778650247283

Это выглядит гораздо более реалистично, чем раньше. Наша модель имеет среднюю точность 82% со стандартным отклонением 4 %. Стандартное отклонение показывает нам, насколько точны оценки .

Это означает в нашем случае, что точность нашей модели может отличаться + – 4%.

Я думаю, что точность все еще очень хороша, и поскольку случайный лес-это простая в использовании модель, мы постараемся еще больше повысить ее производительность в следующем разделе.

Важность функции

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

importances = pd.DataFrame({'feature':X_train.columns,'importance':np.round(random_forest.feature_importances_,3)})
importances = importances.sort_values('importance',ascending=False).set_index('feature')
importances.plot.bar()
Еще одним замечательным качеством случайных лесов является то, что они позволяют очень легко измерить относительную важность каждого признака. Sklearn измеряет важность признаков, глядя на то, насколько узлы дерева, использующие этот признак, уменьшают примеси в среднем (по всем деревьям в лесу). Он автоматически вычисляет этот балл для каждой функции после обучения и масштабирует результаты так, чтобы сумма всех значений была равна 1. Мы получим доступ к этому ниже:

Вывод:

not_alone и Parch не играют существенной роли в процессе прогнозирования наших классификаторов случайных лесов. Из-за этого я отброшу их из набора данных и снова обучу классификатор. Мы также можем удалить больше или меньше функций, но для этого потребуется более детальное исследование влияния функций на нашу модель. Но я думаю, что это просто прекрасно, чтобы удалить только Один и Пергамент.

train_df  = train_df.drop("not_alone", axis=1)
test_df  = test_df.drop("not_alone", axis=1)

train_df  = train_df.drop("Parch", axis=1)
test_df  = test_df.drop("Parch", axis=1)

Снова тренировка случайного леса:

# Random Forest

random_forest = RandomForestClassifier(n_estimators=100, oob_score = True)
random_forest.fit(X_train, Y_train)
Y_prediction = random_forest.predict(X_test)

random_forest.score(X_train, Y_train)

acc_random_forest = round(random_forest.score(X_train, Y_train) * 100, 2)
print(round(acc_random_forest,2,), "%")
92.7 %

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

Существует также другой способ оценки классификатора случайных лесов, который, вероятно, гораздо более точен, чем оценка, которую мы использовали ранее. То, о чем я говорю,-это образцы из мешка для оценки точности обобщения. Я не буду здесь вдаваться в подробности о том, как это работает. Просто обратите внимание, что оценка вне мешка так же точна, как использование тестового набора того же размера, что и обучающий набор. Таким образом, использование оценки ошибки out-of-bag устраняет необходимость в выделенном тестовом наборе.

print("oob score:", round(random_forest.oob_score_, 4)*100, "%")
oob score: 82.38 %

Теперь можно приступать к настройке гиперпараметров случайного леса.

Настройка гиперпараметра

Ниже вы можете увидеть код настройки гиперпараметра для параметров criterion, min_samples_leaf, min_samples_split и n_estimators.

param_grid = { "criterion" : ["gini", "entropy"], 
              "min_samples_leaf" : [1, 5, 10, 25, 50, 70], 
              "min_samples_split" : [2, 4, 10, 12, 16, 18, 25, 35], 
              "n_estimators": [100, 400, 700, 1000, 1500]}
from sklearn.model_selection import GridSearchCV, cross_val_score
rf = RandomForestClassifier(n_estimators=100, max_features='auto', oob_score=True, random_state=1, n_jobs=-1)
clf = GridSearchCV(estimator=rf, param_grid=param_grid,
                    n_jobs=-1)
clf.fit(X_train, Y_train)        
clf.best_params_

Тестируйте новые параметры:

# Random Forest
random_forest = RandomForestClassifier(criterion = "gini", 
                                       min_samples_leaf = 1, 
                                       min_samples_split = 10,   
                                       n_estimators=100, 
                                       max_features='auto', 
                                       oob_score=True, 
                                       random_state=1, 
                                       n_jobs=-1)

random_forest.fit(X_train, Y_train)
Y_prediction = random_forest.predict(X_test)

random_forest.score(X_train, Y_train)

print("oob score:", round(random_forest.oob_score_, 4)*100, "%")
oob score: 82.15 %

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

Матрица путаницы:

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix
predictions = cross_val_predict(random_forest, X_train, Y_train, cv=3)
confusion_matrix(Y_train, predictions)
array([[490,  59],
       [ 91, 251]], dtype=int64)

Первая строка касается не-выживших-предсказаний: 493 пассажира были правильно классифицированы как не выжившие (называемые истинными негативами) и 56, где ошибочно классифицированы как не выжившие (ложные негативы).

Вторая строка посвящена выжившим-предсказаниям: 93 пассажира были ошибочно классифицированы как выжившие (ложные срабатывания) и 249, где правильно классифицированы как выжившие (истинные срабатывания).

Матрица путаницы дает вам много информации о том, насколько хорошо работает ваша модель, но есть способ получить еще больше, например вычислить точность классификаторов.

Точность и отзыв:

from sklearn.metrics import precision_score, recall_score

print("Precision:", precision_score(Y_train, predictions))
print("Recall:",recall_score(Y_train, predictions))
Precision: 0.8096774193548387
Recall: 0.7339181286549707

Наша модель предсказывает в 81% случаев правильную выживаемость пассажиров (точность). Отзыв говорит нам, что он предсказал выживание 73% людей, которые действительно выжили.

F-Оценка

Вы можете объединить точность и отзыв в один балл, который называется F-score. F-оценка вычисляется с помощью гармонического среднего значения точности и отзыва. Обратите внимание, что он придает гораздо больший вес низким значениям. В результате классификатор получит высокий балл F только в том случае, если и отзыв, и точность будут высокими.

from sklearn.metrics import f1_score
f1_score(Y_train, predictions)
0.7699386503067485

Вот он, 77 % F-балл. Оценка не так высока, потому что у нас есть отзыв 73%.

Но, к сожалению, F-оценка не идеальна, потому что она благоприятствует классификаторам, которые имеют аналогичную точность и отзывчивость. Это проблема, потому что иногда требуется высокая точность, а иногда и высокая отзывчивость. Дело в том, что увеличение точности иногда приводит к уменьшению отзыва и наоборот (в зависимости от порога). Это называется компромиссом точности/отзыва. Мы обсудим это в следующем разделе.

Кривая Точного Отзыва

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

Мы построим график точности и отзыва с порогом с помощью matplotlib:

from sklearn.metrics import precision_recall_curve

# getting the probabilities of our predictions
y_scores = random_forest.predict_proba(X_train)
y_scores = y_scores[:,1]

precision, recall, threshold = precision_recall_curve(Y_train, y_scores)
def plot_precision_and_recall(precision, recall, threshold):
    plt.plot(threshold, precision[:-1], "r-", label="precision", linewidth=5)
    plt.plot(threshold, recall[:-1], "b", label="recall", linewidth=5)
    plt.xlabel("threshold", fontsize=19)
    plt.legend(loc="upper right", fontsize=19)
    plt.ylim([0, 1])

plt.figure(figsize=(14, 7))
plot_precision_and_recall(precision, recall, threshold)
plt.show()
Мы построим график точности и отзыва с порогом с помощью matplotlib:

Выше вы можете ясно видеть, что отзыв быстро падает с точностью около 85%. Из – за этого вы можете выбрать компромисс точности/отзыва до этого-возможно, около 75 %.

Теперь вы можете выбрать порог, который дает вам наилучший компромисс точности/отзыва для вашей текущей проблемы машинного обучения. Если вы хотите, например, точность 80%, вы можете легко посмотреть на графики и увидеть, что вам понадобится порог около 0,4. Тогда вы можете обучить модель именно с этим порогом и получить желаемую точность.

Другой способ заключается в построении графика точности и отзыва друг против друга:

def plot_precision_vs_recall(precision, recall):
    plt.plot(recall, precision, "g--", linewidth=2.5)
    plt.ylabel("recall", fontsize=19)
    plt.xlabel("precision", fontsize=19)
    plt.axis([0, 1.5, 0, 1.5])

plt.figure(figsize=(14, 7))
plot_precision_vs_recall(precision, recall)
plt.show()
Другой способ заключается в построении графика точности и отзыва друг против друга:

Кривая ROC AUC

Еще один способ оценить и сравнить ваш бинарный классификатор-это кривая ROC AUC. Эта кривая строит истинную положительную скорость (также называемую отзывом) против ложной положительной скорости (отношение неправильно классифицированных отрицательных экземпляров), вместо того чтобы строить график точности по сравнению с отзывом.

from sklearn.metrics import roc_curve
# compute true positive rate and false positive rate
false_positive_rate, true_positive_rate, thresholds = roc_curve(Y_train, y_scores)
# plotting them against each other
def plot_roc_curve(false_positive_rate, true_positive_rate, label=None):
    plt.plot(false_positive_rate, true_positive_rate, linewidth=2, label=label)
    plt.plot([0, 1], [0, 1], 'r', linewidth=4)
    plt.axis([0, 1, 0, 1])
    plt.xlabel('False Positive Rate (FPR)', fontsize=16)
    plt.ylabel('True Positive Rate (TPR)', fontsize=16)

plt.figure(figsize=(14, 7))
plot_roc_curve(false_positive_rate, true_positive_rate)
plt.show()
Еще один способ оценить и сравнить ваш бинарный классификатор-это кривая ROC AUC. Эта кривая строит истинную положительную скорость (также называемую отзывом) против ложной положительной скорости (отношение неправильно классифицированных отрицательных экземпляров), вместо того чтобы строить график точности по сравнению с отзывом.

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

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

Оценка ROC AUC

Оценка ROC AUC – это соответствующая оценка кривой ROC AUC. Он просто вычисляется путем измерения площади под кривой, которая называется AUC.

Классификатор, который является 100% правильным, будет иметь оценку ROC AUC 1, а полностью случайный классификатор будет иметь оценку 0,5.

from sklearn.metrics import roc_auc_score
r_a_score = roc_auc_score(Y_train, y_scores)
print("ROC-AUC-Score:", r_a_score)
ROC-AUC-Score: 0.9423113795417506
submission = pd.DataFrame({
        "PassengerId": test_df["PassengerId"],
        "Survived": Y_prediction
    })
submission.head(10)

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

В следующей части мы рассмотрим, как различные AutoML, такие как H2O, tpot, Gauze AutoML и т. Д., Позволяют нам Найти наиболее подходящую модель, применяя и сравнивая оценку всех моделей/Alorithums, рассмотренных выше.