[PYTHON] [TensorFlow 2 / Keras] Comment exécuter l'apprentissage avec CTC Loss dans Keras

introduction

Dans l'article précédent, j'ai écrit comment utiliser CTC (Connectionist Temporal Classification) Loss pour apprendre un modèle (RNN) qui prend des données de longueur variable pour l'entrée et la sortie avec TensorFlow 2.x. [\ TensorFlow 2 ] Learn RNN with CTC Loss-Qiita

Cependant, il en restait un déchargé. ** Comment bien gérer la perte de CTC avec Keras **. Je l'ai essayé dans l'article précédent, mais le résultat était imperceptible, avec beaucoup de hacks douteux et un traitement lent. Cette fois, j'ai trouvé la solution, alors prenez-en note.

Je pense que la méthode décrite ici peut être appliquée non seulement à la perte CTC, mais aussi lorsque vous souhaitez définir et apprendre une fonction de perte spéciale.

Environnement de vérification

Compile () n'est pas le seul moyen de définir une fonction de perte

Je pense que la raison de la dernière défaite était, après tout, que je me suis retrouvé coincé à essayer de définir la perte de CTC avec Model.compile () '' de Keras. Cependant, en fait, il y avait un moyen d'ajouter une fonction de perte et une échelle d'évaluation (taux de réponse correcte, etc.) autre que Model.compile () ''.

Train and evaluate with Keras | TensorFlow Core

The overwhelming majority of losses and metrics can be computed from y_true and y_pred, where y_pred is an output of your model. But not all of them. For instance, a regularization loss may only require the activation of a layer (there are no targets in this case), and this activation may not be a model output.

In such cases, you can call self.add_loss(loss_value) from inside the call method of a custom layer. Here's a simple example that adds activity regularization (note that activity regularization is built-in in all Keras layers -- this layer is just for the sake of providing a concrete example): (Omis) You can do the same for logging metric values: (Omis)

** Si vous définissez votre propre calque et utilisez ʻadd_loss () , vous pouvez définir la fonction de perte quel que soit le prototype de (y_true, y_pred) `! ** ** Non, je dois lire le tutoriel correctement ... orz

Une description des API pour ʻadd_loss () , qui définit la fonction de perte, et ʻadd_metric () , qui définit l'échelle de valorisation, peut être trouvée sur les pages suivantes. tf.keras.layers.Layer | TensorFlow Core v2.1.0

Comme vous pouvez le voir dans l'exemple de code dans le didacticiel, ** la fonction de perte et l'échelle d'évaluation sont Tensor '', et elles sont assemblées par l'opération Tensor ''. ** Le x1 '' inclus dans l'exemple de code est un Tensor '' qui représente la sortie de la couche et peut être utilisé pour représenter la fonction de perte.

inputs = keras.Input(shape=(784,), name='digits')
x1 = layers.Dense(64, activation='relu', name='dense_1')(inputs)
x2 = layers.Dense(64, activation='relu', name='dense_2')(x1)
outputs = layers.Dense(10, name='predictions')(x2)
model = keras.Model(inputs=inputs, outputs=outputs)

model.add_loss(tf.reduce_sum(x1) * 0.1)

model.add_metric(keras.backend.std(x1),
                 name='std_of_activation',
                 aggregation='mean')

Dans ce cas, les informations sur la série de quantités de caractéristiques / la longueur de la série d'étiquettes, ainsi que la série d'étiquettes elle-même, sont des informations nécessaires pour le calcul de la perte CTC, elles doivent donc être considérées comme `` Tenseur ''. En d'autres termes, ceux-ci doivent également être donnés en tant qu'entrées du modèle (comme x, pas y). En d'autres termes, ** créez un modèle avec plusieurs entrées **.

Exemple de code qui a bien fonctionné

Le code source d'origine est GitHub - igormq/ctc_tensorflow_example: CTC + Tensorflow Example for ASR est.

ctc_tensorflow_example_tf2_keras.py


#  Compatibility imports
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import time

import tensorflow as tf
import scipy.io.wavfile as wav
import numpy as np

from six.moves import xrange as range

try:
    from python_speech_features import mfcc
except ImportError:
    print("Failed to import python_speech_features.\n Try pip install python_speech_features.")
    raise ImportError

from utils import maybe_download as maybe_download
from utils import sparse_tuple_from as sparse_tuple_from

# Constants
SPACE_TOKEN = '<space>'
SPACE_INDEX = 0
FIRST_INDEX = ord('a') - 1  # 0 is reserved to space
FEAT_MASK_VALUE = 1e+10

# Some configs
num_features = 13
num_units = 50 # Number of units in the LSTM cell
# Accounting the 0th indice +  space + blank label = 28 characters
num_classes = ord('z') - ord('a') + 1 + 1 + 1

# Hyper-parameters
num_epochs = 400
num_layers = 1
batch_size = 2
initial_learning_rate = 0.005
momentum = 0.9

# Loading the data

audio_filename = maybe_download('LDC93S1.wav', 93638)
target_filename = maybe_download('LDC93S1.txt', 62)

fs, audio = wav.read(audio_filename)

# create a dataset composed of data with variable lengths
inputs = mfcc(audio, samplerate=fs)
inputs = (inputs - np.mean(inputs))/np.std(inputs)
inputs_short = mfcc(audio[fs*8//10:fs*20//10], samplerate=fs)
inputs_short = (inputs_short - np.mean(inputs_short))/np.std(inputs_short)
# Transform in 3D array
train_inputs = tf.ragged.constant([inputs, inputs_short], dtype=np.float32)
train_seq_len = tf.cast(train_inputs.row_lengths(), tf.int32)
train_inputs = train_inputs.to_tensor(default_value=FEAT_MASK_VALUE)

# Reading targets
with open(target_filename, 'r') as f:

    #Only the last line is necessary
    line = f.readlines()[-1]

    # Get only the words between [a-z] and replace period for none
    original = ' '.join(line.strip().lower().split(' ')[2:]).replace('.', '')
    targets = original.replace(' ', '  ')
    targets = targets.split(' ')

# Adding blank label
targets = np.hstack([SPACE_TOKEN if x == '' else list(x) for x in targets])

# Transform char into index
targets = np.asarray([SPACE_INDEX if x == SPACE_TOKEN else ord(x) - FIRST_INDEX
                      for x in targets])
# Creating sparse representation to feed the placeholder
train_targets = tf.ragged.constant([targets, targets[13:32]], dtype=np.int32) 
train_targets_len = tf.cast(train_targets.row_lengths(), tf.int32)
train_targets = train_targets.to_sparse() 

# We don't have a validation dataset :(
val_inputs, val_targets, val_seq_len, val_targets_len = train_inputs, train_targets, \
                                                        train_seq_len, train_targets_len

# THE MAIN CODE!

# add loss and metrics with a custom layer
class CTCLossLayer(tf.keras.layers.Layer):
    def call(self, inputs):
        labels = inputs[0]
        logits = inputs[1]
        label_len = inputs[2]
        logit_len = inputs[3]

        logits_trans = tf.transpose(logits, (1, 0, 2))
        label_len = tf.reshape(label_len, (-1,))
        logit_len = tf.reshape(logit_len, (-1,))
        loss = tf.reduce_mean(tf.nn.ctc_loss(labels, logits_trans, label_len, logit_len, blank_index=-1))
        # define loss here instead of compile()
        self.add_loss(loss)

        # decode
        decoded, _ = tf.nn.ctc_greedy_decoder(logits_trans, logit_len)

        # Inaccuracy: label error rate
        ler = tf.reduce_mean(tf.edit_distance(tf.cast(decoded[0], tf.int32),
                                          labels))
        self.add_metric(ler, name="ler", aggregation="mean")

        return logits  # Pass-through layer.

# Defining the cell
# Can be:
#   tf.nn.rnn_cell.RNNCell
#   tf.nn.rnn_cell.GRUCell 
cells = []
for _ in range(num_layers):
    cell = tf.keras.layers.LSTMCell(num_units)  # Or LSTMCell(num_units)
    cells.append(cell)
stack = tf.keras.layers.StackedRNNCells(cells)

input_feature     = tf.keras.layers.Input((None, num_features), name="input_feature")
input_label       = tf.keras.layers.Input((None,), dtype=tf.int32, sparse=True, name="input_label")
input_feature_len = tf.keras.layers.Input((1,), dtype=tf.int32, name="input_feature_len")
input_label_len   = tf.keras.layers.Input((1,), dtype=tf.int32, name="input_label_len")

layer_masking = tf.keras.layers.Masking(FEAT_MASK_VALUE)(input_feature)
layer_rnn     = tf.keras.layers.RNN(stack, return_sequences=True)(layer_masking)
layer_output  = tf.keras.layers.Dense(
                   num_classes,
                   kernel_initializer=tf.keras.initializers.TruncatedNormal(0.0, 0.1),
                   bias_initializer="zeros",
                   name="logit")(layer_rnn)
layer_loss = CTCLossLayer()([input_label, layer_output, input_label_len, input_feature_len])

# create models for training and prediction (sharing weights)
model_train = tf.keras.models.Model(
            inputs=[input_feature, input_label, input_feature_len, input_label_len],
            outputs=layer_loss)

model_predict = tf.keras.models.Model(inputs=input_feature, outputs=layer_output)

optimizer = tf.keras.optimizers.SGD(initial_learning_rate, momentum)
# adding no loss: we have already defined with a custom layer
model_train.compile(optimizer=optimizer)

# training: y is dummy!
model_train.fit(x=[train_inputs, train_targets, train_seq_len, train_targets_len], y=None,
                validation_data=([val_inputs, val_targets, val_seq_len, val_targets_len], None),
                epochs=num_epochs)

# Decoding
print('Original:')
print(original)
print(original[13:32])
print('Decoded:')
decoded, _ = tf.nn.ctc_greedy_decoder(tf.transpose(model_predict.predict(train_inputs), (1, 0, 2)), train_seq_len)
d = tf.sparse.to_dense(decoded[0], default_value=-1).numpy()
str_decoded = [''.join([chr(x + FIRST_INDEX) for x in np.asarray(row) if x != -1]) for row in d]
for s in str_decoded:
    # Replacing blank label to none
    s = s.replace(chr(ord('z') + 1), '')
    # Replacing space label to space
    s = s.replace(chr(ord('a') - 1), ' ')
    print(s)

Le résultat de l'exécution est le suivant.

Train on 2 samples, validate on 2 samples
Epoch 1/400
2/2 [==============================] - 2s 991ms/sample - loss: 546.3565 - ler: 1.0668 - val_loss: 464.2611 - val_ler: 0.8801
Epoch 2/400
2/2 [==============================] - 0s 136ms/sample - loss: 464.2611 - ler: 0.8801 - val_loss: 179.9780 - val_ler: 1.0000
(Omis)
Epoch 400/400
2/2 [==============================] - 0s 135ms/sample - loss: 1.6670 - ler: 0.0000e+00 - val_loss: 1.6565 - val_ler: 0.0000e+00
Original:
she had your dark suit in greasy wash water all year
dark suit in greasy
Decoded:
she had your dark suit in greasy wash water all year
dark suit in greasy

Il ne semble y avoir aucun problème avec le temps de traitement et la valeur du taux d'erreur, et il semble que cela a finalement fonctionné correctement ...! (Puisque le temps de traitement est la valeur divisée par le nombre d'échantillons, le temps réel sera le double de la valeur affichée, mais si 2 échantillons font 300 ms ou moins, on peut dire que c'est le même que le temps précédent)

Commentaire

Ajout de perte, métriques

Comme mentionné au début, vous êtes libre d'utiliser le Tensor '' lié au modèle pour définir la fonction de perte en utilisant Layer.add_loss () ''. Le code ci-dessus définit une couche appelée CTCLossLayer, et dans call () '', la version TensorFlow 2.x ([article précédent](https://qiita.com/everylittle/items/4aed10f7a8fbf5c705eb) #% E3% 82% B5% E3% 83% B3% E3% 83% 97% E3% 83% AB% E3% 82% B3% E3% 83% BC% E3% 83% 892) J'écris presque le même processus. Enfin, l'entrée logits '' est sortie telle quelle.

Ici, call () '' a quatre arguments à l'exception de self ''. Avec ces quatre informations, vous pouvez effectuer une perte CTC et décoder. Lors de la création d'un modèle, vous avez également besoin de quatre entrées pour la couche, comme indiqué ci-dessous.

layer_loss = CTCLossLayer()(input_label, layer_output, input_label_len, input_feature_len)

Couche d'entrée

Les informations données dans l'argument précédent doivent être Tensor ''. layer_output est le même que le modèle Keras normal, mais pour `ʻinput_label, input_label_len, input_feature_len, une couche d'entrée est ajoutée pour le supporter.

input_feature     = tf.keras.layers.Input((None, num_features), name="input_feature")
input_label       = tf.keras.layers.Input((None,), dtype=tf.int32, sparse=True, name="input_label")
input_feature_len = tf.keras.layers.Input((1,), dtype=tf.int32, name="input_feature_len")
input_label_len   = tf.keras.layers.Input((1,), dtype=tf.int32, name="input_label_len")

Comme vous pouvez le voir, nous créons un calque Input`` avec la forme et le `` dtype`` appropriés. Le `` dtype`` de la couche autre que la quantité de caractéristiques doit être int32. Compte tenu de la spécification de l'argument de tf.nn.ctc_loss, je veux vraiment changer la forme de ```input_feature_len et de ```input_label_lenen () , mais j'obtiens une erreur plus tard. Je ne pouvais pas bien le déplacer. Par conséquent, la forme est écrite comme (1,) '', et remodeler '' est effectuée dans CTCLossLayer``.

Une autre chose que j'ai ajoutée est sparse = True lorsque input_label`` a été créé. Si cela est spécifié, le `` Tensor`` correspondant à input_labeldevient SparseTensor``. tf.keras.Input | TensorFlow Core v2.1.0

Ce sparse = True '' est une mesure pour avoir à passer l'étiquette de réponse correcte avec SparseTensor '' lors du calcul du taux d'erreur du résultat de décodage (tf.nn.ctc_loss utilisé dans le calcul de la perte CTC). peut également recevoir SparseTensor ''). Les données fournies par Model.fit () '' etc. sont également créées par `` SparseTensor ''. tf.nn.ctc_loss | TensorFlow Core v2.1.0 tf.edit_distance | TensorFlow Core v2.1.0

De même, en supposant l'entrée de RaggedTensor, il semble y avoir ragged = True.

Comment faire un modèle

Les modèles d'entraînement et de prédiction (inférence) sont créés séparément, comme illustré ci-dessous.

model_train = tf.keras.models.Model(
            inputs=[input_feature, input_label, input_feature_len, input_label_len],
            outputs=layer_loss)

model_predict = tf.keras.models.Model(inputs=input_feature, outputs=layer_output)

Quatre entrées étaient nécessaires pendant la formation, mais seuls les Logits (c'est-à-dire la sortie de Dense '') sont nécessaires pour la prédiction (décodage), il suffit donc d'avoir des fonctionnalités comme entrées. Par conséquent, le modèle de prédiction fonctionne avec une seule entrée. Bien sûr, la perte ne peut pas être calculée, mais elle n'est pas nécessaire uniquement à des fins de décodage, alors spécifiez layer_outputavant de passer par CTCLossLayer`` comme sortie.

Si vous le dessinez dans un diagramme, ce sera comme ça. image.png

Étant donné que les couches avec des poids sont créées pour être partagées, il est possible de déduire avec le modèle de prédiction tel qu'il est après l'entraînement avec le modèle d'apprentissage.

Compiler le modèle

Puisque CTC Loss a été défini dans sa propre couche, il n'est pas nécessaire de définir une fonction de perte dans compile () ''. Dans ce cas, vous n'avez pas à spécifier simplement l'argument perte ''.

model_train.compile(optimizer=optimizer)

Exécution de l'apprentissage

Les informations d'étiquette et de longueur correctes utilisées pour calculer la fonction de perte sont les informations à envoyer à la couche d'entrée, elles doivent donc être spécifiées du côté x '' de l'argument de Model.fit () ''. Il n'y a rien à spécifier pour y '', alors écrivez Aucun ''.

De même pour validation_data '', écrivez None '' comme deuxième partie du tapple.

model_train.fit(x=[train_inputs, train_targets, train_seq_len, train_targets_len], y=None,
                validation_data=([val_inputs, val_targets, val_seq_len, val_targets_len], None),
                epochs=num_epochs)

Pour être honnête, je ne sais pas si c'est l'usage que la formule attend, mais si vous ne spécifiez pas loss dans compile () '', y = Nonefonctionne bien ( Lorsque lossest spécifié, une étiquette à passer à l'argument y_truede la fonction de perte est requise, donc naturellement une erreur se produira à moins que certaines données ne soient données à y '')

Sortie du résultat du décodage

Comme mentionné ci-dessus, utilisez model_predict lors de l'inférence. Il est correct de ne donner que la série de quantités de caractéristiques à l'argument de `` predire () ''.

decoded, _ = tf.nn.ctc_greedy_decoder(tf.transpose(model_predict.predict(train_inputs), (1, 0, 2)), train_seq_len)

D'autres choses à craindre

―― Masking et ```input_feature_len`` ont des fonctions similaires, donc cela semble quelque peu redondant ...

Résumé

Après avoir lu correctement le didacticiel, Keras a pu effectuer un apprentissage à l'aide de CTC Loss. Étonnamment, Keras a également un petit tour. Je suis désolé.

Recommended Posts

[TensorFlow 2 / Keras] Comment exécuter l'apprentissage avec CTC Loss dans Keras
Comment exécuter du code TensorFlow 1.0 en 2.0
Comment exécuter CNN en notation système 1 avec Tensorflow 2
Comment exécuter des tests avec Python unittest
[TensorFlow 2] Apprendre RNN avec perte CTC
Pour les débutants, comment gérer les erreurs courantes dans les keras
Comment utiliser BigQuery en Python
Comment gérer les fuites de mémoire dans matplotlib.pyplot
Comment réduire l'utilisation de la mémoire GPU avec Keras
[REAPER] Comment jouer à Reascript avec Python
J'ai essayé d'intégrer Keras dans TFv1.1
Comment gérer les erreurs d'exécution dans subprocess.call
Comment utiliser tkinter avec python dans pyenv
Comment exécuter LeapMotion avec Python non-Apple
[TF] Comment créer Tensorflow dans un environnement Proxy
Comment installer le framework d'apprentissage en profondeur Tensorflow 1.0 dans l'environnement Windows Anaconda
Comment convertir / restaurer une chaîne avec [] en python
Comment exécuter le module Ansible ajouté dans Ansible Tower
Comment exécuter AutoGluon dans un environnement GPU Google Colab
Comment faire un calcul de hachage avec Salt en Python
Expliquez en détail comment créer un son avec python
Comment exécuter python dans l'espace virtuel (pour MacOS)
Comment gérer l'échec de l'initialisation pyenv dans Fish 3.1.0
Comment faire du zéro-padding sur une ligne avec OpenCV
Un mémorandum sur l'utilisation de keras.preprocessing.image de Keras
Comment charger des fichiers dans Google Drive avec Google Colaboratory
Comment partager des dossiers avec Docker et Windows avec tensorflow
Comment accéder avec cache lors de la lecture_json avec pandas
Comment effectuer un apprentissage avec SageMaker sans délai d'expiration de session
Comment exécuter setUp une seule fois dans Python Unittest
J'ai essayé d'implémenter Grad-CAM avec keras et tensorflow
Configuration pour exécuter l'application dans un sous-répertoire avec nginx + uwsgi
Comment gérer l'exécution de la transaction: échec dans Anaconda
[Comment!] Apprenez et jouez à Super Mario avec Tensorflow !!
[TF] Comment enregistrer et charger les paramètres d'entraînement Tensorflow
Comment créer un environnement virtuel Anaconda à utiliser avec Azure Machine Learning et comment créer un lien avec Jupyter
Essayez l'apprentissage en profondeur avec TensorFlow
Comment sortir un document au format pdf avec Sphinx
Apprentissage par renforcement dans les plus brefs délais avec Keras avec OpenAI Gym
Comment extraire n'importe quel rendez-vous dans Google Agenda avec Python
Comment vérifier le comportement d'ORM avec un fichier avec django
Comment dessiner de manière interactive un pipeline d'apprentissage automatique avec scikit-learn et l'enregistrer au format HTML
Comment mettre à jour avec SQLAlchemy?
[Django] Comment donner des valeurs d'entrée à l'avance avec ModelForm
Comment manipuler le DOM dans iframe avec Selenium
Pour ceux qui souhaitent démarrer l'apprentissage automatique avec TensorFlow2
Pour exécuter gym_torcs avec ubutnu16
Comment lancer avec Theano
[AWS] Comment gérer l'erreur "Point de code non valide" dans CloudSearch
Comment exécuter une application construite avec Python + py2app construite avec Anaconda
Comment exécuter Notepad ++ Python
Comment modifier avec SQLAlchemy?
Comment séparer les chaînes avec ','
[TF] Comment charger / enregistrer le modèle et le paramètre dans Keras
Comment faire RDP sur Fedora31
Comment développer en Python
Comment créer une trame de données et jouer avec des éléments avec des pandas
Comment supprimer avec SQLAlchemy?