Welche Qualität hat der Rotwein?¶
1. Business Understanding¶
Eine Weinkellerei möchte sicherstellen, dass sie stets qualitativ hochwertigen und schmackhaften Wein produziert. Sie beschäftigen professionelle Weinverkoster, die alle neuen Weinpartien testen. Diese Fachleute bewerten den Wein dann auf einer Skala von 1 bis 10. Dieses Verfahren ist jedoch teuer und subjektiv. Die Weinmanufaktur möchte ein maschinelles Lernmodell einsetzen, um Vorhersagen über die Qualität zu treffen. Das ultimative Ziel ist die Entwicklung eines Modells zu entwickeln, das neue Weinpartien anhand ihrer chemischen Eigenschaften automatisch kategorisiert.
2. Data Understanding¶
2.1. Bibliotheken Importieren¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math
import pickle
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import metrics
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import LogisticRegression
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation, Dropout
%matplotlib inline
2.2. Daten Auslesen¶
data = pd.read_csv('https://storage.googleapis.com/ml-service-repository-datastorage/What_Quality_does_the_Red_wine_have_winequality.csv')
2.3. Deskriptive Datenanalyse¶
Datenbeschreibung¶
Der Datensatz mit dem Namen "Wine Quality Data Set" wurde der UCI-Website entnommen entnommen und liefert uns reale Daten für unsere Weinmanufaktur. Die Daten wurden im Jahr 2009 erhoben und stammen aus Nordportugal. Der Datensatz enthält Einträge für Rot- und Weißweine. Der Datensatz für die Rotweine hat also 12 Spalten und 1600 Einträge. Die Spalten sind nach den Inhaltsstoffen und der Qualität eines Rotweins in Tabelle 1 benannt.
Feature | Data Type |
---|---|
fixed acidity | float64 |
volatile acidity | float64 |
citric acid | float64 |
residual sugar | float64 |
chlorides | float64 |
free sulfur dioxide | float64 |
total sulfur dioxide | float64 |
density | float64 |
pH | float64 |
sulphates | float64 |
alcohol | float64 |
quality | int64 |
Ein Rotwein besteht aus Wasser, Alkohol und Extrakten. Die genaue Mischung dieser 3 wesentlichen Bestandteile kann einen Rotwein zu einem echten ein wahrer Genuss. Meiner Meinung nach enthält ein guter Rotwein einen hohen Alkohol- und Zuckergehalt. Ob meine Meinung über einen guten Wein richtig ist wahr ist, können wir im nächsten Kapitel näher betrachten.
data.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1599 entries, 0 to 1598 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 fixed acidity 1599 non-null float64 1 volatile acidity 1599 non-null float64 2 citric acid 1599 non-null float64 3 residual sugar 1599 non-null float64 4 chlorides 1599 non-null float64 5 free sulfur dioxide 1599 non-null float64 6 total sulfur dioxide 1599 non-null float64 7 density 1599 non-null float64 8 pH 1599 non-null float64 9 sulphates 1599 non-null float64 10 alcohol 1599 non-null float64 11 quality 1599 non-null int64 dtypes: float64(11), int64(1) memory usage: 150.0 KB
data.describe(include="all")
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 | 1599.000000 |
mean | 8.319637 | 0.527821 | 0.270976 | 2.538806 | 0.087467 | 15.874922 | 46.467792 | 0.996747 | 3.311113 | 0.658149 | 10.422983 | 5.636023 |
std | 1.741096 | 0.179060 | 0.194801 | 1.409928 | 0.047065 | 10.460157 | 32.895324 | 0.001887 | 0.154386 | 0.169507 | 1.065668 | 0.807569 |
min | 4.600000 | 0.120000 | 0.000000 | 0.900000 | 0.012000 | 1.000000 | 6.000000 | 0.990070 | 2.740000 | 0.330000 | 8.400000 | 3.000000 |
25% | 7.100000 | 0.390000 | 0.090000 | 1.900000 | 0.070000 | 7.000000 | 22.000000 | 0.995600 | 3.210000 | 0.550000 | 9.500000 | 5.000000 |
50% | 7.900000 | 0.520000 | 0.260000 | 2.200000 | 0.079000 | 14.000000 | 38.000000 | 0.996750 | 3.310000 | 0.620000 | 10.200000 | 6.000000 |
75% | 9.200000 | 0.640000 | 0.420000 | 2.600000 | 0.090000 | 21.000000 | 62.000000 | 0.997835 | 3.400000 | 0.730000 | 11.100000 | 6.000000 |
max | 15.900000 | 1.580000 | 1.000000 | 15.500000 | 0.611000 | 72.000000 | 289.000000 | 1.003690 | 4.010000 | 2.000000 | 14.900000 | 8.000000 |
Der Datensatz enthält 1599 Einträge. Die Spalten "freies Schwefeldioxid" und "Gesamtschwefeldioxid" haben sehr hohe Maximalwerte. Außerdem sehen wir, dass unsere Zielvariable den kleinsten Wert von 3 und den größten Wert von 8 hat. Hier müssen wir überlegen, ob die Zielvariable in gut und schlecht unterteilt werden muss.
2.4 Datenbereinigung¶
data.isnull().sum()
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
There are no Null values in the dataset
data[data.duplicated(keep=False)]
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 7.4 | 0.700 | 0.00 | 1.9 | 0.076 | 11.0 | 34.0 | 0.99780 | 3.51 | 0.56 | 9.4 | 5 |
4 | 7.4 | 0.700 | 0.00 | 1.9 | 0.076 | 11.0 | 34.0 | 0.99780 | 3.51 | 0.56 | 9.4 | 5 |
9 | 7.5 | 0.500 | 0.36 | 6.1 | 0.071 | 17.0 | 102.0 | 0.99780 | 3.35 | 0.80 | 10.5 | 5 |
11 | 7.5 | 0.500 | 0.36 | 6.1 | 0.071 | 17.0 | 102.0 | 0.99780 | 3.35 | 0.80 | 10.5 | 5 |
22 | 7.9 | 0.430 | 0.21 | 1.6 | 0.106 | 10.0 | 37.0 | 0.99660 | 3.17 | 0.91 | 9.5 | 5 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
1567 | 7.2 | 0.695 | 0.13 | 2.0 | 0.076 | 12.0 | 20.0 | 0.99546 | 3.29 | 0.54 | 10.1 | 5 |
1579 | 6.2 | 0.560 | 0.09 | 1.7 | 0.053 | 24.0 | 32.0 | 0.99402 | 3.54 | 0.60 | 11.3 | 5 |
1581 | 6.2 | 0.560 | 0.09 | 1.7 | 0.053 | 24.0 | 32.0 | 0.99402 | 3.54 | 0.60 | 11.3 | 5 |
1592 | 6.3 | 0.510 | 0.13 | 2.3 | 0.076 | 29.0 | 40.0 | 0.99574 | 3.42 | 0.75 | 11.0 | 6 |
1596 | 6.3 | 0.510 | 0.13 | 2.3 | 0.076 | 29.0 | 40.0 | 0.99574 | 3.42 | 0.75 | 11.0 | 6 |
460 rows × 12 columns
Es gibt Duplikate in dem Datensatz. Aber in diesem Fall wollen wir diese nicht entfernen, da es sich eigentlich um verschiedene Weinpartien handelt, die zufällig die gleichen chemischen Eigenschaften und die gleiche Qualität haben. Es wäre nicht schlecht, wenn sich das Modell auf die häufigeren Weine spezialisieren würde
data_clean = data
# Analyse the target variable
plt.figure(figsize =(12, 8))
sns.countplot(x="quality", data=data_clean )
<AxesSubplot:xlabel='quality', ylabel='count'>
3. Data Preparation¶
3.1 Ausreißer entfernen¶
data_clean.hist(figsize=(20,20), bins= 60)
array([[<AxesSubplot:title={'center':'fixed acidity'}>, <AxesSubplot:title={'center':'volatile acidity'}>, <AxesSubplot:title={'center':'citric acid'}>], [<AxesSubplot:title={'center':'residual sugar'}>, <AxesSubplot:title={'center':'chlorides'}>, <AxesSubplot:title={'center':'free sulfur dioxide'}>], [<AxesSubplot:title={'center':'total sulfur dioxide'}>, <AxesSubplot:title={'center':'density'}>, <AxesSubplot:title={'center':'pH'}>], [<AxesSubplot:title={'center':'sulphates'}>, <AxesSubplot:title={'center':'alcohol'}>, <AxesSubplot:title={'center':'quality'}>]], dtype=object)
Anhand der Histogramme aller Spalten können wir nun auf einen Blick sehen, wie deren Werte verteilt sind. Es fällt auf, dass einige Merkmale keine Normalverteilung aufweisen und daher Ausreißer in ihren Werten haben.
Fester Säuregehalt
sns.histplot(data_clean['fixed acidity'])
<AxesSubplot:xlabel='fixed acidity', ylabel='Count'>
Das Diagramm sieht normalverteilt aus, deshalb bleiben die Werte im ersten Schritt
Volatile Acidity
sns.histplot(data_clean['volatile acidity'])
<AxesSubplot:xlabel='volatile acidity', ylabel='Count'>
q1 = data_clean['volatile acidity'].quantile(0.99)
q1
1.02
data1 = data_clean[data_clean['volatile acidity']<q1]
sns.histplot(data1['volatile acidity'])
<AxesSubplot:xlabel='volatile acidity', ylabel='Count'>
Citric Acid
sns.histplot(data1['citric acid'])
<AxesSubplot:xlabel='citric acid', ylabel='Count'>
Residual Sugar
sns.histplot(data1['residual sugar'])
<AxesSubplot:xlabel='residual sugar', ylabel='Count'>
plt.figure(figsize=(12, 7))
sns.boxplot(x='quality',y='residual sugar', data=data1,palette='winter')
<AxesSubplot:xlabel='quality', ylabel='residual sugar'>
Residual Sugar zeigt viele Ausreißer in Bezug auf die Zielvariable. Ein Teil der Ausreißer wird nun entfernt, um ein genaueres Datenbild zu erhalten.
q2 = data1['residual sugar'].quantile(0.99)
q2
8.36300000000001
data2= data1[data1['residual sugar']<q2]
sns.histplot(data2['residual sugar'])
<AxesSubplot:xlabel='residual sugar', ylabel='Count'>
plt.figure(figsize=(12, 7))
sns.boxplot(x='quality',y='residual sugar', data=data2,palette='winter')
<AxesSubplot:xlabel='quality', ylabel='residual sugar'>
Es sind noch Ausreißer zu sehen, die aber nach einem ersten Durchlauf des Modells noch verändert werden können
Chlorides
sns.histplot(data2['chlorides'])
<AxesSubplot:xlabel='chlorides', ylabel='Count'>
q3 = data2['chlorides'].quantile(0.98)
q3
0.226
data3= data2[data2['chlorides']<q3]
sns.histplot(data3['chlorides'])
<AxesSubplot:xlabel='chlorides', ylabel='Count'>
Free Sulfur Dioxide
sns.histplot(data3['free sulfur dioxide'])
<AxesSubplot:xlabel='free sulfur dioxide', ylabel='Count'>
plt.figure(figsize=(12, 7))
sns.boxplot(x='quality',y='free sulfur dioxide', data=data3,palette='winter')
<AxesSubplot:xlabel='quality', ylabel='free sulfur dioxide'>
q4 = data3['free sulfur dioxide'].quantile(0.99)
q4
47.700000000000045
data4= data3[data3['free sulfur dioxide']<q4]
sns.histplot(data4['free sulfur dioxide'])
<AxesSubplot:xlabel='free sulfur dioxide', ylabel='Count'>
plt.figure(figsize=(12, 7))
sns.boxplot(x='quality',y='free sulfur dioxide', data=data4,palette='winter')
<AxesSubplot:xlabel='quality', ylabel='free sulfur dioxide'>
Zuvor erkannte Ausreißer der Spalte 'free sulfur dioxide' entfernt und ein ausgeglichenes Bild zu unserer Zielvariablen geschaffen
Total Sulfur Dioxide
sns.histplot(data4['total sulfur dioxide'])
<AxesSubplot:xlabel='total sulfur dioxide', ylabel='Count'>
plt.figure(figsize=(12, 7))
sns.boxplot(x='quality',y='total sulfur dioxide', data=data4,palette='winter')
<AxesSubplot:xlabel='quality', ylabel='total sulfur dioxide'>
q5 = data4['total sulfur dioxide'].quantile(0.99)
q5
143.8599999999999
data5 = data4[data4['total sulfur dioxide']<q5]
sns.histplot(data5['total sulfur dioxide'])
<AxesSubplot:xlabel='total sulfur dioxide', ylabel='Count'>
Sulphates
sns.histplot(data5['sulphates'])
<AxesSubplot:xlabel='sulphates', ylabel='Count'>
q6 = data5['sulphates'].quantile(0.99)
q6
1.1602
data6 = data5[data5['sulphates']<q6]
sns.histplot(data6['sulphates'])
<AxesSubplot:xlabel='sulphates', ylabel='Count'>
sns.histplot(data6['alcohol'])
<AxesSubplot:xlabel='alcohol', ylabel='Count'>
plt.figure(figsize=(12, 7))
sns.boxplot(x='quality',y='alcohol', data=data6,palette='winter')
<AxesSubplot:xlabel='quality', ylabel='alcohol'>
je höher der Alkoholgehalt des Weins ist, desto besser wird er von den Fachleuten bewertet
Alle Merkmale wurden im ersten Schritt bereinigt und können nun für das Modell verwendet werden.
ie Zielvariable Qualität wird in gut 1 und schlecht 0 aufgeteilt.
Dies ändert das Problem von einer Regressionsaufgabe zu einer (binären) Kategorisierungsaufgabe
Umwandlung der Zielvariablen 'Qualität' in eine binäre Klassifikation¶
def quality_range(quality):
if quality <= 5:
return 0
elif quality >=6:
return 1
# make a (deep) copy, to make sure pandas doesn't complain and makes the correct thing
# see https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
data6 = data6.copy()
data6['quality_range'] = data6['quality'].apply(quality_range)
data6.describe()
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality | quality_range | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 |
mean | 8.331267 | 0.520044 | 0.266961 | 2.427763 | 0.081051 | 15.176550 | 43.828841 | 0.996705 | 3.316846 | 0.643652 | 10.455492 | 5.661725 | 0.546496 |
std | 1.761040 | 0.165732 | 0.192996 | 0.975109 | 0.020813 | 9.343802 | 29.133109 | 0.001854 | 0.149700 | 0.129927 | 1.071880 | 0.800449 | 0.498001 |
min | 4.600000 | 0.120000 | 0.000000 | 0.900000 | 0.012000 | 1.000000 | 6.000000 | 0.990070 | 2.860000 | 0.330000 | 8.400000 | 3.000000 | 0.000000 |
25% | 7.100000 | 0.390000 | 0.090000 | 1.900000 | 0.070000 | 7.000000 | 21.000000 | 0.995570 | 3.220000 | 0.550000 | 9.500000 | 5.000000 | 0.000000 |
50% | 7.900000 | 0.520000 | 0.250000 | 2.200000 | 0.079000 | 13.000000 | 37.000000 | 0.996700 | 3.315000 | 0.620000 | 10.200000 | 6.000000 | 1.000000 |
75% | 9.225000 | 0.630000 | 0.420000 | 2.600000 | 0.089000 | 21.000000 | 59.000000 | 0.997800 | 3.400000 | 0.720000 | 11.100000 | 6.000000 | 1.000000 |
max | 15.900000 | 1.010000 | 0.790000 | 8.300000 | 0.222000 | 47.000000 | 143.000000 | 1.003200 | 4.010000 | 1.160000 | 14.900000 | 8.000000 | 1.000000 |
data6.head()
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality | quality_range | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 7.4 | 0.70 | 0.00 | 1.9 | 0.076 | 11.0 | 34.0 | 0.9978 | 3.51 | 0.56 | 9.4 | 5 | 0 |
1 | 7.8 | 0.88 | 0.00 | 2.6 | 0.098 | 25.0 | 67.0 | 0.9968 | 3.20 | 0.68 | 9.8 | 5 | 0 |
2 | 7.8 | 0.76 | 0.04 | 2.3 | 0.092 | 15.0 | 54.0 | 0.9970 | 3.26 | 0.65 | 9.8 | 5 | 0 |
3 | 11.2 | 0.28 | 0.56 | 1.9 | 0.075 | 17.0 | 60.0 | 0.9980 | 3.16 | 0.58 | 9.8 | 6 | 1 |
4 | 7.4 | 0.70 | 0.00 | 1.9 | 0.076 | 11.0 | 34.0 | 0.9978 | 3.51 | 0.56 | 9.4 | 5 | 0 |
Mit der Methode quality_range() haben wir die Werte der Spalte "quality" aufgeteilt. Alle Werte kleiner und gleich 5 erhielten den Wert 0 (, was auf eine schlechtere als durchschnittliche Qualität hinweist). Alle Werte von 6 und größer erhielten den Wert 1 (für überdurchschnittliche Qualität).
plt.figure(figsize =(12, 8))
sns.countplot(data=data6, x="quality_range")
<AxesSubplot:xlabel='quality_range', ylabel='count'>
Die Zielvariable ist ausgeglichen
data6.drop('quality', axis=1, inplace=True)
data6.describe()
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality_range | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 | 1484.000000 |
mean | 8.331267 | 0.520044 | 0.266961 | 2.427763 | 0.081051 | 15.176550 | 43.828841 | 0.996705 | 3.316846 | 0.643652 | 10.455492 | 0.546496 |
std | 1.761040 | 0.165732 | 0.192996 | 0.975109 | 0.020813 | 9.343802 | 29.133109 | 0.001854 | 0.149700 | 0.129927 | 1.071880 | 0.498001 |
min | 4.600000 | 0.120000 | 0.000000 | 0.900000 | 0.012000 | 1.000000 | 6.000000 | 0.990070 | 2.860000 | 0.330000 | 8.400000 | 0.000000 |
25% | 7.100000 | 0.390000 | 0.090000 | 1.900000 | 0.070000 | 7.000000 | 21.000000 | 0.995570 | 3.220000 | 0.550000 | 9.500000 | 0.000000 |
50% | 7.900000 | 0.520000 | 0.250000 | 2.200000 | 0.079000 | 13.000000 | 37.000000 | 0.996700 | 3.315000 | 0.620000 | 10.200000 | 1.000000 |
75% | 9.225000 | 0.630000 | 0.420000 | 2.600000 | 0.089000 | 21.000000 | 59.000000 | 0.997800 | 3.400000 | 0.720000 | 11.100000 | 1.000000 |
max | 15.900000 | 1.010000 | 0.790000 | 8.300000 | 0.222000 | 47.000000 | 143.000000 | 1.003200 | 4.010000 | 1.160000 | 14.900000 | 1.000000 |
3.3. Test for Multicollinearity¶
plt.figure(figsize=(20,10))
feature_corr = data6.corr()
sns.heatmap(feature_corr, annot=True, cmap='coolwarm')
<AxesSubplot:>
data6.drop('fixed acidity', axis=1, inplace=True)
der feste Säuregehalt entfällt, da er stark mit dem pH-Wert korreliert
plt.figure(figsize=(20,10))
feature_corr = data6.corr()
sns.heatmap(feature_corr, annot=True, cmap='coolwarm')
<AxesSubplot:>
3.4. Test- und Trainingsdaten erstellen¶
x_train, x_test, y_train, y_test = train_test_split(data6.drop('quality_range', axis=1),data6['quality_range'], test_size=0.2, random_state=365)
4. Modellierung und Bewertung¶
4.1. Daten skalieren und transformieren¶
scaler = StandardScaler()
scaler.fit(x_train)
X_train = scaler.transform(x_train)
X_test = scaler.transform(x_test)
4.1 Modellbildung logistsiche Regression¶
model_logistic_regression = LogisticRegression(random_state=0, C=1e8, max_iter=1000)
# Train LogistModel
model_logistic_regression.fit(x_train,y_train)
prediction_test = model_logistic_regression.predict(x_test)
prediction_train = model_logistic_regression.predict(x_train)
Evaluierung¶
acc = metrics.accuracy_score(y_test, prediction_test)
print('Accuracy on the Test dataset: {}'.format(acc))
Accuracy on the Test dataset: 0.6936026936026936
print("Trainingsdaten:")
print(classification_report(y_train,prediction_train))
print("Testdaten:")
print(classification_report(y_test,prediction_test))
Training dataset: precision recall f1-score support 0 0.74 0.74 0.74 534 1 0.79 0.79 0.79 653 accuracy 0.76 1187 macro avg 0.76 0.76 0.76 1187 weighted avg 0.76 0.76 0.76 1187 Test dataset: precision recall f1-score support 0 0.67 0.68 0.67 139 1 0.71 0.71 0.71 158 accuracy 0.69 297 macro avg 0.69 0.69 0.69 297 weighted avg 0.69 0.69 0.69 297
# Confusion-Matrix Test dataset
cm = confusion_matrix(y_test,prediction_test)
df_cm = pd.DataFrame(cm, index=['worse wine','better wine'], columns=['worse wine', 'better wine'],)
fig = plt.figure(figsize=[10,7])
heatmap = sns.heatmap(df_cm, annot=True, fmt="d")
heatmap.yaxis.set_ticklabels(heatmap.yaxis.get_ticklabels(), rotation=0, ha='right', fontsize=14)
heatmap.xaxis.set_ticklabels(heatmap.xaxis.get_ticklabels(), rotation=45, ha='right', fontsize=14)
plt.ylabel('True label')
plt.xlabel('Predicted label')
Text(0.5, 42.0, 'Predicted label')
# confusion Matrix
tn, fp, fn, tp = cm.ravel()
recall = tp/(fn+tp)
precision = tp/(tp+fp)
print("True Negatives: " + str(tn))
print("False Positives: " + str(fp))
print("False Negatives: " + str(fn))
print("True Positives: " + str(tp))
print("Recall: " + str(recall))
print("Precision: " + str(precision))
True Negatives: 94 False Positives: 45 False Negatives: 46 True Positives: 112 Recall: 0.7088607594936709 Precision: 0.7133757961783439
# ROC-Kurve, AUC
fig, ax = plt.subplots(figsize=(8,6))
ax.set_title('ROC Curve')
plot = metrics.plot_roc_curve(model_logistic_regression, x_test, y_test, ax=ax)
ax.plot([0,1], [0,1], '--')
[<matplotlib.lines.Line2D at 0x1ffeab0a280>]
# print out Regression parameters (weights and biases)
weights = pd.Series(model_logistic_regression.coef_[0], index=x_train.columns.values)
weights.sort_values(ascending = False)
sulphates 5.210340 alcohol 0.913617 free sulfur dioxide 0.019869 total sulfur dioxide -0.018066 residual sugar -0.177201 citric acid -1.293662 pH -1.618701 density -1.921585 volatile acidity -3.134072 chlorides -3.875051 dtype: float64
# grafically ilustrate the most important features of a good wine
weights = pd.Series(model_logistic_regression.coef_[0], index=x_train.columns.values)
print (weights.sort_values(ascending = False)[:7].plot(kind='bar'))
AxesSubplot(0.125,0.125;0.775x0.755)
3. Lineare Regression¶
X = data6.drop(['quality_range'], axis=1)
Y = data6['quality_range']
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=365)
model_linear_regression = LinearRegression()
model_linear_regression.fit(x_train, y_train)
LinearRegression()
print(model_linear_regression.intercept_)
-10.073788720357635
coeff_data = pd.DataFrame(model_linear_regression.coef_, X.columns, columns=['Coefficient'])
coeff_data
Coefficient | |
---|---|
volatile acidity | -0.560376 |
citric acid | -0.234547 |
residual sugar | -0.035414 |
chlorides | -0.557458 |
free sulfur dioxide | 0.004425 |
total sulfur dioxide | -0.003436 |
density | 9.804198 |
pH | -0.254166 |
sulphates | 0.843459 |
alcohol | 0.164131 |
predictions = model_linear_regression.predict(x_test)
plt.scatter(y_test,predictions)
<matplotlib.collections.PathCollection at 0x1ffeabd7a60>
sns.histplot((y_test-predictions),bins=50)
<AxesSubplot:xlabel='quality_range', ylabel='Count'>
print('MAE:', metrics.mean_absolute_error(y_test, predictions))
print('MSE:', metrics.mean_squared_error(y_test, predictions))
print('RMSE:', np.sqrt(metrics.mean_squared_error(y_test, predictions)))
MAE: 0.3896858212406587 MSE: 0.20325428149258176 RMSE: 0.4508373115577079
print('R sq: ',model_linear_regression.score(x_train,y_train))
R sq: 0.3315244678627577
print('Correlation: ', math.sqrt(model_linear_regression.score(x_train,y_train)))
Correlation: 0.5757816147314515
print('R sq: ',model_linear_regression.score(x_test,y_test))
R sq: 0.1836418852481948
Tesnorflow Classification¶
tf.__version__
'2.4.1'
X_train.shape
(1187, 10)
# Define the Tensor flow Neural Network with Inputlayer, 2x Hiddenlayer and one Outputlayer
model = Sequential()
model.add(Dense(units=10,activation='relu'))
model.add(Dense(units=8,activation='relu')) #Hiddenlayer
model.add(Dense(units=5,activation='relu')) #Hiddenlayer
model.add(Dense(units=1,activation='sigmoid')) #Outputlayer 1 Target
# For a binary classification Problem
model.compile(loss='binary_crossentropy', optimizer='adam')
model.fit(x=X_train,
y=y_train,
epochs=7,
validation_data=(X_test, y_test), verbose=1
)
Epoch 1/7 38/38 [==============================] - 1s 6ms/step - loss: 0.6612 - val_loss: 0.6592 Epoch 2/7 38/38 [==============================] - 0s 2ms/step - loss: 0.6298 - val_loss: 0.6465 Epoch 3/7 38/38 [==============================] - 0s 1ms/step - loss: 0.6198 - val_loss: 0.6371 Epoch 4/7 38/38 [==============================] - 0s 1ms/step - loss: 0.5942 - val_loss: 0.6305 Epoch 5/7 38/38 [==============================] - 0s 2ms/step - loss: 0.6049 - val_loss: 0.6230 Epoch 6/7 38/38 [==============================] - 0s 2ms/step - loss: 0.5675 - val_loss: 0.6186 Epoch 7/7 38/38 [==============================] - 0s 1ms/step - loss: 0.5691 - val_loss: 0.6111
<tensorflow.python.keras.callbacks.History at 0x1ffeb282c70>
#model.history.history
model_loss = pd.DataFrame(model.history.history)
# Create a plot to compare the performance on the training dataset to the loss on the validation dataset
model_loss.plot()
<AxesSubplot:>
#Vorhersagen bestimmen
predictions = (model.predict(X_test) > 0.5).astype("int32")
prediction = (model.predict(X_train) > 0.5).astype("int32")
# Klassifikations Report
print("Trainingsdaten:")
print(classification_report(y_train,prediction))
print("Testdaten:")
print(classification_report(y_test,predictions))
Trainingsdaten: precision recall f1-score support 0 0.70 0.72 0.71 534 1 0.76 0.75 0.76 653 accuracy 0.74 1187 macro avg 0.73 0.73 0.73 1187 weighted avg 0.74 0.74 0.74 1187 Testdaten: precision recall f1-score support 0 0.66 0.74 0.70 139 1 0.74 0.66 0.70 158 accuracy 0.70 297 macro avg 0.70 0.70 0.70 297 weighted avg 0.70 0.70 0.70 297
# Confusion-Matrix Testdaten
cm = confusion_matrix(y_test,predictions)
df_cm = pd.DataFrame(cm, index=['worse wine','better wine'], columns=['worse wine', 'better wine'],)
fig = plt.figure(figsize=[10,7])
heatmap = sns.heatmap(df_cm, annot=True, fmt="d")
heatmap.yaxis.set_ticklabels(heatmap.yaxis.get_ticklabels(), rotation=0, ha='right', fontsize=14)
heatmap.xaxis.set_ticklabels(heatmap.xaxis.get_ticklabels(), rotation=45, ha='right', fontsize=14)
plt.ylabel('True label')
plt.xlabel('Predicted label')
Text(0.5, 42.0, 'Predicted label')
# Confusion Matrix
tn, fp, fn, tp = cm.ravel()
recall = tp/(fn+tp)
precision = tp/(tp+fp)
print("True Negatives: " + str(tn))
print("False Positives: " + str(fp))
print("False Negatives: " + str(fn))
print("True Positives: " + str(tp))
print("Recall: " + str(recall))
print("Precision: " + str(precision))
True Negatives: 103 False Positives: 36 False Negatives: 54 True Positives: 104 Recall: 0.6582278481012658 Precision: 0.7428571428571429
Deployment¶
Um dieses trainierte ML-Modell zu nutzen, muss eine API erstellt werden.
Das Ziel ist, dass Sie mit einer API-Anfrage über das Internet auf das ML-Modell zugreifen können. (Oder Ihr Intranet)
Der Code für den Einsatz ist hier zu finden. https://github.com/Dustin-dusTir/ml-services-api
eventuell läuft dort noch eine Live-Version der API und des Frontends.
Wenn wir eine API für ein ML-Modell erstellen, wollen wir nicht jedes Mal ein neues Modell trainieren, wenn wir den API-Server bereitstellen oder neu bereitstellen wollen. Deshalb müssen wir das ML-Modell exportieren und speichern.
Für dieses spezielle Modell verwenden wir die Tensorflow-Funktionalität, um das Tensorflow-Modell zu exportieren. Und wir verwenden das Pickle-Modul, um den Datenskalierer auch auf der Festplatte zu speichern.
# save the ml model
model.save(filepath='wine_NN_model_12epochs')
# load the model again
new_model = tf.keras.models.load_model('wine_NN_model_12epochs')
# make sure, that the model has been saved and loaded correctly
sum((model.predict(X_test) == new_model.predict(X_test)) == False)
# save the data scaler with pickle
pickle.dump(scaler, open('scaler.sav', 'wb'))