В этом посте я расскажу вам о процессе создания модели машинного обучения для оценки качества Red Wine. Я буду использовать Google Colab в качестве своей IDE и sklearn в качестве модуля для создания алгоритмов машинного обучения. Набор данных был загружен с Kaggle:

Качество красного вина, 28 ноября 2017 г., общедоступный источник: https://www.kaggle.com/uciml/red-wine-quality-cortez-et-al-2009.

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

Набор данных состоит почти из 2000 строк с химическими показателями, распределенными по 11 столбцам, а произвольные отзывы о качестве красного вина записываются в последний столбец. Даже если заявленная в заголовке точность в 72% не выглядит удивительной (я объясню проблему с набором данных в следующем заголовке), это достаточная степень точности для прогнозирования качества вина.

***Отмечено, что точность 72% относится к способности программного обеспечения определять ТОЧНУЮ оценку, присвоенную вину. Например, если рейтинг равен 5, а программное обеспечение оценивает его как 4, это все равно считается ошибкой. Итак, остальные 28% прогнозов не совсем неверны, просто не соответствуют точным рейтингам.

Несколько обязательных шагов, прежде чем мы начнем:

Импорт модулей

import matplotlib.pyplot as plt
import matplotlib
import pandas as pd
import seaborn as sns
import numpy as np

tf_dataset_extractor

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

СКАЧАТЬ МОДУЛЬ tf_dataset_extractory_v1_1.py ЗДЕСЬ

***Рекомендую не скачивать старые версии, так как код постоянно редактируется

Вариант №1: запуск tf_dataset_extractor как части кода

Если вы новичок в программировании и у вас все еще возникают проблемы с ориентацией:

1. Copy the content of the tf_dataset_extractor_v1_1 module here
2. Substitute every e.X with X
3. Substitute every e.y with y
4. Run the code

Вариант № 2: импортировать tf_dataset_extractor как модуль

Предпочтительный выбор. Просто загрузите модуль tf_dataset_extractor.py на свой Google Диск и создайте соединение с sys.path.append («местоположение tf_dataset_extractor_v1_1.py»), чтобы вы могли его импортировать.

#I connect with my Google Drive folder
import sys
sys.path.append(‘/content/drive/My Drive/Colab Notebooks/TensorFlow 2.0/modules’)
import pandas as pd
import tf_dataset_extractor as e
v = e.v

tf_dataset_extractor имеет три разных класса. Я активирую только первый класс, создав его экземпляр как объект, который я назову v. Этот класс предназначен для быстрой предварительной обработки набора данных.

Глядя на данные

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

v.upload.offline_csv()
e.K = v.upload.make_backup()

Вызывая модуль make_backup, я копирую свою основную глобальную переменную (e.X) в e.K. В случае, если мне нужно будет сбросить мою базу данных, я просто использую модуль retrieve_backup(e.K), как показано ниже.

e.X.head()

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

Почему мы должны ожидать такой малой точности?

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

corr = e.X.corr()
fig, ax = plt.subplots(figsize=(15,15))
#If you want to Generate a mask for the upper triangle
#mask = np.triu(np.ones_like(corr, dtype=np.bool))
#ax.set_facecolor((.21875, .21875, .21875))
#I choose not to because I can see the strength of the correlation across all variables
ax = sns.heatmap(
corr,
#mask=mask,
vmin=-1, vmax=1, center=0,
cmap=sns.diverging_palette(0, 256, n=200),
square=True,
ax=ax,
annot=True
)

Чтобы продемонстрировать свою точку зрения, я сделал гистограмму со всеми данными, найденными в корреляционной матрице. Мы видим, что наши значения корреляции нормально распределены со средним значением около 0.

corr = e.X.corr()
df = pd.DataFrame(corr.values)
df = df.to_numpy()
df = df.reshape(121, )
df = pd.DataFrame(df)
df = df[df[0] != 1.0]
df.hist(bins=20, figsize=(10,10), grid=False)

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

Предварительная обработка

v.upload.retrieve_backup(e.K)

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

v.partition.label_encoder([‘quality’], to_float=True)

Прежде чем изолировать метки, нам нужно отредактировать их, пока они еще прикреплены к e.X. Каждый раз, когда мы вызываем класс v, e.X. модифицируется целиком.

Поскольку наши метки не начинаются с 0, проще использовать labelencoder из sklearn (все включено в tf_dataset_extractor, без него вам пришлось бы создавать свои собственные функции, обращающиеся к библиотеке sklearn).

#we extract y from X, separating the two partitions of the dataset
v.extract.labels([‘quality’])
#in case we wish to scale the data. For classification purposes this is not necessary.
#v.partition.scale(partitions=’all_df’, to_float=True, df=e.X)

Разделение

e.X_numerical = e.X
e.X_categorical = pd.DataFrame()

***Для выполнения разделения tf_dataset_extractor требует: y, e.X_numerical, e.X_categorical. Поскольку у нас есть только y и e.X, мы можем просто сделать e.X_categorical пустым, а e.X_numerical равным e.X. В обычном машинном обучении нет необходимости делить набор данных на числовой и категориальный перед разбиением.

X_train, X_test, y_train, y_test = v.cross_sectional_split(0.2)

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

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

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

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier()
param_grid = {
  ‘n_estimators’: [1, 200, 400, 600, 800, 1000, 1200, 1400, 1600,     1800, 2000],
  ‘criterion’: [‘gini’, ‘entropy’],
  ‘min_samples_split’: [2, 3, 4, 5],
  ‘bootstrap’: [False, True]
}
# Instantiate the grid search model
grid_search = GridSearchCV(estimator = clf, param_grid = param_grid, n_jobs = -1, verbose = 2, cv=None)
# Fit the grid search to the data
clf = grid_search.fit(X_train, y_train)
clf.best_params_

После 46 минут ожидания компилятор выбрал для нас следующие гиперпараметры:

output:
{‘bootstrap’: True,
‘criterion’: ‘entropy’,
‘min_samples_split’: 3,
‘n_estimators’: 1400}

Создание модели: классификатор случайного леса

Почему бы не запустить DNN (глубокую нейронную сеть)? Я попытался запустить MLP для классификации (Multilayer Perceptron), получив только 50% точности. По сравнению с первоначальными результатами, 72% — это роскошь.

#RandomForestClassifier
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(
  bootstrap=True,
  criterion=’entropy’,
  min_samples_split=3,
  n_estimators= 1400
)

Обучение модели

Как видите, я ввожу гиперпараметры, рекомендованные GridSearch:

clf = clf.fit(X_train, y_train)

Тестирование модели

y_predict = clf.predict(X_test)
from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_predict))
output:
0.659375

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

Перекрестная проверка

def cross_validation(clf, X, y, cv, return_scores=False):
 from sklearn.model_selection import cross_val_score
 scores = cross_val_score(clf, X, y, cv=cv)
 acc = (“Accuracy: %0.2f (+/- %0.2f)” % (scores.mean(), scores.std() * 2))
 if return_scores == True:
  return scores
 else:
  return acc, scores

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

***Почему мы не можем просто перезагрузить наш первоначальный набор данных, если у нас есть такая возможность? Наш первоначальный набор данных не был предварительно обработан: нам нужно было бы повторно запустить эту часть кода без извлечения меток. Это просто проще.

X = pd.concat([X_train, X_test], axis=0)
y = pd.concat([y_train, y_test], axis=0)
cross_validation(clf, X, y, 10)
output:
(‘Accuracy: 0.72 (+/- 0.08)’,
array([ 0.69375 , 0.6875 , 0.75625 , 0.71875 , 0.7 ,
        0.7375 , 0.70625 , 0.8125 , 0.65 , 0.71069182]))

Как мы видим, диапазон оценок при 10-кратной проверке колеблется от 0,65 до 0,81. Таким образом, мы можем получить среднюю точность 72% (с погрешностью 0,08).