1. Business Understanding¶

Viele Online-Versandhändler haben eine hohe Rückgabequote (bis zu 50 %), wobei 97 % aller zurückgesandten Produkte wieder aufgefüllt werden können und verkauft werden können. Um die Waren weiterverkaufen zu können müssen die Waren identifiziert, etikettiert und entsprechend wieder aufgestockt werden. wieder. Geht man davon aus, dass im Jahr 2020 185,5 Millionen Bestellungen (Statista, 2021) mit 6 Stück (Annahme) eingehen würden, dann würde eine Retourenquote von 50% bedeuten, dass Bei einer Rücklaufquote von 50 % müssten 556,5 Millionen Artikel neu identifiziert und kategorisiert werden. Zur Unterstützung dieses Prozesses und zur leichteren Identifizierung der Kleidungsstücke zu erleichtern, soll eine Bilderkennungssoftware entwickelt werden, die die zugehörigen Kategorien der einzelnen Kleidungsstücke auf der Grundlage von Bildern. der einzelnen Kleidungsstücke auf der Grundlage von Bildern.

Übersetzt mit DeepL.com (kostenlose Version)

2. Auslesen der Daten¶

2.1. Bibliotheken importieren¶

In [2]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf

import tensorflow_datasets as tfds
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten, Dense, Conv2D, MaxPooling2D, Dropout, Layer
from tensorflow.keras.utils import to_categorical, plot_model
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
In [3]:
# Should be 2.5.0
tf.__version__
Out[3]:
'2.6.0'

2.2 Auslesen der Daten¶

Die Trainings- und Testdaten sind bereits beschriftet und in zwei Datensätze aufgeteilt

Es ist erforderlich, den Datensatz herunterzuladen und zu extrahieren von:¶

https://reutlingenuniversityde-my.sharepoint.com/:f:/g/personal/elias_waschin-scholvin_student_reutlingen-university_de/EnMJgHGtwG5Egw42bRMioMABm5MIJ3ydPJJI36qmse7VpA?e=bRYeWg

Bitte entpacken Sie in einen /data Ordner

In [4]:
csv_file_train = "https://storage.googleapis.com/ml-service-repository-datastorage/Classification_of_clothing_through_images_fashion-mnist_train.csv"
csv_file_test = "https://storage.googleapis.com/ml-service-repository-datastorage/Classification_of_clothing_through_images_fashion-mnist_test.csv"
df_train = pd.read_csv(csv_file_train) 
df_test = pd.read_csv(csv_file_test)
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-4-bda36a925e0f> in <module>
      1 csv_file_train = "data/fashion-mnist_train.csv"
      2 csv_file_test = "data/fashion-mnist_test.csv"
----> 3 df_train = pd.read_csv(csv_file_train)
      4 df_test = pd.read_csv(csv_file_test)

~\Anaconda3\lib\site-packages\pandas\io\parsers.py in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, dialect, error_bad_lines, warn_bad_lines, delim_whitespace, low_memory, memory_map, float_precision)
    684     )
    685 
--> 686     return _read(filepath_or_buffer, kwds)
    687 
    688 

~\Anaconda3\lib\site-packages\pandas\io\parsers.py in _read(filepath_or_buffer, kwds)
    450 
    451     # Create the parser.
--> 452     parser = TextFileReader(fp_or_buf, **kwds)
    453 
    454     if chunksize or iterator:

~\Anaconda3\lib\site-packages\pandas\io\parsers.py in __init__(self, f, engine, **kwds)
    944             self.options["has_index_names"] = kwds["has_index_names"]
    945 
--> 946         self._make_engine(self.engine)
    947 
    948     def close(self):

~\Anaconda3\lib\site-packages\pandas\io\parsers.py in _make_engine(self, engine)
   1176     def _make_engine(self, engine="c"):
   1177         if engine == "c":
-> 1178             self._engine = CParserWrapper(self.f, **self.options)
   1179         else:
   1180             if engine == "python":

~\Anaconda3\lib\site-packages\pandas\io\parsers.py in __init__(self, src, **kwds)
   2006         kwds["usecols"] = self.usecols
   2007 
-> 2008         self._reader = parsers.TextReader(src, **kwds)
   2009         self.unnamed_cols = self._reader.unnamed_cols
   2010 

pandas\_libs\parsers.pyx in pandas._libs.parsers.TextReader.__cinit__()

pandas\_libs\parsers.pyx in pandas._libs.parsers.TextReader._setup_parser_source()

FileNotFoundError: [Errno 2] No such file or directory: 'data/fashion-mnist_train.csv'

2.3. Daten Analyse¶

In [16]:
df_train.head()
Out[16]:
label pixel1 pixel2 pixel3 pixel4 pixel5 pixel6 pixel7 pixel8 pixel9 ... pixel775 pixel776 pixel777 pixel778 pixel779 pixel780 pixel781 pixel782 pixel783 pixel784
0 2 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
1 9 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 6 0 0 0 0 0 0 0 5 0 ... 0 0 0 30 43 0 0 0 0 0
3 0 0 0 0 1 2 0 0 0 0 ... 3 0 0 0 0 1 0 0 0 0
4 3 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 785 columns

Beschreiben Sie den Datenrahmen, was in diesem Fall nicht wirklich hilfreich ist, aber es zeigt, dass die Daten nicht durch die Auswertung verfälscht wurden:

  • Die Bezeichnung muss zwischen 0 und 9 liegen.
  • Die Pixelwerte müssen zwischen 0 und 255 liegen (nicht negativ)
  • Die Anzahl muss 60000 (train) 10000 (test) betragen
  • Die maximale Anzahl der Pixel muss 784 für alle Zeilen betragen.
In [17]:
df_train.describe()
Out[17]:
label pixel1 pixel2 pixel3 pixel4 pixel5 pixel6 pixel7 pixel8 pixel9 ... pixel775 pixel776 pixel777 pixel778 pixel779 pixel780 pixel781 pixel782 pixel783 pixel784
count 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 ... 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.000000 60000.00000
mean 4.500000 0.000900 0.006150 0.035333 0.101933 0.247967 0.411467 0.805767 2.198283 5.682000 ... 34.625400 23.300683 16.588267 17.869433 22.814817 17.911483 8.520633 2.753300 0.855517 0.07025
std 2.872305 0.094689 0.271011 1.222324 2.452871 4.306912 5.836188 8.215169 14.093378 23.819481 ... 57.545242 48.854427 41.979611 43.966032 51.830477 45.149388 29.614859 17.397652 9.356960 2.12587
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000
25% 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000
50% 4.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000
75% 7.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 58.000000 9.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000
max 9.000000 16.000000 36.000000 226.000000 164.000000 227.000000 230.000000 224.000000 255.000000 254.000000 ... 255.000000 255.000000 255.000000 255.000000 255.000000 255.000000 255.000000 255.000000 255.000000 170.00000

8 rows × 785 columns

In [123]:
df_test.describe()
Out[123]:
label pixel1 pixel2 pixel3 pixel4 pixel5 pixel6 pixel7 pixel8 pixel9 ... pixel775 pixel776 pixel777 pixel778 pixel779 pixel780 pixel781 pixel782 pixel783 pixel784
count 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 ... 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.000000 10000.00000
mean 4.500000 0.000400 0.010300 0.052100 0.077000 0.208600 0.349200 0.826700 2.321200 5.457800 ... 34.320800 23.071900 16.432000 17.870600 22.860000 17.790200 8.353500 2.541600 0.629500 0.06560
std 2.872425 0.024493 0.525187 2.494315 2.208882 4.669183 5.657849 8.591731 15.031508 23.359019 ... 57.888679 49.049749 42.159665 44.140552 51.706601 45.128107 28.765769 16.417363 7.462533 1.93403
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000
25% 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000
50% 4.500000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.00000
75% 7.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 ... 55.000000 6.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 0.00000
max 9.000000 2.000000 45.000000 218.000000 185.000000 227.000000 223.000000 247.000000 218.000000 244.000000 ... 254.000000 252.000000 255.000000 255.000000 255.000000 255.000000 240.000000 225.000000 205.000000 107.00000

8 rows × 785 columns

Sowohl die Test- als auch die Trainingsdaten scheinen gültig und unverfälscht zu sein.

Definieren Sie menschenlesbare Namen für die 10 Kategorien

In [125]:
class_names = ['Top','Trouser','Pullover','Dress','Coat',
               'Sandal','Shirt','Sneaker','Bag','Ankle boot']

Aufteilung des Datensatzes und Überprüfung der Verteilung der einzelnen Klassen (die Aufteilung kann auch später erfolgen)

In [126]:
df_train, df_val = train_test_split(df_train, test_size=0.1, random_state=365)
print(f"{len(df_train)} train examples")
print(f"{len(df_val)} validation examples")
print(f"{len(df_test)} test examples")
48600 train examples
5400 validation examples
10000 test examples

Zeigen Sie die Verteilung für jeden Satz

In [127]:
def get_classes_distribution(data):
    # Get the count for each label
    label_counts = data["label"].value_counts()

    # Get total number of samples
    total_samples = len(data)


    # Count the number of items in each class
    for i in range(len(label_counts)):
        label = class_names[label_counts.index[i]]
        count = label_counts.values[i]
        percent = (count / total_samples) * 100
        print("{:<20s}:   {} or {}%".format(label, count, percent))

print("\nTRAIN DISTRIBUTION\n")
get_classes_distribution(df_train)
print("\nVALIDATION DISTRIBUTION\n")
get_classes_distribution(df_val)
print("\nTEST DISTRIBUTION\n")
get_classes_distribution(df_test)
TRAIN DISTRIBUTION

Sandal              :   4904 or 10.090534979423868%
Sneaker             :   4897 or 10.076131687242798%
Dress               :   4888 or 10.05761316872428%
Pullover            :   4872 or 10.024691358024691%
Coat                :   4862 or 10.004115226337449%
Top                 :   4848 or 9.975308641975309%
Trouser             :   4845 or 9.969135802469136%
Shirt               :   4842 or 9.962962962962962%
Ankle boot          :   4840 or 9.958847736625515%
Bag                 :   4802 or 9.880658436213992%

VALIDATION DISTRIBUTION

Ankle boot          :   564 or 10.444444444444445%
Bag                 :   560 or 10.37037037037037%
Coat                :   559 or 10.351851851851853%
Shirt               :   555 or 10.277777777777777%
Top                 :   548 or 10.148148148148147%
Trouser             :   539 or 9.981481481481481%
Pullover            :   535 or 9.907407407407408%
Sandal              :   525 or 9.722222222222223%
Dress               :   517 or 9.574074074074074%
Sneaker             :   498 or 9.222222222222221%

TEST DISTRIBUTION

Top                 :   1000 or 10.0%
Bag                 :   1000 or 10.0%
Trouser             :   1000 or 10.0%
Ankle boot          :   1000 or 10.0%
Pullover            :   1000 or 10.0%
Dress               :   1000 or 10.0%
Coat                :   1000 or 10.0%
Sandal              :   1000 or 10.0%
Shirt               :   1000 or 10.0%
Sneaker             :   1000 or 10.0%

Das ist bereits hilfreich, denn wir können sehen, dass sie ziemlich gleichmäßig aufgeteilt sind. Drucken Sie die Daten als Tortendiagramm aus, um es noch schöner zu machen.

In [21]:
def func(pct, allvalues):
    absolute = int(pct / 100.*np.sum(allvalues))
    return "{:.1f}%\n({:d})".format(pct, absolute)

def plot_pie(title, data):
    # Creating plot
    fig, ax = plt.subplots(figsize =(10, 7))
    plt.pie(data, autopct = lambda pct: func(pct, data), labels = class_names)
    ax.set_title(title)
  
    # show plot
    plt.show()

plot_pie("Train data distribution", df_train["label"].value_counts())
plot_pie("Validation data distribution", df_val["label"].value_counts())
plot_pie("Test data distribution", df_test["label"].value_counts())
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Print eines einzelnen Bildes

In [22]:
# Make copies of the data to allow easy exploration
df_train_exp_copy = df_train.copy() 
y_train_exp = df_train_exp_copy.pop('label').to_numpy()
x_train_exp = df_train_exp_copy.to_numpy()


# Take a single image, and remove the color dimension by reshaping
image = x_train_exp[0].reshape((28,28)) / 255.0

plt.figure()
plt.imshow(image, cmap=plt.cm.binary)
plt.colorbar()
plt.grid(False)
plt.show()
No description has been provided for this image

Printen eines Bildes aus jeder Kategorie, um zu sehen, wie sie aussehen und wie sie sich unterscheiden

In [24]:
plt.figure(figsize=(10,10))
i = 0
for index in range(len(x_train_exp)):
    label = y_train_exp[index]
    image = x_train_exp[index] / 255.0
    if label == i:
        image = image.reshape((28,28))
        plt.subplot(5,5,i+1)
        plt.xticks([])
        plt.yticks([])
        plt.grid(False)
        plt.imshow(image, cmap=plt.cm.binary)
        plt.title(class_names[label])
        i += 1
    if i == 10:
        break
plt.show()
No description has been provided for this image

3. Data preperation¶

3.1. Daten Testen und Trainieren¶

In [25]:
df_train_copy = df_train.copy() 
y_train = df_train_copy.pop('label').to_numpy()
x_train = df_train_copy.to_numpy()
df_val_copy = df_val.copy() 
y_val = df_val_copy.pop('label').to_numpy()
x_val = df_val_copy.to_numpy()
df_test_copy = df_test.copy() 
y_test = df_test_copy.pop('label').to_numpy()
x_test = df_test_copy.to_numpy()

3.2. Merkmal Skalierung¶

In [26]:
x_train = x_train / 255.0
x_val = x_val / 255.0
x_test = x_test / 255.0

Konvertierung der Bildform von 784 in 28x28 (nur wenn als CSV mit 784 Spalten geladen)

In [27]:
IMG_ROWS = 28
IMG_COLS = 28
IMAGE_SHAPE = (IMG_ROWS, IMG_COLS, 1) 
x_train = x_train.reshape(x_train.shape[0], *IMAGE_SHAPE)
x_val = x_val.reshape(x_val.shape[0], *IMAGE_SHAPE)
x_test = x_test.reshape(x_test.shape[0], *IMAGE_SHAPE)

3.3. Labels Konvertieren¶

In [28]:
y_train = to_categorical(y_train, 10)
y_val = to_categorical(y_val, 10)
y_test = to_categorical(y_test, 10)

Überprüfen der Datenformen, um sicherzustellen, dass die Daten im richtigen Format vorliegen.

In [128]:
print(x_train.shape)
print(y_train.shape)

print(x_val.shape)
print(y_val.shape)

print(x_test.shape)
print(y_test.shape)
(54000, 28, 28, 1)
(54000, 10)
(6000, 28, 28, 1)
(6000, 10)
(10000, 28, 28, 1)
(10000, 10)

4. Modellierung und Evaluation¶

Definieren Sie, wie das Modell aussehen soll. Nachfolgend finden Sie einige Beschreibungen für verschiedene Schichttypen. Der erste Versuch dieses Modells war nur ein DNN mit einfachen dichten Schichten, aber die Ergebnisse können durch die Verwendung eines CNN verbessert werden. Als Startarchitektur wurde die LeNet-5-Implementierung gewählt und dann verändert. Die Hyperparameter konnten auch mit dem Keras Optimizer optimiert werden, der mehrere definierte Kombinationen ausprobiert. Die aktuellen Parameter wurden durch Exploration ausgewählt.

  • Dense: empfängt alle Eingaben aus der vorherigen Schicht und bildet das Punktprodukt
  • Dropout-Schicht: entfernt Rauschen für Overfitting, fällt mit bestimmter Rate ab
  • Reshape-Schicht: verändert die Form der Eingabe, wird nicht verwendet
  • Permute-Schicht: verändert die Form der Eingabe, wird nicht verwendet
  • ReapeatVector-Schicht: wiederholt die Eingabe für eine bestimmte Anzahl von Malen, nicht verwendet
  • Flatten Layer: flacht die Matrix ab
  • MaxPooling2D-Ebene: reduziert die Anzahl der Eingaben
  • Conv2D-Ebene: faltet eine Eingabe zusammen
In [196]:
model = Sequential()
model.add(Conv2D(filters=32,kernel_size=3, activation='relu', padding='same', input_shape=(28, 28,1)))
model.add(Conv2D(filters=32,kernel_size=3,padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dropout(0.40))
model.add(Dense(units=128, activation='relu'))
model.add(Dense(units=10, activation='softmax'))
model.summary()
Model: "sequential_5"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_10 (Conv2D)           (None, 28, 28, 32)        320       
_________________________________________________________________
conv2d_11 (Conv2D)           (None, 28, 28, 32)        9248      
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 14, 14, 32)        0         
_________________________________________________________________
flatten_5 (Flatten)          (None, 6272)              0         
_________________________________________________________________
dropout_5 (Dropout)          (None, 6272)              0         
_________________________________________________________________
dense_10 (Dense)             (None, 128)               802944    
_________________________________________________________________
dense_11 (Dense)             (None, 10)                1290      
=================================================================
Total params: 813,802
Trainable params: 813,802
Non-trainable params: 0
_________________________________________________________________

Kompiliere das Model

In [197]:
model.compile(
    optimizer=tf.keras.optimizers.Adam(), 
    loss= tf.keras.losses.categorical_crossentropy, 
    metrics=['accuracy']
)

Trainiere das Model

In [198]:
# Determine the maximum number of epochs
NUM_EPOCHS = 10
BATCH_SIZE = 64

# Fit the model, 
# specify the training data
# the total number of epochs
# and the validation data we just created 
history = model.fit(
    x_train,
    y_train,
    batch_size=BATCH_SIZE,
    epochs=NUM_EPOCHS,
    validation_data=(x_val, y_val), 
    validation_steps=10,
    verbose =2
)
Epoch 1/10
844/844 - 45s - loss: 0.4327 - accuracy: 0.8459 - val_loss: 0.2944 - val_accuracy: 0.9109
Epoch 2/10
844/844 - 45s - loss: 0.2855 - accuracy: 0.8970 - val_loss: 0.2446 - val_accuracy: 0.9094
Epoch 3/10
844/844 - 45s - loss: 0.2387 - accuracy: 0.9125 - val_loss: 0.2268 - val_accuracy: 0.9125
Epoch 4/10
844/844 - 45s - loss: 0.2071 - accuracy: 0.9248 - val_loss: 0.2362 - val_accuracy: 0.9062
Epoch 5/10
844/844 - 46s - loss: 0.1823 - accuracy: 0.9328 - val_loss: 0.2032 - val_accuracy: 0.9328
Epoch 6/10
844/844 - 45s - loss: 0.1606 - accuracy: 0.9400 - val_loss: 0.2207 - val_accuracy: 0.9250
Epoch 7/10
844/844 - 46s - loss: 0.1426 - accuracy: 0.9470 - val_loss: 0.2414 - val_accuracy: 0.9203
Epoch 8/10
844/844 - 45s - loss: 0.1240 - accuracy: 0.9540 - val_loss: 0.2205 - val_accuracy: 0.9312
Epoch 9/10
844/844 - 48s - loss: 0.1099 - accuracy: 0.9589 - val_loss: 0.2306 - val_accuracy: 0.9344
Epoch 10/10
844/844 - 47s - loss: 0.0979 - accuracy: 0.9633 - val_loss: 0.2215 - val_accuracy: 0.9344

Wir wollen wissen, wie sich insbesondere der Verlust im Laufe der Zeit verändert

In [225]:
acc = history.history["accuracy"]
val_acc = history.history["val_accuracy"]
loss = history.history["loss"]
val_loss = history.history["val_loss"]

epochs = range(len(acc))

plt.plot(epochs, acc, "darkgreen", label="Training accuracy")
plt.plot(epochs, val_acc, "darkblue", label="Validation accuracy")
plt.plot(epochs, loss, "lightgreen", label="Training loss")
plt.plot(epochs, val_loss, "lightblue", label="Validation loss")
plt.title("Training and validation accuracy")
plt.xlabel("Epochs")
plt.ylabel("Precent/100")
plt.legend(loc=0)
plt.figure()


plt.show()
No description has been provided for this image
<Figure size 432x288 with 0 Axes>

Es ist zu erkennen, dass das Modell recht gut funktioniert, aber nach der zweiten epoche beginnt es zu overfitten. Um dies zu verhindern, könnten wir verschiedene Trainings-Validierungs-Splits ausprobieren, mehr Dropouts hinzufügen oder Teile des Modells umstrukturieren.

Evaluierung der Testdaten

In [200]:
test_loss, test_accuracy = model.evaluate(x_test, y_test, verbose=0)
In [202]:
print('Test loss: {0:.2f}. Test accuracy: {1:.2f}%'.format(test_loss, test_accuracy*100.))
Test loss: 0.21. Test accuracy: 93.24%

Das Modell schneidet mit einer Genauigkeit von > 90 % bei den Testdaten recht gut ab. Der Verlust ist akzeptabel.

Ergebnisse nach Klasse anzeigen

In [205]:
predicted_classes = (model.predict(x_test) > 0.5).astype("int32")
In [206]:
print(classification_report(y_test, predicted_classes, target_names=class_names))
              precision    recall  f1-score   support

         Top       0.90      0.88      0.89      1000
     Trouser       0.99      0.99      0.99      1000
    Pullover       0.91      0.88      0.89      1000
       Dress       0.93      0.96      0.94      1000
        Coat       0.89      0.92      0.90      1000
      Sandal       0.99      0.98      0.98      1000
       Shirt       0.84      0.77      0.80      1000
     Sneaker       0.97      0.97      0.97      1000
         Bag       0.99      0.98      0.99      1000
  Ankle boot       0.97      0.97      0.97      1000

   micro avg       0.94      0.93      0.93     10000
   macro avg       0.94      0.93      0.93     10000
weighted avg       0.94      0.93      0.93     10000
 samples avg       0.93      0.93      0.93     10000

/usr/local/anaconda3/envs/tensorflow_37/lib/python3.7/site-packages/sklearn/metrics/_classification.py:1245: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in samples with no predicted labels. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))

Wir können beobachten, dass das Modell vor allem mit Oberteilen, Hemden und Pullovern zu kämpfen hat. Das liegt wahrscheinlich daran, dass sie ziemlich ähnlich aussehen. Um dies zu vermeiden, könnte es hilfreich sein, das Bild zu vergrößern, um mehr Details zu erhalten, mehr Farbkanäle hinzuzufügen oder zu versuchen, die Textur des Artikels mit einigen bestimmten Ebenen zu identifizieren.

5. Deployment¶

Stellen Sie das Modell als Docker-Container bereit, um es als http-Dienst verfügbar zu machen

In [187]:
model.save('fashion_model/1', save_format='tf')
INFO:tensorflow:Assets written to: fashion_model/1/assets

Aufzeigen der Daten

In [189]:
!ls -ll fashion_model/1
total 320

drwxr-xr-x  2 elwa  staff      64 Jul 11 17:23 assets

-rw-r--r--  1 elwa  staff   14391 Jul 11 17:23 keras_metadata.pb

-rw-r--r--  1 elwa  staff  146943 Jul 11 17:23 saved_model.pb

drwxr-xr-x  4 elwa  staff     128 Jul 11 17:23 variables

Zippen des Models

In [190]:
!zip -r fashion_model_v1.zip fashion_model
  adding: fashion_model/ (stored 0%)
  adding: fashion_model/1/ (stored 0%)
  adding: fashion_model/1/keras_metadata.pb (deflated 90%)
  adding: fashion_model/1/variables/ (stored 0%)
  adding: fashion_model/1/variables/variables.data-00000-of-00001 (deflated 6%)
  adding: fashion_model/1/variables/variables.index (deflated 64%)
  adding: fashion_model/1/saved_model.pb (deflated 88%)
  adding: fashion_model/1/assets/ (stored 0%)

Starten Sie mit dem Docker-Container tensorflow/serving, wenn Docker installiert ist

docker pull tensorflow/serving
docker run -t --rm -p 8501:8501 \
    -v "<pfad_zum_modell>/fashion_model:/models/fashion_model" \
    -e MODEL_NAME=fashion_model \
    tensorflow/serving

Das Modell sollte nicht für Post-Anfragen unter http://localhost:8501/v1/models/fashion_model:predict verfügbar sein.

Erweiterung 1: Verwendung von RGB-Bildern¶

Importieren der allgemeinen RGB-Bilder, die aus dem Internet heruntergeladen wurden, und versuchen, deren Kategorie mit dem Modell vorherzusagen. In diesem Fall wurden einige zufällige Bilder von Google heruntergeladen und in eine quadratische Form gebracht. Für jede Kategorie befinden sich drei Bilder im "Custom Set".

Erweiterung 1: Verwendung von RGB-Bildern¶

Importieren Sie allgemeine RGB-Bilder, die Sie aus dem Internet heruntergeladen haben, und versuchen Sie, deren Kategorie mit dem Modell vorherzusagen. In diesem Fall wurden einige zufällige Bilder von Google heruntergeladen und in eine quadratische Form gebracht. Für jede Kategorie befinden sich drei Bilder im "Custom Set".

In [207]:
import matplotlib.image as mpimg
from mpl_toolkits.axes_grid1 import ImageGrid
from PIL import Image
import os, os.path

Alle Bilder nach Dateinamen laden (nach Namen sortiert)

In [208]:
basewidth = 72
path = "data/colored"
images= {}

valid_images = [".jpg",".jpeg", ".gif",".png",".tga"]
for fullname in sorted(os.listdir(path)):
    ext = os.path.splitext(fullname)[1]
    if ext.lower() not in valid_images:
        continue
    img = Image.open(os.path.join(path,fullname))
    images[fullname] = img 

Printen aller Beispiel Bilder

In [209]:
fig = plt.figure(figsize=(20, 20))
grid = ImageGrid(fig, 111,
                 nrows_ncols=(3, 10),
                 axes_pad=0.1,
                 )

for ax, (name, img) in zip(grid, images.items()):
    basewidth = 72
    display_image = img.resize((basewidth,basewidth), Image.ANTIALIAS)
    ax.imshow(display_image)

plt.show()
No description has been provided for this image

Dies ist eine wirklich wichtige Funktion, da sie die eigentliche Vorverarbeitung des Bildes in ein Format vornimmt, das für das Modell "verständlich" ist. Es wäre wahrscheinlich sinnvoll, diese Funktion aufzuteilen. Der kommentierte Code unten zeigt, wie diese Funktion als benutzerdefinierte Ebene implementiert werden könnte, um sie in ein Modell einzubinden. Wir subclassen dafür einen Layer, aber wahrscheinlich könnten wir alternativ auch einfach einen "Lambda"-Layer verwenden (oder ihn subclassen)

In [212]:
def convert_image(image, output_size):
    img = image.convert('L') # Convert to grayscale
    img = img.resize(output_size, Image.ANTIALIAS) # Reduce the size to 28x28 no matter how big the image
    img = np.asarray(img) # Convert to array
    img = 1- img # Invert colors
    img = img.reshape((28,28, 1))/ 255.0 # Reshape and scale colors to 0-1
    return img
In [213]:
# Note: it would also be possible to create a custom layer for preprocessing, this is currently not used though

# basewidth = 28

# class ConvertRGBImageLayer(Layer):
#     def __init__(self, output_size):
#         super(ConvertRGBImageLayer, self).__init__()
#         self.output_size = output_size

#     def call(self, inputs):
#         return lambda x: convert_image(x, self.output_size)

# preprocessing_layer = ConvertRGBImageLayer((28, 28))

Anzeigen der konvertierten Bilder

In [214]:
fig = plt.figure(figsize=(20, 20))
grid = ImageGrid(fig, 111,
                 nrows_ncols=(3, 10),
                 axes_pad=0.1,
                 )

for ax, (name, img) in zip(grid, images.items()):
    basewidth = 28
    display_image = convert_image(img, (basewidth, basewidth))
    ax.imshow(display_image, cmap=plt.cm.binary)

plt.show()
No description has been provided for this image
In [215]:
scaled_images = []

Jedes Bild vorhersagen, das erwartete Ergebnis durch eine Beschriftung darstellen und das Bild anzeigen

In [216]:
basewidth = 28
for name, img in images.items():
    test = convert_image(img, (basewidth, basewidth))
    plt.imshow(test, cmap=plt.cm.binary)
    plt.colorbar()
    plt.show()
    # We predict every single image one after another (there might be more efficient methods) 
    test = test.reshape(1, 28, 28, 1)
    predictions = (model.predict(test) > 0.5).astype("int32")
    for prediction in predictions:
        result, = np.where(prediction == np.amax(prediction))
        print(f"Predicted {class_names[result[0]]}, Expected: {name.split('.')[0]}")
No description has been provided for this image
Predicted Bag, Expected: ankle_boot_0
No description has been provided for this image
Predicted Ankle boot, Expected: ankle_boot_1
No description has been provided for this image
Predicted Bag, Expected: ankle_boot_2
No description has been provided for this image
Predicted Bag, Expected: bag_0
No description has been provided for this image
Predicted Bag, Expected: bag_1
No description has been provided for this image
Predicted Bag, Expected: bag_2
No description has been provided for this image
Predicted Top, Expected: coat_0
No description has been provided for this image
Predicted Coat, Expected: coat_1
No description has been provided for this image
Predicted Coat, Expected: coat_2
No description has been provided for this image
Predicted Dress, Expected: dress_0
No description has been provided for this image
Predicted Dress, Expected: dress_1
No description has been provided for this image
Predicted Dress, Expected: dress_2
No description has been provided for this image
Predicted Coat, Expected: pullover_0
No description has been provided for this image
Predicted Pullover, Expected: pullover_1
No description has been provided for this image
Predicted Coat, Expected: pullover_2
No description has been provided for this image
Predicted Bag, Expected: sandal_0
No description has been provided for this image
Predicted Sandal, Expected: sandal_1
No description has been provided for this image
Predicted Sandal, Expected: sandal_2
No description has been provided for this image
Predicted Coat, Expected: shirt_0
No description has been provided for this image
Predicted Shirt, Expected: shirt_1
No description has been provided for this image
Predicted Coat, Expected: shirt_2
No description has been provided for this image
Predicted Sneaker, Expected: sneaker_0
No description has been provided for this image
Predicted Sneaker, Expected: sneaker_1
No description has been provided for this image
Predicted Bag, Expected: sneaker_2
No description has been provided for this image
Predicted Top, Expected: top_0
No description has been provided for this image
Predicted Top, Expected: top_1
No description has been provided for this image
Predicted Top, Expected: top_2
No description has been provided for this image
Predicted Trouser, Expected: trouser_0
No description has been provided for this image
Predicted Trouser, Expected: trouser_1
No description has been provided for this image
Predicted Trouser, Expected: trouser_2

Erstellen von Numpy-Arrays aus den geladenen Daten (dies könnte zur Vereinfachung auch früher geschehen)

In [217]:
# Note: Could be transformed also earlier
colored_y = []
colored_x = []
for name, image in images.items():
    for index in range(len(class_names)):
        if class_names[index].lower().replace(" ", "_") in name.lower():
            colored_y.append(index)
            colored_x.append(convert_image(image, (28, 28)))
            continue
        continue
In [218]:
# Transform again
colored_y = np.array(to_categorical(colored_y, 10)).reshape(30, 10)
colored_x = np.array(colored_x).reshape(30, 28, 28, 1)

Bewerten Sie unseren benutzerdefinierten Datensatz

In [219]:
test_loss, test_accuracy = model.evaluate(colored_x, colored_y, verbose=0)
In [220]:
# Apply some nice formatting
print('Test loss: {0:.2f}. Test accuracy: {1:.2f}%'.format(test_loss, test_accuracy*100.))
Test loss: 1.99. Test accuracy: 73.33%
In [221]:
predicted_classes = (model.predict(colored_x) > 0.5).astype("int32")
In [222]:
print(classification_report(colored_y, predicted_classes, target_names=class_names))
              precision    recall  f1-score   support

         Top       1.00      1.00      1.00         3
     Trouser       1.00      1.00      1.00         3
    Pullover       1.00      0.33      0.50         3
       Dress       1.00      1.00      1.00         3
        Coat       0.33      0.67      0.44         3
      Sandal       1.00      0.67      0.80         3
       Shirt       1.00      0.33      0.50         3
     Sneaker       1.00      0.67      0.80         3
         Bag       0.43      1.00      0.60         3
  Ankle boot       1.00      0.33      0.50         3

   micro avg       0.72      0.70      0.71        30
   macro avg       0.88      0.70      0.71        30
weighted avg       0.88      0.70      0.71        30
 samples avg       0.70      0.70      0.70        30

/usr/local/anaconda3/envs/tensorflow_37/lib/python3.7/site-packages/sklearn/metrics/_classification.py:1245: UndefinedMetricWarning: Precision and F-score are ill-defined and being set to 0.0 in samples with no predicted labels. Use `zero_division` parameter to control this behavior.
  _warn_prf(average, modifier, msg_start, len(result))

Das Modell schneidet mit dem benutzerdefinierten Datensatz wesentlich schlechter ab als mit den eigentlichen Testdaten. Dies hat mehrere Gründe und zeigt, wie schwierig es ist, mit "echten" Daten zu arbeiten. Obwohl die ausgewählten Bilder bereits in gewisser Weise vorverarbeitet wurden, indem nur Bilder ausgewählt wurden, bei denen der Artikel mit einem weißen Hintergrund dargestellt wird, das Bild quadratisch ist und das Produkt "vorne" gezeigt wird, gibt es immer noch eine Menge Dinge, die es dem Modell schwer machen, die richtige Kategorie vorherzusagen. Einige Probleme könnten sein:

  • Das Bild sollte gespiegelt sein (z. B. linker Schuh/rechter Schuh im Bild)
  • Der Hintergrund sollte komplett weiß sein (einige Bilder haben einen Schatten)
  • Der Rahmen um den Artikel ist nicht bei allen Bildern gleich groß
  • Drehungen können das Modell verwirren (z. B. ankle_boot_2)

Es ist klar, dass diese Daten in keiner Weise repräsentativ sind.

Erweiterung 2¶

Versuchen Sie, die Ebenenausgabe bestimmter Ebenen zu visualisieren.

In [119]:
from tensorflow.keras import models

Abrufen eines Beispielbildes aus den Trainingsdaten

In [182]:
test_im = x_train[789]
plt.imshow(test_im.reshape(28,28), cmap=plt.cm.binary, interpolation='none')
plt.show()
No description has been provided for this image

Wir iterieren über die Ausgabe der Schichten und zeigen die Ergebnisse für dieses spezifische Bild nach der ersten und der zweiten Faltungsschicht an

In [183]:
layer_outputs = [layer.output for layer in model.layers[:8]]
activation_model = models.Model(model.input, layer_outputs)
activations = activation_model.predict(test_im.reshape(1,28,28,1))
first_layer_activation = activations[0]
plt.matshow(first_layer_activation[0, :, :, 4], cmap=plt.cm.binary)
second_layer_activation = activations[1]
plt.matshow(second_layer_activation[0, :, :, 4], cmap=plt.cm.binary)
Out[183]:
<matplotlib.image.AxesImage at 0x7fcbe95c8690>
No description has been provided for this image
No description has been provided for this image

Jetzt wollen wir jeden Filter für die Faltungsschichten und die Max-Pool-Schicht für das jeweilige Bild ausgeben

In [184]:
layer_names = []
for layer in model.layers[:-1]:
    layer_names.append(layer.name) 
images_per_row = 16
for layer_name, layer_activation in zip(layer_names, activations):
    if layer_name.startswith('conv') or layer_name.startswith('max'):
        n_features = layer_activation.shape[-1]
        size = layer_activation.shape[1]
        n_cols = n_features // images_per_row
        display_grid = np.zeros((size * n_cols, images_per_row * size))
        for col in range(n_cols):
            for row in range(images_per_row):
                channel_image = layer_activation[0,:, :, col * images_per_row + row]
                channel_image -= channel_image.mean()
                channel_image /= channel_image.std()
                channel_image *= 64
                channel_image += 128
                channel_image = np.clip(channel_image, 0, 255).astype('uint8')
                display_grid[col * size : (col + 1) * size,
                             row * size : (row + 1) * size] = channel_image
        scale = 1. / size
        plt.figure(figsize=(scale * display_grid.shape[1],
                            scale * display_grid.shape[0]))
        plt.title(layer_name)
        plt.grid(False)
        plt.imshow(display_grid, aspect='auto', cmap=plt.cm.binary)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image