Keras und moderne Convnets auf TPUs

1. Übersicht

In diesem Lab erfahren Sie, wie Sie mit Keras und TensorFlow 2 Ihre eigenen Convolutional Neural Networks erstellen, trainieren und optimieren. Dank TPUs ist dies jetzt innerhalb weniger Minuten möglich. Außerdem lernen Sie verschiedene Ansätze kennen, von sehr einfachen Transfer-Lernmethoden bis hin zu modernen Convolutional-Architekturen wie Squeezenet. Dieses Lab enthält theoretische Erklärungen zu neuronalen Netzwerken und ist ein guter Ausgangspunkt für Entwickler, die sich mit Deep Learning vertraut machen möchten.

Das Lesen von Deep-Learning-Artikeln kann schwierig und verwirrend sein. Sehen wir uns moderne Architekturen von Convolutional Neural Networks an.

ca8cc21f6838eccc.png

Lerninhalte

  • Sie möchten Ihre benutzerdefinierten Modelle mit Keras und Tensor Processing Units (TPUs) schneller erstellen.
  • Um die tf.data.Dataset API und das TFRecord-Format zu verwenden, um Trainingsdaten effizient zu laden.
  • Um 😈 zu betrügen, indem du Lerntransfers nutzt, anstatt eigene Modelle zu entwickeln.
  • Keras-Sequenz- und Funktionsmodellstile verwenden
  • Sie lernen, einen eigenen Keras-Klassifikator mit einer Softmax-Schicht und Kreuzentropie-Verlust zu erstellen.
  • Sie können Ihr Modell mit einer guten Auswahl von Convolutional-Ebenen optimieren.
  • Es sollen Ideen für moderne Convnet-Architekturen wie Module, globales Durchschnitts-Pooling usw. erforscht werden.
  • Mit der Squeezenet-Architektur ein einfaches modernes Convnet erstellen

Feedback

Wenn Sie in diesem Codelab etwas Ungewöhnliches bemerken, teilen Sie uns dies bitte mit. Sie können Feedback über GitHub-Probleme [ feedback link] geben.

2. Kurzanleitung für Google Colaboratory

In diesem Lab wird Google Collaboratory verwendet und es ist keine Einrichtung erforderlich. Sie können es auf einem Chromebook ausführen. Öffnen Sie die folgende Datei und führen Sie die Zellen aus, um sich mit Colab-Notebooks vertraut zu machen.

c3df49e90e5a654f.png Welcome to Colab.ipynb

TPU-Back-End auswählen

8832c6208c99687d.png

Wählen Sie im Colab-Menü Laufzeit > Laufzeittyp ändern und dann TPU aus. In diesem Codelab verwenden Sie eine leistungsstarke TPU (Tensor Processing Unit), die für hardwaregestütztes Training unterstützt wird. Die Verbindung zur Laufzeit erfolgt bei der ersten Ausführung automatisch. Alternativ können Sie auf die Schaltfläche „Verbinden“ in der oberen rechten Ecke klicken.

Notebook-Ausführung

76d05caa8b4db6da.png

Führen Sie die Zellen einzeln aus, indem Sie auf eine Zelle klicken und die Umschalttaste und die Eingabetaste drücken. Sie können das gesamte Notebook auch mit Laufzeit > Alle ausführen ausführen.

Inhaltsverzeichnis

429f106990037ec4.png

Alle Notebooks haben ein Inhaltsverzeichnis. Sie können es über den schwarzen Pfeil links öffnen.

Ausgeblendete Zellen

edc3dba45d26f12a.png

In einigen Zellen wird nur der Titel angezeigt. Dies ist eine Colab-spezifische Notebookfunktion. Sie können darauf doppelklicken, um den Code zu sehen, der aber in der Regel nicht sehr interessant ist. In der Regel Support- oder Visualisierungsfunktionen. Sie müssen diese Zellen trotzdem ausführen, damit die darin enthaltenen Funktionen definiert werden.

Authentifizierung

cdd4b41413100543.png

Colab kann auf Ihre privaten Google Cloud Storage-Buckets zugreifen, sofern Sie sich mit einem autorisierten Konto authentifizieren. Das obige Code-Snippet löst einen Authentifizierungsprozess aus.

3. [INFO] Was sind Tensor Processing Units (TPUs)?

Kurz und bündig

f88cf6facfc70166.png

Code zum Trainieren eines Modells auf einer TPU in Keras (mit automatischem Wechsel zu GPU oder CPU, falls keine TPU verfügbar ist):

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

Heute verwenden wir TPUs, um einen Blumenklassifikator in interaktiver Geschwindigkeit (Minuten pro Trainingslauf) zu erstellen und zu optimieren.

688858c21e3beff2.png

Warum TPUs?

Moderne GPUs sind um programmierbare „Kerne“ herum organisiert. Diese sehr flexible Architektur ermöglicht es ihnen, eine Vielzahl von Aufgaben wie 3D-Rendering, Deep Learning und physikalische Simulationen zu bewältigen. TPUs kombinieren dagegen einen klassischen Vektorprozessor mit einer speziellen Matrixmultiplikationseinheit und eignen sich hervorragend für Aufgaben, bei denen große Matrixmultiplikationen dominieren, z. B. für neuronale Netze.

8eb3e718b8e2ed08.png

Abbildung: Eine dichte Schicht eines neuronalen Netzes als Matrixmultiplikation, bei der acht Bilder gleichzeitig durch das neuronale Netzwerk verarbeitet werden. Bitte führen Sie eine Zeile × Spalte-Multiplikation durch, um zu prüfen, ob tatsächlich eine gewichtete Summe aller Pixelwerte eines Bildes berechnet wird. Faltungsschichten können auch als Matrixmultiplikationen dargestellt werden, obwohl dies etwas komplizierter ist ( Erläuterung in Abschnitt 1).

Hardware

MXU und VPU

Ein TPU v2-Kern besteht aus einer Matrix Multiply Unit (MXU), die Matrixmultiplikationen durchführt, und einer Vector Processing Unit (VPU) für alle anderen Aufgaben wie Aktivierungsfunktionen und Softmax. Die VPU verarbeitet float32- und int32-Berechnungen. Die MXU hingegen arbeitet in einem Gleitkommaformat mit gemischter Präzision von 16–32 Bit.

7d68944718f76b18.png

Gemischte Precision-/Gleitkommazahlen und bfloat16

Die MXU berechnet Matrixmultiplikationen mit bfloat16-Eingängen und float32-Ausgaben. Zwischenakkumulierungen werden mit der Genauigkeit float32 ausgeführt.

19c5fc432840c714.png

Das Training in einem neuronalen Netzwerk widerstandsfähig gegen Störgeräusche, die durch eine reduzierte Gleitkommagenauigkeit entstehen. Es gibt Fälle, in denen Rauschen dem Optimierungstool sogar beim Konvergieren hilft. Traditionell wurde eine Gleitkommagenauigkeit von 16 Bit verwendet, um Berechnungen zu beschleunigen, aber die Formate float16 und float32 haben sehr unterschiedliche Bereiche. Die Reduzierung der Precision von float32 auf float16 führt in der Regel zu Über- und Unterflüssen. Lösungen gibt es, aber es ist in der Regel zusätzliche Arbeit erforderlich, damit float16 funktioniert.

Aus diesem Grund hat Google das bfloat16-Format in TPUs eingeführt. bfloat16 ist eine abgeschnittene Gleitkommazahl (FLOAT32) mit genau denselben Exponentenbits und demselben Exponentenbereich wie float32. Hinzu kommt, dass TPUs Matrixmultiplikationen mit gemischter Präzision mit bfloat16-Eingaben und float32-Ausgaben berechnen, was bedeutet, dass normalerweise keine Codeänderungen erforderlich sind, um von den Leistungssteigerungen durch reduzierte Genauigkeit zu profitieren.

Systolic Array

Die MXU implementiert Matrixmultiplikationen in der Hardware mithilfe einer sogenannten „systolischen Array“-Architektur, in der Datenelemente durch eine Reihe von Hardware-Recheneinheiten fließen. (In der Medizin bezieht sich „systolisch“ auf Herzkontraktionen und den Blutfluss, hier auf den Datenfluss.)

Das grundlegende Element einer Matrixmultiplikation ist ein Skalarprodukt zwischen einer Zeile aus einer Matrix und einer Spalte aus der anderen Matrix (siehe Abbildung oben in diesem Abschnitt). Für eine Matrixmultiplikation Y=X*W wäre ein Element des Ergebnisses:

Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]

Auf einer GPU würde man diese Punktprodukt in einen GPU-Kern programmieren und dann parallel auf so vielen Kernen wie verfügbar ausführen, um alle Werte der resultierenden Matrix gleichzeitig zu berechnen. Wenn die resultierende Matrix 128 × 128 groß ist, müssten 128 × 128 = 16.000 „Kerne“ verfügbar sein, was in der Regel nicht möglich ist. Die größten GPUs haben etwa 4.000 Kerne. Eine TPU hingegen verwendet nur das Nötigste an Hardware für die Recheneinheiten in der MXU: nur bfloat16 x bfloat16 => float32 Multiplikator-Akkumulatoren, sonst nichts. Diese sind so klein, dass eine TPU 16.000 davon in einer 128 x 128 MXU implementieren und diese Matrixmultiplikation auf einmal verarbeiten kann.

f1b283fc45966717.gif

Illustration: Das systolische Array von MXU. Die Rechenelemente sind Multiplikatoren. Die Werte einer Matrix werden in das Array geladen (rote Punkte). Die Werte der anderen Matrix fließen durch das Array (graue Punkte). Vertikale Linien leiten die Werte nach oben weiter. Horizontale Linien geben Teilsummen weiter. Es bleibt dem Nutzer als Übung überlassen, um zu prüfen, ob Sie das Ergebnis der Matrixmultiplikation auf der rechten Seite erhalten, während die Daten durch das Array fließen.

Während die Punktprodukte in einer MXU berechnet werden, fließen Zwischensummen einfach zwischen benachbarten Recheneinheiten. Sie müssen nicht im Arbeitsspeicher oder in einer Registerdatei gespeichert und abgerufen werden. Das Endergebnis ist, dass die systolische TPU-Array-Architektur bei der Berechnung von Matrixmultiplikationen einen erheblichen Dichte- und Leistungsvorteil sowie einen nicht vernachlässigbaren Geschwindigkeitsvorteil gegenüber einer GPU hat.

Cloud TPU

Wenn Sie eine Cloud TPU v2 auf der Google Cloud Platform anfordern, erhalten Sie eine virtuelle Maschine (VM) mit einem PCI-angebundenen TPU-Board. Das TPU-Board hat vier Dual-Core-TPU-Chips. Jeder TPU-Kern verfügt über eine VPU (Vector Processing Unit) und eine 128 × 128 MXU (MatriX Multiply Unit). Diese „Cloud TPU“ wird dann in der Regel über das Netzwerk mit der VM verbunden, die sie angefordert hat. Das vollständige Bild sieht so aus:

dfce5522ed644ece.png

Abbildung: VM mit einem mit dem Netzwerk verbundenen „Cloud TPU“-Beschleuniger. „Die Cloud TPU“ selbst besteht aus einer VM mit einem PCI-angebundenen TPU-Board mit vier Dual-Core-TPU-Chips.

TPU-Pods

In den Rechenzentren von Google sind TPUs mit einer Hochleistungs-Computing-Verbindung (High Performance Computing, HPC) verbunden, wodurch sie wie ein sehr großer Beschleuniger erscheinen können. Google nennt sie Pods und sie können bis zu 512 TPU v2-Kerne oder 2048 TPU v3-Kerne umfassen.

2ec1e0d341e7fc34.jpeg

Abbildung: TPU v3-Pod TPU-Boards und ‑Racks, die über eine HPC-Interconnect verbunden sind.

Während des Trainings werden Gradienten zwischen TPU-Kernen mit dem Algorithmus zur vollständigen Reduzierung ausgetauscht ( hier eine gute Erklärung von All-Reduce). Das trainierte Modell kann die Hardware nutzen, indem es mit großen Batchgrößen trainiert wird.

d97b9cc5d40fdb1d.gif

Illustration: Synchronisierung von Gradienten während des Trainings mit dem Algorithmus „All-Reduce“ im 2-D-Toroidal-Mesh-HPC-Netzwerk von Google TPU.

Die Software

Training mit großer Batchgröße

Die ideale Batchgröße für TPUs beträgt 128 Datenelemente pro TPU-Kern. Die Hardware kann jedoch bereits bei 8 Datenelementen pro TPU-Kern eine gute Auslastung erzielen. Denken Sie daran, dass eine Cloud TPU 8 Kerne hat.

In diesem Codelab verwenden wir die Keras API. In Keras ist der von Ihnen angegebene Batch die globale Batchgröße für die gesamte TPU. Ihre Batches werden automatisch in acht Teile aufgeteilt und auf den acht Kernen der TPU ausgeführt.

da534407825f01e3.png

Weitere Tipps zur Leistung finden Sie im Leitfaden zur TPU-Leistung. Bei sehr großen Batchgrößen sind bei einigen Modellen besondere Sorgfalt erforderlich. Weitere Informationen finden Sie unter LARSOptimizer.

Details: XLA

TensorFlow-Programme definieren Berechnungsgrafiken. Auf der TPU wird Python-Code nicht direkt ausgeführt, sondern der von Ihrem Tensorflow-Programm definierte Berechnungsgraph. Intern wandelt ein Compiler namens XLA (beschleunigte lineare Algebra-Compiler-) den Tensorflow-Graphen von Rechenknoten in TPU-Maschinencode um. Dieser Compiler führt auch viele erweiterte Optimierungen an Ihrem Code und Ihrem Speicherlayout durch. Die Kompilierung erfolgt automatisch, wenn die Arbeit an die TPU gesendet wird. Sie müssen XLA nicht explizit in Ihre Build-Kette aufnehmen.

edce61112cd57972.png

Illustration: Zur Ausführung auf TPU wird der von Ihrem Tensorflow-Programm definierte Berechnungsgraph zuerst in eine XLA-Darstellung (beschleunigte lineare Algebra-Compiler-Darstellung) übersetzt und dann von XLA in TPU-Maschinencode kompiliert.

TPUs in Keras verwenden

TPUs werden ab TensorFlow 2.1 über die Keras API unterstützt. Die Keras-Unterstützung funktioniert auf TPUs und TPU-Pods. Das folgende Beispiel funktioniert mit TPU, GPU(s) und CPU:

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

In diesem Code-Snippet gilt Folgendes:

  • TPUClusterResolver().connect() findet die TPU im Netzwerk. Die Funktion funktioniert ohne Parameter in den meisten Google Cloud-Systemen (AI Platform-Jobs, Colaboratory, Kubeflow sowie Deep Learning-VMs, die mit dem Dienstprogramm „ctpu up“ erstellt wurden). Diese Systeme wissen dank einer TPU_NAME-Umgebungsvariable, wo sich ihre TPU befindet. Wenn Sie eine TPU manuell erstellen, legen Sie entweder die TPU_NAME-Umgebungsvariable auf der VM fest, von der aus Sie sie verwenden, oder rufen Sie TPUClusterResolver mit expliziten Parametern auf: TPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy ist der Teil, der die Verteilung und den Gradientensynchronisierungsalgorithmus „All-Reduce“ implementiert.
  • Die Strategie wird über einen Umfang angewendet. Das Modell muss innerhalb des Strategiebereichs (Scope()) definiert werden.
  • Die Funktion tpu_model.fit erwartet ein tf.data.Dataset-Objekt als Eingabe für das TPU-Training.

Häufige Aufgaben bei der TPU-Portierung

  • Es gibt zwar viele Möglichkeiten, Daten in ein TensorFlow-Modell zu laden, für TPUs ist jedoch die Verwendung der tf.data.Dataset API erforderlich.
  • TPUs sind sehr schnell und die Datenaufnahme führt bei der Ausführung oft zum Engpass. Im TPU-Leistungsleitfaden finden Sie Tools zur Erkennung von Datenengpässen und weitere Leistungstipps.
  • int8- oder int16-Zahlen werden als int32 behandelt. Die TPU hat keine Ganzzahl-Hardware, die mit weniger als 32 Bit arbeitet.
  • Einige Tensorflow-Vorgänge werden nicht unterstützt. Die Liste finden Sie hier. Die gute Nachricht ist, dass diese Einschränkung nur für Trainingscode gilt, d.h. für den Vorwärts- und Rückwärtsdurchlauf durch Ihr Modell. Sie können weiterhin alle Tensorflow-Vorgänge in Ihrer Dateneingabepipeline verwenden, da sie auf der CPU ausgeführt wird.
  • tf.py_func wird auf TPU nicht unterstützt.

4. Daten werden geladen

c0ecb860e4cad0a9.jpeg cc4781a7739c49ae.jpeg 81236b00f8bbf39e.jpeg 961e2228974076bb.jpeg 7517dc163bdffcd5.jpeg 96392df4767f566d.png

Wir arbeiten mit einem Dataset mit Blumenbildern. Das Ziel besteht darin, sie in fünf Blumentypen zu kategorisieren. Die Daten werden mit der tf.data.Dataset API geladen. Machen wir uns zuerst mit der API vertraut.

Praktische Übung

Öffnen Sie das folgende Notebook, führen Sie die Zellen aus (Umschalttaste + Eingabetaste) und folgen Sie den Anweisungen, wenn das Label „ARBEITSERFORDERLICH“ angezeigt wird.

c3df49e90e5a654f.png Fun with tf.data.Dataset (playground).ipynb

Weitere Informationen

Datensatz „Blumen“

Der Datensatz ist in 5 Ordner unterteilt. Jeder Ordner enthält Blumen einer Art. Die Ordner heißen Sonnenblumen, Gänseblümchen, Löwenzahn, Tulpen und Rosen. Die Daten werden in einem öffentlichen Bucket in Google Cloud Storage gehostet. Auszug:

gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg

Warum tf.data.Dataset?

Keras und Tensorflow akzeptieren Datasets in allen Trainings- und Bewertungsfunktionen. Nachdem Sie Daten in ein Dataset geladen haben, bietet die API alle allgemeinen Funktionen, die für das Trainieren neuronaler Netzwerke nützlich sind:

dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training

Tipps zur Leistung und Best Practices für Datasets finden Sie in diesem Artikel. Die Referenzdokumentation finden Sie hier.

Grundlagen von tf.data.Dataset

Die Daten liegen in der Regel in mehreren Dateien vor, hier in Bildern. Sie können einen Datensatz mit Dateinamen erstellen, indem Sie Folgendes aufrufen:

filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.

Sie ordnen dann jedem Dateinamen eine Funktion zu, die die Datei in der Regel in den Arbeitsspeicher lädt und in tatsächliche Daten decodiert:

def decode_jpeg(filename):
  bits = tf.io.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  return image

image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)

So iterieren Sie ein Dataset:

for data in my_dataset:
  print(data)

Datasets mit Tupeln

Beim überwachten Lernen besteht ein Trainings-Dataset in der Regel aus Paaren von Trainingsdaten und richtigen Antworten. Dazu kann die Dekodierungsfunktion Tupel zurückgeben. Sie haben dann einen Datensatz mit Tupeln, die bei der Iteration zurückgegeben werden. Die zurückgegebenen Werte sind Tensorflow-Tensoren, die von Ihrem Modell verwendet werden können. Sie können für sie .numpy() aufrufen, um Rohwerte zu sehen:

def decode_jpeg_and_label(filename):
  bits = tf.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  label = ... # extract flower name from folder name
  return image, label

image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs 

for image, label in dataset:
  print(image.numpy().shape, label.numpy())

Fazit: Das Laden von Bildern einzeln ist langsam.

Wenn Sie diesen Datensatz iterieren, sehen Sie, dass Sie etwa 1–2 Bilder pro Sekunde laden können. Das ist zu langsam. Die Hardwarebeschleuniger, die wir für das Training verwenden, können diese Geschwindigkeit um ein Vielfaches erreichen. Im nächsten Abschnitt erfahren Sie, wie wir das erreichen.

Lösung

Hier ist das Lösungsnotizbuch. Sie können sie verwenden, wenn Sie nicht weiterkommen.

c3df49e90e5a654f.png Fun with tf.data.Dataset (solution).ipynb

Behandelte Themen

  • 🤔 tf.data.Dataset.list_files
  • 🤔 tf.data.Dataset.map
  • 🤔 Datasets mit Tupeln
  • 😀 Durch Datasets iterieren

Bitte nehmen Sie sich einen Moment Zeit, um diese Checkliste durchzugehen.

5. Daten schnell laden

Die TPU-Hardwarebeschleuniger (Tensor Processing Unit), die wir in diesem Lab verwenden, sind sehr schnell. Die Herausforderung besteht oft darin, sie so schnell mit Daten zu versorgen, dass sie damit auf dem Laufenden bleiben. Google Cloud Storage (GCS) kann einen sehr hohen Durchsatz aufrechterhalten. Wie bei allen Cloud-Speichersystemen ist jedoch ein gewisser Netzwerkverkehr erforderlich, um eine Verbindung herzustellen. Daher ist es nicht ideal, wenn unsere Daten in Tausenden von einzelnen Dateien gespeichert werden. Wir werden sie in einer kleineren Anzahl von Dateien zusammenfassen und die Leistungsfähigkeit von tf.data.Dataset nutzen, um parallel aus mehreren Dateien zu lesen.

Lesen

Der Code, der Bilddateien lädt, ihre Größe auf eine gemeinsame Größe umwandelt und sie dann in 16 TFRecord-Dateien speichert, befindet sich im folgenden Notebook. Bitte lesen Sie sich das schnell durch. Die Ausführung ist nicht erforderlich, da ordnungsgemäß TFRecord-formatierte Daten für den Rest des Codelabs bereitgestellt werden.

c3df49e90e5a654f.png Flower pictures to TFRecords.ipynb

Ideales Datenlayout für optimalen GCS-Durchsatz

Das TFRecord-Dateiformat

Das bevorzugte Dateiformat zum Speichern von Daten ist das protobuf-basierte TFRecord-Format. Andere Serialization-Formate würden auch funktionieren, aber Sie können einen Datensatz direkt aus TFRecord-Dateien laden, indem Sie Folgendes schreiben:

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

Für eine optimale Leistung wird empfohlen, den folgenden komplexeren Code zu verwenden, um gleichzeitig aus mehreren TFRecord-Dateien zu lesen. Dieser Code liest parallel aus N Dateien und ignoriert die Datenreihenfolge zugunsten der Lesegeschwindigkeit.

AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

TFRecord-Referenz

Drei Arten von Daten können in TFRecords gespeichert werden: Bytestrings (Liste der Byte), 64-Bit-Ganzzahlen und 32-Bit-Gleitkommazahlen. Sie werden immer als Listen gespeichert. Ein einzelnes Datenelement ist eine Liste mit einer Größe von 1. Mit den folgenden Hilfsfunktionen können Sie Daten in TFRecords speichern.

Bytestrings schreiben

# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
  return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))

Ganzzahlen schreiben

def _int_feature(list_of_ints): # int64
  return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))

Text wird als „float“ dargestellt

def _float_feature(list_of_floats): # float32
  return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))

TFRecord mithilfe der oben genannten Hilfsfunktionen schreiben

# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
  feature = {
    "image": _bytestring_feature([my_img_bytes]), # one image in the list
    "class": _int_feature([my_class]),            # one class in the list
    "size": _int_feature([my_height, my_width]),  # fixed length (2) list of ints
    "float_data": _float_feature(my_floats)       # variable length  list of floats
  }
  tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
  out_file.write(tf_record.SerializeToString())

Wenn Sie Daten aus TFRecords lesen möchten, müssen Sie zuerst das Layout der gespeicherten Einträge deklarieren. In der -Deklaration können Sie auf jedes benannte Feld als Liste mit fester Länge oder als Liste mit variabler Länge zugreifen:

aus TFRecords lesen

def read_tfrecord(data):
  features = {
    # tf.string = byte string (not text string)
    "image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
    "class": tf.io.FixedLenFeature([], tf.int64),  # shape [] means scalar, i.e. a single item
    "size": tf.io.FixedLenFeature([2], tf.int64),  # two integers
    "float_data": tf.io.VarLenFeature(tf.float32)  # a variable number of floats
  }

  # decode the TFRecord
  tf_record = tf.io.parse_single_example(data, features)

  # FixedLenFeature fields are now ready to use
  sz = tf_record['size']

  # Typical code for decoding compressed images
  image = tf.io.decode_jpeg(tf_record['image'], channels=3)

  # VarLenFeature fields require additional sparse.to_dense decoding
  float_data = tf.sparse.to_dense(tf_record['float_data'])

  return image, sz, float_data

# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)

Nützliche Code-Snippets:

einzelne Datenelemente lesen

tf.io.FixedLenFeature([], tf.string)   # for one byte string
tf.io.FixedLenFeature([], tf.int64)    # for one int
tf.io.FixedLenFeature([], tf.float32)  # for one float

Listen von Elementen mit fester Größe lesen

tf.io.FixedLenFeature([N], tf.string)   # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64)    # list of N ints
tf.io.FixedLenFeature([N], tf.float32)  # list of N floats

Lesen einer variablen Anzahl von Datenelementen

tf.io.VarLenFeature(tf.string)   # list of byte strings
tf.io.VarLenFeature(tf.int64)    # list of ints
tf.io.VarLenFeature(tf.float32)  # list of floats

Eine VarLenFeature gibt einen spärlichen Vektor zurück. Nach der Dekodierung des TFRecord ist ein zusätzlicher Schritt erforderlich:

dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])

Es ist auch möglich, optionale Felder in TFRecords zu haben. Wenn Sie beim Lesen eines Felds einen Standardwert angeben, wird dieser Wert anstelle eines Fehlers zurückgegeben, wenn das Feld fehlt.

tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional

Behandelte Themen

  • 🤔 Dateien für schnellen Zugriff über GCS partitionieren
  • 😓 TFRecords schreiben. (Sie haben die Syntax bereits vergessen? Kein Problem, erstellen Sie ein Lesezeichen für diese Seite als Spickzettel.)
  • 🤔 Dataset mit TFRecordDataset aus TFRecords laden

Bitte nehmen Sie sich einen Moment Zeit und gehen Sie diese Checkliste durch.

6. [INFO] Einführung in neuronale Klassifikatoren

Kurz und bündig

Wenn Ihnen alle im nächsten Absatz fett formatierten Begriffe bereits bekannt sind, können Sie mit der nächsten Übung fortfahren. Wenn Sie gerade erst mit dem Thema "Deep Learning" beginnen, ist dies willkommen. Bitte lesen Sie weiter.

Für Modelle, die als Folge von Ebenen erstellt wurden, bietet Keras die Sequential API an. Ein Bildklassifikator mit drei dichten Schichten kann beispielsweise in Keras so geschrieben werden:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
    tf.keras.layers.Dense(500, activation="relu"),
    tf.keras.layers.Dense(50, activation="relu"),
    tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 classes
])

# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy']) # % of correct answers

# train the model
model.fit(dataset, ... )

688858c21e3beff2.png

Kompaktes neuronales Netzwerk

Dies ist das einfachste neuronale Netzwerk zur Klassifizierung von Bildern. Es besteht aus „Neuronen“, die in Schichten angeordnet sind. Die erste Ebene verarbeitet Eingabedaten und speist ihre Ausgaben in andere Ebenen ein. Sie wird als „dicht“ bezeichnet, weil jedes Neuron mit allen Neuronen aus der vorherigen Schicht verbunden ist.

c21bae6dade487bc.png

Sie können ein Bild in ein solches Netzwerk einspeisen, indem Sie die RGB-Werte aller Pixel in einen langen Vektor flachlegen und ihn als Eingabe verwenden. Das ist nicht die beste Methode für die Bilderkennung, aber wir werden sie später verbessern.

Neuronen, Aktivierungen, RELU

Ein „Neuron“ berechnet eine gewichtete Summe aller seiner Eingaben, fügt einen Wert namens „Bias“ hinzu und leitet das Ergebnis durch eine sogenannte „Aktivierungsfunktion“ weiter. Die Gewichte und die Voreingenommenheit sind anfangs unbekannt. Sie werden zufällig initialisiert und „erlernt“, indem das neuronale Netzwerk mit vielen bekannten Daten trainiert wird.

644f4213a4ee70e5.png

Die am häufigsten verwendete Aktivierungsfunktion ist RELU für die korrigierte Lineareinheit. Wie Sie in der Abbildung oben sehen, ist es eine sehr einfache Funktion.

Softmax-Aktivierung

Das obige Netzwerk endet mit einer Schicht mit 5 Neuronen, da wir Blumen in fünf Kategorien unterteilen: Rose, Tulpe, Löwenzahn, Gänseblümchen, Sonnenblume. Neuronen in Zwischenschichten werden mit der klassischen RELU-Aktivierungsfunktion aktiviert. In der letzten Ebene möchten wir jedoch Zahlen zwischen 0 und 1 berechnen, die die Wahrscheinlichkeit darstellen, dass diese Blume eine Rose, eine Tulpe usw. ist. Dazu verwenden wir die Aktivierungsfunktion „Softmax“.

Die Anwendung von Softmax auf einen Vektor erfolgt, indem die Exponentialfunktion jedes Elements ermittelt und dann der Vektor normalisiert wird. Normalerweise wird die L1-Norm (Summe der absoluten Werte) verwendet, sodass die Werte addiert 1 ergeben und als Wahrscheinlichkeiten interpretiert werden können.

ef0d98c0952c262d.png d51252f75894479e.gif

Kreuzentropie-Verlust

Jetzt, da unser neuronales Netzwerk Vorhersagen aus Eingabebildern generiert, müssen wir messen, wie gut sie sind, d. h. den Abstand zwischen den Angaben des Netzwerks und den richtigen Antworten, die oft als „Labels“ bezeichnet werden. Denken Sie daran, dass wir für alle Bilder im Datensatz korrekte Labels haben.

Jede Distanz würde funktionieren, aber für Klassifizierungsprobleme ist die sogenannte „Kreuzentropie-Distanz“ am effektivsten. Wir nennen dies unsere Fehler- oder „Verlust“-Funktion:

7bdf8753d20617fb.png

Gradientenabstieg

Das „Training“ des neuronalen Netzes bedeutet, mithilfe von Trainingsbildern und Labels Gewichte und Voreingenommenheiten so anzupassen, dass die Kreuzentropie-Verlustfunktion minimiert wird. So funktionierts:

Die Kreuzentropie ist eine Funktion von Gewichten, Voreingenommenheiten, Pixeln des Trainingsbilds und seiner bekannten Klasse.

Wenn wir die partiellen Ableitungen der Kreuzentropie relativ zu allen Gewichten und Voreingenommenheiten berechnen, erhalten wir einen „Gradienten“, der für ein bestimmtes Bild, Label und den aktuellen Wert der Gewichte und Voreingenommenheiten berechnet wird. Denken Sie daran, dass wir Millionen von Gewichten und Voreingenommenheiten haben können, sodass die Berechnung des Gradienten viel Arbeit bedeutet. Zum Glück macht Tensorflow das für uns. Die mathematische Eigenschaft eines Farbverlaufs besteht darin, dass er nach oben zeigt. Da wir hingehen möchten, wo die Kreuzentropie gering ist, gehen wir in die entgegengesetzte Richtung. Wir aktualisieren die Gewichte und Voreingenommenheiten um einen Bruchteil des Gradienten. Dann wiederholen wir den Vorgang in einem Trainings-Loop immer wieder mit den nächsten Batches von Trainingsbildern und Labels. Hoffentlich konvergiert dies zu einem Punkt, an dem die Kreuzentropie minimal ist, obwohl es keine Garantie dafür gibt, dass dieses Minimum eindeutig ist.

gradient descent2.png

Mini-Batching und Impuls

Sie können den Gradienten anhand eines einzelnen Beispielbilds berechnen und die Gewichte und Voreingenommenheiten sofort aktualisieren. Wenn Sie dies jedoch für einen Batch von beispielsweise 128 Bildern tun, erhalten Sie einen Gradienten, der die Einschränkungen verschiedener Beispielbilder besser darstellt und daher wahrscheinlich schneller zur Lösung konvergiert. Die Größe des Mini-Batches ist ein anpassbarer Parameter.

Diese Methode, die manchmal als „stochastischer Gradientenabstieg“ bezeichnet wird, hat noch einen weiteren, pragmatischeren Vorteil: Die Arbeit mit Batches bedeutet auch die Arbeit mit größeren Matrizen, die sich in der Regel leichter auf GPUs und TPUs optimieren lassen.

Die Konvergenz kann jedoch immer noch etwas chaotisch sein und sogar aufhören, wenn der Gradientenvektor nur Nullen enthält. Heißt das, dass wir einen Mindestwert gefunden haben? Nimmt immer. Eine Farbverlaufskomponente kann bei einem Minimum oder Maximum null sein. Bei einem Gradientenvektor mit Millionen von Elementen, die alle Nullen sind, ist die Wahrscheinlichkeit, dass jede Null einem Minimum und keine einem Maximum entspricht, ziemlich gering. In einem Raum mit vielen Dimensionen sind Sattelpunkte ziemlich häufig und wir sollten uns nicht dort aufhalten.

52e824fe4716c4a0.png

Illustration: ein Sattelpunkt. Der Farbverlauf ist 0, aber kein Mindestwert in alle Richtungen. (Bildzuordnung Wikimedia: By Nicoguaro – Own work, CC BY 3.0)

Die Lösung besteht darin, dem Optimierungsalgorithmus etwas Schwung zu verleihen, damit er ohne Unterbrechung an Sattelpunkten vorbeifahren kann.

Glossar

batch oder mini-batch: Das Training wird immer mit Batches von Trainingsdaten und Labels durchgeführt. Dies trägt dazu bei, dass der Algorithmus konvergiert. Die „Batch“-Dimension ist normalerweise die erste Dimension von Datentensoren. Beispiel: Ein Tensor der Form [100, 192, 192, 3] enthält 100 Bilder mit 192 × 192 Pixeln mit drei Werten pro Pixel (RGB).

Kreuzentropie-Verlust: Eine spezielle Verlustfunktion, die häufig in Klassifikatoren verwendet wird.

dichte Schicht: Eine Schicht aus Neuronen, bei der jedes Neuron mit allen Neuronen aus der vorherigen Schicht verbunden ist.

Features: Die Eingaben eines neuronalen Netzes werden manchmal als „Features“ bezeichnet. Die Kunst, herauszufinden, welche Teile eines Datensatzes (oder Kombinationen von Teilen) in ein neuronales Netzwerk eingespeist werden müssen, um gute Vorhersagen zu erhalten, wird als „Feature Engineering“ bezeichnet.

Labels: Ein anderer Name für „Klassen“ oder richtige Antworten bei einem Klassifizierungsproblem mit beaufsichtigtem Lernen

Lernrate: Anteil des Gradienten, um den Gewichtungen und Verzerrungen bei jeder Iteration der Trainingsschleife aktualisiert werden.

Logits: Die Ausgaben einer Neuronenschicht vor Anwendung der Aktivierungsfunktion werden als „Logits“ bezeichnet. Der Begriff leitet sich von der „Logistischen Funktion“ bzw. „Sigmoidfunktion“ ab, die früher die beliebteste Aktivierungsfunktion war. „Neuron-Ausgaben vor der logistischen Funktion“ wurde zu „Logits“ verkürzt.

loss: Die Fehlerfunktion, die die Ausgaben des neuronalen Netzes mit den richtigen Antworten vergleicht

neuron: berechnet die gewichtete Summe der Eingaben, fügt eine Verzerrung hinzu und speist das Ergebnis über eine Aktivierungsfunktion ein.

One-Hot-Codierung: Klasse 3 von 5 wird als Vektor aus fünf Elementen codiert, alle Nullen mit Ausnahme des dritten, also 1.

relu: korrigierte lineare Einheit. Eine beliebte Aktivierungsfunktion für Neuronen.

Sigmoid: Eine weitere Aktivierungsfunktion, die früher häufig verwendet wurde und in Sonderfällen immer noch nützlich ist.

Softmax-Parameter: eine spezielle Aktivierungsfunktion, die auf einen Vektor wirkt, die Differenz zwischen der größten und allen anderen Komponenten erhöht und außerdem den Vektor auf die Summe 1 normalisiert, sodass er als Vektor von Wahrscheinlichkeiten interpretiert werden kann. Wird als letzter Schritt in Klassifikatoren verwendet.

tensor: Ein "Tensor" ist wie eine Matrix, aber mit einer beliebigen Anzahl von Dimensionen. Ein eindimensionaler Tensor ist ein Vektor. Ein zweidimensionaler Tensor ist eine Matrix. Es gibt auch Tensoren mit 3, 4, 5 oder mehr Dimensionen.

7. Lerntransfer

Für ein Bildklassifizierungsproblem genügen dichte Schichten wahrscheinlich nicht. Wir müssen etwas über Faltungsebenen und die vielen Möglichkeiten lernen, sie anzuordnen.

Wir können aber auch einen Umweg gehen. Es stehen vollständig trainierte Convolutional Neural Networks zum Download zur Verfügung. Es ist möglich, die letzte Schicht, den Softmax-Klassifizierungskopf, abzuschneiden und durch Ihre eigene zu ersetzen. Alle trainierten Gewichte und Vorurteile bleiben unverändert. Sie trainieren nur die hinzugefügte Softmax-Schicht neu. Diese Technik wird als Transferlernen bezeichnet und funktioniert erstaunlicherweise, solange der Datensatz, auf dem das neuronale Netzwerk vorab trainiert wurde, „nahe genug“ an Ihrem liegt.

Praktische Übung

Öffnen Sie das folgende Notebook, führen Sie die Zellen aus (Umschalttaste + Eingabetaste) und folgen Sie den Anweisungen, wenn das Label „ARBEITSERFORDERLICH“ angezeigt wird.

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

Weitere Informationen

Mit Lerntransfers profitieren Sie sowohl von fortschrittlichen, von Top-Forschern entwickelten Convolutional Neural Network-Architekturen als auch vom Vorabtraining mit einem riesigen Dataset von Bildern. In unserem Fall verwenden wir das Transfer-Lernen aus einem Netzwerk, das auf ImageNet trainiert wurde, einer Datenbank mit Bildern vieler Pflanzen und Außenszenen, die Blumen sehr ähnlich sind.

b8fc1efd2001f072.png

Illustration: Mithilfe eines komplexen Convolutional Neural Network, das bereits als Blackbox trainiert wurde, wird nur der Klassifizierungskopf neu trainiert. Das ist Lerntransfer. Wie diese komplizierten Anordnungen von Convolutional-Schichten funktionieren, sehen wir uns später an. Im Moment ist es das Problem eines anderen.

Lerntransfer in Keras

In Keras können Sie ein vortrainiertes Modell aus der Sammlung tf.keras.applications.* instanziieren. MobileNet V2 ist beispielsweise eine sehr gute Convolutional-Architektur, die eine angemessene Größe beibehält. Wenn Sie include_top=False auswählen, erhalten Sie das vortrainierte Modell ohne die finale Softmax-Schicht, damit Sie Ihre eigene hinzufügen können:

pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False

model = tf.keras.Sequential([
    pretrained_model,
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(5, activation='softmax')
])

Beachten Sie auch die Einstellung pretrained_model.trainable = False. Es friert die Gewichtungen und Verzerrungen des vortrainierten Modells ein, sodass Sie nur Ihre Softmax-Ebene trainieren. Dies umfasst in der Regel relativ wenige Gewichte und kann schnell und ohne einen sehr großen Datensatz durchgeführt werden. Wenn Sie jedoch viele Daten haben, kann die Lerntransferfunktion mit pretrained_model.trainable = True noch besser funktionieren. Die vorab trainierten Gewichte bieten dann hervorragende Anfangswerte und können durch das Training noch an Ihr Problem angepasst werden.

Außerdem wird der Flatten()-Layer vor dem dichten Softmax-Layer eingefügt. Dichte Ebenen funktionieren auf flachen Datenvektoren, aber wir wissen nicht, ob das vortrainierte Modell diese Ergebnisse zurückgibt. Deshalb müssen wir vereinfachen. Im nächsten Kapitel, in dem wir uns mit Convolutional Neural Networks befassen, erklären wir das Datenformat, das von Convolutional-Schichten zurückgegeben wird.

Bei diesem Ansatz sollten Sie eine Genauigkeit von fast 75% erreichen.

Lösung

Hier ist das Lösungsnotizbuch. Sie können sie verwenden, wenn Sie nicht weiterkommen.

c3df49e90e5a654f.png Keras Flowers transfer learning (solution).ipynb

Behandelte Themen

  • 🤔 Klassifikator in Keras schreiben
  • 🤓 konfiguriert mit einem letzten Softmax-Layer und Kreuzentropieverlust
  • 😈 Lerntransfer
  • 🤔 Das erste Modell trainieren
  • 🧐 Nach dem Verlust und der Genauigkeit während des Trainings

Bitte nehmen Sie sich einen Moment Zeit und gehen Sie diese Checkliste durch.

8. [INFO] Convolutional Neural Networks

Kurz und bündig

Wenn Ihnen alle Begriffe im nächsten Abschnitt, die fett hervorgehoben sind, bereits bekannt sind, können Sie mit der nächsten Übung fortfahren. Wenn Sie gerade erst mit Convolutional Neural Networks (CNNs) beginnen, lesen Sie weiter.

convolutional.gif

Abbildung: Bild mit zwei aufeinanderfolgenden Filtern aus jeweils 4 × 3=48 lernbaren Gewichtungen filtern.

So sieht ein einfaches Convolutional Neural Network in Keras aus:

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy'])

688858c21e3beff2.png

Convolutional Neural Networks (CNN) – Einführung

In einer Schicht eines Convolutional Network macht ein „Neuron“ eine gewichtete Summe der Pixel direkt darüber, nur über einen kleinen Bereich des Bildes hinweg. Sie fügt eine Verzerrung hinzu und füttert die Summe durch eine Aktivierungsfunktion, so wie es ein Neuron in einer regulären dichten Schicht tun würde. Dieser Vorgang wird dann für das gesamte Bild mit denselben Gewichtungen wiederholt. Denken Sie daran, dass in dichten Schichten jedes Neuron seine eigenen Gewichte hatte. Hier gleitet ein einzelner „Patch“ von Gewichten in beide Richtungen über das Bild (eine „Konvolution“). Die Ausgabe hat so viele Werte, wie Pixel im Bild enthalten sind. An den Rändern ist jedoch ein gewisser Abstand erforderlich. Es handelt sich um einen Filtervorgang, bei dem ein Filter mit einer Gewichtung von 4x4x3=48 verwendet wird.

48 Gewichtungen reichen jedoch nicht aus. Um weitere Freiheitsgrade hinzuzufügen, wiederholen wir denselben Vorgang mit einem neuen Satz von Gewichtungen. Dadurch werden neue Filterausgaben generiert. Nennen wir ihn in Analogie zu den R,G,B-Kanälen im Eingabebild einen „Kanal“ der Ausgabe.

Screen Shot 2016-07-29 at 16.02.37.png

Die zwei (oder mehr) Gewichtungssätze können durch Hinzufügen einer neuen Dimension zu einem Tensor addiert werden. Dies ergibt die generische Form des Gewichtstensors für eine Convolutional-Schicht. Da die Anzahl der Eingabe- und Ausgabekanäle Parameter sind, können wir Convolutional-Layers stapeln und verketten.

d1b557707bcd1cb9.png

Abbildung: Ein Convolutional Neural Network wandelt Datenwürfel in andere Datenwürfel um.

Schrittweise Faltungen, max. Pooling

Durch die Durchführung der Convolutionen mit einem Schritt von 2 oder 3 können wir den resultierenden Datenwürfel auch in seinen horizontalen Dimensionen verkleinern. Dafür gibt es zwei gängige Möglichkeiten:

  • Schrittweise Convolution: Ein gleitender Filter wie oben, aber mit einem Schritt von mehr als 1
  • Max-Pooling: Ein Schiebefenster, auf das der MAX-Vorgang angewendet wird (in der Regel auf 2 × 2 Patches, alle 2 Pixel wiederholt)

2b2d4263bb8470b.gif

Abbildung: Wenn Sie das Berechnungsfenster um 3 Pixel verschieben, werden weniger Ausgabewerte berechnet. Gestrichelte Faltungen oder Max-Pooling (Maximum auf einem 2 x 2-Fenster, das in einem Schritt von 2 verschoben wird) sind eine Möglichkeit, den Datenwürfel in horizontalen Dimensionen zu verkleinern.

Convolutional Classifier

Schließlich fügen wir einen Klassifizierungskopf an, indem wir den letzten Datenwürfel vereinfachen und durch eine dichte, Softmax-aktivierte Ebene speisen. Ein typischer Convolutional Classifier kann so aussehen:

4a61aaffb6cba3d1.png

Abbildung: Bildklassifikator mit Convolutional- und Softmax-Layers. Es werden 3x3- und 1x1-Filter verwendet. Die Maxpool-Ebenen verwenden das Maximum von Gruppen von 2 x 2 Datenpunkten. Der Klassifizierungskopf ist mit einer dichten Schicht mit Softmax-Aktivierung implementiert.

In Keras

Der oben dargestellte Convolutional Stack kann in Keras wie folgt geschrieben werden:

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)    
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy'])

9. Ihre benutzerdefinierte ConvNet

Praktische Übung

Lassen Sie uns ein Convolutional Neural Network von Grund auf neu erstellen und trainieren. Mit einer TPU können wir sehr schnell iterieren. Öffnen Sie das folgende Notebook, führen Sie die Zellen aus (Umschalttaste + Eingabetaste) und folgen Sie den Anweisungen, wenn das Label „ARBEITSERFORDERLICH“ angezeigt wird.

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

Ziel ist es, die Genauigkeit von 75% des Lerntransfermodells zu übertreffen. Dieses Modell hatte den Vorteil, dass es mit einem Dataset von Millionen Bildern vortrainiert wurde, obwohl wir hier nur 3.670 Bilder haben. Können Sie es zumindest zuordnen?

Weitere Informationen

Wie viele Schichten, wie groß?

Die Auswahl der Ebenengröße ist eher eine Kunst als eine Wissenschaft. Sie müssen das richtige Gleichgewicht zwischen zu wenigen und zu vielen Parametern (Gewichtung und Verzerrungen) finden. Mit zu wenig Gewichten kann das neuronale Netzwerk nicht die Komplexität von Blumenformen darstellen. Bei zu vielen Bildern kann dies anfällig für „Überanpassung“ sein, d.h., Sie spezialisieren sich auf die Trainingsbilder und können nicht verallgemeinern. Mit vielen Parametern wird das Modell auch nur langsam trainiert. In Keras zeigt die Funktion model.summary() die Struktur und die Anzahl der Parameter Ihres Modells an:

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 192, 192, 16)      448       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 192, 192, 30)      4350      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 96, 96, 60)        16260     
_________________________________________________________________
 ... 
_________________________________________________________________
global_average_pooling2d (Gl (None, 130)               0         
_________________________________________________________________
dense (Dense)                (None, 90)                11790     
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 455       
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________

Hier einige Tipps:

  • Die mehreren Schichten machen „Deep“-neuronale Netzwerke effektiv. Für dieses einfache Problem der Blumenerkennung sind 5 bis 10 Ebenen sinnvoll.
  • Verwenden Sie kleine Filter. Normalerweise eignen sich 3 × 3-Filter überall.
  • Auch 1:1-Filter können verwendet werden und sind kostengünstig. Eigentlich „filtern“ sie nichts anderes als die Berechnung linearer Kombinationen von Channels. Wechseln Sie zwischen echten Filtern und diesen aus. Weitere Informationen zu „1x1-Faltungen“ finden Sie im nächsten Abschnitt.
  • Bei einem solchen Klassifizierungsproblem sollten Sie häufig mit Max-Pooling-Ebenen (oder Faltungen mit Schritt > 1) herunterrechnen. Es ist Ihnen egal, wo die Blume ist, nur, dass es sich um eine Rose oder einen Löwenzahn handelt. Der Verlust von X- und Y-Informationen ist also nicht wichtig und das Filtern kleinerer Bereiche ist günstiger.
  • Die Anzahl der Filter entspricht in der Regel der Anzahl der Klassen am Ende des Netzwerks. Warum? Sehen Sie sich den Trick „Global Average Pooling“ unten an. Wenn Sie in Hunderte von Klassen klassifizieren, erhöhen Sie die Filteranzahl in aufeinanderfolgenden Ebenen schrittweise. Für den Blumendatensatz mit 5 Klassen würde das Filtern mit nur 5 Filtern nicht ausreichen. In den meisten Ebenen können Sie dieselbe Filteranzahl verwenden, z. B. 32, und sie zum Ende hin verringern.
  • Die letzten Dense-Ebenen sind teuer. Sie können mehr Gewichtungen haben als alle Convolutional Layer zusammen. Selbst bei einer sehr angemessenen Ausgabe aus dem letzten Datenwürfel mit 24 × 24 × 10 Datenpunkten würde eine dichte Neuronenschicht aus 100 Neuronen 24 × 24 × 10 × 100=576.000 Gewichtungen kosten. Gehen Sie dabei umsichtig vor oder verwenden Sie globales Durchschnitts-Pooling (siehe unten).

Globales durchschnittliches Pooling

Anstatt am Ende eines Convolutional Neural Networks eine teure dichte Schicht zu verwenden, können Sie den eingehenden Datenwürfel in so viele Teile wie Klassen aufteilen, ihre Werte mitteln und diese durch eine Softmax-Aktivierungsfunktion leiten. Diese Art der Erstellung des Klassifizierungskopfs kostet 0 Gewichtungen. Die Syntax in Keras lautet tf.keras.layers.GlobalAveragePooling2D().

93240029f59df7c2.png

Lösung

Hier ist das Lösungsnotizbuch. Sie können sie verwenden, wenn Sie nicht weiterkommen.

c3df49e90e5a654f.png Keras_Flowers_TPU (solution).ipynb

Behandelte Themen

  • 🤔 Faltungsebenen gespielt
  • 🤓 Experimente mit max. Pooling, Schritten, globalem Durchschnitts-Pooling...
  • 😀 ein reales Modell schnell auf TPU iteriert hat

Bitte nehmen Sie sich einen Moment Zeit und gehen Sie diese Checkliste durch.

10. [INFO] Moderne Convolutional-Architekturen

Kurz und bündig

7968830b57b708c0.png

Abbildung: ein Convolutional-„Modul“. Was ist in diesem Fall am besten? Eine Max-Pool-Ebene, gefolgt von einer 1 × 1-Konvolutionsschicht oder eine andere Kombination von Schichten? Probieren Sie sie alle aus, verknüpfen Sie die Ergebnisse und lassen Sie das Netzwerk entscheiden. Rechts: die Convolutional Architecture „ inception“ mit solchen Modulen.

In Keras müssen Sie den „funktionalen“ Modellstil verwenden, um Modelle zu erstellen, bei denen sich der Datenfluss verzweigen kann. Hier ein Beispiel:

l = tf.keras.layers # syntax shortcut

y = l.Conv2D(filters=32, kernel_size=3, padding='same',
             activation='relu', input_shape=[192, 192, 3])(x) # x=input image

# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation

# many more layers ...

# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)

688858c21e3beff2.png

Andere billige Tricks

Kleine 3 × 3-Filter

40a7b15fb7dbe75c.png

In dieser Abbildung sehen Sie das Ergebnis von zwei aufeinanderfolgenden 3 × 3-Filtern. Versuchen Sie, nachzuvollziehen, welche Datenpunkte zum Ergebnis beigetragen haben: Diese beiden aufeinanderfolgenden 3 × 3-Filter berechnen eine Kombination aus einer 5 × 5-Region. Dies ist nicht genau dieselbe Kombination, die ein 5x5-Filter berechnen würde, aber es lohnt sich, es auszuprobieren, da zwei aufeinanderfolgende 3x3-Filter günstiger sind als ein einzelner 5x5-Filter.

1x1-Faltungen?

fd7cac16f8ecb423.png

Mathematisch gesehen ist eine „1 × 1“-Konvolution eine Multiplikation mit einer Konstante, was nicht sehr nützlich ist. Denken Sie bei Convolutional Neural Networks jedoch daran, dass der Filter auf einen Datenwürfel und nicht nur auf ein 2D-Bild angewendet wird. Ein „1x1“-Filter berechnet daher eine gewichtete Summe einer 1x1-Datenspalte (siehe Abbildung). Wenn Sie den Filter über die Daten bewegen, erhalten Sie eine lineare Kombination der Kanäle der Eingabe. Das ist wirklich nützlich. Wenn Sie sich die Kanäle als Ergebnisse einzelner Filtervorgänge vorstellen, z. B. einen Filter für „spitzen Ohren“, einen weiteren für „Schnurrbärte“ und einen dritten für „Schlitzaugen“, dann berechnet eine „1x1“-Konvolutionsschicht mehrere mögliche lineare Kombinationen dieser Merkmale, was bei der Suche nach einer „Katze“ nützlich sein kann. Darüber hinaus verbrauchen 1 x 1-Ebenen weniger Gewichte.

11. Squeezenet

Eine einfache Möglichkeit, diese Ideen zusammenzuführen, wurde im Artikel „Squeezenet“ vorgestellt. Die Autoren schlagen ein sehr einfaches Faltungsmoduldesign vor, das nur 1x1- und 3x3-Faltungsebenen verwendet.

1730ac375379269b.png

Abbildung: SqueezeNet-Architektur auf Basis von „Fire-Modulen“. Sie wechseln abwechselnd eine 1 × 1-Schicht, die die eingehenden Daten in der vertikalen Dimension „drückt“, gefolgt von zwei parallelen 1 × 1- und 3 × 3-Faltungsebenen, die die Tiefe der Daten wieder „erweitern“.

Praxisorientiert

Fahren Sie mit Ihrem vorherigen Notebook fort und erstellen Sie ein von Squeezenet inspiriertes Convolutional Neural Network. Sie müssen den Modellcode in den „funktionalen Stil“ von Keras ändern.

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

Weitere Informationen

Für diese Übung ist es hilfreich, eine Hilfsfunktion für ein Squeezenet-Modul zu definieren:

def fire(x, squeeze, expand):
  y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
  y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
  y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
  return tf.keras.layers.concatenate([y1, y3])

# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
  return lambda x: fire(x, squeeze, expand)

# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)

Dieses Mal besteht das Ziel darin, eine Genauigkeit von 80% zu erreichen.

Neues ausprobieren

Beginnen Sie mit einer einzelnen Convolutional-Schicht, gefolgt von „fire_modules“, abwechselnd mit MaxPooling2D(pool_size=2)-Schichten. Sie können mit 2 bis 4 max. Pooling-Ebenen im Netzwerk und auch mit 1, 2 oder 3 aufeinanderfolgenden Brandmodulen zwischen den maximalen Pooling-Ebenen experimentieren.

In Auslösermodulen sollte der Parameter „squeeze“ in der Regel kleiner als der Parameter „expand“ sein. Bei diesen Parametern handelt es sich eigentlich um die Anzahl der Filter. Sie können in der Regel zwischen 8 und 196 liegen. Sie können mit Architekturen experimentieren, bei denen die Anzahl der Filter im Netzwerk allmählich zunimmt, oder mit einfachen Architekturen, bei denen alle Feuermodule die gleiche Anzahl von Filtern haben.

Hier ein Beispiel:

x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB

y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)

model = tf.keras.Model(x, y)

Möglicherweise stellen Sie jetzt fest, dass Ihre Tests nicht so gut laufen und dass das Ziel von 80 % Genauigkeit in weiter Ferne liegt. Zeit für ein paar weitere günstige Tricks.

Batchnormalisierung

Die Batch-Norm unterstützt Sie bei den Konvergenzproblemen, die bei Ihnen auftreten. Im nächsten Workshop werden wir diese Technik ausführlicher erläutern. Verwenden Sie sie vorerst als „magische“ Black-Box-Hilfe, indem Sie diese Zeile nach jeder Convolutional-Schicht in Ihrem Netzwerk hinzufügen, einschließlich der Schichten in Ihrer fire_module-Funktion:

y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context

Der Momentum-Parameter muss von seinem Standardwert von 0,99 auf 0,9 gesenkt werden, da unser Datensatz klein ist. Lassen Sie uns diese Details vorerst außer Acht lassen.

Datenergänzung

Wenn Sie die Daten mit einfachen Transformationen wie Links-Rechts-Umkehrung oder Sättigungsänderungen ergänzen, können Sie noch ein paar Prozentpunkte mehr herausholen:

4ed2958e09b487ca.png

ad795b70334e0d6b.png

In TensorFlow ist das mit der tf.data.Dataset API ganz einfach. Definieren Sie eine neue Transformationsfunktion für Ihre Daten:

def data_augment(image, label):
    image = tf.image.random_flip_left_right(image)
    image = tf.image.random_saturation(image, lower=0, upper=2)
    return image, label

Verwenden Sie ihn dann für die abschließende Datentransformation (Zelle "Trainings- und Validierungs-Datasets", Funktion "get_batched_dataset"):

dataset = dataset.repeat() # existing line
# insert this
if augment_data:
  dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line

Vergessen Sie nicht, die Datenerweiterung optional zu machen und den erforderlichen Code hinzuzufügen, damit nur das Trainings-Dataset erweitert wird. Es macht keinen Sinn, den Validierungsdatensatz zu erweitern.

Eine Genauigkeit von 80 % in 35 Epochen sollte jetzt in Reichweite sein.

Lösung

Hier ist das Lösungsnotizbuch. Sie können sie verwenden, wenn Sie nicht weiterkommen.

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

Behandelte Themen

  • 🤔 Keras-Modelle im „Funktionalen Stil“
  • 🤓 Squeezenet-Architektur
  • 🤓 Datenerweiterung mit tf.data.Dataset

Bitte nehmen Sie sich einen Moment Zeit, um diese Checkliste durchzugehen.

12. Xception optimiert

Separable Convolutionen

In letzter Zeit hat eine andere Methode zur Implementierung von Convolutional-Layers an Beliebtheit gewonnen: die tiefenseperierbaren Convolutionen. Ich weiß, es ist ein Bissen, aber das Konzept ist recht einfach. Sie sind in Tensorflow und Keras als tf.keras.layers.SeparableConv2D implementiert.

Bei einer trennbaren Convolution wird ebenfalls ein Filter auf das Bild angewendet, aber für jeden Kanal des Eingabebilds werden unterschiedliche Gewichte verwendet. Es folgt eine „1 × 1-Konvolution“, eine Reihe von Punktprodukten, die zu einer gewichteten Summe der gefilterten Kanäle führt. Bei jeder neuen Gewichtung werden so viele gewichtete Neukombinationen der Kanäle wie nötig berechnet.

615720b803bf8dda.gif

Illustration: Trennbare Faltungen. Phase 1: Faltungen mit einem separaten Filter für jeden Kanal. Phase 2: Lineare Kanalkombinationen Wird mit einem neuen Satz von Gewichtungen wiederholt, bis die gewünschte Anzahl von Ausgabekanälen erreicht ist. Phase 1 kann ebenfalls wiederholt werden, wobei jedes Mal eine neue Gewichtung erfolgt, aber in der Praxis ist dies selten der Fall.

Trennbare Faltungen werden in den neuesten Convolutional-Network-Architekturen verwendet: MobileNetV2, Xception, EffectiveNet. Übrigens haben Sie MobileNetV2 bereits für den Transfer-Lernprozess verwendet.

Sie sind kostengünstiger als reguläre Faltungen und in der Praxis hat sich als genauso effektiv erwiesen. Hier sehen Sie die Anzahl der Gewichtungen für das obige Beispiel:

CNN-Ebene: 4 × 4 × 3 × 5 = 240

Trennbare Convolutional Layer: 4 × 4 × 3 + 3 × 5 = 48 + 15 = 63

Es bleibt dem Leser überlassen, die Anzahl der Multiplikationen zu berechnen, die erforderlich sind, um die einzelnen Arten von Convolutional-Layer-Skalen auf ähnliche Weise anzuwenden. Trennbare Convolutionen sind kleiner und viel leistungsfähiger.

Praktische Übung

Führen Sie einen Neustart über das Playground-Notebook „Lerntransfer“ durch, wählen Sie dieses Mal aber Xception als vortrainiertes Modell aus. Xception verwendet nur separierbare Convolutionen. Lassen Sie alle Gewichte trainierbar. Wir werden die vortrainierten Gewichtungen für unsere Daten optimieren, anstatt die vortrainierten Ebenen als solche zu verwenden.

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

Ziel: Genauigkeit > 95% (Nein, im Ernst, das ist möglich!)

Dies ist die letzte Übung. Sie erfordert etwas mehr Arbeit im Code und in Data Science.

Weitere Informationen zur Feinabstimmung

Xception ist in den standardmäßigen vortrainierten Modellen in tf.keras.application verfügbar.* Vergiss nicht, dieses Mal alle Gewichte trainierbar zu machen.

pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
                                                  include_top=False)
pretrained_model.trainable = True

Um bei der Feinabstimmung eines Modells gute Ergebnisse zu erzielen, müssen Sie auf die Lernrate achten und einen Lernratenplan mit einer Anlaufzeit verwenden. Ein Beispiel:

9b1af213b2b36d47.png

Wenn Sie mit einer Standard-Lernrate beginnen, würden die vortrainierten Gewichtungen des Modells gestört werden. Bei der progressiven Methode werden die Daten so lange beibehalten, bis das Modell sie sinnvoll ändern kann. Nach der Erhöhung können Sie mit einer konstanten oder exponentiell abnehmenden Lernrate fortfahren.

In Keras wird die Lernrate über einen Callback angegeben, mit dem Sie die entsprechende Lernrate für jede Epoche berechnen können. Keras übergibt dem Optimierer für jede Epoche die richtige Lernrate.

def lr_fn(epoch):
  lr = ...
  return lr

lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)

model.fit(..., callbacks=[lr_callback])

Lösung

Hier ist das Lösungsnotizbuch. Sie können sie verwenden, wenn Sie nicht weiterkommen.

c3df49e90e5a654f.png 07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb

Behandelte Themen

  • 🤔 Tiefentrennbare Faltung
  • 🤓 Zeitpläne für die Lernrate
  • 😈 Vortrainiertes Modell optimieren

Bitte nehmen Sie sich einen Moment Zeit und gehen Sie diese Checkliste durch.

13. Glückwunsch!

Sie haben Ihr erstes modernes Convolutional Neural Network erstellt und es mit einer Genauigkeit von über 90% trainiert. Dank TPUs iterieren Sie aufeinanderfolgende Trainingsläufe in nur wenigen Minuten.

TPUs in der Praxis

TPUs und GPUs sind in Vertex AI von Google Cloud verfügbar:

Zu guter Letzt freuen wir uns über Feedback. Bitte teilen Sie uns mit, wenn Sie in diesem Lab etwas Ungewöhnliches bemerken oder der Meinung sind, dass es verbessert werden sollte. Sie können Feedback über GitHub-Probleme [ feedback link] geben.

HR.png

Martin Görner ID small.jpg
Der Autor: Martin Görner
Twitter: @martin_gorner