[PYTHON] Tipps zur Implementierung eines etwas schwierigen Modells oder Trainings mit Keras

Einführung

Keras-Code ist einfach und sehr modular, daher einfach zu schreiben, leicht zu verstehen und leicht zu verwenden. Wenn Sie jedoch versuchen, andere als die Standardmuster zu trainieren oder zu schichten, gibt es nicht viele Beispiele und Sie wissen oft nicht, wie man schreibt.

Ich werde die Tipps teilen, die ich gelernt habe, als ich kürzlich einige ungewöhnliche Modelle als Memorandum geschrieben habe.

Inhaltsverzeichnis

Tips

Verwenden Sie die funktionale API

Es gibt zwei Möglichkeiten, ein Modell in Keras zu schreiben. Sequential Model und Functional API Model -führen /).

Sequentielles Modell ist

model = Sequential()
model.add(Dense(32, input_dim=784))
model.add(Activation('relu'))

Ich denke, es gibt viele Leute, die das zuerst gesehen und gedacht haben: "Keras ist wirklich leicht zu verstehen!"

Abgesehen davon

inputs = Input(shape=(784,))
x = Dense(64, activation='relu')(inputs)
x = Dense(64, activation='relu')(x)
predictions = Dense(10, activation='softmax')(x)
model = Model(input=inputs, output=predictions)

Es gibt einen Weg zu schreiben. Dies ist eine Schreibweise mit dem Rhythmus "LayerInstance (InputTensor) -> OutputTensor".

Dicht (64, Aktivierung = 'relu') (x) mag denen seltsam erscheinen, die mit Python-ähnlichen Sprachen nicht vertraut sind. Ich habe gerade eine Instanz ** der ** Dense-Klasse im Teil von Dense (64, Aktivierung = 'relu') undDenseInstance (x)dafür erstellt.

dense = Dense(64, activation='relu')
x = dense(x)

Die Bedeutung ist die gleiche.

Der Ablauf besteht darin, die ** Eingabeebene ** ** Ausgabeschicht ** zu bestimmen und an die Klasse "Modell" zu übergeben. Wenn es sich bei der Eingabeebene um echte Daten handelt, geben Sie diese mit der Klasse "Eingabe" an (z. B. Platzhalter).

Was Sie hier beachten sollten, ist, dass ** für jede LayerInstance ** ein Gewicht enthält **. Mit anderen Worten, ** wenn Sie dieselbe Ebeneninstanz verwenden, teilen Sie das Gewicht **. Achten Sie auf unbeabsichtigtes und absichtliches Teilen.

Auf diese Weise können Sie einfach denselben Ausgangstensor in eine andere Ebene eingeben. Der Umfang der Beschreibung ändert sich nicht wesentlich. Wenn Sie sich daran gewöhnt haben, wird empfohlen, das Schreiben mit dieser funktionalen API zu üben, um sich auf zukünftige schwierige Modelle vorzubereiten.

Es ist praktisch, "Container" zu verwenden, wenn Sie das Gewicht mehrerer Schichten teilen möchten.

Manchmal haben Sie eine andere Eingabeebene und eine andere Ausgabeebene, möchten jedoch das zugrunde liegende Netzwerk und die zugrunde liegende Gewichtung gemeinsam nutzen. In diesem Fall ist die Handhabung einfacher, wenn Sie sie in der Klasse "Container" zusammenstellen. Da Container wie Layer eine Unterklasse von Layer ist, bedeutet ** die Verwendung derselben ContainerInstance, dass das Gewicht geteilt wird **.

Zum Beispiel

inputs = Input(shape=(784,))
x = Dense(64, activation='relu')(inputs)
x = Dense(64, activation='relu')(x)
predictions = Dense(10, activation='softmax')(x)
shared_layers = Container(inputs, predictions, name="shared_layers")

Solche "shared_layers" können so behandelt werden, als wäre es eine einzelne Ebene.

Der "Container" selbst hat grundsätzlich kein eigenes Gewicht, sondern dient nur als Bündel für andere "Layer".

Wenn Sie dagegen kein Gewicht teilen möchten, müssen Sie "LayerInstance" einzeln verbinden, ohne "Container" zu teilen.

"Layer Output" und "Raw Tensor" sind ähnlich und unterschiedlich

Oft beim Schreiben eigener Berechnungen oder Tensor-Transformationen

TypeError: ('Not a Keras tensor:', Elemwise{add,no_inplace}.0)

Ich sehe den Fehler.

Dies geschieht normalerweise, wenn Sie "raw Tensor" in die Eingabe von "LayerInstance" anstelle von "Ausgabe einer anderen Ebene" einfügen. Zum Beispiel

from keras import backend as K

inputs = Input((10, ))
x = K.relu(inputs * 2 + 1)
x = Dense(64, activation='relu')(x)

Und so weiter. Ich bin nicht sicher, aber Layer's Output ist ein Objekt mit einer internen Form namens KerasTensor, die sich von dem Berechnungsergebnis wie "K.hogehoge" zu unterscheiden scheint.

In einem solchen Fall funktioniert es gut, wenn Sie das unten stehende "Lambda" verwenden (es ist sicherer, die "_keras_shape" ^^ nicht mit Gewalt auszufüllen ^^).

Eine einfache Konvertierung mit Lambda ist praktisch

Angenommen, Sie möchten einen Vektor mit 10 Elementen in der ersten Hälfte in 5 und in der zweiten Hälfte in 5 teilen. Wie oben erwähnt

inputs = Input((10, ))
x0_4 = inputs[:5]
x5_9 = inputs[5:]
d1 = Dense(10)(x0_4)
d2 = Dense(10)(x5_9)

In diesem Fall tritt ein Fehler auf.

Deshalb

inputs = Input((10, ))
x0_4 = Lambda(lambda x: x[:, :5], output_shape=(5, ))(inputs)
x5_9 = Lambda(lambda x: x[:, 5:], output_shape=lambda input_shape: (None, int(input_shape[1]/2), ))(inputs)
d1 = Dense(10)(x0_4)
d2 = Dense(10)(x5_9)

Wickeln Sie mit Lambda Klasse wie diese und es wird funktionieren. Hier gibt es einige Punkte.

** In Lambda müssen Sie eine Tensorformel ** schreiben, die die Beispieldimension enthält.

In Keras ist die erste Dimension konsistent die Beispieldimension (Dimension batch_size). Schreiben Sie beim Implementieren einer Ebene wie "Lambda" eine Berechnungsformel, die die Beispieldimension intern enthält. Sie müssen also "Lambda x: x [:,: 5]" anstelle von "Lambda x: x [: 5]" schreiben.

Geben Sie ** output_shape ** an, wenn sich die Eingabeform und die Ausgabeform unterscheiden.

output_shape kann weggelassen werden, wenn die Eingabe- und Ausgabeformen gleich sind, muss jedoch angegeben werden, wenn sie unterschiedlich sind. Sie können Tupel oder Funktion als Argument für output_shape angeben, aber ** Beispieldimension für Tupel nicht einschließen **, ** Beispieldimension für Funktion einschließen **. Im Fall von Function ist es grundsätzlich in Ordnung, wenn die Sample-Dimension auf "None" gesetzt ist. Darüber hinaus ist es "input_shape", ein Argument, wenn es von Function angegeben wird. Es sollte jedoch beachtet werden, dass es die Beispieldimension enthält.

Die benutzerdefinierte Verlustfunktion gibt den Verlust nach Stichprobe zurück

Sie können die Verlustfunktion mit der Kompilierungsmethode "Modell" angeben und Sie können auch Ihre eigene benutzerdefinierte Verlustfunktion angeben. In Bezug auf die Form der Funktion werden zwei Argumente verwendet, "y_true" und "y_pred", und die Anzahl der ** Samples ** wird zurückgegeben. Zum Beispiel:

def generator_loss(y_true, y_pred):  # y_true's shape=(batch_size, row, col, ch)
	return K.mean(K.abs(y_pred - y_true), axis=[1, 2, 3])

[Ergänzung: 20170802]

In diesem LSGAN mit Keras schreiben,

Die Funktionen zum Anwenden von Gewichten und Masken sind genau das. Wenn Sie sie nicht verwenden, können Sie im Gegenteil über Stichproben wie diese Zeit berechnen.

Es wurde darauf hingewiesen, dass ich denke, dass das wahr ist. Wenn Sie nicht vorhaben, es anderweitig zu verwenden, und sample_weight usw. nicht verwenden müssen, ist es daher in Ordnung, einen Verlustwert zurückzugeben.

Wenn Sie der Verlustfunktion einen Ausdruck von einer anderen Stelle als "Ebene" hinzufügen möchten

Wenn Sie von einer Ebene übergeben möchten, können Sie einfach "Ebene # add_loss" aufrufen, aber es ist etwas schwierig, von einer Nicht-Ebene zu übergeben (oder ich kenne den richtigen Weg nicht).

Andere Verlustformeln als die Verlustfunktion werden von jeder Ebene durch "Modell # Verluste" gesammelt, wenn "Kompilieren" der "Modell" -Instanz ausgeführt wird (vom Regularisierer usw.). Mit anderen Worten, Sie können es hier irgendwie weitergeben. Sie können beispielsweise "Container" oder "Modell" erben und "# Verluste" übertreiben. Als ich VATModel erstellt habe, habe ich es auf diese Weise bestanden.

Wenn die Parameter während des Lernens aktualisiert und in Verlust wiedergegeben werden

Möglicherweise möchten Sie, dass die Verlustberechnung während des Trainings die vorherigen Verlustergebnisse widerspiegelt. Zum Beispiel im Fall von "DiscriminatorLoss" der BEGAN-Berechnung wie unten gezeigt. https://github.com/mokemokechicken/keras_BEGAN/blob/master/src/began/training.py#L104

Parameteraktualisierung während des Lernens Die von Model # updates übergebenen Parameterinformationen werden verwendet, wenn compile der Model-Instanz ausgeführt wird. Es gibt normalerweise keine Möglichkeit, Daten von der Verlustfunktion an diese Model # -Updates zu übergeben (wahrscheinlich), daher mache ich einen kleinen Trick.

Wenn Sie darüber nachdenken, ist es möglich, Folgendes zu tun.

class DiscriminatorLoss:
    __name__ = 'discriminator_loss'

    def __init__(self, lambda_k=0.001, gamma=0.5):
        self.lambda_k = lambda_k
        self.gamma = gamma
        self.k_var = K.variable(0, dtype=K.floatx(), name="discriminator_k")
        self.m_global_var = K.variable(0, dtype=K.floatx(), name="m_global")
        self.loss_real_x_var = K.variable(0, name="loss_real_x")  # for observation
        self.loss_gen_x_var = K.variable(0, name="loss_gen_x")    # for observation
        self.updates = []

    def __call__(self, y_true, y_pred):  # y_true, y_pred shape: (BS, row, col, ch * 2)
        data_true, generator_true = y_true[:, :, :, 0:3], y_true[:, :, :, 3:6]
        data_pred, generator_pred = y_pred[:, :, :, 0:3], y_pred[:, :, :, 3:6]
        loss_data = K.mean(K.abs(data_true - data_pred), axis=[1, 2, 3])
        loss_generator = K.mean(K.abs(generator_true - generator_pred), axis=[1, 2, 3])
        ret = loss_data - self.k_var * loss_generator

        # for updating values in each epoch, use `updates` mechanism
        # DiscriminatorModel collects Loss Function's updates attributes
        mean_loss_data = K.mean(loss_data)
        mean_loss_gen = K.mean(loss_generator)

        # update K
        new_k = self.k_var + self.lambda_k * (self.gamma * mean_loss_data - mean_loss_gen)
        new_k = K.clip(new_k, 0, 1)
        self.updates.append(K.update(self.k_var, new_k))

        # calculate M-Global
        m_global = mean_loss_data + K.abs(self.gamma * mean_loss_data - mean_loss_gen)
        self.updates.append(K.update(self.m_global_var, m_global))

        # let loss_real_x mean_loss_data
        self.updates.append(K.update(self.loss_real_x_var, mean_loss_data))

        # let loss_gen_x mean_loss_gen
        self.updates.append(K.update(self.loss_gen_x_var, mean_loss_gen))

        return ret


class DiscriminatorModel(Model):
    """Model which collects updates from loss_func.updates"""

    @property
    def updates(self):
        updates = super().updates
        if hasattr(self, 'loss_functions'):
            for loss_func in self.loss_functions:
                if hasattr(loss_func, 'updates'):
                    updates += loss_func.updates
        return updates


discriminator = DiscriminatorModel(all_input, all_output, name="discriminator")
discriminator.compile(optimizer=Adam(), loss=DiscriminatorLoss())
 

schließlich

Vielleicht sind die letzten beiden Tipps, die nach einer Weile unnötig werden. Keras ist einfach, dem Quellcode zu folgen, daher ist es überraschend hübsch. Mit Keras2 ist die Fehlermeldung außerdem verständlicher geworden, was beim Debuggen hilfreich ist.

Recommended Posts

Tipps zur Implementierung eines etwas schwierigen Modells oder Trainings mit Keras
Tipps für eine gute Verwendung von Elastic Search
Visualisieren Sie das Keras-Modell mit Python 3.5
Tipps zur Verwendung von Selen und Headless Chrome in einer CUI-Umgebung
Lösung für ValueError in Keras imdb.load_data
Implementierung eines einfachen Algorithmus in Python 2
So erstellen Sie mit YOLO in 3 Stunden ein Modell für die Objekterkennung
Tipps zum Wiederherstellen eines Modells beim Ersetzen des Platzhalters von INPUT in der FreezeGraphed .pb-Datei von Tensorflow [Ersetzen / Ersetzen / Konvertieren / Ändern / Aktualisieren / Ersetzen]