Updates dieser Seite:
Nachdem wir die Grundmechanismen von CNNs kennengelernt haben, sehen wir uns hier einige praktische Aspekte für das Training an. Zunächst sehen wir Regularisierungsmethoden wie Weight Decay und lernen, wie man die Lernrate dynamisch anpassen kann. Wir lernen ferner Methoden kennen, um den Effekt von Overfitting abzuschwächen: Data Augmentation, Dropout und Batch Normalization.
Weight Decay, Data Augmentation, Dropout, Batch Normalization
Wir haben L2-Regularisierung in Kapitel 4 als Teil der Fehlerfunktion definiert. Dort wurde bereits angemerkt, dass dies dazu dient, Overfitting zu mildern. Regularisierung bedeutet, dass hohe Gewicht ganz allgemein "bestraft" werden. So wird erreicht, dass keine zu großen Unterschiede unter den Gewichten entstehen. Wenn in einer Publikation von weight decay gesprochen wird, ist damit Regularisierung gemeint.
In Keras kann man L1- oder L2-Regularisierung verwenden oder beides gleichzeitig. Die relevanten Klassen finden sich im Paket keras.regularizers:
Man kann auch eigene Regularisierer über eine Funktion und eine Unterklasse von Regularizer definieren.
Die Regularisierung für jede Schicht spezifiziert. Man kann einen String Identifier übergeben:
layer = Dense(30, kernel_regularizer='l2')
Alternativ als Objekt, dann kann man den Regularisierungsfaktor (bei uns $\lambda$) angeben:
layer = Dense(30, kernel_regularizer=l2(l2=0.01))
Bei l1_l2 hat man zwei Argumente:
layer = Dense(30, kernel_regularizer=l1_l2(l1=0.01, l2=0.01))
Neben kernel_regularizer gibt es noch
https://keras.io/api/layers/regularizers
Artikel: Keras Weight Decay Hack
Mit LearningRateScheduler kann man die Lernrate dynmisch während des Trainings anpassen. Oft möchte man mit einer hohen Lernrate wie 0.1 oder 0.01 beginnen und später die Lernrate reduzieren, z.B. um Faktor 10 verkleinern. Die Idee ist, dass man sich in der Fehlerlandschaft erst schnell dem Minimum nähert und in der Nähe des Minimums langsamer wird, um nicht um das Minimum herum zu oszillieren.
In Keras definiert man zunächst eine Funktion mit zwei Parametern. Parameter 1 ist die aktuelle Epochenzahl, Parameter 2 die aktuelle Lernrate. Die Funktion gibt die neue Lernrate zurück.
Hier ein Beispiel, wo die Lernrate bei Epoche 10, 15, 20 ... um jeweils Faktor 10 veringert wird (also z.B. 0.1, 0.01, 0.001...)
def scheduler(epoch, lr):
if epoch >= 10 and epoch%5 == 0:
return lr/10.0
else:
return lr
Die obige Funktion wird in ein Objekt des Typs LearningRateScheduler (Paket keras.callbacks) gepackt. Als zweites Argument kann man einen Wert für verbose angeben (0: ohne Meldungen, 1: mit Meldungen).
Sie müssen dazu den LearningRateScheduler importiert haben:
from keras.callbacks import LearningRateScheduler
Anschließend kann man es für das Training mit fit der Liste von Callback-Funktion hinzufügen. Hier mit verbose=1:
model.fit(..., callbacks=[LearningRateScheduler(scheduler, 1)])
Das Problem des Overfittings besteht darin, dass ein Modell sich zu stark an die Trainingsdaten anpasst. So werden bestimmte Eigenarten eines Trainingsbildes zu bestimmenden Faktoren bei der Klassifikation. Klar ist, dass eine größere Menge an Daten immer hilft und daher ist eine Idee, die Menge der Trainingsdaten künstlich zu erhöhen, indem man systematisch Varianten von jedem Trainingsbeispiel herstellt.
Bei Bildern kommen zum Beispiel folgende Manipulationen in Frage, um Varianten herzustellen:
Diese Operationen können einzeln oder in Kombination angewandt werden, in der Regel mit zufälligen Parametern (z.B. Drehwinkel oder Verschiebungsvektor).
Zu beachten ist, dass die Bildgröße dabei gleich bleiben muss. Bei einigen Operationen wie Drehung oder Scherung entstehen "Lücken" im Bild, die entsprechend einer Systematik aufgefüllt werden müssen, z.B. indem man die nächsten Randpixel einfach kopiert.
Die folgende Abbildung zeigt ein Originalbild (links oben) und entsprechende Varianten.
Data Augmentation wurde u.a. bei den Netzen LeNet-5 und AlexNet eingesetzt.
In Keras stehen Ihnen Mechanismen zur Verfügung, um Data Augmentation relativ leicht einzusetzen.
Keras bietet dazu spezialisierte Preprocessing-Schichten an, die eine Vorverarbeitung wie Skalierung im Prozess der Forward Propagation einbauen. In diesem Fall findet also die Variantenbildung im Netz selbst statt (natürlich nur beim Training).
Siehe dazu: https://www.tensorflow.org/tutorials/images/data_augmentation
Ein zweite Möglichkeit ist die Variantenbildung während der Bereitstellung der Trainingsdaten. Die Klasse ImageDataGenerator (Paket tensorflow.keras.preprocessing.image) stellt beim Training die Trainingsbeispiele bereits (als Batches) und nimmt die Variantenbildung automatisiert vor. Man kann dort verschiedene Operationen mit entsprechenden Grenzwerten (z.B. für den Drehwinkel) definieren und beim Training werden dann die Varianten on-the-fly gebildet.
Siehe auch: https://keras.io/api/preprocessing/image
Dropout ist eine sehr effektive Methode gegen Overfitting aus der Arbeitsgruppe von Geoffrey Hinton (University of Toronto) mit der Idee, ein Netz in jedem Batch-Durchlauf nur mit einer zufälligen Teilmenge der Neuronen zu trainieren (Hinton et al. 2012, Srivastava et al., 2014).
Ein Gedanke dahinter ist, dass ein einzelnes Neuron so lernen muss, in verschiedenen Kontexten zu arbeiten, weil mal dieses Nachbarneuron ausfällt und mal jenes. Es kann sich also nicht darauf verlassen, mit einem ganz bestimmten Neuron eine klare Aufgabenteilung einzurichten (das nennt man auch co-adaptation, daher das preventing co-adaptation im Titel von Hinton et al. 2012). Umgekehrt heißt das, die Fähigkeit zur Generalisierung bzw. zur Zusammenarbeit mit vielen andern Neuronen, wird gestärkt.
Jedes Neuron wird mit einer bestimmten Wahrscheinlichkeit ein- oder ausgeschaltet. Wir nehmen $p$ als Wahrscheinlichkeit, dass ein Neuron aktiv ist. Wenn z.B. $p = 0.5$ ist, dann ist im Mittel immer nur die Hälfte des Netzwerks aktiv, d.h. das Netz ist ausgedünnt.
Hier sehen wir ein Beispiel:
In jedem Trainingsdurchlauf wird das ausgedünnte Netz erst neu bestimmt und dann trainiert. Man kann sich überlegen, dass es bei $n$ Neuronen $2^n$ verschiedene Varianten von ausgedünnten Netzen gibt. Man kann sich also vorstellen, dass $2^n$ verschiedene Netze gleichzeitig trainiert werden und sich dabei die Gewichte teilen.
In unseren Formeln für Forward Propagation haben wir folgende Formel für die Aktivierungen:
$$ a^{(l)} = g(z^{(l)}) = \left( \begin{array}{c} g(z_1^{(l)}) \\ \vdots \\ g(z_{n_l}^{(l)}) \end{array} \right)$$Hier würden wir einen Bernoulli-Vektor $B(p)$ einbauen, der Nullen und Einsen mit der Wahrscheinlichkeitsverteilung $p$ (für die Einsen) enthält. Ein Beispiel für einen Bernoulli-Vektor mit $p=0.75$ wäre
$$ B(0.75) = \begin{pmatrix} 1 \\ 1 \\ 0 \\ 1 \end{pmatrix} $$Dann können wir die Aktivierung $\tilde{a}$ unseres ausgedünnten Netzes schreiben als:
$$ \tilde{a}^{(l)} = B(p) \odot \left( \begin{array}{c} g(z_1^{(l)}) \\ \vdots \\ g(z_{n_l}^{(l)}) \end{array} \right)$$Dabei ist $\odot$ die elementweise Multiplikation, d.h. es werden ganz einfach einige der Komponenten des rechten Vektors "ausgeschaltet" (= auf Null gesetzt).
Beim Training wird für jeden Minibatch-Durchlauf eine Konfiguration über die Bernoulli-Vektoren hergestellt und Backpropagation wird genau auf diesem ausgedünnten Netz durchgeführt. Das heißt, es werden bei Backpropagation nur die Gewichte zwischen den "aktiven" Neuronen angepasst.
Methoden wie Momentum und Decay können auch zusammen mit Dropout angewendet werden.
Beim Testen - oder allgemein beim Berechnen einer Vorhersage - können für eine Eingabe nicht alle $2^n$ möglichen Netze durchlaufen werden. Stattdessen wird das vollständige Netz genommen und jedes Gewicht mit dem Faktor $p$ multipliziert, also effektiv runterskaliert. Das ist notwendig, weil beim Training die Rohinputs eine um $p$ reduzierte "Masse" an Eingaben der vorigen Schicht bekommen haben (also zum Beispiel nur die Hälfte bei $p=0.5$).
In unseren Formeln für Forward Propagation müsste $p$ also beim Rohinput eingebaut werden, zum Beispiel so:
$$ z_i^{(l)} = p \, \sum_{j=1}^{n_{l-1}} w^{(l-1)}_{i,j} \; a_j^{(l-1)} + b_i^{(l-1)} $$Wie schon erwähnt brauchen wir dies, weil im Training weniger Neuronen/Gewichte aktiv waren und beim vollständigen Netz sonst viel mehr Aktivierung aufkommt als im Training. Bei $p=0.75$ sind nur 75% aller Neuronen im Training aktiv gewesen, also reduzieren wir die Aktivität bei der Vorhersage um 25%.
In Keras kann man Dropout an bestimmte Schichten hängen. Dropout wird als eigene Schicht umgesetzt - eine sogenannte Regularisierungsschicht. Für jede Dropout-Schicht legt man den Wert $p$ fest.
Wichtig ist, dass im Gegensatz zur obigen Darstellung das $p$ bei Keras bestimmt, mit welcher Wahrscheinlichkeit Neuronen deaktiviert werden. Das heißt bei $p=0.25$ sind 75% der Neuronen aktiv.
Hier ein Beispielnetz mit Dropout an zwei Stellen (jeweils $p=0.25$):
model = Sequential()
model.add(Conv2D(filters=30,
kernel_size=5,
activation='relu',
padding='same',
input_shape=x_train[0].shape))
model.add(MaxPool2D(pool_size=2))
...
model.add(MaxPool2D(pool_size=2, strides=2))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(200, activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(10, activation='softmax'))
Siehe: https://keras.io/api/layers/regularization_layers/dropout
Dropout wird ausschließlich zwischen FC-Schichten eingesetzt, um dort die Dichte und effektive Parameterzahl zu reduzieren. Dies ist bei Konvolutionsschichten nicht sinnvoll, da wir dort eine reduzierte Parameterzahl haben, die auch mehrfach angewendet werden.
Dropout reduziert Overfitting, führt aber auch zu einer längeren Trainingszeit. In AlexNet wird Dropout zwischen zwei FC-Schichten eingesetzt und Krizhevsky et al. (2014) sagen dazu: "Without dropout, our network exhibits substantial overfitting. Dropout roughly doubles the number of iterations required to converge."
Welchen Wert wählt man für $p$? Wir sehen hier $p$ im Sinne von Keras, also für den Anteil der deaktivierten Neuronen. Hinten et al. (2012) haben Dropout mit $p=0.5$ eingeführt und dieser Wert wird auch in Srivastava et al. (2014) empfohlen. In den beiden Papers werden umfangreiche Experimente mit $p=0.5$ vorgestellt. In der Praxis sieht man auch Werte unter $0.5$, zum Beispiel $p = 0.25$.
Batch Normalization ist ein wirkungsvolles Verfahren, um Overfitting abzuschwächen. Man kann es auch als Alternative zu Dropout betrachten.
Wir haben gesehen, dass es sinnvoll ist, die Featurevektoren unserer Trainingsdaten zu skalieren, entweder durch Normalisierung (auf den Bereich [0, 1]) oder durch Standardisierung (mit Hilfe von Mittelwert und Varianz).
Die Grundidee von Batch Normalization ist es nun, etwas ähnliches für die Ausgabewerte jeder Schicht zu tun. Das heißt zum Beispiel für Schicht $l$, dass wir alle berechneten Werte $z^l$ (oder $a^l$) nehmen und zunächst skalieren, bevor sie in die nächste Schicht $l+1$ gehen, so dass die weitere Verarbeitung (d.h. das Lernen der Gewichte) schneller/kontrollierter vonstatten geht. Man nimmt tatsächlich die Rohinputs $z^l$ (nicht die Aktivierungen $a^l$) und skaliert mit Hilfe von Mittelwert und Varianz. Diese Mittelwerte werden in der Praxis auf Mini-Batches berechnet und nicht dem gesamten Trainingsdatensatz. Wichtig ist noch, dass für das Verfahren zwei neue Parameter pro Schicht hinzukommen: Gamma $\gamma$ und Beta $\beta$. Durch den Parameter $\beta$ wird das entsprechende Bias-Neuron der jeweiligen Schicht überflüssig, so dass bei Anwendung von Batch Normalization die Bias-Gewichte weggelassen werden.
Die Methode Batch Normalization kommt von Ioffe und Szegedy (2015), beide von Google. Hier das Abstract des Papers (Hervorhebungen von mir):
Training Deep Neural Networks is complicated by the fact that the distribution of each layer’s inputs changes during training, as the parameters of the previous layers change. This slows down the training by requiring lower learning rates and careful parameter initialization, and makes it notoriously hard to train models with saturating nonlinearities. We refer to this phenomenon as internal covariate shift, and address the problem by normalizing layer inputs. Our method draws its strength from making normalization a part of the model architecture and performing the normalization for each training mini-batch. Batch Normalization allows us to use much higher learning rates and be less careful about initialization. It also acts as a regularizer, in some cases eliminating the need for Dropout. Applied to a state-of-the-art image classification model, Batch Normalization achieves the same accuracy with 14 times fewer training steps, and beats the original model by a significant margin. Using an ensemble of batch-normalized networks, we improve upon the best published result on ImageNet classification: reaching 4.9% top-5 validation error (and 4.8% test error), exceeding the accuracy of human raters.
Hier ist ein guter Artikel, inklusive eines Benchmarking: One simple trick to train Keras model faster with Batch Normalization
Der Autor weist auf Folgendes hin:
Batch normalization uses weights as usual but does NOT add a bias term. This is because its calculations include gamma and beta variables that make the bias term unnecessary.
We add the normalization before calling the activation function.
Er zeigt dann, wie das für FC-Schichten und Konv-Schichten aussehen muss:
FC-Schicht normalerweise:
model.add(layers.Dense(64, activation='relu'))
FC-Schicht mit Batch Normalization:
model.add(layers.Dense(64, use_bias=False))
model.add(layers.BatchNormalization())
model.add(Activation("relu"))
Konv-Schicht normalerweise:
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
Konv-Schicht mit Batch Normalization:
model.add(layers.Conv2D(64, (3, 3), use_bias=False))
model.add(layers.BatchNormalization())
model.add(layers.Activation("relu"))
Anmerkung: In dem Artikel Achieving 90% accuracy in Object Recognition Task on CIFAR-10 Dataset with Keras wird teils anders vorgegangen - unbedingt vergleichen.
Die Standardisierung wird über einen Batch vorgenommen. Ein Batch ist einfach eine Teilmenge der Trainingsdaten. Mit $m$ bezeichnen wir die Größe des Batches. Die Ausgangslage besteht also aus $m$ Aktivierungsvektoren, z.B. $z^1, \ldots, z^{32}$ für den ersten Batch bei einer Batchgröße von 32. Wir bezeichnen mit $B$ die Menge der Indizes eines Batches und berechnen zunächst Mittelwert und Varianz - nur für diesen einen Batch:
$$ \begin{align} \mu &= \frac{1}{m} \sum_{k \in B} z^k \\[3mm] \sigma^2 & =\frac{1}{m} \sum_{k \in B} (z^k - \mu_B)^2 \end{align} $$Im Grunde tut man dies für jede Komponente eines Vektors $z$ separat, d.h. wir können auch die Komponenten der Vektoren betrachten. Wir nennen $n$ die Anzahl der Neuronen in der aktuellen Schicht. Dann gilt für $i \in {1, \ldots, n}$:
$$ \begin{align} \mu_i &= \frac{1}{m} \sum_{k \in B} z_i^k \\[3mm] \sigma_i^2 & =\frac{1}{m} \sum_{k \in B} (z_i^k - \mu_i)^2 \end{align} $$Jetzt verändern wir die Aktivierungswerte $z_i^k$ für alle $k$ in dem Batch $B$ und nennen sie $\hat{z}_i$:
$$ \hat{z}_i^k = \frac{x_i^k - \mu_i}{\sqrt{\sigma_i^2 + \epsilon}} $$Der endgültige neue Wert $\tilde{z}_i^k$ wird noch einmal skaliert - mit Faktor $\gamma$ - und verschoben - mit dem Wert $\beta$.
$$ \tilde{z}_i^k = \gamma \hat{z}_i^k + \beta = BN_{\gamma, \beta}(x_i^k) $$Bei der Forward Propagation werden nach Verarbeitung eines Batch alle Aktivierungen $z$ durch $\tilde{z}$ ausgetauscht. Im Lernschritt werden auch $\gamma$ und $\beta$ mit Hilfe von Gradientenabstieg angepasst, d.h. die Parameter $\gamma$ und $\beta$ werden auch gelernt.
https://keras.io/api/layers/normalization_layers/batch_normalization
Andrews Ng beschreibt das Verfahren in den folgenden drei Videos:
Geoffrey E. Hinton, Nitish Srivastava, Alex Krizhevsky, Ilya Sutskever, Ruslan Salakhutdinov (2012) Improving neural networks by preventing co-adaptation of feature detectors In: CoRR abs/1207.0580
Sergey Ioffe, Christian Szegedy (2015) Batch normalization: accelerating deep network training by reducing internal covariate shift. In: Proceedings of the 32nd International Conference on International Conference on Machine Learning (ICML), pp. 448–456.
Nitish Srivastava, Geoffrey Hinton, Alex Krizhevsky, Ilya Sutskever, and Ruslan Salakhutdinov (2014) Dropout: a simple way to prevent neural networks from overfitting. In: J. Mach. Learn. Res. 15, 1, 1929–1958.