[PYTHON] Prédiction d'images vidéo à l'aide de la convolution LSTM avec TensorFlow

Cet article est l'article du 18e jour du TensorFlow Advent Calendar 2016.

À l'origine, je prévoyais d'implémenter PredNet et d'implémenter ConvLSTM, mais comme je peux prédire l'image de la vidéo par elle-même, j'ai écrit cet article avec l'intention de l'essayer. Depuis que je l'ai annoncé lors du précédent événement du groupe d'utilisateurs Tensor Flow "NN Thesis Drinking Party", je suis intéressé par le plan de [l'article] original (https://arxiv.org/abs/1506.04214). Veuillez consulter cette diapositive.

LSTM convolutif (LSTM convolutif)

Je pense qu'il est facile d'imaginer à quoi cela ressemble à partir du nom. Dans le LSTM conventionnel, l'état de transition temporelle était le tenseur du 2ème étage (taille du lot, nombre d'unités dans la couche intermédiaire), mais il s'agit maintenant du tenseur du 4ème étage (taille du lot, longueur, largeur, nombre de canaux). À ce moment-là, étant donné que l'état à traiter est une information d'image, la connexion entre les couches a été modifiée de connexion totale à convolution dans le passé. Le LSTM conventionnel est ↓ 従来のLSTM Le LSTM plié est ↓ スクリーンショット 2016-12-13 18.34.41.png

Cela n'a pas l'air très différent, mais c'est juste une convolution de la multiplication matricielle. Cependant, notez que la partie du produit Adamal à laquelle le peephole contribue reste telle quelle. (Je pense toujours, pourquoi le judas n'est pas la multiplication de la matrice mais le produit Adamal ...?)

Implémentation dans TensorFlow

Bien sûr, le LSTM convolutif n'existe pas en tant que fonction par défaut, vous devez donc l'implémenter. Implémentons ConvLSTMCell en héritant de tf.nn.rnn_cell.RNNCell. Le code source est donné à ici. J'ai également créé un traitement de l'ensemble de données et un script DL, mais cela devient compliqué et l'ensemble de données d'origine est terriblement volumineux, donc je le traite autant que j'utilise et le mets dans le référentiel tel quel.

Le code de référence est ici.

Ce que j'ai fait cette fois

Ce que j'ai créé cette fois est une prédiction du paysage de conduite en utilisant KITTI Dataset. À l'origine, nous devrions prédire combien de trames dans le futur à partir des dernières trames, mais comme il y a beaucoup de codes et que cela prend du temps à apprendre, nous allons construire un réseau qui prédit une trame dans le futur à partir des 4 dernières trames. スクリーンショット 2016-12-15 14.02.42.png

J'ai essayé différentes tailles d'image, mais lorsque je l'ai déplacée avec la GFORCE GTX 1070, j'ai senti que 128 x 128 était la limite. Je l'ai donc vérifié avec 64x64. Dans l'article, la couche LSTM était multicouche, mais il était difficile de modifier l'emballage, donc je l'ai construit en une seule couche. Pour une raison quelconque, la fonction d'erreur utilisait l'entropie croisée dans l'article, mais je me sentais mal à l'aise, alors j'utiliserai l'erreur absolue.

À propos de tf.nn.RNNCell

Il y a au moins trois méthodes qui doivent être héritées dans le tf.nn.RNNCell d'origine: state_size, ʻoutput_size et call. Le reste est zero_state, mais cette fois, il crée toutes les valeurs initiales de l'état interne avec 0, il n'est donc pas nécessaire de l'implémenter à l'origine, mais cette fois la forme de l'état interne est le tenseur du 4ème étage Pour le prendre, nous devons le changer. Le rôle de chacun est ʻoutput_size, qui est le nombre d'unités de sortie (pas l'état interne). En raison de la nature de RNN, il correspond au nombre d'unités de la couche intermédiaire qui sont à l'état interne. Si vous souhaitez projeter la sortie pour réduire la quantité de calcul, modifiez-la en conséquence.

rnn_cell.py


    if num_proj:
      self._state_size = (
          LSTMStateTuple(num_units, num_proj)
          if state_is_tuple else num_units + num_proj)
      self._output_size = num_proj
    else:
      self._state_size = (
          LSTMStateTuple(num_units, num_units)
          if state_is_tuple else 2 * num_units)
      self._output_size = num_units

  @property
  def state_size(self):
    return self._state_size

  @property
  def output_size(self):
    return self._output_size

Vient ensuite state_size, qui est le nombre d'unités dans l'état interne. Dans le cas d'un RNN ou GRU général, il correspond naturellement au nombre de couches intermédiaires qui sont à l'état interne, mais dans LSTM, l'état interne et la sortie affectent l'état suivant, donc ce qui précède La taille est doublée comme rnn_cell.py. zero_state renvoie l'état initial complété avec 0 pour correspondre à cette state_size. Enfin, concernant le __call__ de l'appel de fonction de l'objet, le traitement ici est la partie qui décrit le traitement à chaque pas de temps en multipliant effectivement le poids et l'entrée. Les opérations liées au RNN de TensorFlow ont un petit nombre de lignes, donc si vous êtes intéressé, veuillez le lire.

tf.nn.ConvLSTMCell Maintenant, l'implémentation du sujet principal, ConvLSTMCell. Il y a deux points.

  1. Concattez la sortie de l'instant précédent et la nouvelle entrée, ce qui est fait dans un LSTM général, au niveau du canal de l'image.
  2. Afin de rendre l'état interne et l'entrée / sortie tous de la même taille (à l'exclusion des canaux), 0 remplissage est effectué lors du pliage et la foulée est fixée à 1 pixel à la fois verticalement et horizontalement.

Le premier point est que vous devez combiner l'entrée avec la sortie de l'instant précédent lors de la création de la porte d'entrée ou de la porte d'oubli. (Traitement dans la partie inférieure de la figure ci-dessous) スクリーンショット 2016-12-13 19.42.57.png

A ce moment, dans le LSTM conventionnel, la taille peut être différente entre la sortie et l'entrée de l'instant précédent, il n'est donc pas possible d'ajouter simplement. Par conséquent, les tenseurs sont combinés dans les directions de la longueur d'entrée et de la longueur de sortie. J'espère que vous pouvez imaginer PPAP. (Figure ci-dessous)

スクリーンショット 2016-12-13 19.56.48.png

Cependant, cette fois, l'entrée, la sortie et l'état ne peuvent pas être combinés tels quels car ils ont un tenseur du 4ème ordre. Pour le résoudre, le deuxième point est également couvert, mais l'entrée et la sortie sont combinées dans la direction du canal en unifiant uniquement les tailles verticale et horizontale de l'image. L'image est comme indiqué dans la figure ci-dessous. スクリーンショット 2016-12-13 20.13.16.png

rnn_cell.py


    if len(args) == 1:
      res = math_ops.matmul(args[0], weights)
    else:
      res = math_ops.matmul(array_ops.concat(1, args), weights)

conv_lstm_cell.py


        #Assurez-vous de rembourrer car ce sera un poids partagé='SAME'Replier
        if len(args) == 1:
            res = tf.nn.conv2d(args[0],kernel, stride, padding='SAME')
        else:
            res = tf.nn.conv2d(array_ops.concat(3, args), kernel, stride, padding='SAME')

Le branchement par si minutes est seulement divisé entre le cas général rnn et le cas lstm. Comme la différence dans la partie imbriquée de else, «concat» est appliqué avant le pliage. Dans le cas de la méthode conventionnelle, il est connecté dans la direction de rang 1, mais dans conv_lstm, vous pouvez voir qu'il est connecté dans la direction de rang 3 (canal).

Le deuxième point est le problème de couplage évoqué ci-dessus, et surtout, du fait des caractéristiques de RNN et des caractéristiques de propagation temporelle utilisant des poids partagés, le tenseur à l'état interne doit toujours avoir la même forme. Il y a. Par conséquent, le rembourrage pliant est bien sûr «MÊME». De plus, bien sûr, le rembourrage pliant ne corrige que la taille du filtre, donc si vous définissez stride sur 1 ou plus, l'image deviendra plus petite. Par conséquent, fixez toujours la taille de la foulée à [1,1,1,1]. Pour cette raison, le coût de calcul est très élevé et l'apprentissage ne se fera pas du tout à moins qu'il ne soit fait avec une image un peu petite.

Développement du temps

Maintenant que nous avons implémenté le comportement de chaque fois avec Convlstmcell, nous allons étendre cela à l'époque de RNN. Il existe à peu près deux méthodes pour étendre les cellules dans le temps: l'une consiste à utiliser une instruction for en utilisant reuse_variables (), et l'autre à utiliser tf.nn.rnn () ou tf.nn.dynamic_rnn (). est. Cette fois, j'utiliserai la fonction TensorFlow. Dans ce cas, tf.nn.rnn () est utilisé cette fois. Personnellement, je voulais utiliser dynamic_rnn (), ce qui n'est pas gênant pour créer des données d'entrée, mais comme l'axe du temps est fixé à la partie du deuxième étage du tenseur avec l'option time_major etc., je modifie cette partie Comme c'était ennuyeux, j'utiliserai rnn (). Par conséquent, les données d'entrée seront une liste de tenseurs du 4ème étage (taille du lot, horizontal, vertical, canal).

train.py


    #Des données d'entrée(batch, width, height, channel)Liste des séries chronologiques du tenseur du 4e étage
    images = []
    for i in xrange(4):
        input_ph = tf.placeholder(tf.float32,[None, IMG_SIZE[0], IMG_SIZE[1], 3])
        tf.add_to_collection("input_ph", input_ph)
        images.append(input_ph)

    #Corriger les données de réponse(batch, width, height, channel)Tenseur du 4ème étage
    y = tf.placeholder(tf.float32,[None, IMG_SIZE[0], IMG_SIZE[1], 3])

Mmm, j'ai fini par faire feed_dict d'une manière assez maladroite, mais n'y avait-il pas une meilleure façon?

train.py


            feed_dict = {}

            #Obtenez la première image de l'image utilisée pour l'apprentissage de la taille du lot
            target = []
            for i in xrange(FLAGS.batch_size):
                target.append(random.randint(0,104))

            #Flux pour l'espace réservé de l'image d'entrée_Remplissez le dict
            for i in xrange(4):
                inputs = []
                for j in target:
                    file = FLAGS.data_dir+str(i+j)+'.png'
                    img = cv2.imread(file)/255.0
                    inputs.append(img)

                feed_dict[tf.get_collection("input_ph")[i]] = inputs

Cependant, pour le moment, la construction du modèle de la partie d'expansion temporelle peut être écrite très simplement.

train.py


    cell = conv_lstm_cell.ConvLSTMCell(FLAGS.conv_channel, img_size=IMG_SIZE, kernel_size=KERNEL_SIZE,
        stride= STRIDE, use_peepholes=FLAGS.use_peepholes, cell_clip=FLAGS.cell_clip, initializer=initializer,
        forget_bias=FLAGS.forget_bias, state_is_tuple=False, activation=activation)

    outputs, state = tf.nn.rnn(cell=cell, inputs=images, dtype=tf.float32)

Génération d'images

Une image sera générée en fonction de la sortie de la dernière fois de la convolution LSTM. Les informations requises sont le dernier tenseur de la liste ʻoutputs retourné par tf.nn.rnn () , donc obtenez-le avec ʻoutputs [-1] et son (taille du lot, largeur, hauteur, nombre de canaux) L'image est générée en pliant le tenseur du 4ème étage. Comme je l'ai mentionné dans le point de convolution LSTM, toutes les données d'image qui apparaissent dans le réseau sont de la même taille. En l'utilisant, l'image de l'image attendue est sortie par convolution avec 1x1.

train.py


    #Obtenir la sortie à la dernière fois
    last_output=outputs[-1]

    #Pliez le résultat en 1x1 et traitez-le à la même taille que l'image d'origine
    kernel = tf.Variable(tf.truncated_normal([1,1 ,FLAGS.conv_channel, 3],stddev=0.1))
    result = tf.nn.conv2d(last_output, kernel,[1,1,1,1], padding='SAME')
    result = tf.nn.sigmoid(result)

Étant donné que la valeur de pixel de l'image de sortie doit être comprise entre 0 et 255, la fonction de déclenchement est une fonction sigmoïde et le résultat est multiplié par 255. Vous pouvez sortir l'image en toute sécurité.

résultat

Eh bien, je n'utilise pas TensorBoard pour enregistrer ou générer un fichier de point de contrôle, mais je vais coller le résultat du flux approprié.

スクリーンショット 2016-12-15 15.59.18.png

Certes, j'apprends, et à la fin, la ligne blanche sur la route semble étonnamment bonne, mais je pense que les paramètres sont appropriés, donc c'est comme ça. Dans la seconde moitié, la moyenne d'erreur absolue oscille autour de 0,1. À propos, il a fallu beaucoup de temps pour s'entraîner en augmentant la taille de l'image, mais l'erreur absolue est devenue encore plus petite et elle est devenue assez claire.

Considération et avenir

Je n'ai fait aucun réglage de paramètre, donc le résultat est hmm. Parfois, il y avait des cas où cela fonctionnait assez bien, mais les arbres sur la route avaient disparu et il y avait encore plein de choses.

Puisque nous n'avons implémenté que le LSTM de convolution au minimum, si vous voulez essayer diverses choses, vous devez jouer avec le wrapper de cellules, les méthodes système tf.nn et seq2seq. Si je l'utilise pour le travail, je pense que je vais l'implémenter et l'ajuster. Quoi qu'il en soit, le meilleur résultat était que je pouvais lire fermement le code autour de RNN.

Bonne année à tous.

Recommended Posts

Prédiction d'images vidéo à l'aide de la convolution LSTM avec TensorFlow
Enregistrer la vidéo image par image avec Python OpenCV
Prévision de stock avec TensorFlow (LSTM) ~ Prévision de stock Partie 1 ~
Prédire FX avec LSTM à l'aide de Keras + Tensorflow Partie 2 (Calculer avec GPU)
Essayez de prédire FX avec LSTM en utilisant Keras + Tensorflow Partie 3 (essayez d'atteindre tous les paramètres)
Zundokokiyoshi avec TensorFlow
Casser des blocs avec Tensorflow