Mit TensorFlow, einer von Google entwickelten Deep-Learning-Bibliothek, können Sie Modelle erstellen und Trainingsschleifen auf verschiedene Arten implementieren. Dies ist nützlich für Experten, kann jedoch für Anfänger ein Hindernis für das Verständnis sein.
Dieses Mal werden wir den von TensorFlow 2.X empfohlenen Schreibstil umfassend vorstellen und seine Verwendung bei der Implementierung von VGG16 und ResNet50 erläutern, die auf dem Gebiet der Bilderkennung bekannte Modelle sind.
Schauen wir uns zunächst die vier Modellbildungs-APIs von TensorFlow an. Danach werde ich zwei Trainingsmethoden erklären. Schließlich werden wir VGG16 und ResNet50 mit diesen Schreibstilen implementieren.
>>> import sys
>>> sys.version
'3.7.7 (default, Mar 10 2020, 15:43:33) \n[Clang 11.0.0 (clang-1100.0.33.17)]'
pip list | grep tensorflow
tensorflow 2.2.0
tensorflow-estimator 2.2.0
TensorFlow bietet zwei Haupt-APIs zum Erstellen von Modellen und vier unterteilte APIs.
Zunächst werde ich kurz die wichtigsten Klassifikationen vorstellen.
Dies ist eine Schreibmethode, die die Form des Modells deklariert (≒ kompiliert), bevor das Training ausgeführt wird.
Modelle, die mit dieser API geschrieben wurden, können während des Trainings ihre Form nicht ändern. Daher können einige sich dynamisch ändernde Modelle (z. B. Tree-RNN) nicht implementiert werden. Stattdessen können Sie die Modellform überprüfen, bevor Sie die Daten an das Modell übergeben.
Im Gegensatz zur symbolischen API handelt es sich um einen zwingenden (≒ intuitiven) Schreibstil, der nicht deklariert wird.
Dies war der erste Schreibstil, der von Chainer, einer aus Japan stammenden Deep-Learning-Bibliothek (Preferred Networks), übernommen wurde, und PyTorch übernahm auch diesen Schreibstil. Sie können das Modell so implementieren, als würden Sie eine Klasse in Python schreiben. Dies erleichtert das Anpassen von Ebenenänderungen und -erweiterungen. Stattdessen kann das Programm erst erkennen, wie das Modell aussehen wird, wenn die Daten einmal angegeben wurden.
Als nächstes werde ich eine konkrete Schreibmethode mit einem einfachen Beispiel vorstellen.
Sequential API Wie der Name schon sagt, handelt es sich um eine API, die ein Modell implementiert, indem sie Sequential Layer hinzufügt. Dies wird häufig in Keras- und TensorFlow-Tutorials verwendet, sodass Sie es möglicherweise einmal gesehen haben.
Wie unten gezeigt, fügen Sie nach dem Instanziieren einer leeren Klasse "tensorflow.keras.Sequential" Ebenen mit der Methode "add" hinzu und geben Sie den Argumenten der Klasse "tensorflow.keras.Sequential" Ebenen als Liste. Es ist üblich zu instanziieren.
import tensorflow as tf
from tensorflow.keras import layers
def sequential_vgg16_a(input_shape, output_size):
model = tf.keras.Sequential()
model.add(layers.Conv2D(64, 3, 1, padding="same", batch_input_shape=input_shape))
model.add(layers.BatchNormalization())
# ...(Unterlassung)...
model.add(layers.Dense(output_size, activation="softmax"))
return model
def sequential_vgg16_b(input_shape, output_size):
model = tf.keras.Sequential([
layers.Conv2D(64, 3, 1, padding="same", batch_input_shape=input_shape),
layers.BatchNormalization(),
# ...(Unterlassung)...
layers.Dense(output_size, activation="softmax")
]
return model
Es werden nur Methoden zum Hinzufügen von Ebenen unterstützt, sodass Sie keine komplexen Netzwerke mit mehreren Eingaben, Zwischenfunktionen, mehreren Ausgaben oder bedingten Verzweigungen schreiben können. Mit dieser API können Sie ein einfaches Netzwerk (wie VGG) implementieren, das die Ebenen nacheinander durchläuft.
Functional API Eine API, die komplexe Modelle implementiert, die von der sequentiellen API nicht beschrieben werden können.
Instanziieren Sie zuerst "tensorflow.keras.layers.Input" und übergeben Sie es an die erste Ebene. Danach wird der Datenfluss des Modells definiert, indem die Ausgabe einer Schicht an die nächste Schicht übergeben wird. Schließlich können Sie das Modell erstellen, indem Sie die resultierende Ausgabe und die erste Eingabe als Argumente für "tensorflow.keras.Model" angeben.
from tensorflow.keras import layers, Model
def functional_vgg16(input_shape, output_size, batch_norm=False):
inputs = layers.Input(batch_input_shape=input_shape)
x = layers.Conv2D(64, 3, 1, padding="same")(inputs)
if batch_norm:
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
# ...(Unterlassung)...
outputs = layers.Dense(output_size, activation="softmax")(x)
return Model(inputs=inputs, outputs=outputs)
Im obigen Beispiel ändert der Wert der Variablen "batch_norm" das Vorhandensein oder Fehlen der Batch-Normalisierungsschicht. Wenn Sie eine flexible Definition benötigen, die die Form des Modells abhängig von den Bedingungen ändert, benötigen Sie die funktionale API anstelle der sequentiellen API.
Beachten Sie, dass es eine scheinbar seltsame Art gibt, Klammern gefolgt von Klammern zu schreiben. Dies ist jedoch nicht TensorFlow-spezifisch und wird häufig in Python verwendet. Die folgenden beiden stehen für dasselbe.
#Wie schreibe ich 1
x = layers.BatchNormalization()(x)
#Wie schreibe ich 2
layer = layers.BatchNormalization()
x = layer(x)
Primitive API Diese API wurde hauptsächlich in der TensorFlow 1.X-Serie verwendet. ** 2.X-Serie ist derzeit veraltet. ** **.
Die oben erwähnte sequentielle API und funktionale API könnten das Modell definieren, indem sie den Datenfluss beschreiben, der durch das Modell läuft, aber die primitive API beschreibt deklarativ den gesamten Verarbeitungsfluss einschließlich anderer rechnerischer Verarbeitung. Machen.
Es gibt nicht viel Verdienst, diese Schreibmethode von nun an zu lernen, daher werde ich die Erklärung weglassen, aber wenn Sie mit tensorflow.Session
trainieren, ist diese Schreibmethode anwendbar.
import tensorflow as tf
sess = tf.Session()
Subclassing API Eine API, die mit dem Update auf TensorFlow 2.X verfügbar wurde. Es ist ähnlich wie Chainer und PyTorch geschrieben und intuitiv und einfach anzupassen, da Sie das Modell so implementieren können, als würden Sie eine Klasse in Python schreiben.
Erstellen Sie zunächst eine Klasse, indem Sie tensorflow.keras.Model
erben.
Erstellen Sie dann das Modell, indem Sie die Methoden __init__
und call
implementieren.
Die Methode init in der Klasse ruft die Methode init der übergeordneten Klasse auf und registriert die Ebene, die Sie lernen möchten. ** Ebenengewichte, die hier nicht aufgeführt sind, werden standardmäßig nicht trainiert. ** **.
Die call
-Methode in der Klasse beschreibt die Vorwärtsausbreitung von Schichten. (Ähnlich wie Chainers __call__
, PyTorchs forward
.)
from tensorflow.keras import layers, Model
class VGG16(Model):
def __init__(self, output_size=1000):
super().__init__()
self.layers_ = [
layers.Conv2D(64, 3, 1, padding="same"),
layers.BatchNormalization(),
# ...(Unterlassung)...
layers.Dense(output_size, activation="softmax"),
]
def call(self, inputs):
for layer in self.layers_:
inputs = layer(inputs)
return inputs
Es sieht im Vergleich zu anderen Schreibstilen etwas redundant aus, aber Sie können sehen, dass Sie das Modell so implementieren können, als würden Sie eine Klasse normal schreiben.
Die "super" -Methode, die die übergeordnete Klasse initialisiert, hat auch ein Muster, das ein Argument angibt. Dies wird jedoch unter Berücksichtigung des Python der 2. Serie geschrieben, und im Python der 3. Serie wird dieselbe Verarbeitung ohne Argument ausgeführt.
from tensorflow.keras import Model
#Wie schreibe ich Python 3-Serien
class VGG16_PY3(Model):
def __init__(self, output_size=1000):
super().__init__()
#Wie schreibe ich Python 2-Serien
class VGG16_PY2(Model):
def __init__(self, output_size=1000):
super().__init__(VGG16_PY2, self)
Damit ist die Erklärung zum Erstellen eines Modells abgeschlossen. Zusammenfassend denke ich, dass Sie es wie folgt richtig verwenden können.
Es gibt zwei Möglichkeiten zu trainieren:
Dies ist eine Trainingsmethode mit der integrierten Funktion von tensorflow.keras.Model.
Viele von Ihnen wissen es vielleicht, weil es auch in den Keras- und TensorFlow-Tutorials verwendet wird. Obwohl es sich um eine andere Bibliothek handelt, verwendet scikit-learn auch diese Methode.
Instanziieren Sie zunächst das von der obigen API implementierte Modell (tensorflow.keras.Model
oder ein Objekt, das es erbt).
Diese Instanz verfügt über eine Kompilierungs- und eine Anpassungsmethode als integrierte Funktionen.
Führen Sie diese Kompilierungsmethode aus, um die Verlustfunktion, die Optimierungsfunktion und die Metrik zu registrieren. Trainieren Sie dann, indem Sie die Methode "fit" ausführen.
import tensorflow as tf
(train_images, train_labels), _ = tf.keras.datasets.cifar10.load_data()
#Ich benutze ein trainiertes Modell zur Veranschaulichung
model = tf.keras.applications.VGG16()
model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
model.fit(train_images, train_labels)
Sie können jetzt das Training durchführen.
Das Festlegen der Stapelgröße, der Anzahl der Epochen, das Registrieren der Rückruffunktion, das Auswerten mit Validierungsdaten usw. kann als Schlüsselwortargumente der "fit" -Methode registriert werden, sodass einige Anpassungen möglich sind.
In vielen Fällen mag dies ausreichend sein, aber Fälle, die nicht in diesen Rahmen passen (z. B. Fälle, in denen mehrere Modelle wie GAN gleichzeitig trainiert werden), müssen in der später beschriebenen benutzerdefinierten Schulung beschrieben werden.
Es hat keine spezielle API, es ist nur eine normale Art, mit einer Python for-Schleife zu trainieren.
Instanziieren Sie zunächst das von der obigen API implementierte Modell (tensorflow.keras.Model
oder ein Objekt, das es erbt).
Als nächstes stapeln Sie zusätzlich zur Definition der Verlustfunktion und der Optimierungsfunktion den Datensatz. Danach werden Epoche und Batch in einer for-Schleife gedreht.
Beschreiben Sie in der for-Schleife zunächst den Vorwärtsausbreitungsprozess im Bereich "tf.GradientTape". Anschließend wird die Methode "gradient" aufgerufen, um den Gradienten zu berechnen, und die Methode "apply_gradients" aktualisiert die Gewichte gemäß der Optimierungsfunktion.
import tensorflow as tf
batch_size = 32
epochs = 10
(train_images, train_labels), _ = tf.keras.datasets.cifar10.load_data()
#Ich benutze ein trainiertes Modell zur Veranschaulichung
model = tf.keras.applications.VGG16()
buffer_size = len(train_images)
train_ds = tf.data.Dataset.from_tensor_slices((train_images, train_labels))
train_ds = train_ds.shuffle(buffer_size=buffer_size).batch(batch_size)
criterion = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam()
for epoch in range(epochs):
for x, y_true in train_ds:
with tf.GradientTape() as tape:
y_pred = model(x, training=True)
loss = criterion(y_true=y_true, y_pred=y_pred)
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
Sie können jetzt das Training durchführen.
Im obigen Beispiel werden die Validierungsdaten überhaupt nicht ausgewertet und die Ausgabe an TensorBoard wird überhaupt nicht ausgeführt. Da die for-Schleife jedoch normalerweise gedreht wird, können Sie die Verarbeitung nach Belieben hinzufügen.
Andererseits wird es ein wenig schwierig, die Qualität des Quellcodes zu garantieren, da die Menge der Beschreibung unvermeidlich groß ist. Chainer und PyTorch können fast gleich geschrieben werden (obwohl es geringfügige Unterschiede gibt).
Damit ist die Erläuterung der Trainingsmethode abgeschlossen. Zusammenfassend denke ich, dass Sie es wie folgt richtig verwenden können.
Ich denke, es gibt einige Teile, die allein aus der Erklärung nicht verstanden werden können, daher werde ich mein Verständnis durch Implementierung vertiefen.
Beginnen wir mit einer kurzen Einführung in die beiden Modelle.
Es ist ein Hochleistungsmodell mit einer sehr einfachen Struktur, die 13 Schichten 3x3-Faltung und 3 Schichten vollständig verbundener Schichten aufweist. Es wird verwendet, um Bildmerkmale in verschiedenen Bilderkennungsaufgaben zu extrahieren. Das Originalpapier hat über 37.000 Zitate und ist sehr bekannt.
Es kann mit der Sequential API, der Functional API und der Subclassing API implementiert werden.
Das Originalpapier ist hier. https://arxiv.org/abs/1409.1556
Dies ist ein mehrschichtiges Modell mit einem Restmechanismus (49 Schichten für Faltung und 1 Schicht für vollständig verbundene Schichten). Ab 2020 ist diese ResNet-Variante immer noch eine der besten in Bezug auf die Genauigkeit der Bildklassifizierung und auch ein Hochleistungsmodell. Ähnlich wie VGG16 wird es zum Extrahieren von Bildmerkmalen bei verschiedenen Bilderkennungsaufgaben verwendet. Das Originalpapier enthält mehr als 45.000 Zitate (etwa das Zehnfache von BERT), was ebenfalls sehr berühmt ist.
Es kann nicht allein von der Sequential API implementiert werden. Es kann mit der Functional API und der Subclassing API implementiert werden.
Das Originalpapier ist hier. https://arxiv.org/abs/1512.03385
Lassen Sie es uns in jedem Schreibstil implementieren.
VGG16 Sequential API
Ich muss nicht darüber nachdenken, also schreibe ich es normal.
from tensorflow.keras import layers, Sequential
def sequential_vgg16(input_shape, output_size):
params = {
"padding": "same",
"use_bias": True,
"kernel_initializer": "he_normal",
}
model = Sequential()
model.add(layers.Conv2D(64, 3, 1, **params, batch_input_shape=input_shape))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(64, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.MaxPool2D(2, padding="same"))
model.add(layers.Conv2D(128, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(128, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.MaxPool2D(2, padding="same"))
model.add(layers.Conv2D(256, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(256, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(256, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.MaxPool2D(2, padding="same"))
model.add(layers.Conv2D(512, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(512, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(512, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.MaxPool2D(2, padding="same"))
model.add(layers.Conv2D(512, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(512, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.Conv2D(512, 3, 1, **params))
model.add(layers.BatchNormalization())
model.add(layers.ReLU())
model.add(layers.MaxPool2D(2, padding="same"))
model.add(layers.Flatten())
model.add(layers.Dense(4096))
model.add(layers.Dense(4096))
model.add(layers.Dense(output_size, activation="softmax"))
return model
Es ist ziemlich einfach zu schreiben, aber es stellt sich heraus, dass es aufgrund der vielen Ebenen schwer zu sehen ist. Zum Beispiel scheinen Sie nicht zu bemerken, ob "ReLU" irgendwo fehlt. Wenn Sie beispielsweise die "Batch-Normalisierung" beseitigen möchten, müssen Sie diese zeilenweise auskommentieren, was schlecht wiederverwendbar und anpassbar ist.
VGG16 Functional API Es ist flexibler zu schreiben als die sequentielle API. Verwenden wir dieses Mal eine Gruppe von Ebenen, die als Funktion wiederverwendet werden sollen (Convolution --Batch Normalization --ReLU).
from tensorflow.keras import layers, Model
def functional_cbr(x, filters, kernel_size, strides):
params = {
"filters": filters,
"kernel_size": kernel_size,
"strides": strides,
"padding": "same",
"use_bias": True,
"kernel_initializer": "he_normal",
}
x = layers.Conv2D(**params)(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
return x
def functional_vgg16(input_shape, output_size):
inputs = layers.Input(batch_input_shape=input_shape)
x = functional_cbr(inputs, 64, 3, 1)
x = functional_cbr(x, 64, 3, 1)
x = layers.MaxPool2D(2, padding="same")(x)
x = functional_cbr(x, 128, 3, 1)
x = functional_cbr(x, 128, 3, 1)
x = layers.MaxPool2D(2, padding="same").__call__(x) #Sie können so schreiben
x = functional_cbr(x, 256, 3, 1)
x = functional_cbr(x, 256, 3, 1)
x = functional_cbr(x, 256, 3, 1)
x = layers.MaxPool2D(2, padding="same").call(x) #Sie können so schreiben
x = functional_cbr(x, 512, 3, 1)
x = functional_cbr(x, 512, 3, 1)
x = functional_cbr(x, 512, 3, 1)
x = layers.MaxPool2D(2, padding="same")(x)
x = functional_cbr(x, 512, 3, 1)
x = functional_cbr(x, 512, 3, 1)
x = functional_cbr(x, 512, 3, 1)
x = layers.MaxPool2D(2, padding="same")(x)
x = layers.Flatten()(x)
x = layers.Dense(4096)(x)
x = layers.Dense(4096)(x)
outputs = layers.Dense(output_size, activation="softmax")(x)
return Model(inputs=inputs, outputs=outputs)
Ich konnte ganz klar schreiben. Wenn Sie "BatchNormalization" entfernen oder "ReLU" in "LeaklyReLU" ändern möchten, müssen Sie nur einige Zeilen korrigieren.
VGG16 Subclassing API Schreiben wir eine Gruppe von Ebenen (Convolution --BatchNormalization --ReLU), die wie die Functional API als Klasse wiederverwendet werden.
from tensorflow.keras import layers, Model
class CBR(Model):
def __init__(self, filters, kernel_size, strides):
super().__init__()
params = {
"filters": filters,
"kernel_size": kernel_size,
"strides": strides,
"padding": "same",
"use_bias": True,
"kernel_initializer": "he_normal",
}
self.layers_ = [
layers.Conv2D(**params),
layers.BatchNormalization(),
layers.ReLU()
]
def call(self, inputs):
for layer in self.layers_:
inputs = layer(inputs)
return inputs
class VGG16(Model):
def __init__(self, output_size=1000):
super().__init__()
self.layers_ = [
CBR(64, 3, 1),
CBR(64, 3, 1),
layers.MaxPool2D(2, padding="same"),
CBR(128, 3, 1),
CBR(128, 3, 1),
layers.MaxPool2D(2, padding="same"),
CBR(256, 3, 1),
CBR(256, 3, 1),
CBR(256, 3, 1),
layers.MaxPool2D(2, padding="same"),
CBR(512, 3, 1),
CBR(512, 3, 1),
CBR(512, 3, 1),
layers.MaxPool2D(2, padding="same"),
CBR(512, 3, 1),
CBR(512, 3, 1),
CBR(512, 3, 1),
layers.MaxPool2D(2, padding="same"),
layers.Flatten(),
layers.Dense(4096),
layers.Dense(4096),
layers.Dense(output_size, activation="softmax"),
]
def call(self, inputs):
for layer in self.layers_:
inputs = layer(inputs)
return inputs
Es ist intuitiver zu verstehen als die Funktions-API, da "init" für die Definition des Modells und "call" für den Aufruf des Modells verantwortlich ist, der Code jedoch länger ist. Ein weiterer Punkt ist, dass die Anweisungs-Subclassing-API beim Generieren eines Modells keine Eingabeform benötigt (sie benötigt keine "input_shape" als Argument).
Ich wollte es so einfach wie möglich machen, aber wie war es?
Diese Implementierung verwendet die Chargennormalisierung zwischen den Faltungsschichten und die He-Initialisierung für die Gewichtsinitialisierung. Diese Techniken wurden jedoch noch nicht veröffentlicht, als das Originalpapier eingereicht wurde. Es gab also keine Batch-Normalisierungsschicht und die Grolot-Initialisierung wurde verwendet, um die Gewichte zu initialisieren. Daher wird in der Originalarbeit eine lernähnliche Lernmethode für den Transfer angewendet, bei der ein 7-Schicht-Modell trainiert und anschließend schrittweise Schichten hinzugefügt werden, um ein Verschwinden des Gradienten zu vermeiden.
Es wäre interessant auszuprobieren, was passiert, wenn Sie die Ebene "Stapelnormalisierung" entfernen, was passiert, wenn Sie die Gewichtsinitialisierungsmethode usw. ändern, um die obige Implementierung besser zu verstehen.
Implementieren Sie dann ResNet50. Da es nicht allein von der sequentiellen API geschrieben werden kann, wird es von der funktionalen API und der Unterklassen-API geschrieben.
ResNet50 Functional API Funktionalisieren und implementieren Sie den wiederverwendeten Restmechanismus.
from tensorflow.keras import layers, Model
def functional_bottleneck_residual(x, in_ch, out_ch, strides=1):
params = {
"padding": "same",
"kernel_initializer": "he_normal",
"use_bias": True,
}
inter_ch = out_ch // 4
h1 = layers.Conv2D(inter_ch, kernel_size=1, strides=strides, **params)(x)
h1 = layers.BatchNormalization()(h1)
h1 = layers.ReLU()(h1)
h1 = layers.Conv2D(inter_ch, kernel_size=3, strides=1, **params)(h1)
h1 = layers.BatchNormalization()(h1)
h1 = layers.ReLU()(h1)
h1 = layers.Conv2D(out_ch, kernel_size=1, strides=1, **params)(h1)
h1 = layers.BatchNormalization()(h1)
if in_ch != out_ch:
h2 = layers.Conv2D(out_ch, kernel_size=1, strides=strides, **params)(x)
h2 = layers.BatchNormalization()(h2)
else:
h2 = x
h = layers.Add()([h1, h2])
h = layers.ReLU()(h)
return h
def functional_resnet50(input_shape, output_size):
inputs = layers.Input(batch_input_shape=input_shape)
x = layers.Conv2D(64, 7, 2, padding="same", kernel_initializer="he_normal")(inputs)
x = layers.BatchNormalization()(x)
x = layers.MaxPool2D(pool_size=3, strides=2, padding="same")(x)
x = functional_bottleneck_residual(x, 64, 256)
x = functional_bottleneck_residual(x, 256, 256)
x = functional_bottleneck_residual(x, 256, 256)
x = functional_bottleneck_residual(x, 256, 512, 2)
x = functional_bottleneck_residual(x, 512, 512)
x = functional_bottleneck_residual(x, 512, 512)
x = functional_bottleneck_residual(x, 512, 512)
x = functional_bottleneck_residual(x, 512, 1024, 2)
x = functional_bottleneck_residual(x, 1024, 1024)
x = functional_bottleneck_residual(x, 1024, 1024)
x = functional_bottleneck_residual(x, 1024, 1024)
x = functional_bottleneck_residual(x, 1024, 1024)
x = functional_bottleneck_residual(x, 1024, 1024)
x = functional_bottleneck_residual(x, 1024, 2048, 2)
x = functional_bottleneck_residual(x, 2048, 2048)
x = functional_bottleneck_residual(x, 2048, 2048)
x = layers.GlobalAveragePooling2D()(x)
outputs = layers.Dense(
output_size, activation="softmax", kernel_initializer="he_normal"
)(x)
return Model(inputs=inputs, outputs=outputs)
Innerhalb der Methode "function_bottleneck_residual" erscheinen "h1", "h2" und "h". Auf diese Weise kann ein Modell, in dem der Datenfluss in der Mitte verzweigt, von der sequentiellen API nicht beschrieben werden.
Außerdem unternimmt "h2" nichts, wenn die Anzahl der Eingangs- / Ausgangskanäle gleich ist, und führt den Vorgang (Projektion) durch, um die Anzahl der Kanäle anzupassen, wenn sie unterschiedlich sind. Eine solche bedingte Verzweigung kann von der sequentiellen API nicht beschrieben werden.
Nachdem Sie diese Methode erstellt haben, müssen Sie sie nur noch nacheinander schreiben.
ResNet50 Subclassing API
Klassifizieren und implementieren Sie einen Restmechanismus, der wie die Funktions-API wiederverwendet wird.
from tensorflow import layers, Model
class BottleneckResidual(Model):
"""ResNets Engpass-Restmodul.
Durch Reduzieren der ch-Dimension mit 1x1 conv auf der ersten Schicht
Der Rechenaufwand für 3x3 Conv in der zweiten Schicht wurde reduziert
Stellen Sie die Abmessungen der ch-Ausgabe mit einer 1x1-Conv auf der dritten Ebene wieder her.
Dies wird als Engpass bezeichnet, da es die Abmessung der zweiten Schicht 3x3 Conv reduziert, was viel Berechnung erfordert..
"""
def __init__(self, in_ch, out_ch, strides=1):
super().__init__()
self.projection = in_ch != out_ch
inter_ch = out_ch // 4
params = {
"padding": "same",
"kernel_initializer": "he_normal",
"use_bias": True,
}
self.common_layers = [
layers.Conv2D(inter_ch, kernel_size=1, strides=strides, **params),
layers.BatchNormalization(),
layers.ReLU(),
layers.Conv2D(inter_ch, kernel_size=3, strides=1, **params),
layers.BatchNormalization(),
layers.ReLU(),
layers.Conv2D(out_ch, kernel_size=1, strides=1, **params),
layers.BatchNormalization(),
]
if self.projection:
self.projection_layers = [
layers.Conv2D(out_ch, kernel_size=1, strides=strides, **params),
layers.BatchNormalization(),
]
self.concat_layers = [layers.Add(), layers.ReLU()]
def call(self, inputs):
h1 = inputs
h2 = inputs
for layer in self.common_layers:
h1 = layer(h1)
if self.projection:
for layer in self.projection_layers:
h2 = layer(h2)
outputs = [h1, h2]
for layer in self.concat_layers:
outputs = layer(outputs)
return outputs
class ResNet50(Model):
"""ResNet50.
Das Element ist
conv * 1
resblock(conv * 3) * 3
resblock(conv * 3) * 4
resblock(conv * 3) * 6
resblock(conv * 3) * 3
dense * 1
Besteht aus, conv * 49 + dense *50 Schichten von 1.
"""
def __init__(self, output_size=1000):
super().__init__()
self.layers_ = [
layers.Conv2D(64, 7, 2, padding="same", kernel_initializer="he_normal"),
layers.BatchNormalization(),
layers.MaxPool2D(pool_size=3, strides=2, padding="same"),
BottleneckResidual(64, 256),
BottleneckResidual(256, 256),
BottleneckResidual(256, 256),
BottleneckResidual(256, 512, 2),
BottleneckResidual(512, 512),
BottleneckResidual(512, 512),
BottleneckResidual(512, 512),
BottleneckResidual(512, 1024, 2),
BottleneckResidual(1024, 1024),
BottleneckResidual(1024, 1024),
BottleneckResidual(1024, 1024),
BottleneckResidual(1024, 1024),
BottleneckResidual(1024, 1024),
BottleneckResidual(1024, 2048, 2),
BottleneckResidual(2048, 2048),
BottleneckResidual(2048, 2048),
layers.GlobalAveragePooling2D(),
layers.Dense(
output_size, activation="softmax", kernel_initializer="he_normal"
),
]
def call(self, inputs):
for layer in self.layers_:
inputs = layer(inputs)
return inputs
Es unterscheidet sich nicht wesentlich von der funktionalen API. Die Ebene "init" wird geschrieben, um die Ebenen in einer Liste zusammenzufassen. Solange sie jedoch in der Klassenvariablen registriert ist, kann dieser Bereich frei geschrieben werden.
Wir haben ResNet50 als Modell eingeführt, das nicht allein von der Sequential API implementiert werden kann. Um ehrlich zu sein, gibt es keinen großen Unterschied. Ich denke, es ist in Ordnung, die funktionale API und die Unterklassen-API nach Ihren Wünschen zu verwenden.
Vergleichen wir abschließend die Implementierung der Trainingsschleife.
Da es ziemlich lang wäre, den gesamten Quellcode abzulegen, ist die Methode teilweise auf "src.utils" zugeschnitten. Es ist nicht so kompliziert, daher wäre es hilfreich, wenn Sie es lesen und ergänzen könnten.
Derzeit befinden sich alle Quellen im folgenden Repository. Wenn Sie interessiert sind, schauen Sie bitte nach. https://github.com/Anieca/deep-learning-models
Geben Sie einige Optionen an, z. B. die Berechnung der Genauigkeit der Testdaten und die Protokollausgabe für TensorBoard.
import os
import tensorflow as tf
from src.utils import load_dataset, load_model, get_args, get_current_time
def builtin_train(args):
# 1. load dataset and model
(train_images, train_labels), (test_images, test_labels) = load_dataset(args.data)
input_shape = train_images[: args.batch_size, :, :, :].shape
output_size = max(train_labels) + 1
model = load_model(args.arch, input_shape=input_shape, output_size=output_size)
model.summary()
# 2. set tensorboard cofigs
logdir = os.path.join(args.logdir, get_current_time())
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=logdir)
# 3. loss, optimizer, metrics setting
model.compile(
optimizer=tf.keras.optimizers.Adam(),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
# 4. dataset config (and validation, callback config)
fit_params = {}
fit_params["batch_size"] = args.batch_size
fit_params["epochs"] = args.max_epoch
if args.steps_per_epoch:
fit_params["steps_per_epoch"] = args.steps_per_epoch
fit_params["verbose"] = 1
fit_params["callbacks"] = [tensorboard_callback]
fit_params["validation_data"] = (test_images, test_labels)
# 5. start train and test
model.fit(train_images, train_labels, **fit_params)
Es ist ziemlich einfach zu schreiben.
Es gibt viele andere Rückruffunktionen. Wenn Sie interessiert sind, lesen Sie bitte die Dokumentation. https://www.tensorflow.org/api_docs/python/tf/keras/callbacks
Lassen Sie uns den gleichen Prozess wie im obigen integrierten Training implementieren.
import os
import tensorflow as tf
from src.utils import load_dataset, load_model, get_args, get_current_time
def custom_train(args):
# 1. load dataset and model
(train_images, train_labels), (test_images, test_labels) = load_dataset(args.data)
input_shape = train_images[: args.batch_size, :, :, :].shape
output_size = max(train_labels) + 1
model = load_model(args.arch, input_shape=input_shape, output_size=output_size)
model.summary()
# 2. set tensorboard configs
logdir = os.path.join(args.logdir, get_current_time())
train_writer = tf.summary.create_file_writer(os.path.join(logdir, "train"))
test_writer = tf.summary.create_file_writer(os.path.join(logdir, "test"))
# 3. loss, optimizer, metrics setting
criterion = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam()
train_loss_avg = tf.keras.metrics.Mean()
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
test_loss_avg = tf.keras.metrics.Mean()
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
# 4. dataset config
buffer_size = len(train_images)
train_ds = tf.data.Dataset.from_tensor_slices((train_images, train_labels))
train_ds = train_ds.shuffle(buffer_size=buffer_size).batch(args.batch_size)
test_ds = tf.data.Dataset.from_tensor_slices((test_images, test_labels))
test_ds = test_ds.batch(args.batch_size)
# 5. start train and test
for epoch in range(args.max_epoch):
# 5.1. initialize metrics
train_loss_avg.reset_states()
train_accuracy.reset_states()
test_loss_avg.reset_states()
test_loss_avg.reset_states()
# 5.2. initialize progress bar
train_pbar = tf.keras.utils.Progbar(args.steps_per_epoch)
test_pbar = tf.keras.utils.Progbar(args.steps_per_epoch)
# 5.3. start train
for i, (x, y_true) in enumerate(train_ds):
if args.steps_per_epoch and i >= args.steps_per_epoch:
break
# 5.3.1. forward
with tf.GradientTape() as tape:
y_pred = model(x, training=True)
loss = criterion(y_true=y_true, y_pred=y_pred)
# 5.3.2. calculate gradients from `tape` and backward
gradients = tape.gradient(loss, model.trainable_variables)
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
# 5.3.3. update metrics and progress bar
train_loss_avg(loss)
train_accuracy(y_true, y_pred)
train_pbar.update(
i + 1,
[
("avg_loss", train_loss_avg.result()),
("accuracy", train_accuracy.result()),
],
)
# 5.4. start test
for i, (x, y_true) in enumerate(test_ds):
if args.steps_per_epoch and i >= args.steps_per_epoch:
break
# 5.4.1. forward
y_pred = model(x)
loss = criterion(y_true, y_pred)
# 5.4.2. update metrics and progress bar
test_loss_avg(loss)
test_accuracy(y_true, y_pred)
test_pbar.update(
i + 1,
[
("avg_test_loss", test_loss_avg.result()),
("test_accuracy", test_accuracy.result()),
],
)
# 5.5. write metrics to tensorboard
with train_writer.as_default():
tf.summary.scalar("Loss", train_loss_avg.result(), step=epoch)
tf.summary.scalar("Acc", train_accuracy.result(), step=epoch)
with test_writer.as_default():
tf.summary.scalar("Loss", test_loss_avg.result(), step=epoch)
tf.summary.scalar("Acc", test_accuracy.result(), step=epoch)
Es ändert sich bis zum Beginn des Trainings nicht so viel, aber der Umfang der Beschreibung in der Trainingsschleife (Kommentar 5.) ist beträchtlich groß.
Das Verwalten von Dienstprogrammen wie das Verwalten der TensorBoard-Ausgabe und das Erstellen von Fortschrittsbalken selbst kann kostspielig sein, die Integration ist jedoch recht einfach.
Wenn Sie einen Prozess schreiben möchten, der nicht in der integrierten Funktion bereitgestellt wird, müssen Sie ihn in einer benutzerdefinierten Schulung schreiben. Wenn nicht, ist es besser, die integrierte Funktion zu verwenden.
das ist alles. Danke für deine harte Arbeit.
Ich habe verschiedene Schreibmethoden des TensorFlow 2-Systems mit Implementierung eingeführt.
Ich beabsichtige, flach zu schreiben, ohne jedem Schreibstil zu viel Überlegenheit oder Unterlegenheit zu geben.
Wenn Sie selbst schreiben, sollten Sie je nach Situation und Geschmack schreiben. Wenn Sie jedoch nach dem Quellcode suchen, werden Sie auf verschiedene Schreibstile stoßen. Ich denke, es ist gut, wenn Sie alle Schreibstile irgendwie verstehen.
Ich hoffe es hilft dir.
Recommended Posts