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.
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 **.
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)
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)
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
.
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.
É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.
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)
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 '')
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)
―― Masking
et ```input_feature_len`` ont des fonctions similaires, donc cela semble quelque peu redondant ...
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