Heute möchte ich euch zeigen, wie man mit einem Convolutional Neural Network (CNN) anhand von Fotos (mit wirklich schlechter Qualität) Unterarten der Honigbiene mit beeindruckender Genauigkeit vorhersagen kann. Im Anschluss basteln wir um den Algorithmus eine kleine App und packen diese auf eine Sharing-Plattform, damit sie für jede und jeden im Web verfügbar ist.
Was brauchen wir nun dafür?
Die Datenbasis bildete das BeeImage Dataset von Kaggle. Zur Erstellung des CNNs, habe ich die Python-Deep-Learning-Bibliothek Keras eingesetzt. Für die App nutzte ich Streamlit, ein Python-Framework für Machine Learning-Apps. Zu guter Letzt wurde die App dann in ein GitHub-Repository gepackt und Streamlit Sharing der Zugriff auf dieses gewährt. Fertig 🙂
Und so sieht das Ganze dann aus:
Wer das Ganze in Aktion ausprobieren möchte, der klickt einfach hier.
Bevor wir ans Coden gehen aber noch eine grundlegende Frage: was sind denn nun eigentlich CNNs?!?
CNNs
CNN steht für Convolutional Neural Network, also eine spezielle Art künstlicher neuronaler Netze (ANNs; „a“ für artificial). ANNs bilden die Grundlage des sogenannten Deep Learnings und basier(t)en auf der Grundidee, die Architektur des Gehirns nachzuempfinden. Solche ANNs (vereinfachte Darstellung siehe unten) werden heute in unterschiedlichen Formen und Topologien für eine Vielzahl komplexer Aufgabenstellungen genutzt.
Bei der Verarbeitung von Bild- und Audiodateien (z.B. Gesichtserkennung oder maschinelle Übersetzung) kommen nun vor allem CNNs zum Einsatz. Warum ist das so?
CNNs ahmen in ihrer Funktionsweise den visuellen Kortex des Gehirns nach. Die Neuronen auf einer höheren Schicht erhalten ihre Informationen von Neuronen in niedrigeren Schichten, wobei nicht jedes Neuron mit allen Neuronen der nächsten Schichten verbunden ist (d.h. partielle Verknüpfung –> dadurch weniger Parameter/Rechenleistung nötig). Sprich: eine Schicht könnte zum Beispiel lernen gerade Linien von gekrümmten zu unterscheiden und die nächsthöhere könnte dann lernen aus diesen Formen Rechtecke oder Kreise zusammenzusetzen. In weiteren Schichten könnten dann noch komplexere Muster abstrahiert werden (z.B. Unterscheidung Hund vs. Katze).
Um diese Funktionsweise zu erreichen, nutzen CNNs spezielle Schichten, Convolutions- und Pooling-Schichten. Deren genaue Funktionsweise zu erklären, würde hier den Rahmen sprengen. Ich kann aber an dieser Stelle die sehr gute Einführung ins Thema und die Umsetzung in Python durch Aurélien Géron (Hands-on Machine Learning with Scikit-Learn, Keras, and TensorFlow; O’Reilly) empfehlen.
Lasst das Coden beginnen
Auf die Vorbereitung der Daten möchte ich hier nicht im Detail eingehen, da das BeeImage Dataset in zahlreichen Tutorials bereits abgehandelt wurde. Ich habe die Daten weitestgehend deckungsgleich mit Gabriel Preda’s Notebook auf Kaggle vorbereitet (von dort stammen auch die verwendeten helper functions im Code unten). Der einzige signifikante Unterschied ist der, dass ich die „Unterarten“ -1 (= Unterart unbekannt) und Mixed local stock aus dem Datensatz entfernt habe. Für mich macht es einfach keinen Sinn, einen Algorithmus auf solche Labels (die eigentlich keine sind) zu trainieren und dadurch Vorhersagekraft zu verlieren.
Im Anschluss habe ich mit einigen CNN-Architekturen herumgespielt und bin final bei einem Modell mit drei Convolutional Layers mit relativ hohem Dropout gelandet, das eine Akkuratheit bei der Vorhersage des Test-Sets (20% der Originaldaten, die nicht zum Training des Modells genutzt wurden) von über 99% erzielte.
Dieses Modell sah in Keras-Notation dann so aus:
model=Sequential()
model.add(Conv2D(16, kernel_size=3, input_shape=(100, 100,3), activation='relu', padding='same'))
model.add(MaxPool2D(2))
model.add(Dropout(0.5))
model.add(Conv2D(16, kernel_size=3, activation='relu', padding='same'))
model.add(Dropout(0.5))
model.add(Conv2D(16, kernel_size=3, activation='relu', padding='same'))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(y_train.columns.size, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
train_model = model.fit_generator(image_generator.flow(X_train, y_train, batch_size=32),
epochs=70,
validation_data=[X_val, y_val],
steps_per_epoch=len(X_train)/32,
callbacks=[earlystopper, checkpointer, annealer])
Das fertig trainierte Modell kann dann einfach abgespeichert werden, um in der App Vorhersagen machen zu können. Dabei habe ich für mich feststellen müssen, dass es Sinn macht, die Modellkonfiguration im .json-Format abzuspeichern und die Modell-Gewichte in einer zusätzlichen .h5-Datei. Alle anderen Formate und Konstellationen führten zu Problemen, wenn das Modell wieder geladen wurde.
model_config = model.to_json()
with open("model_config.json", "w") as f:
f(model_config)
model.save_weights("model.h5")
Und noch eine weitere lesson learned: die aktuelle Version der h5py-Bibliothek funktioniert nicht korrekt beim Laden von Keras-Modellen. Daher unbedingt auf eine funktionale Version zurücksetzen.
Und jetzt basteln wir unsere App. Die Syntax von Streamlit ist wirklich sehr einfach zu erlernen und die Dokumentation der Bibliothek lässt eigentlich nichts zu wünschen übrig. Der einzige Punkt, der ein bisschen tricky war, war das Speichern von Session-Zuständen. Streamlit-Apps laden bei jeder Eingabe neu. Das ist aber ein Problem, wenn sich mehrere Eingaben nacheinander bedingen bzw. wir eine vorherige Eingabe beibehalten wollen. In unserem Fall wollte ich, dass der Nutzer zunächst über einen Button ein zufällig ausgewähltes Bienen-Bild lädt. Dann darf der Nutzer raten und eine der zur Auswahl stehenden Unterarten anklicken. Standardmäßig wäre aber durch den zweiten Klick das Ergebnis des ersten wieder verloren gewesen. Abhilfe schuf hier das Modul SessionState.py, dass sich hier herunterladen lässt und Zwischenstände speichert. Problem gelöst 🙂
Unten seht ihr den finalen Code für die App. Nicht erschrecken, das meiste ist Text, Bilder laden und ein bisschen stylen. Der einzig etwas komplexere Code kommt dann, wenn Buttons im Spiel sind, deren Zustand über SessionState zwischengespeichert wird und die Vorhersage mit dem CNN. That’s it.
Wenn wir diese App-Datei nun mit allen anderen notwendigen Dateien (SessionState.py, die beiden Modelldateien von oben, eine .csv mit den Bild-Labels, eine requirements.txt (das ist einfach eine Auflistung aller für die App benötigten Python-Bibliotheken) sowie einem Ordner mit den Test-Bildern und einem mit allen anderen benötigten Bildern) in einen Ordner packen, können wir die App theoretisch lokal nutzen.
Das war ja nun aber nicht Ziel dieser Übung. Die App soll ja raus ins World Wide Web. Das macht uns die neue Plattform Streamlit Sharing aber ganz einfach. Wir schieben einfach all die eben genannten Dateien/Ordner in ein GitHub-Repository. Solltet ihr noch nie etwas von Git oder GitHub gehört haben, kann ich euch nur wärmstes den Git-Komplettkurs von Jannis Seemann auf Udemy empfehlen. Das Repo zur App findet ihr hier. In der readme-Datei des Repos wird euch auch erklärt, wie ihr die App lokal auf eurem Rechner zum Laufen bekommt.
Nun musste ich nur noch einen Account bei Streamlit Sharing beantragen, wo ihr momentan kostenlos bis zu drei Streamlit-Apps hosten könnt, und das GitHub-Repo mit meinem Account verknüpfen. Geschafft 🙂
Hier nochmal der Link zur auf Streamlit Sharing gehosteten App. Hier könnt ihr euch nun gerne mal daran versuchen, das trainierte CNN im Erkennen von Unterarten der Honigbiene zu schlagen. Ich wüsste, auf wen ich wette 😉
Falls ihr Fragen, Anmerkungen, Ideen zu diesem Post habt, schreibt mir gerne eine Mail.