Machine Learning mit Tensorflow

25.05.2022 Ricky Elfner
Tech machine-learning deep-learning

Machine Learning mit Tensorflow

In meinem letzten TechUp ging es um die Grundlagen rund um das Thema Deep Learning. Dabei haben wir zentrale Konzepte und Begriffe kennengelernt. Heute gehen wir einen Schritt weiter und befassen uns mit dem Thema TensorFlow. Bei TensorFlow handelt es sich um eine "End-to-end open source machine learning platform". Diese wurde ursprünglich von Google entwickelt, doch mittlerweile ist es unter einer Open-Source Lizenz verfügbar. Der Fokus liegt vor allem auf Spracherkennung und Bildverarbeitung. Dies ist möglich über lernende neuronale Netze.

Für das Verwenden von TensorFlow sind auf jeden Fall Grundlagen in den Bereichen Python, Machine Learning Konzepte und die Basics von Matrixrechnung zu empfehlen.

Setup 💻

Für das grundlegende Setup ist eine Installation von Python notwendig. Ob du die richtige Version nutzt, kannst du mit dem Kommando python3 --version prüfen. Dabei wird eine Version zwischen 3.7 und 3.10. empfohlen. Ebenfalls benötigst du den Python Packagemanager (pip) in der Version >19.0 ( oder >20.3 für macOS). Zusätzlich benötigst du am besten eine IDE, hier ist PyCharm von JetBrains zu empfehlen. Das kannst du hier runterladen.

Anschliessend kannst du ein neues Projekt über PyCharm erstellen. Hier ist einfach zu beachten, dass du den gewünschten Namen angibst und die korrekte Python Version auswählst.

tensorFlow_01

tensorFlow_02

Sobald das Projekt aufgesetzt wurde, benötigst du ein File mit dem Namen requirements.txt, in dem du bestimmst, welche Versionen von den angegebenen Packages verwendet werden sollen. Innerhalb des Installations Guide von Tensorflow wird dies wie folgt beschrieben:

tensorflow==2.7.0
tensorflow-datasets==4.4.0
Pillow==8.4.0
pandas==1.3.4
numpy==1.21.4
scipy==1.7.3

Die IDE zeigt dir anschliessend eine Meldung, damit du die Requirements installieren kannst. Wenn du diesen Weg nutzt, bekommst du diese Anzeige:

tensorFlow_03

Sobald die Installation abgeschlossen ist, kannst du die Python Console öffnen.

Leider ist es in unserem Fall zu Fehlern mit dem M1 Prozessor gekommen. Aus diesem Grund haben wir eine andere Variante gewählt, um TensorFlow lokal zu nutzen. Hierfür war zunächst einmal eine Installation von Miniforge notwendig. Denn mit diesem ist es möglich, Python Packages zu installieren, welche Nativ für den Apple Silicon Chip kompiliert wurden.

brew install miniforge

Nach der Installation kannst du das standard Environment deaktivieren.

conda config --set auto_activate_base false

Für unser Beispiel haben wir eine virtuelle Umgebung mit der Python Version 3.8 erstellt. Wenn wenn diese erstellt wurde, muss sie noch aktiviert werden.

conda create --name mlp python=3.8
conda activate mlp

Nun kannst du damit beginnen, alle notwendigen Dependencies zu installieren. Dazu gehören als erstes alle TensorFlow Dependencies, bevor du weitere Requirements über pip für TensorFlow installierst.

conda install -c apple tensorflow-deps
pip install tensorflow-macos
conda install -c conda-forge -y pandas jupyter
pip install tensorflow_datasets
pip install Pillow
pip install numpy
python -m pip install -U matplotlib

Anschliessend solltest du auf dem gleichen Stand sein, wie über das Installieren innerhalb der IDE. Vorausgesetzt es gab keine Fehler. In unserem Fall haben wir anschliessend mittels jupyter notebook die Konsole innerhalb von Jupyter genutzt.

Nun müssen auf jeden Fall die folgenden Schritte ausführen:

import tensorflow as tf

Hier kann es passieren, dass du einige Warnungen angezeigt bekommst, wenn du ein GPU Setup auf deiner Maschine hast. Dies ist jedoch für unseren Fall nicht relevant.

Um zu prüfen ob es sich um die Korrekte Version handelt, kannst du die Version in der Konsole ausgeben: print(tf.__version__).

Dies sind alle notwendigen Imports:

import tensorflow as tf
import tensorflow_datasets as tfds
from PIL import Image
import numpy as np
import urllib3 
import pandas as pd
import matplotlib.pyplot as plt

print(tf.__version__)
print(tfds.__version__)
print(Image.__version__)
print(np.__version__)
print(pd.__version__)

Hands-on 🙏

Beispiel MNIST-Datenbank

Nun wollen wir als erstes Beispiel die MNIST-Datenbank verwenden, welche im Vergleich zu anderen Programmiersprachen als Hello-World-Programm verwendet wird. Dabei soll das Ziel sein, ein Machine Learning Model zu nutzen, welches handgeschriebene Ziffern erkennt.

Dafür legen wir zunächst ein neues Python File an und importieren alle notwendigen Libraries. Die restlichen Requirements wurden bereits über das Requirements File geladen. Mit dem Import von os ist es möglich, Environment-Variablen zu setzen, wie man anhand des Loglevels sehen kann.

Als dritter Schritt wird ein Main-Block erstellt, in den wir die Trainingsdaten aus der MNIST-Datenbank inklusive Infos laden. Die Trainingsdaten werden dabei als mnist_train definiert und die geladenen Informationen in der Variable info gespeichert. Zusätzlich müssen noch die Trainingsdaten geladen werden.

import tensorflow as tf
import tensorflow_datasets as tfds
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

if __name__ == '__main__':
    mnist_train, info = tfds.load('mnist', split='train', as_supervised=True, with_info=True)
    mnist_test = tfds.load('mnist', split='test', as_supervised=True)   

Sobald du dieses File ausführst, bekommst du in der Konsole folgenden Output.

Downloading and preparing dataset 11.06 MiB (download: 11.06 MiB, generated: 21.00 MiB, total: 32.06 MiB) to /Users/relfner/tensorflow_datasets/mnist/3.0.1...

Dl Completed...: 100% 4/4 [00:03<00:00, 1.01 file/s]

Dataset mnist downloaded and prepared to /Users/relfner/tensorflow_datasets/mnist/3.0.1. Subsequent calls will reuse this data.

Da du nun die Variable info defniert hast, kannst du sie in der Konsole einfach auslesen, in dem du sie aufrufst. Du kannst nun auch deine Trainingsdaten visualisieren lassen. Im Beispiel der MNIST-Datenbank handelt es sich um Bilder von Handgeschriebenen Zahlen. Dazu verwendest du diesen Befehl:

tfds.show_examples(mnist_train, info)

Sobald du diesen Befehl ausgeführt hast, bekommst du folgendes Bild angezeigt:

tensorFlow_04

Nun benötigst du noch eine Methode, welche die Daten in die gewünschte Form bringt. In unserem Fall werden die Bilder als Pixelzahlen von 1 bis 255 dargestellt. Nutzt man jedoch Machine Learning, sollen sich die Daten am besten sich zwischen 0 und 1 befinden. Hierfür wird nun eine Map-Funktion verwendet, welche eine Lambda enthält. Dabei wird als Parameter einmal das Image übergeben, welches normalisiert werden soll und zusätzlich das Label. Dieses soll jedoch gleich bleiben. Damit man an Performance gewinnt werden die Daten in den Cache geladen, dies hat bei der Datenbank keinen grossen Einfluss auf das restliche System.

Handelt es sich bei dem Dataset um die Trainingsdaten, sollten die Zahlen durchgemixt werden. Anschliessend kann das Dataset wieder zurück gegeben werden.

def wrangle_data(dataset, split):
    wrangled = dataset.map(lambda img, lbl: (tf.cast(img, tf.float32) / 255.0, lbl))
    wrangled = wrangled.cache()
    if split == 'train':
        wrangled = wrangled.shuffle(60000)
    return wrangled.batch(64).prefetch(tf.data.AUTOTUNE)

Diese Methode kann vor dem Main-Block definiert werden. Sie kann im Anschluss innerhalb des Main-Blocks aufgerufen werden und der Variable neu zugewiesen werden.

train_data = wrangle_data(mnist_train, 'train')
test_data = wrangle_data(mnist_test, 'test')

Nun ist es noch notwendig, ein Modell zu erstellen. Hierfür wird eine neue Funktion mit dem Namen create_model erstellt. Hier kommt auch das erste mal Keras zum Einsatz. Dabei hat der erste Layer einen "Shape" von 28 Pixel x 28 Pixel x 1 Colorchannel. Mit der Funktion Flatten() wird der Layer zu einem einzelnen Layer. Dabei wird der Inhalt des Shapes multipliziert (28x28x1 = 784). Mit der Funktion Dense() wird das Model "gefüttert", es erlernt die Beziehungen und findet heraus, wie die Daten zu klassifizieren sind.

Zum Schluss wird eine weitere Funktion aufgerufen, welche im nächsten Schritt erstellt wird.

def create_model():
    new_model = tf.keras.Sequential([
        tf.keras.layers.InputLayer((28, 28, 1)),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(64, activation='relu'),
        tf.keras.layers.Dense(10, activation='softmax')
    ])
    return compile_model(new_model)

Diese Funktion muss innerhalb des Main-Blocks aufgerufen werden:

model = create_model()

Nun ist eine weitere Funktion für das Kompilieren des Modells notwendig, welche als Inputparamter definiert wird. Mit der Funktion compile() wird der Optimizer, die Loss-Funktion und die Metrics für weitere Information definiert.

def compile_model(new_model):
    new_model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
    print(new_model.summary())
    return new_model

Sobald auch diese Funktion fertig ist, kannst du dein Programm wieder ausführen. Dabei erhältst du als Output einige Informationen. Man kann sehen wie viele Paramter pro Layer verarbeitet werden und wie sich die Korrektheit pro Durchgang verbessert.

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten (Flatten)           (None, 784)               0         
                                                                 
 dense (Dense)               (None, 64)                50240     
                                                                 
 dense_1 (Dense)             (None, 10)                650       
                                                                 
=================================================================
Total params: 50,890
Trainable params: 50,890
Non-trainable params: 0
_________________________________________________________________
None
Epoch 1/5
938/938 [==============================] - 7s 5ms/step - loss: 0.3554 - accuracy: 0.9024
Epoch 2/5
938/938 [==============================] - 4s 5ms/step - loss: 0.1787 - accuracy: 0.9489
Epoch 3/5
938/938 [==============================] - 4s 4ms/step - loss: 0.1326 - accuracy: 0.9621
Epoch 4/5
938/938 [==============================] - 4s 5ms/step - loss: 0.1050 - accuracy: 0.9691
Epoch 5/5
938/938 [==============================] - 4s 5ms/step - loss: 0.0875 - accuracy: 0.9743

Um nun zu Prüfen, wie gut das Model angelernt wurde, kannst du die Testdaten aus dem ersten Schritt verwenden. Und die evaluate() Funktion nutzen. Dabei siehst du die Genauigkeit, die Erreicht wurde anhand von Daten, welches das Model quasi noch nicht gekannt hat.

model.evaluate(test_data)

157/157 [==============================] - 1s 5ms/step - loss: 0.0971 - accuracy: 0.9702
[0.09706369042396545, 0.9702000617980957]

Wenn du deine Arbeit nun speichern willst, kannst du dies mit model.save('mnist.h5') machen. Innerhalb deines Projekts wirst du nun diese Datei finden.

Beispiel Online Daten

Wenn du ein Modell mit Daten aus dem Internet nutzen möchtest, gibt es das Machine Learning Repository. Das meistverwendete Datenset is dabei das Iris Dataset.

Hierfür benötigst du zunächst einmal eine Funktion, welche die entsprechenden Daten aus dem Internet lädt. Dazu benötigst du die entsprechende Url und der gewünschte Speicherort muss definiert werden.

def loadData():
    data_source_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
    cache_dir = '.'
    cache_subdir = 'data'
    data_file = tf.keras.utils.get_file('iris.data', data_source_url, cache_dir=cache_dir, cache_subdir=cache_subdir)

    return data_file

Innerhalb eines Main-Block kann diese Funktion nun aufgerufen werden, damit die Daten geladen werden.

if __name__ == "__main__":
    iris_filepath = loadData()

Sobald du das File zum ersten Mal ausgeführt hast, stehen dir die Daten zur Verfügung. Hier ist es auf jeden Fall zu Empfehlen, sich die Daten für das Model zuerst anzuschauen, um zuprüfen, ob diese dem gewünschten Format entsprechen, oder ob sie erst noch angepasst werden müssen.

head -5 data/iris.data

5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa

In diesem Fall fehlen uns die Namen der Spalten. Deshalb musst du diese noch hinzufügen, damit die Daten zugeordnet werden können. Dafür legst du zunächst einmal ein Array mit dem entsprechenden Namen an. Anstatt von Namen sollen Zahlen verwendet werden, damit das Model besser damit umgehen kann. Hierfür legen wir eine Map an welche die entsprechenden Arten einer Nummer zu weisen.

iris_columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
label_map= {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2}

def parseData(iris_path):
    iris_df = pd.read_csv(iris_path, names=iris_columns)
    iris_df['species'].replace(label_map, inplace=True)
    return iris_df

Innerhalb des Main Blocks kann nun auch diese Funktion aufgerufen werden iris_data = parse_iris_data(iris_filepath). Um dein Ergebnis zu prüfen, kannst du die Anzahl der "Spezien" vergleichen. Hier sollten überall 50 Stück zur Verfügung stehen.

iris_data['species'].value_counts()

0    50
1    50
2    50
Name: species, dtype: int64

Zum Schluss müssen die Daten in ein TensorFlow-Dataset geladen werden. Hierfür erstellst du nun eine weitere Funktion. Um dieses Set zu definieren, musst du zunächst einmal die Features bestimmen. Diese entsprechen den Spaltennamen. In diesem Beispielfall kann hier direkt iris_columsn verwendet werden. Anschliessend müssen die Labels auch aus dem dataframe ausgelesen werden.

iris_columns[:4] --> ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']

iris_columns[-1] --> species

Auch diese Funktion muss in dem Main-Block wieder aufgerufen werden.

def createDataset(iris_dataframe):
    features = iris_dataframe[iris_columns[:4]]
    labels = iris_dataframe[iris_columns[-1]]
    iris_dataset = tf.data.Dataset.from_tensor_slices((features, labels))
    return iris_dataset

Das Endresultat würde nun so aussehen:

import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

def loadData():
    data_source_url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
    cache_dir = '.'
    cache_subdir = 'data'
    data_file = tf.keras.utils.get_file('iris.data', data_source_url, cache_dir=cache_dir, cache_subdir=cache_subdir)

    return data_file


iris_columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
label_map = {'Iris-setosa': 0, 'Iris-versicolor': 1, 'Iris-virginica': 2}


def parseData(iris_path):
    iris_df = pd.read_csv(iris_path, names=iris_columns)
    iris_df['species'].replace(label_map, inplace=True)
    return iris_df


def createDataset(iris_dataframe):
    features = iris_dataframe[iris_columns[:4]]
    labels = iris_dataframe[iris_columns[-1]]
    iris_dataset = tf.data.Dataset.from_tensor_slices((features, labels))
    return iris_dataset

if __name__ == "__main__":
    iris_filepath = loadData()

iris_data = parseData(iris_filepath);
iris_ds = createDataset(iris_data)

Sobald dieser Code ausgeführt wird, werden die entsprechenden Daten heruntergeladen, aufbereitet und in das Dateset geladen.

Fazit ✨

In diesem TechUp konnten wir einen Schritt weiter gehen, da wir bereits letztes Mal die Grundlagen für das Thema gelegt haben. Deshalb haben wir uns heute dem Praxisteil gewidmet und dabei die bekannteste Datenbank im Bereich Machine Learning genutzt. Des Weiteren konnten wir zeigen, wie leicht man mit der Hilfe von TensorFlow ein ML Model erstellen und es auch direkt trainieren und testen kann. Ebenfalls konnten wir einen Schritt weiter gehen und Daten aus dem Internet nutzen. Dadurch ist es uns nun möglich, bereits vorhandene Daten zu verwenden, da man hier sehr viel Zeit sparen kann, wenn man keine eigene Datenerhebung durchführen muss. Man muss aber an diesem Punkt auf jeden Fall sagen, dass es sich hier um sehr einfache Grundlagen im Bereich von TensorFlow handelt. Somit gibt es in diesem Bereich noch viele weitere spannende Themen, die wir uns anschauen werden.

Bleib dran! 🚀

Ricky Elfner – Denker, Überlebenskünstler, Gadget-Sammler. Dabei ist er immer auf der Suche nach neuen Innovationen, sowie Tech News, um immer über aktuelle Themen schreiben zu können.