вторник, 24 января 2023 г.

Машинное обучение с библиотеками PyCaret и TROT : классификация, часть третья

В этой статье продолжим  рассмотрение практических примеров автоматизированного машинного обучение в задачах классификации с помощью TPOT, взятых из книги "Radečić Dario. Machine Learning Automation with TPOT: Build, validate, and deploy fully automated machine learning models with Python". Рассмотрим основные темы, такие как загрузка набора данных, очистка и предварительный анализ данных, создадим базовую модель классификации с помощью логистической регрессии и пакета sklean.Затем углубимся в классификацию с помощью ТPОТ. Узнаем, как обучать и оценивать автоматизированные модели классификации. 

Перед автоматическим обучением моделей увидим, как можно получить хорошие модели с базовыми алгоритмами классификации, такими как логистическая регрессия. Эта модель будет служить в качестве базового уровня, который должен превзойти TPOT. Будут рассмотрены следующие темы: • Применение автоматизированного классификационного моделирования к набору данных Iris. • Применение автоматизированного классификационного моделирования к набору данных Titanic.

В начале, как обычно, импортируем необходимые библиотеки

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib import rcParams
rcParams['axes.spines.top'] = False
rcParams['axes.spines.right'] = False

Загружаем и знакомимся с данными :

df = pd.read_csv('iris_data.csv')
df.head()


sepal_lengthsepal_widthpetal_lengthpetal_widthspecies
05.13.51.40.2setosa
14.93.01.40.2setosa
24.73.21.30.2setosa
34.63.11.50.2setosa
45.03.61.40.2setosa


Следующий шаг является проверка пропущенных значений

df.isnull().sum()

sepal_length    0
sepal_width     0
petal_length    0
petal_width     0
species         0
dtype: int64

Пропущенных значений нет. Проверим распределение классов в целевой переменной. 
Это относится к количество экземпляров, принадлежащих к каждому классу – setosa, 
versicolor и virginica. Как известно, модели машинного обучения работают плохо, если
присутствует серьезный дисбаланс классов. Визуализируем распределение классов:

ax = df.groupby('species').count().plot(kind='bar',
figsize=(10, 6), fontsize=13, color='#4f4f4f')
ax.set_title('Iris Dataset target variable distribution',
size=20, pad=30)
ax.set_ylabel('Count', fontsize=14)
ax.set_xlabel('Species', fontsize=14)
ax.get_legend().remove()





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

plt.figure(figsize=(12, 9))
plt.title('Correlation matrix', size=20)
sns.heatmap(df.corr(), annot=True, cmap='Blues');



Как и ожидалось, существует сильная корреляция между большинством функций. 

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

служить стартовой моделью, которую TPOT должен превзойти. Первым шагом в этом 

процессе является разделение на обучения/тестирования. 


from sklearn.model_selection import train_test_split
X = df.drop('species', axis=1)
y = df['species']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=3)
y_train.shape, y_test.shape

Количество экземпляров в обучающих и тестовых наборах

((112,), (38,))

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

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix
lm = LogisticRegression(random_state=42)
lm.fit(X_train, y_train)
lm_preds = lm.predict(X_test)
print(confusion_matrix(y_test, lm_preds))


[[15  0  0]
 [ 0 11  1]
 [ 0  0 11]]

Как видим, есть только одна неправильная классификация — ложное срабатывание для virginica.
Базовая модель сработала исключительно хорошо. Выведем точность для базовой
модели:

from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, lm_preds))
print(f'accuracy = {accuracy_score(y_test, lm_preds)):.2f}')

accuracy= 0.97

Точность тестового набора с логистической регрессией  97% и только одна
неправильная классификация.  Посмотрим, сможет ли TPOT превзойти логистическую
регрессию. Построим модель автоматической классификации, оптимизируем точность и тренируем в течение 10 минут. 

from tpot import TPOTClassifier
pipeline_optimizer = TPOTClassifier(
scoring='accuracy',
max_time_mins=10,
random_state=42,
verbosity=2)
pipeline_optimizer.fit(X_train, y_train)

TPOT удалось за 10 минут установить на мою машину 9 поколений

Generation 1 - Current best internal CV score: 0.9826086956521738

Generation 2 - Current best internal CV score: 0.9826086956521738

Generation 3 - Current best internal CV score: 0.9826086956521738

Generation 4 - Current best internal CV score: 0.9826086956521738

Generation 5 - Current best internal CV score: 0.9826086956521738

Generation 6 - Current best internal CV score: 0.9826086956521738

Generation 7 - Current best internal CV score: 0.9826086956521738

Generation 8 - Current best internal CV score: 0.9826086956521738

Generation 9 - Current best internal CV score: 0.9826086956521738

10.00 minutes have elapsed. TPOT will close down.
TPOT closed during evaluation in one generation.
WARNING: TPOT may not provide a good pipeline if TPOT is stopped/interrupted in a early generation.


TPOT closed prematurely. Will use the current best pipeline.

Best pipeline: MLPClassifier(input_matrix, alpha=0.01, learning_rate_init=0.001)

Выведем полученную точность

tpot_preds = pipeline_optimizer.predict(X_test)
accuracy_score(y_test, tpot_preds)
print(f'accuracy = {accuracy_score(y_test, tpot_preds)):.2f}')

accuracy= 0.97

Как видим, точность на тестовом наборе не улучшилась. Если сделать точечную
диаграмма целевой переменной и функций, можно некоторое перекрытие для классы
virginica и versicolor. Здесь, скорее всего, дело обстоит так, что никакая попытка
обучения не даст правильно классифицировать этот единственный экземпляр. 
Посмотри что TPOT объявил оптимальным конвейером после
10 минут обучения, следующий фрагмент кода выведет этот конвейер : 

pipeline_optimizer.fitted_pipeline_

Pipeline(steps=[('mlpclassifier', MLPClassifier(alpha=0.01, random_state=42))])

Переходим к следующему набору данных - набору данных катастрофы «Титаника», он
принадлежит к числу самых популярных наборов данных машинного обучения. 
Цель состоит в том, чтобы построить автоматизированную модель, способную
прогнозировать, пережил бы пассажир аварию, основываясь на различных входных
характеристиках, таких как класс пассажира, пол, возраст, каюта.

Загружаем и знакомимся с данными :

df = pd.read_csv('titanic_train.csv')
df.head()

Проверяем наличие пропусков

df.isnull().sum()

PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

Как видим, в наборе данных много пропущенных значений. Большинство из
недостающих значений находятся в атрибутах Age и Cabin. Это легко понять для Cabin – значение отсутствует, если у пассажира не было собственной каюты. Мы разберемся с этими отсутствующими значениями позже, а пока давайте сосредоточимся на данных и используем для этого визуализацию.  Для этого определим одну функцию для
отображения гистограммы. 

def make_bar_chart(column, title, ylabel, xlabel, y_offset=10, x_offset=0.2):
    ax = df.groupby(column).count()[['PassengerId']].plot(kind='bar',
    figsize=(10, 6), fontsize=13,color='#4f4f4f'    )
    ax.set_title(title, size=20, pad=30)
    ax.set_ylabel(ylabel, fontsize=14)
    ax.set_xlabel(xlabel, fontsize=14)
    ax.get_legend().remove()
    for i in ax.patches:
        ax.text(i.get_x() + x_offset, i.get_height() + y_offset, i.get_height(), fontsize=20)
    return ax

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

make_bar_chart(column='Survived',
title='Distribution of the Survived variable',ylabel='Count',
xlabel='Has the passenger survived? (0 = No, 1 = Yes)');


Как видно, большинство пассажиров не выжили при крушении Титаника. Этот сама
по себе информация мало поможет нашему анализу, потому что мы не знаем 
распределение выживших пассажиров по полу, классу пассажиров и другим признакам.  
Продолжим визуализацию данных и покажем количество пассажиров каждого
пассажирского класса. Чем ниже номер класса, тем лучше — дороже билет, лучше
сервис и может быть, больше шансов на выживание:

make_bar_chart(column='Pclass',
title='Distirbution of the Passenger Class variable',
ylabel='Count',xlabel='Passenger Class (smaller is better)',
x_offset=0.15);


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


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

make_bar_chart(column='Sex',
title='Distirbution of the Sex variable',
ylabel='Count',xlabel='Gender');



Как видим, на борту было больше мужчин. Это связано с вывод, сделанный в
предыдущей визуализации, где мы пришли к выводу, что много рабочих на борту. 
Далее визуализируем изменение непрерывной переменной. Цель состоит в том, чтобы сделать гистограмму атрибута Fare, которая покажет распределение сумм, уплаченных за билет. 

plt.figure(figsize=(12, 7))
plt.title('Fare cost distribution', size=20)
plt.xlabel('Cost', size=14)
plt.ylabel('Count', size=14)
plt.hist(df['Fare'], bins=15, color='#4f4f4f',ec='#040404');




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

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

df['Title'] = df['Name'].apply(lambda x: x.split(',')[1].
strip().split(' ')[0])
make_bar_chart(column='Title',
title='Distirbution of the Passenger Title variable',
ylabel='Count',xlabel='Title',x_offset=-0.2);


Как видно, большинство пассажиров имеют общие титулы, такие как мистер и мисс и

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


Пришло время подготовить набор данных для машинного обучения и сделать

следующие шаги :

    1)Отбросить бесполезные столбцы — Ticket и PassengerId. Первый представляет

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

Второй — произвольный идентификатор, скорее всего, сгенерированный с помощью

последовательности базы данных. Удаляем их вызвав функцию drop(). 

    2) Переназначить значения атрибута Пол на целые числа. Текстовые значения пола

не могут быть передана алгоритму машинного обучения напрямую, поэтому заменим

мужчин на 0, а женщин на 1. Функция replace() — идеальный кандидат на эту работу. 

    3) Используем ранее сгенерированный столбец Title и преобразуем его в двоичный
показатель – значение равно нулю, если титул является общим (например, Мистер,
 Мисс и Миссис) и иначе единица. Затем переименуем столбец  в Title_Unusal. Столбец
Name больше не нужен, мы его удаляем.

    4) Обработаем отсутствующие значения в столбце Cabin, превратив этот атрибут
 в двоичный – значение равно нулю, если значение для кабины отсутствует, и единице в противном случае. Назовем этот новый столбец Cabin_Known. После этого можно
удалить столбец Cabin, потому что он также больше не нужен 

    5) Создайте фиктивные переменные с атрибутом Embarked. Этот атрибут указывает
порт, в котором пассажиры прибыли на судно. Сложно судить о его полезности, оставим это на усмотрение TPOT. 

    6) Отсутствующие значения в атрибуте Age для простоты заменим средним
 значением

В следующем фрагменте кода показано, как применить все эти преобразования:


df.drop(['Ticket', 'PassengerId'], axis=1, inplace=True)
gender_mapper = {'male': 0, 'female': 1}
df['Sex'].replace(gender_mapper, inplace=True)
pd.concat([df.head(3),df.tail(3)])

SurvivedPclassNameSexAgeSibSpParchFareCabinEmbarkedTitle
003Braund, Mr. Owen Harris022.0107.2500NaNSMr.
111Cumings, Mrs. John Bradley (Florence Briggs Th...138.01071.2833C85CMrs.
213Heikkinen, Miss. Laina126.0007.9250NaNSMiss.
88803Johnston, Miss. Catherine Helen "Carrie"1NaN1223.4500NaNSMiss.
88911Behr, Mr. Karl Howell026.00030.0000C148CMr.
89003Dooley, Mr. Patrick032.0007.7500NaNQMr.




df['Title'] = [0 if x in ['Mr.', 'Miss.', 'Mrs.'] else 1 for x in df['Title']] df = df.rename(columns={'Title': 'Title_Unusual'}) df.drop('Name', axis=1, inplace=True) pd.concat([df.head(3),df.tail(3)])


SurvivedPclassSexAgeSibSpParchFareCabinEmbarkedTitle_Unusual
003022.0107.2500NaNS0
111138.01071.2833C85C0
213126.0007.9250NaNS0
888031NaN1223.4500NaNS0
88911026.00030.0000C148C0
89003032.0007.7500NaNQ0


df['Cabin_Known'] = [0 if str(x) == 'nan' else 1 for x in df['Cabin']] df.drop('Cabin', axis=1, inplace=True) pd.concat([df.head(3),df.tail(3)])


SurvivedPclassSexAgeSibSpParchFareEmbarkedTitle_UnusualCabin_Known
003022.0107.2500S00
111138.01071.2833C01
213126.0007.9250S00
888031NaN1223.4500S00
88911026.00030.0000C01
89003032.0007.7500Q00



emb_dummies = pd.get_dummies(df['Embarked'], drop_first=True, prefix='Embarked') df = pd.concat([df, emb_dummies], axis=1) df.drop('Embarked', axis=1, inplace=True) df['Age'] = df['Age'].fillna(int(df['Age'].mean())) pd.concat([df.head(3),df.tail(3)])

SurvivedPclassSexAgeSibSpParchFareTitle_UnusualCabin_KnownEmbarked_QEmbarked_S
003022.0107.25000001
111138.01071.28330100
213126.0007.92500001
88803129.01223.45000001
88911026.00030.00000100
89003032.0007.75000010

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

Прежде чем обучить модель классификации, разделим набор данных на обучающие и
тестирующие части. 

from sklearn.model_selection import train_test_split

X = df.drop('Survived', axis=1)
y = df['Survived']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
y_train.shape, y_test.shape

((668,), (223,))

Переходим к моделям. Начнем с базовой модели — логистической регрессии. Мы будем тренировать его на обучающем наборе и оценить его на тестовом. Следующий
фрагмент кода обучает модель и выводит матрицу ошибок:

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix

m = LogisticRegression(random_state=42)
lm.fit(X_train, y_train)

lm_preds = lm.predict(X_test) print(confusion_matrix(y_test, lm_preds))

[[111  23]
 [ 23  66]]

Количество ложноположительных и ложноотрицательных результатов одинаково (23).
 Если учитывать долю, то ложноотрицательных результатов 
больше. Это значит, что модель с большей вероятностью скажет, что пассажир выжил,
даже если это не так. Для оценки качества модели используем F1 оценку. Значение
этого показателя находится в диапазоне от 0 до 1 (чем выше, тем лучше) и 
представляет собой гармоническое среднее между точностью и полнотой. Вот как это
рассчитать с помощью Python

from sklearn.metrics import f1_score
print(f'F1 = {f1_score(y_test, lm_preds):.2f}')

F1 = 0.74

Значение 0,74 неплохо для базовой модели. Может ли TPOT превзойти его?  Так же, как и раньше, мы ограничиваем обучение модели 10 минутами. Вместо точности мы будем оптимизировать оценку F1. Тем самым мы можем сравните оценки F1
автоматизированной модели с базовой. Следующий фрагмент кода обучает модель на
обучающем наборе: TPOT удалось обучить 4 поколения за 10 минут, а результат
немного увеличивается по мере обучения модели:

from tpot import TPOTClassifier
pipeline_optimizer = TPOTClassifier(
scoring='f1',
max_time_mins=10,
random_state=42,
verbosity=2)
pipeline_optimizer.fit(X_train, y_train)

Generation 1 - Current best internal CV score: 0.7668883546121398

Generation 2 - Current best internal CV score: 0.7687520926005644

Generation 3 - Current best internal CV score: 0.7696645770327197

Generation 4 - Current best internal CV score: 0.7696645770327197

10.01 minutes have elapsed. TPOT will close down.
TPOT closed during evaluation in one generation.
WARNING: TPOT may not provide a good pipeline if TPOT is stopped/interrupted in a early generation.


TPOT closed prematurely. Will use the current best pipeline.

Best pipeline: XGBClassifier(ZeroCount(input_matrix), learning_rate=0.1, max_depth=6, min_child_weight=2, n_estimators=100, n_jobs=1, subsample=0.45, verbosity=0)

Значение оценки F1 на тестовом наборе. У базовой модели мы получили 0,74.

print(f'F1 = {pipeline_optimizer.score(X_test, y_test):.2f}')

F1 = 0.77

TPOT превзошел базовую модель на 4%. Сравним другой показатель - точность.

tpot_preds = pipeline_optimizer.predict(X_test)
from sklearn.metrics import accuracy_score
print(f'Baseline model accuracy: {accuracy_score(y_test,lm_preds):.2f}')
print(f'TPOT model accuracy: {accuracy_score(y_test,tpot_preds):.2f}')

Baseline model accuracy: 0.79
TPOT model accuracy: 0.82

Здесь тоже побеждает TROT.

Так можно вывести оптимальный конвейер 

pipeline_optimizer.fitted_pipeline_



















Комментариев нет:

Отправить комментарий