[PYTHON] Erklärt, wie TensorFlow 2.X mit der Implementierung von VGG16 / ResNet50 verwendet wird

Einführung

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.

Zielgruppe

fließen

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.

Überprüfungsumgebung

>>> 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

4 Modellierungs-APIs in TensorFlow

TensorFlow bietet zwei Haupt-APIs zum Erstellen von Modellen und vier unterteilte APIs.

Zunächst werde ich kurz die wichtigsten Klassifikationen vorstellen.

Symbolische (deklarative) API

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.

Instructional (Model Subclassing) API

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)

Überprüfung der Modellbau-API

Damit ist die Erklärung zum Erstellen eines Modells abgeschlossen. Zusammenfassend denke ich, dass Sie es wie folgt richtig verwenden können.

Zwei Trainingsmethoden in TensorFlow

Es gibt zwei Möglichkeiten zu trainieren:

eingebautes Training

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.

Kundenspezifisches Training

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).

Rückblick auf die Trainingsmethode

Damit ist die Erläuterung der Trainingsmethode abgeschlossen. Zusammenfassend denke ich, dass Sie es wie folgt richtig verwenden können.

Übersicht über VGG16 / ResNet 50

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.

Was ist VGG16?

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

Was ist ResNet50?

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

Implementierung von VGG16

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).

Überprüfung der VGG16-Implementierung

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.

ResNet50-Implementierung

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.

Überprüfung der ResNet50-Implementierung

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.

Durchführung von Schulungen

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

integrierte Schulungsimplementierung

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

Implementierung von benutzerdefinierten Schulungen

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ß.

Rückblick auf die Durchführung der Schulung

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.

Am Ende

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

Erklärt, wie TensorFlow 2.X mit der Implementierung von VGG16 / ResNet50 verwendet wird
Python: So verwenden Sie Async mit
So verwenden Sie virtualenv mit PowerShell
Zusammenfassung der Verwendung von pyenv-virtualenv
Zusammenfassung der Verwendung von csvkit
NumPy> [1, 2, 3, 4, 5, 6, 7, 8, 9] mit 3 ndarrays ([1,4,7], [2,5,8], [3,6,9]) Implementierung zur Aufteilung in> {Verwendung von Slice / Reshape () + Methode der Matrixtranslokation}
Verwendung von ManyToManyField mit Djangos Admin
Verwendung von OpenVPN mit Ubuntu 18.04.3 LTS
Verwendung von Cmder mit PyCharm (Windows)
Berechnen Verwenden Sie% des Befehls df
Verwendung von Japanisch mit NLTK-Plot
[Python2.7] Zusammenfassung der Verwendung von unittest
Verwendung des Jupyter-Notebooks mit ABCI
Verwendung von Tensorflow unter Docker-Umgebung
Jupyter Notebook Grundlagen der Verwendung
Verwendung des CUT-Befehls (mit Beispiel)
Grundlagen von PyTorch (1) - Verwendung von Tensor-
Zusammenfassung der Verwendung der Python-Liste
[Python2.7] Zusammenfassung der Verwendung des Unterprozesses
Verwendung von SQLAlchemy / Connect mit aiomysql
[Frage] Wie verwende ich plot_surface von Python?
Verwendung des JDBC-Treibers mit Redash
Verwendung der GCP-Ablaufverfolgung mit offener Telemetrie
Verwendung von Folium (Visualisierung von Standortinformationen)
[Python] Verwendung von zwei Arten von type ()
Nicht viel erwähnt, wie man Pickle benutzt
Zusammenfassung der Verwendung von MNIST mit Python
So legen Sie Attribute mit Mock of Python fest
So implementieren Sie "named_scope" von RubyOnRails mit Django
Die Geschichte des Versuchs, Tensorboard mit Pytorch zu verwenden
[Algorithmus x Python] Verwendung der Liste
Wie man tkinter mit Python in Pyenv benutzt
Verwendung von xml.etree.ElementTree
Wie benutzt man Python-Shell
Hinweise zur Verwendung von tf.data
Verwendung von virtualenv
Verwendung von xgboost: Mehrklassenklassifizierung mit Irisdaten
Verwendung von Image-Match
Wie man Shogun benutzt
Hinweise zur Verwendung von AIST Spacon ABCI
Verwendung von Pandas 2
Ich habe versucht zusammenzufassen, wie man Matplotlib von Python verwendet
Vorbereiten der Verwendung von Tensorflow (Anaconda) mit Visual Studio Code
Verwendung von Virtualenv
Erklären Sie ausführlich, wie Sie mit Python einen Sound erzeugen
Verwendung von numpy.vectorize
Hinweise zur Verwendung beim Kombinieren von pandas.DataFrame
Verwendung von pytest_report_header
So installieren Sie caffe unter OS X mit Macports
Wie man teilweise verwendet
Wie man Bio.Phylo benutzt
Verwendung von TensorFlow auf GPUs unter Titan
Verwendung von SymPy
Verwendung des interaktiven Python-Modus mit Git Bash
Wie man x-means benutzt
Verwendung von WikiExtractor.py
Verwendung von IPython
Verwendung von Python Kivy ~ ~ Grundlagen der Kv-Sprache ~