[PYTHON] Conseils pour mettre en œuvre un modèle ou un entraînement légèrement difficile avec Keras

introduction

Le code Keras est simple et hautement modulaire, il est donc simple à écrire, facile à comprendre et facile à utiliser. Cependant, si vous essayez de former ou de superposer des couches autres que celles standard, il n'y a pas beaucoup d'échantillons et vous ne savez souvent pas comment écrire.

Je partagerai les astuces que j'ai apprises en écrivant récemment des modèles inhabituels sous forme de mémorandum.

table des matières

Tips

Utilisez l'API fonctionnelle

Il existe deux façons d'écrire un modèle dans Keras. Modèle séquentiel et Modèle d'API fonctionnelle -guider /).

Le modèle séquentiel est

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

Je pense que beaucoup de gens ont vu cela pour la première fois et ont pensé: "Keras est vraiment facile à comprendre!"

Mis à part cela

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)

Il y a une manière d'écrire. C'est une manière d'écrire avec le rythme LayerInstance (InputTensor) -> OutputTensor.

Dense (64, activation = 'relu') (x) peut sembler étrange à ceux qui ne sont pas familiers avec les langages de type Python, Je viens de créer une instance ** de ** classe Dense dans la partie de Dense (64, activation = 'relu') ʻet DenseInstance (x)` pour cela.

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

Le sens est le même.

Le flux consiste à déterminer la ** couche d'entrée ** ** couche de sortie ** et à la transmettre à la classe Model. Si la couche d'entrée est de vraies données, spécifiez-la en utilisant la classe ʻInput` (comme Placeholder).

Ce que vous devez savoir ici, c'est que ** détient un poids pour chaque LayerInstance **. En d'autres termes, ** si vous utilisez la même instance de calque, vous partagez le poids **. Faites attention au partage non intentionnel ainsi qu'au partage intentionnel.

De cette façon, vous pouvez facilement entrer le même Tensor de Sortie dans une autre couche. La quantité de description ne change pas beaucoup, et au fur et à mesure que vous vous y habituez, il est recommandé de vous entraîner à écrire avec cette API fonctionnelle pour vous préparer à de futurs modèles difficiles.

Il est pratique d'utiliser Container si vous souhaitez partager le poids de plusieurs couches.

Parfois, vous avez une couche d'entrée différente et une couche de sortie différente, mais vous souhaitez partager le réseau et le poids sous-jacents. Dans ce cas, il sera plus facile à gérer si vous les mettez ensemble dans la classe Container. Puisque Container est une sous-classe de Layer, comme Layer, ** utiliser le même ContainerInstance signifie partager Weight **.

Par exemple

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

De tels shared_layers peuvent être traités comme s'il s'agissait d'une seule couche.

Le "Conteneur" lui-même n'a fondamentalement pas son propre poids, mais sert uniquement de bundle pour les autres "calques".

D'un autre côté, si vous ne voulez pas partager Weight, vous devez connecter LayerInstance individuellement sans partagerContainer.

"Layer Output" et "Raw Tensor" sont similaires et différents

Souvent lors de l'écriture de vos propres calculs ou transformations Tensor

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

Je vois l'erreur.

Cela se produit généralement lorsque vous mettez "raw Tensor" dans l'entrée de LayerInstance au lieu de" Output of another layer ". Par exemple

from keras import backend as K

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

Etc. Je ne suis pas sûr, mais la sortie de la couche est un objet avec une forme interne appelée KerasTensor, qui semble être différente du résultat du calcul tel que K.hogehoge.

Dans un tel cas, cela fonctionne bien si vous utilisez le Lambda ci-dessous (il est plus sûr de ne pas remplir de force le _keras_shape ^^;).

Une conversion simple en utilisant Lambda est pratique

Par exemple, supposons que vous souhaitiez diviser un vecteur de 10 éléments en 5 dans la première moitié et 5 dans la seconde moitié. Comme mentionné ci-dessus

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

Si vous le faites, une erreur se produira.

Donc

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)

Enveloppez avec la classe Lambda comme ceci et cela fonctionnera. Il y a quelques points ici.

** Dans Lambda, vous devez écrire une formule Tensor ** qui inclut la dimension Sample.

Dans Keras, la première dimension est systématiquement la dimension Sample (dimension batch_size). Lors de l'implémentation d'une couche telle que «Lambda», écrivez une formule de calcul qui inclut la dimension Sample en interne. Vous devez donc écrire lambda x: x [:,: 5] au lieu de lambda x: x [: 5].

Spécifiez ** output_shape ** lorsque la forme d'entrée et la forme de sortie sont différentes.

ʻOutput_shape peut être omis si les formes d'entrée et de sortie sont identiques, mais doivent être spécifiées si elles sont différentes. Vous pouvez spécifier Tuple ou Function comme argument de output_shape, mais ** Ne pas inclure la dimension Sample pour Tuple **, ** Inclure la dimension Sample pour Function **. Dans le cas de Function, c'est OK si la dimension Sample est définie sur "None". Notez également que ʻinput_shape est un argument lorsqu'il est spécifié par Function, mais il inclut la dimension Sample.

La fonction de perte personnalisée renvoie la perte par échantillon

Vous pouvez spécifier la fonction Loss avec la méthode compile de Model, et vous pouvez également spécifier votre propre fonction Loss personnalisée. Concernant la forme de la fonction, elle prend deux arguments, y_true et y_pred, et renvoie le nombre de ** Samples **. Par exemple:

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

[Ajout: 20170802]

Dans ce Write LSGAN with Keras,

Les fonctions fournies pour appliquer des poids et des masques ne sont que cela, et au contraire, si vous ne les utilisez pas, vous pouvez calculer à travers des échantillons comme cette fois.

Il a été souligné que je pense que c'est vrai. Par conséquent, si vous ne prévoyez pas de l'utiliser ailleurs et que vous n'avez pas besoin d'utiliser sample_weight etc., vous pouvez renvoyer en toute sécurité une valeur de perte.

Si vous souhaitez ajouter une expression à la fonction Loss depuis un endroit autre que Layer

Si vous voulez passer d'un calque, vous pouvez simplement appeler Layer # add_loss, mais c'est un peu difficile de passer d'un non-calque (ou je ne connais pas le bon chemin).

Les formules de perte autres que la fonction de perte sont collectées à partir de chaque couche par des «pertes de modèle» au moment où la «compilation» de l'instance «modèle» est exécutée (à partir du régulariseur, etc.). En d'autres termes, vous pouvez en quelque sorte le transmettre ici. Par exemple, vous pouvez réussir à hériter de Container ou Model et de surestimer # loss. Quand j'ai fait VATModel, je l'ai passé de cette façon.

Lorsque les paramètres sont mis à jour et reflétés dans la perte pendant l'apprentissage

Vous voudrez peut-être que le calcul de la perte pendant l'entraînement reflète les résultats de la perte précédente. Par exemple, dans le cas de «DiscriminatorLoss» du calcul BEGAN comme indiqué ci-dessous. https://github.com/mokemokechicken/keras_BEGAN/blob/master/src/began/training.py#L104

Mise à jour des paramètres pendant l'apprentissage Les informations de paramètre transmises par Model # updates sont utilisées lorsque la compilation de l'instance Model est exécutée. Il n'y a généralement aucun moyen de transmettre des données de la fonction de perte à ces mises à jour de modèle (probablement), donc je vais faire un petit truc.

En y réfléchissant, il est possible de faire ce qui suit.

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

à la fin

Peut-être que les deux derniers sont des conseils qui ne seront plus nécessaires après un certain temps. Keras est facile à suivre le code source, donc c'est étonnamment joli. En outre, Keras2 facilite la compréhension du message d'erreur, ce qui est utile lors du débogage.

Recommended Posts

Conseils pour mettre en œuvre un modèle ou un entraînement légèrement difficile avec Keras
Conseils pour utiliser Elastic Search de manière efficace
Visualiser le modèle Keras avec Python 3.5
Conseils d'utilisation de Selenium et Headless Chrome dans un environnement CUI
Solution pour ValueError dans Keras imdb.load_data
Implémentation d'un algorithme simple en Python 2
Comment faire un modèle pour la détection d'objets avec YOLO en 3 heures
Conseils pour régénérer un modèle tout en remplaçant l'espace réservé de INPUT dans le fichier .pb FreezeGraphed de Tensorflow [Remplacer / Remplacer / Convertir / Modifier / Mettre à jour / Remplacer]