Введение

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

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

Объективный анализ

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

Набор данных

Набор данных состоит из 1599 строк и 12 столбцов. Тип данных всех переменных - float

Разбираясь в различных характеристиках вина, мы видим, что всего имеется 12 столбцов, включая окончательный параметр качества.

  • Фиксированная кислотность: нелетучие кислоты, которые плохо испаряются.
  • Летучие кислоты: высокое содержание уксусной кислоты в вине, что приводит к неприятному вкусу уксуса.
  • Лимонная кислота действует как консервант, повышая кислотность. В небольших количествах придает вину свежесть и аромат.
  • Остаточный сахар: количество сахара, оставшееся после остановки брожения. Главное, чтобы был идеальный баланс между сладостью и кислинкой. Важно отметить, что вина › 45 г/л сладкие.
  • Хлориды: количество соли в вине.
  • Свободный диоксид серы: предотвращает рост микробов и окисление вина.
  • Общее количество диоксида серы: количество свободных и связанных форм SO2.
  • Плотность: более сладкие вина имеют более высокую плотность.
  • pH: описывает уровень кислотности по шкале от 0 до 14. Большинство вин всегда находятся в пределах 3–4 по шкале pH.
  • Алкоголь: доступный в небольших количествах в винах, делает пьющих общительными.
  • Сульфаты: добавка к вину, которая способствует повышению уровня SO2 и действует как антимикробное и антиоксидантное средство.
  • Качество: выходная переменная/предиктор.

Проверка нулевых или отсутствующих значений

#Check null or missing value
df.isnull().sum()

output :
fixed acidity           0
volatile acidity        0
citric acid             0
residual sugar          0
chlorides               0
free sulfur dioxide     0
total sulfur dioxide    0
density                 0
pH                      0
sulphates               0
alcohol                 0
quality                 0
dtype: int64

Похоже, что нет пропущенных значений. Это означает, что набор данных может быть обработан.

Визуализация данных

Распределение переменной целевого качества

sns.countplot(data=df, x= 'quality')
plt.title('Distribution Variable Target')
plt.show()

Визуализируйте содержание корреляции в красном вине

plt.figure(figsize=(20, 10))
sns.heatmap(df.corr(), annot=True)
plt.title("Heatmap for Red Wine Quality")
plt.show()

Рисунок выше: аналитика

  • Алкоголь положительно коррелирует с качеством красного вина.
  • Алкоголь имеет слабую положительную корреляцию со значением рН.
  • Лимонная кислота и плотность имеют сильную положительную корреляцию с фиксированной кислотностью.
  • pH имеет отрицательную корреляцию с плотностью, фиксированной кислотностью, лимонной кислотой и сульфатами.

Исследовательский анализ данных

Масштабирование функций

Масштабируйте набор данных по качеству

Прочитав описание набора данных качества красного вина, мы находим:

  • качество ›= 7 – «хорошее»
  • качество ‹= 7 — «плохое»
df['quality'] = df['quality'].apply(lambda x: 1 if x >= 7 else 0)

sns.countplot(data = df, x = 'quality')
plt.xticks([0,1], ['bad wine','good wine'])
plt.title("Types of Wine")
plt.show()

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

Повторная выборка набора данных

  • для искаженного или несбалансированного набора данных мы можем выполнить повторную выборку, используя метод передискретизации синтетического меньшинства для балансировки данных.
#parameter for requires seed
random_value = 1000

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

oversample = SMOTE()
X_ros, y_ros = oversample.fit_resample(X, y)

sns.countplot(x=y_ros)
plt.xticks([0,1], ['bad wine','good wine'])
plt.title("Types of Wine")
plt.show()

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

  • Разделить набор данных на поезд и тест
# split dataset to train and test variable 
# use test size of 20% of the data proportion
X_train, X_test, y_train, y_test = train_test_split(X_ros, y_ros, test_size=0.2, random_state=random_value)
X_train.shape, X_test.shape

output:
((2211, 11), (553, 11))
  • Масштабирование набора данных с помощью StandardScaler
# scale with StandardScaler
scaler = StandardScaler()

# fit to data training
scaler.fit(X_train)

# transform
x_train = scaler.transform(X_train)
x_test = scaler.transform(X_test)

Тренировочные модели

Логистическая регрессия

# Logistic Regression initialization
logreg = LogisticRegression(class_weight='balanced', random_state=random_value)

# Cross Validation
logreg_score = cross_val_score(estimator = logreg,
                               X = x_train, y= y_train,
                               scoring = 'recall',cv = 10,
                               verbose = 3, n_jobs=-1)

# Fit data training
logreg.fit(x_train, y_train)

# Predict data test
y_pred = logreg.predict(x_test)

print('Avarage Recall score', np.mean(logreg_score))
print('Test Recall score', recall_score(y_test, y_pred))

[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done   3 out of  10 | elapsed:    3.0s remaining:    7.2s
[Parallel(n_jobs=-1)]: Done   7 out of  10 | elapsed:    3.2s remaining:    1.3s
Avarage Recall score 0.8348402948402949
Test Recall score 0.8284671532846716
[Parallel(n_jobs=-1)]: Done  10 out of  10 | elapsed:    3.3s finished
  • Основываясь на аналогичных результатах, полученных из оценок, можно сделать вывод, что производительность модели хорошая при прогнозировании целевой переменной.
# Confusion Matrix
conf_mat = confusion_matrix(y_test, y_pred)

# Heatmap Confusion Matrix
sns.heatmap(conf_mat, cmap = 'Reds', annot = True, fmt='.1f')
plt.title('Confusion Matrix dari Prediksi Logistic Regression')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()

Предсказание модели:

  • модель правильно предсказала «0» 220 раз, а неправильно предсказала «0» 59 раз.
  • Также он неправильно предсказал «1» 47 раз, а правильно предсказал «1» 227 раз.

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

# Grid parameters
log_grid = {
    'penalty': ['l1', 'l2'],
    'C': [0.1, 1, 10],
    'solver': ['liblinear']
    }
# Use RandomizedSearchCV
logreg_cv = RandomizedSearchCV(estimator=logreg, param_distributions=log_grid,
                                scoring='recall', cv=10)

# Fit to model
logreg_cv.fit(X_train, y_train)

# Best Score
print(f'Best score: {logreg_cv.best_score_}')
print(f'Best params: {logreg_cv.best_params_}')

output :
Best score: 0.8348402948402949
Best params: {'solver': 'liblinear', 'penalty': 'l1', 'C': 10}

Сравнить балл

# Logistic Regression initialization
logreg_tuned = LogisticRegression(**logreg_cv.best_params_,class_weight='balanced', random_state=random_value)

# Cross Validation
logreg_tuned_score = cross_val_score(estimator = logreg_tuned,
                               X = x_train, y= y_train,
                               scoring = 'recall',cv = 10,
                               verbose = 0)

# Fit data training
logreg_tuned.fit(x_train, y_train)

# Predict data test
y_pred_tuned = logreg_tuned.predict(x_test)

# Cek Score
print('Avarage Recall score', np.mean(logreg_score))
print('Test Recall score', recall_score(y_test, y_pred))
print('Avarage Recall score Tuning', np.mean(logreg_tuned_score))
print('Test Recall score Tuning', recall_score(y_test, y_pred_tuned))

output :
Avarage Recall score 0.8348402948402949
Test Recall score 0.8284671532846716
Avarage Recall score Tuning 0.833939393939394
Test Recall score Tuning 0.8284671532846716
  • Судя по полученным результатам, производительность немного улучшилась.

Случайный лес

# Random Forest Regression initialization
rfc = RandomForestClassifier(n_estimators=100, random_state=random_value)

# Cross Validation
rf_score = cross_val_score(estimator = rfc,
                               X = x_train, y= y_train,
                               scoring = 'recall',cv = 10,
                               verbose = 3, n_jobs=-1)

# Fit data training
rfc.fit(x_train, y_train)

# Predict data test
y_pred = rfc.predict(x_test)

print('Avarage Recall score', np.mean(rf_score))
print('Test Recall score', recall_score(y_test, y_pred))

output :
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done   3 out of  10 | elapsed:    0.8s remaining:    1.9s
[Parallel(n_jobs=-1)]: Done   7 out of  10 | elapsed:    0.8s remaining:    0.3s
[Parallel(n_jobs=-1)]: Done  10 out of  10 | elapsed:    1.3s finished
Avarage Recall score 0.9638738738738739
Test Recall score 0.9708029197080292
  • Основываясь на аналогичных результатах, полученных из оценок, можно сделать вывод, что производительность модели хорошая при прогнозировании целевой переменной.
# Confusion Matrix
conf_mat = confusion_matrix(y_test, y_pred)

# Heatmap Confusion Matrix
sns.heatmap(conf_mat, cmap = 'Reds', annot = True, fmt='.1f')
plt.title('Confusion Matrix dari Prediksi Random Forest')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()

Предсказание модели:

  • Наша модель правильно предсказала «0» 253 раза, а неверно предсказала «0» 26 раз.
  • Также он неправильно предсказал «1» 8 раз, а правильно предсказал «1» 266 раз.

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

# Grid parameters
rf_grid = {
                'n_estimators': [50, 100, 200],
                'max_depth': [3, 5, 10],
                'min_samples_split': [2, 5, 10]
                }
                
# Use RandomizedSearchCV
rf_cv = RandomizedSearchCV(estimator=rfc, param_distributions=rf_grid,
                            scoring='recall', cv=10)

# Fit to model
rf_cv.fit(X_train, y_train)

# Best Score
print(f'Best score: {rf_cv.best_score_}')
print(f'Best params: {rf_cv.best_params_}')

output :
Best score: 0.9701801801801802
Best params: {'n_estimators': 100, 'min_samples_split': 5, 'max_depth': 10}

Сравнить балл

# Random Forest Regression initialization
rf_tuned = RandomForestClassifier(**rf_cv.best_params_, random_state=random_value)

# Cross Validation
rf_tuned_score = cross_val_score(estimator = rf_tuned,
                               X = x_train, y= y_train,
                               scoring = 'recall',cv = 10,
                               verbose = 0)

# Fit data training
rf_tuned.fit(x_train, y_train)

# Predict data test
y_pred_tuned = rf_tuned.predict(x_test)

# Cek Score
print('Avarage Recall score', np.mean(rf_score))
print('Test Recall score', recall_score(y_test, y_pred))
print('Avarage Recall score Tuning', np.mean(rf_tuned_score))
print('Test Recall score Tuning', recall_score(y_test, y_pred_tuned))

output :
Avarage Recall score 0.9638738738738739
Test Recall score 0.9708029197080292
Avarage Recall score Tuning 0.9701801801801802
Test Recall score Tuning 0.9708029197080292
  • Судя по полученным результатам, производительность немного улучшилась.

Дерево решений

# DecisionTree Regression initialization
dtc = DecisionTreeClassifier(random_state=random_value)

# Cross Validation
dt_score = cross_val_score(estimator = dtc,
                               X = x_train, y= y_train,
                               scoring = 'recall',cv = 10,
                               verbose = 3, n_jobs=-1)

# Fit data training
dtc.fit(x_train, y_train)

# Predict data test
y_pred = dtc.predict(x_test)

print('Avarage Recall score', np.mean(dt_score))
print('Test Recall score', recall_score(y_test, y_pred))

output: 
Avarage Recall score 0.925962325962326
Test Recall score 0.9306569343065694
[Parallel(n_jobs=-1)]: Using backend LokyBackend with 8 concurrent workers.
[Parallel(n_jobs=-1)]: Done   3 out of  10 | elapsed:    0.0s remaining:    0.0s
[Parallel(n_jobs=-1)]: Done   7 out of  10 | elapsed:    0.0s remaining:    0.0s
[Parallel(n_jobs=-1)]: Done  10 out of  10 | elapsed:    0.0s finished
  • Основываясь на аналогичных результатах, полученных из оценок, можно сделать вывод, что производительность модели хорошая при прогнозировании целевой переменной.
# Confusion Matrix
conf_mat = confusion_matrix(y_test, y_pred)

# Heatmap Confusion Matrix
sns.heatmap(conf_mat, cmap = 'Reds', annot = True, fmt='.1f')
plt.title('Confusion Matrix dari Prediksi Decision Tree')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()

Предсказание модели:

  • Наша модель правильно предсказала «0» 250 раз, а неверно предсказала «0» 29 раз.
  • Также он неправильно предсказал «1» 19 раз, а правильно предсказал «1» 255 раз.

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

# Grid parameters
dt_grid = {
            'max_depth': [3, 5, 10],
            'min_samples_split': [2, 5, 10]
            }
                
# Use RandomizedSearchCV
dt_cv = RandomizedSearchCV(estimator=dtc, param_distributions=dt_grid,
                            scoring='recall', cv=10)

# Fit to model
dt_cv.fit(X_train, y_train)

# Best Score
print(f'Best score: {dt_cv.best_score_}')
print(f'Best params: {dt_cv.best_params_}')

output :
Best score: 0.9295659295659296
Best params: {'min_samples_split': 2, 'max_depth': 10}

Сравнить балл

# DecisionTree Regression initialization
dt_tuned = RandomForestClassifier(**dt_cv.best_params_, random_state=random_value)

# Cross Validation
dt_tuned_score = cross_val_score(estimator = dt_tuned,
                               X = x_train, y= y_train,
                               scoring = 'recall',cv = 10,
                               verbose = 0)

# Fit data training
dt_tuned.fit(x_train, y_train)

# Predict data test
y_pred_tuned = dt_tuned.predict(x_test)

# Cek Score
print('Avarage Recall score', np.mean(dt_score))
print('Test Recall score', recall_score(y_test, y_pred))
print('Avarage Recall score Tuning', np.mean(dt_tuned_score))
print('Test Recall score Tuning', recall_score(y_test, y_pred_tuned))

output :
Avarage Recall score 0.925962325962326
Test Recall score 0.9306569343065694
Avarage Recall score Tuning 0.9692710892710892
Test Recall score Tuning 0.9708029197080292
  • Судя по полученным результатам, производительность немного улучшилась.

Заключение и будущие работы

  1. Мы можем построить довольно хорошую модель с довольно высокой точностью.
  2. Случайный лес и дерево решений — модели с очень хорошей производительностью и точностью.
  3. Получите больше данных для тестирования, чтобы наша модель могла учиться и создавать более точные прогнозы.
  4. Сделайте PCA, уменьшите количество функций данных и упростите процесс в нашей модели.
  5. Изучите другой метод и найдите наиболее подходящие решения для качества вина.

Ссылки

Набор данных:



Исходный код на GitHub: https://github.com/ariprachmaan/winepredict.git