[TensorFlow 2] Apprendre RNN avec perte CTC

introduction

J'ai essayé TensorFlow 2.x pour apprendre les paramètres de RNN (Recurrent Neural Network) qui renvoie une série en utilisant la perte CTC (Connectionist Temporal Classification). J'ai fait une note car il y avait peu d'échantillons et j'avais du mal à le déplacer.

La perte de CTC est résumée dans les pages suivantes.

Environnement de vérification

Exemple de code basé

GitHub - igormq/ctc_tensorflow_example: CTC + Tensorflow Example for ASR

Il s'agit d'un exemple implémenté dans TensorFlow 1.x sans utiliser l'API Keras. La correspondance entre les séries de quantités de caractéristiques et les séries d'étiquettes (caractères) est apprise par LSTM, comme un échantillon de reconnaissance vocale de bout en bout.

C'est une version 1.x du code, mais ce n'est pas difficile à exécuter avec TensorFlow 2.x.

#Installez les packages requis
pip3 install python_speech_features --user
#Obtenez le code
git clone https://github.com/igormq/ctc_tensorflow_example.git

Si vous modifiez `` ctc_tensorflow_example.py '' sur 3 lignes comme indiqué ci-dessous, cela fonctionnera avec TensorFlow 2.x.

patch


diff --git a/ctc_tensorflow_example.py b/ctc_tensorflow_example.py
index 579d431..2d96d54 100644
--- a/ctc_tensorflow_example.py
+++ b/ctc_tensorflow_example.py
@@ -5,7 +5,7 @@ from __future__ import print_function
 
 import time
 
-import tensorflow as tf
+import tensorflow.compat.v1 as tf
 import scipy.io.wavfile as wav
 import numpy as np
 
@@ -20,6 +20,8 @@ except 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
@@ -103,9 +105,9 @@ with graph.as_default():
     #   tf.nn.rnn_cell.GRUCell 
     cells = []
     for _ in range(num_layers):
-        cell = tf.contrib.rnn.LSTMCell(num_units)  # Or LSTMCell(num_units)
+        cell = tf.nn.rnn_cell.LSTMCell(num_units)  # Or LSTMCell(num_units)
         cells.append(cell)
-    stack = tf.contrib.rnn.MultiRNNCell(cells)
+    stack = tf.nn.rnn_cell.MultiRNNCell(cells)
 
     # The second output is the last state and we will no use that
     outputs, _ = tf.nn.dynamic_rnn(stack, inputs, seq_len, dtype=tf.float32)

Terminal


python3 ctc_tensorflow_example.py
Epoch 1/200, train_cost = 726.374, train_ler = 1.000, val_cost = 167.637, val_ler = 1.000, time = 0.549
(Omis)
Epoch 200/200, train_cost = 0.648, train_ler = 0.000, val_cost = 0.642, val_ler = 0.000, time = 0.218
Original:
she had your dark suit in greasy wash water all year
Decoded:
she had your dark suit in greasy wash water all year

Convertir en code pour TensorFlow 2

Si vous utilisez TensorFlow 2 avec beaucoup d'efforts, écrire pour TensorFlow 2 améliorera (probablement) l'efficacité du traitement, et ce sera bon pour une maintenance ultérieure. Je vais donc essayer de réécrire l'exemple de code, mais je ne trouve pas d'exemple sur la façon de l'écrire ...

Cela a finalement fonctionné comme si j'avais coupé et collé le code à divers endroits. Les principaux sites de référence sont les suivants.

  1. Effective TensorFlow 2 | TensorFlow Core
  2. TensorFlow 2.0 Alpha: Convertir le code existant en TensorFlow 2.0 - TensorFlow 2.x
  3. [TensorFlow 2.0 Principales modifications - S-Analysis](http://data-analysis-stats.jp/2019/06/09/tensorflow-2-0-%E4%B8%BB%E3%81% AA% E5% A4% 89% E6% 9B% B4% E7% 82% B9 /)

Exemple de code (1)

ctc_tensorflow_example_tf2.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

# 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 = 200
num_hidden = 50
num_layers = 1
batch_size = 1
initial_learning_rate = 1e-2
momentum = 0.9

num_examples = 1
num_batches_per_epoch = int(num_examples/batch_size)

# Loading the data

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

fs, audio = wav.read(audio_filename)

inputs = mfcc(audio, samplerate=fs)
# Transform in 3D array
train_inputs = np.asarray(inputs[np.newaxis, :], dtype=np.float32)
train_inputs = (train_inputs - np.mean(train_inputs))/np.std(train_inputs)

train_seq_len = [train_inputs.shape[1]]

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

train_targets = tf.sparse.SparseTensor(*sparse_tuple_from([targets], dtype=np.int32))

train_targets_len = [train_targets.shape[1]]

# 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!

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

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.RNN(stack, input_shape=(None, num_features), return_sequences=True))
# Truncated normal with mean 0 and stdev=0.1
# Zero initialization        
model.add(tf.keras.layers.Dense(num_classes,
                          kernel_initializer=tf.keras.initializers.TruncatedNormal(0.0, 0.1),
                          bias_initializer="zeros"))
optimizer = tf.keras.optimizers.SGD(initial_learning_rate, momentum)

@tf.function
def step(inputs, targets, seq_len, targets_len, flag_training):
    if flag_training:
        with tf.GradientTape() as tape:
            logits = model(inputs, training=True)
            # Time major
            logits = tf.transpose(logits, (1, 0, 2))
            cost = tf.reduce_mean(tf.nn.ctc_loss(targets, logits, targets_len, seq_len, blank_index=-1))

        gradients = tape.gradient(cost, model.trainable_variables)
        optimizer.apply_gradients(zip(gradients, model.trainable_variables))
    else:
        logits = model(inputs)
        # Time major
        logits = tf.transpose(logits, (1, 0, 2))
        cost = tf.reduce_mean(tf.nn.ctc_loss(targets, logits, targets_len, seq_len, blank_index=-1))

    # Option 2: tf.nn.ctc_beam_search_decoder
    # (it's slower but you'll get better results)
    decoded, _ = tf.nn.ctc_greedy_decoder(logits, seq_len)

    # Inaccuracy: label error rate
    ler = tf.reduce_mean(tf.edit_distance(tf.cast(decoded[0], tf.int32),
                                          targets))
    return cost, ler, decoded

for curr_epoch in range(num_epochs):
    train_cost = train_ler = 0
    start = time.time()

    for batch in range(num_batches_per_epoch):
        batch_cost, batch_ler, _ = step(train_inputs, train_targets, train_seq_len, train_targets_len, True)
        train_cost += batch_cost*batch_size
        train_ler += batch_ler*batch_size

    train_cost /= num_examples
    train_ler /= num_examples

    val_cost, val_ler, decoded = step(val_inputs, val_targets, val_seq_len, val_targets_len, False)
    log = "Epoch {}/{}, train_cost = {:.3f}, train_ler = {:.3f}, val_cost = {:.3f}, val_ler = {:.3f}, time = {:.3f}"
    print(log.format(curr_epoch+1, num_epochs, train_cost, train_ler,
                     val_cost, val_ler, time.time() - start))
# Decoding
d = tf.sparse.to_dense(decoded[0])[0].numpy()
str_decoded = ''.join([chr(x) for x in np.asarray(d) + FIRST_INDEX])
# Replacing blank label to none
str_decoded = str_decoded.replace(chr(ord('z') + 1), '')
# Replacing space label to space
str_decoded = str_decoded.replace(chr(ord('a') - 1), ' ')

print('Original:\n%s' % original)
print('Decoded:\n%s' % str_decoded)

Seule la première époque prend du temps, mais après cela, elle semble être environ 30% plus rapide.

python3 ctc_tensorflow_example_tf2.py
Epoch 1/200, train_cost = 774.063, train_ler = 1.000, val_cost = 505.479, val_ler = 0.981, time = 1.547
Epoch 2/200, train_cost = 505.479, train_ler = 0.981, val_cost = 496.959, val_ler = 1.000, time = 0.158
(Omis)
Epoch 200/200, train_cost = 0.541, train_ler = 0.000, val_cost = 0.537, val_ler = 0.000, time = 0.143
Original:
she had your dark suit in greasy wash water all year
Decoded:
she had your dark suit in greasy wash water all year

Explication des changements

Suppression de tf.Session et tf.placeholder

Le code d'origine était basé sur tf.Session '' de TensorFlow 1.x, il ne fonctionnera donc pas avec TensorFlow 2.x (sans l'API tf.compat.v1 ''). .. Le tf.placeholder est parti, et vous pouvez simplement écrire le code qui manipule directement le Tensor entré.

Fondamentalement, la combinaison de tf.Session et tf.placeholder comme décrit dans Effective TensorFlow 2 Réécrivez comme suit.

# TensorFlow 1.X
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TensorFlow 2.0
outputs = f(input)

A ce moment, ajoutez un décorateur @ tf.function pour déplacer f en mode graphique [^ 1].

[^ 1]: Cela fonctionne sans @ tf.function, mais c'est lent parce que c'est Eager Execution. Eager Execution est utile lors du débogage, donc je pense que c'est une bonne idée de supprimer (commenter) @ tf.function '' et d'ajouter @ tf.function`` quand cela fonctionne.

Donc le code d'origine

# TensorFlow 1.X
feed = {inputs: train_inputs,
        targets: train_targets,
        seq_len: train_seq_len}

batch_cost, _ = session.run([cost, optimizer], feed)
train_cost += batch_cost*batch_size
train_ler += session.run(ler, feed_dict=feed)*batch_size

Est une fonction (avec @ tf.function '') qui donne train_inputs, train_targets, train_seq_lencomme arguments et renvoie cost, optimizer comme valeur de retour, si vous pensez en principe. Il sera réécrit. Cependant, `ʻoptimizer n'a pas besoin de renvoyer une valeur car il doit seulement être exécuté. De plus, le même flux '' est donné dans le session.run '' immédiatement après pour calculer ler '', et décodé '' est utilisé dans le processus de décodage une fois l'apprentissage terminé. Je vais les rendre ensemble (j'ai utilisé décoder '' seulement la dernière fois, mais de toute façon, décodéen interne pour le calcul de ler`` Ce n'est pas un gaspillage de traitement car il sera calculé (probablement ...)).

# TensorFlow 2.0
@tf.function
def step(inputs, targets, seq_len, targets_len, flag_training):
(Omis)
    return cost, ler, decoded

batch_cost, batch_ler, _ = step(train_inputs, train_targets, train_seq_len, train_targets_len, True)
train_cost += batch_cost*batch_size
train_ler += batch_ler*batch_size

Afin de réutiliser la majeure partie du traitement à des fins de vérification, j'ai écrit le nom de la fonction step '' pour changer si vous souhaitez vous entraîner avec l'argument supplémentaire flag_training ''. De plus, l'argument cibles_len a été augmenté, mais c'est parce que l'argument donné à tf.nn.ctc_loss a changé dans TensorFlow 2.x, et il ne devrait pas être directement lié à Eager Executionization.

Le tf.sparse_placeholder '' qui était utilisé pour donner l'étiquette correcte de longueur variable a également disparu. J'ai préparé un taple de (indices, valeurs, forme) '' pour donner des données à tf.sparse_placeholder '', mais maintenant je peux spécifier tf.SparseTensordirectement de l'extérieur. Je créerais donc moi-même tf.SparseTensor ''. dtype correspond au type du tf.sparse_placeholder d'origine, mais notez qu'il s'agit de np.int32 au lieu de tf.int32 (subtilement capturé) point).

# TensorFlow 1.X
train_targets = sparse_tuple_from([targets])

# TensorFlow 2.0
train_targets = tf.sparse.SparseTensor(*sparse_tuple_from([targets], dtype=np.int32))

Changement de partie d'apprentissage

Dans TensorFlow 2.x, `ʻOptimizera été modifié pour utiliser celui de Keras. En ligne avec ça, jusqu'à présent J'avais l'habitude d'utiliser ```Optimizer.minimize (), mais je l'ai changé pour utiliser GradientTape dans TensorFlow 2.x. Ce processus est dans le `` step () '' que nous avons défini plus tôt.

##### TensorFlow 1.X #####
# Time major
logits = tf.transpose(logits, (1, 0, 2))

loss = tf.nn.ctc_loss(targets, logits, seq_len)
cost = tf.reduce_mean(loss)

optimizer = tf.train.MomentumOptimizer(initial_learning_rate,
                                           0.9).minimize(cost)

##### TensorFlow 2.0 #####
optimizer = tf.keras.optimizers.SGD(initial_learning_rate, 0.9)

@tf.function
def step(inputs, targets, seq_len, targets_len, flag_training):
    if flag_training:
        with tf.GradientTape() as tape:
            logits = model(inputs, training=True)
            # Time major                                                                                                                              
            logits = tf.transpose(logits, (1, 0, 2))
            cost = tf.reduce_mean(tf.nn.ctc_loss(targets, logits, targets_len, seq_len, blank_index=-1))

        gradients = tape.gradient(cost, model.trainable_variables)
        optimizer.apply_gradients(zip(gradients, model.trainable_variables))
    else:
(Omis ci-dessous)

Ici, je dois donner une liste de poids à (apprendre) pour le calcul de gradient, mais il est difficile de collecter manuellement tf.Variable '', donc je vais faire un graphique de calcul du modèle par moi-même. J'ai changé la partie que j'écrivais en tf.keras.Model ''. Cela facilite l'obtention d'une liste de poids à entraîner avec `` model.trainable_variables ''. Il simplifie également la création de graphiques de calcul.

##### TensorFlow 1.X #####
# The second output is the last state and we will no use that
outputs, _ = tf.nn.dynamic_rnn(stack, inputs, seq_len, dtype=tf.float32)

shape = tf.shape(inputs)
batch_s, max_timesteps = shape[0], shape[1]

# Reshaping to apply the same weights over the timesteps
outputs = tf.reshape(outputs, [-1, num_hidden])

# Truncated normal with mean 0 and stdev=0.1
# Tip: Try another initialization
# see https://www.tensorflow.org/versions/r0.9/api_docs/python/contrib.layers.html#initializers
W = tf.Variable(tf.truncated_normal([num_hidden,
                                     num_classes],
                                    stddev=0.1))
# Zero initialization
# Tip: Is tf.zeros_initializer the same?
b = tf.Variable(tf.constant(0., shape=[num_classes]))

# Doing the affine projection
logits = tf.matmul(outputs, W) + b

# Reshaping back to the original shape
logits = tf.reshape(logits, [batch_s, -1, num_classes])

##### TensorFlow 2.0 #####
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.RNN(stack, input_shape=(None, num_features), return_sequences=True))
# Truncated normal with mean 0 and stdev=0.1
# Zero initialization        
model.add(tf.keras.layers.Dense(num_classes,
                          kernel_initializer=tf.keras.initializers.TruncatedNormal(0.0, 0.1),
                          bias_initializer="zeros"))

Si un tenseur du 3ème étage ou plus comprenant la dimension de l'échantillon est entré dans `` tf.keras.layers.Dense ''

--Aplatir autre que la dernière dimension --Multiplier la matrice de poids à partir de la droite

Ce sera l'opération. Dans le code d'origine, j'ai écrit l'opération de forme avant et après l'écriture du poids par moi-même, mais c'est aussi très facile car elle peut être envoyée à Keras.

Autre

J'ai spécifié dtype '' et défini le type de fonctionnalité sur float32 ''. Cela fonctionne même s'il n'est pas spécifié, mais un AVERTISSEMENT se produit.

train_inputs = np.asarray(inputs[np.newaxis, :], dtype=np.float32)

Amélioré pour gérer les données de longueur variable

Dans l'exemple de code (1) ci-dessus, il n'y avait qu'une seule donnée d'entraînement, mais bien sûr, en réalité, nous voulons placer plusieurs données dans un mini-lot pour l'entraînement. Les données d'entrée et la série d'étiquettes correcte ont des longueurs différentes, vous devez donc les gérer correctement.

Exemple de code (2)

ctc_tensorflow_example_tf2_multi.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_hidden = 50
num_layers = 1
batch_size = 2
initial_learning_rate = 1e-2
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_sparse()

num_examples = train_inputs.shape[0]

# 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!

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

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Masking(FEAT_MASK_VALUE, input_shape=(None, num_features)))
model.add(tf.keras.layers.RNN(stack, return_sequences=True))
# Truncated normal with mean 0 and stdev=0.1
# Zero initialization        
model.add(tf.keras.layers.Dense(num_classes,
                          kernel_initializer=tf.keras.initializers.TruncatedNormal(0.0, 0.1),
                          bias_initializer="zeros"))
optimizer = tf.keras.optimizers.SGD(initial_learning_rate, momentum)

@tf.function
def step(inputs, targets, seq_len, targets_len, flag_training):
    inputs = tf.sparse.to_dense(inputs, default_value=FEAT_MASK_VALUE)
    if flag_training:
        with tf.GradientTape() as tape:
            logits = model(inputs, training=True)
            # Time major
            logits = tf.transpose(logits, (1, 0, 2))
            cost = tf.reduce_mean(tf.nn.ctc_loss(targets, logits, targets_len, seq_len, blank_index=-1))

        gradients = tape.gradient(cost, model.trainable_variables)
        optimizer.apply_gradients(zip(gradients, model.trainable_variables))
    else:
        logits = model(inputs)
        # Time major
        logits = tf.transpose(logits, (1, 0, 2))
        cost = tf.reduce_mean(tf.nn.ctc_loss(targets, logits, targets_len, seq_len, blank_index=-1))

    # Option 2: tf.nn.ctc_beam_search_decoder
    # (it's slower but you'll get better results)
    decoded, _ = tf.nn.ctc_greedy_decoder(logits, seq_len)

    # Inaccuracy: label error rate
    ler = tf.reduce_mean(tf.edit_distance(tf.cast(decoded[0], tf.int32),
                                          targets))
    return cost, ler, decoded

ds = tf.data.Dataset.from_tensor_slices((train_inputs, train_targets, train_seq_len, train_targets_len)).batch(batch_size)
for curr_epoch in range(num_epochs):
    train_cost = train_ler = 0
    start = time.time()

    for batch_inputs, batch_targets, batch_seq_len, batch_targets_len in ds:
        batch_cost, batch_ler, _ = step(batch_inputs, batch_targets, batch_seq_len, batch_targets_len, True)
        train_cost += batch_cost*batch_size
        train_ler += batch_ler*batch_size

    train_cost /= num_examples
    train_ler /= num_examples

    val_cost, val_ler, decoded = step(val_inputs, val_targets, val_seq_len, val_targets_len, False)
    log = "Epoch {}/{}, train_cost = {:.3f}, train_ler = {:.3f}, val_cost = {:.3f}, val_ler = {:.3f}, time = {:.3f}"
    print(log.format(curr_epoch+1, num_epochs, train_cost, train_ler,
                     val_cost, val_ler, time.time() - start))
# Decoding
print('Original:')
print(original)
print(original[13:32])
print('Decoded:')
d = tf.sparse.to_dense(decoded[0], default_value=-1).numpy()
for i in range(2):
    str_decoded = ''.join([chr(x) for x in np.asarray(d[i][d[i] != -1]) + FIRST_INDEX])
    # Replacing blank label to none
    str_decoded = str_decoded.replace(chr(ord('z') + 1), '')
    # Replacing space label to space
    str_decoded = str_decoded.replace(chr(ord('a') - 1), ' ')
    print(str_decoded)

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

Epoch 1/400, train_cost = 527.789, train_ler = 1.122, val_cost = 201.650, val_ler = 1.000, time = 1.702
Epoch 2/400, train_cost = 201.650, train_ler = 1.000, val_cost = 372.285, val_ler = 1.000, time = 0.238
(Omis)
Epoch 400/400, train_cost = 1.331, train_ler = 0.000, val_cost = 1.320, val_ler = 0.000, time = 0.307
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

Commentaire

Préparation des données de longueur variable

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

num_examples = train_inputs.shape[0]

J'ai préparé une partie des données utilisées dans le code d'origine et j'ai augmenté les données à deux. En fin de compte, les données seront SparseTensor '', mais ce sera plus facile si vous créez d'abord RaggedTensor '' en utilisant `` tf.ragged.constant () '', puis convertissez à partir de là. ..

Entrer des données de longueur variable dans le modèle

Comme je l'ai mentionné dans un autre article, j'utilise le calque `` Masquage '' pour représenter des entrées de longueur variable. Essayez RNN de base (LSTM) avec Keras-Qiita

model.add(tf.keras.layers.Masking(FEAT_MASK_VALUE, input_shape=(None, num_features)))

Étant donné que la forme du mini-lot au moment de la saisie est faite avec la longueur de données maximale, lors de la saisie de données courtes, remplissez `` FEAT_MASK_VALUE '' dans la partie manquante de la longueur.

@tf.function
def step(inputs, targets, seq_len, targets_len, flag_training):
    inputs = tf.sparse.to_dense(inputs, default_value=FEAT_MASK_VALUE)

J'ai expliqué le montant de la fonction d'entrée, mais la même chose s'applique au côté étiquette. `` cibles [13:32] '' est juste en train de récupérer l'étiquette correspondant à la section audio coupée (nombre magique ...).

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

Lors de la formation, créez un Dataset '' qui résume les données requises et utilisez batch () '' pour créer un mini-lot. Vous pouvez récupérer des mini-lots en séquence avec une boucle `` for ''.

ds = tf.data.Dataset.from_tensor_slices((train_inputs, train_targets, train_seq_len, train_targets_len)).batch(batch_size)
for curr_epoch in range(num_epochs):
(Omis)
    for batch_inputs, batch_targets, batch_seq_len, batch_targets_len in ds:
(Omis)

En réalité, je pense que les données d'entraînement seront écrites à l'avance dans un fichier au format TFRecord, et que le jeu de données '' sera créé à partir de celui-ci et utilisé. Si vous utilisez tf.io.VarLenFeature '' pour récupérer le montant de la fonctionnalité en tant que `` SparseTensor '' au moment du chargement, vous pouvez utiliser le traitement du contenu de la boucle actuelle tel quel (probablement). [\ TensorFlow 2 ] Il est recommandé de lire la quantité de fonctionnalités à partir de TFRecord en unités de lot - Qiita

Tu ne peux pas tout faire avec Keras?

Il est bon de dire que cela fonctionne avec le traitement basé sur TensorFlow 2.x, mais comme le modèle a été converti en Keras à la fin, réfléchissons à la possibilité d'exécuter la partie d'apprentissage avec l'API Keras. Je vais.

** C'était le début de Shura no Michi ... De la conclusion, il semble que vous ne devriez pas essayer dur. </ del> **

** (Ajouté le 27/04/2020) J'ai trouvé un moyen de bien travailler avec Keras. Veuillez consulter un autre article pour plus de détails. ** ** [\ TensorFlow 2 / Keras ] Comment exécuter l'apprentissage avec perte CTC dans Keras-Qiita

Exemple de code (3)

Il est basé sur l'exemple de code (1) à apprendre avec une donnée.

ctc_tensorflow_example_tf2_keras.py


(Comme il s'agit de la version TF2, la première moitié est omise)

# Creating sparse representation to feed the placeholder
train_targets = tf.sparse.to_dense(tf.sparse.SparseTensor(*sparse_tuple_from([targets], dtype=np.int32)))

(Omis)

def loss(y_true, y_pred):
    #print(y_true)  # Tensor("dense_target:0", shape=(None, None, None), dtype=float32) ???
    targets_len = train_targets_len[0]
    seq_len = train_seq_len[0]
    targets = tf.cast(tf.reshape(y_true, (-1, targets_len)), tf.int32)
    # Time major
    logits = tf.transpose(y_pred, (1, 0, 2))
    return tf.reduce_mean(tf.nn.ctc_loss(targets, logits,
             tf.fill((tf.shape(targets)[0],), targets_len), tf.fill((tf.shape(logits)[1],), seq_len),
             blank_index=-1))

def metrics(y_true, y_pred):
    targets_len = train_targets_len[0]
    seq_len = train_seq_len[0]
    targets = tf.sparse.from_dense(tf.cast(tf.reshape(y_true, (-1, targets_len)), tf.int32))
    # Time major
    logits = tf.transpose(y_pred, (1, 0, 2))

    # Option 2: tf.nn.ctc_beam_search_decoder
    # (it's slower but you'll get better results)
    decoded, _ = tf.nn.ctc_greedy_decoder(logits, train_seq_len)

    # Inaccuracy: label error rate
    ler = tf.reduce_mean(tf.edit_distance(tf.cast(decoded[0], tf.int32),
                                          targets))
    return ler

model.compile(loss=loss, optimizer=optimizer, metrics=[metrics])
for curr_epoch in range(num_epochs):
    train_cost = train_ler = 0
    start = time.time()
    train_cost, train_ler = model.train_on_batch(train_inputs, train_targets)
    val_cost, val_ler = model.test_on_batch(train_inputs, train_targets)
    log = "Epoch {}/{}, train_cost = {:.3f}, train_ler = {:.3f}, val_cost = {:.3f}, val_ler = {:.3f}, time = {:.3f}"
    print(log.format(curr_epoch+1, num_epochs, train_cost, train_ler,
                     val_cost, val_ler, time.time() - start))

decoded, _ = tf.nn.ctc_greedy_decoder(tf.transpose(model.predict(train_inputs), (1, 0, 2)), train_seq_len)
d = tf.sparse.to_dense(decoded[0])[0].numpy()
str_decoded = ''.join([chr(x) for x in np.asarray(d) + FIRST_INDEX])
# Replacing blank label to none
str_decoded = str_decoded.replace(chr(ord('z') + 1), '')
# Replacing space label to space
str_decoded = str_decoded.replace(chr(ord('a') - 1), ' ')

print('Original:\n%s' % original)
print('Decoded:\n%s' % str_decoded)

On dirait que c'est écrit comme ça. Cependant, le comportement est en fait assez suspect ...

Point suspect

Manipulation d'étiquettes clairsemées

Si vous créez un modèle normalement avec Keras, vous ne pouvez pas utiliser d'étiquettes Sparse avec Model.fit () '' ou Model.train_on_batch () ''. Je ne pouvais pas m'en empêcher, alors je l'ai converti en un `` Tensor '' normal.

Puisque l'étiquette doit être clairsemée lors du calcul du taux d'erreur d'étiquette

targets = tf.sparse.from_dense(tf.cast(tf.reshape(y_true, (-1, targets_len)), tf.int32))

Je reviens à Sparse à nouveau, mais cela supprime le symbole ID: 0 qui correspond à l'espace (oui, c'est vrai, c'est une matrice clairsemée qui n'a pas à l'origine 0 ...). Par conséquent, le taux d'erreur est calculé avec des espaces supprimés de la colonne d'étiquette de réponse correcte et le taux d'erreur ne devient pas 0 pour toujours (les erreurs d'insertion se produisent autant que le nombre d'espaces). Je vais. La solution la plus récente consiste à changer le système d'identification de sorte que ID: 0 devienne un symbole vide (≠ espace). Cependant, il est préférable de résoudre le point que vous prenez la peine de définir Sparse sur Dense et de le renvoyer à nouveau ...

Comportement de la fonction de perte

Lors de l'écriture dans Keras, spécifiez la fonction de perte dans Model.compile () ''. Vous pouvez également spécifier votre propre Callable ''

def loss(y_true, y_pred):

Puisque seulement 2 arguments de peuvent être pris, cette fois, nous allons récupérer les informations de longueur de la variable globale. C'est toujours bon jusqu'à ce point.

def loss(y_true, y_pred):
    #print(y_true)  # Tensor("dense_target:0", shape=(None, None, None), dtype=float32) ???
(Omis)
    targets = tf.sparse.from_dense(tf.cast(tf.reshape(y_true, (-1, targets_len)), tf.int32))

Y_true n'est-il pas les données provenant de la bonne étiquette (c'est-à-dire train_targets et val_targets)? Ces dimensions sont censées être deux dimensions de (échantillon, temps) '', mais pour une raison quelconque, ce sont trois dimensions de Tensor ... De plus, l'étiquette originale aurait dû être faite avec `ʻint32, mais pour une raison quelconque, c'est float32`` ...

C'est pourquoi je ne sais pas ce qui va arriver à y_true

targets = tf.sparse.from_dense(tf.cast(tf.reshape(y_true, (-1, targets_len)), tf.int32))

Il a été transformé en deux dimensions et converti en caractères. C'est trop suspect. Mais il semble que l'apprentissage se fait correctement?

Cela peut être la spécification (concept de conception?) De Keras, et la description du document suivant est également tf.keras.losses.Loss | TensorFlow Core v2.1.0

y_true: Ground truth values. shape = [batch_size, d0, .. dN] y_pred: The predicted values. shape = [batch_size, d0, .. dN]

Il peut être lu comme s'il était censé avoir la même forme. Il n'y a pas de problème lors de l'utilisation de la perte d'entropie croisée, etc. dans un problème de classification normal, mais dans le cas de "la longueur de l'étiquette de réponse correcte et le résultat de la prédiction sont différents" comme la perte de CTC, cela devient immédiatement confus.

... Au fait, sparse_categorical_crossentropy a différentes formes de y_true et y_pred, n'est-ce pas? Comment cela est-il possible?

-- y_true: Variable de catégorie (batch_size,) -- y_pred: Score de sortie pour chaque catégorie (batch_size, num_classes)

En d'autres termes, vous devriez pouvoir imiter cette implémentation. Si vous regardez l'implémentation suivante, la transformation et la conversion de type sont incluses, donc en fait cela peut être à peu près la même que l'implémentation actuelle? (Mais toujours suspect) tensorflow/backend.py at v2.1.0 · tensorflow/tensorflow · GitHub

La vitesse d'exécution est lente

Epoch 1/200, train_cost = 774.764, train_ler = 1.190, val_cost = 387.497, val_ler = 1.000, time = 2.212
Epoch 2/200, train_cost = 387.497, train_ler = 1.000, val_cost = 638.239, val_ler = 1.000, time = 0.459
(Omis)
Epoch 200/200, train_cost = 3.549, train_ler = 0.238, val_cost = 3.481, val_ler = 0.238, time = 0.461
Original:
she had your dark suit in greasy wash water all year
Decoded:
she had your dark suit in greasy wash water all year

Cela prend environ 3 fois plus de temps qu'avant la réécriture dans la version Keras (version TensorFlow 2.x) ... [^ 2] De plus, pour les raisons mentionnées ci-dessus, les valeurs de train_ler et val_ler ne sont pas affichées correctement.

[^ 2]: Comme la quantité de données est faible, l'apprentissage lui-même n'est pas lent, mais il se peut que la conversion Keras entraîne une surcharge qui ne dépend pas de la quantité de données. Une autre cause possible est que les étiquettes sont déplacées d'avant en arrière entre Dense et Sparse.

J'ai fait de mon mieux pour écrire la partie d'apprentissage dans le style Keras, mais je me suis retrouvé avec des hacks suspects, et il n'y a rien de bon pour le moment. </ strong> Il peut être résolu avec la mise à niveau de la version de TensorFlow et Keras, mais qu'en est-il? </ del>

Résumé

  • J'ai expliqué comment apprendre les paramètres à l'aide de la perte CTC dans TensorFlow 2.x. Cela semble fonctionner pour le moment. ―― Cela ressemble à l'écriture d'une boucle d'apprentissage de style TensorFlow avec du code Keras mélangé, mais je ne recommande pas de l'écrire entièrement dans Keras car c'est plutôt gênant. </ del>
  • ** (Ajouté le 27/04/2020) Veuillez consulter Article séparé pour savoir comment apprendre dans le style Keras. ** **

Recommended Posts

[TensorFlow 2] Apprendre RNN avec perte CTC
Essayez TensorFlow MNIST avec RNN
[TensorFlow 2 / Keras] Comment exécuter l'apprentissage avec CTC Loss dans Keras
Apprenez les données distribuées avec TensorFlow Y = 2X
Essayez TensorFlow RNN avec un modèle de base
Pratiquez RNN TensorFlow
Zundokokiyoshi avec TensorFlow
Casser des blocs avec Tensorflow
Apprenez Python avec ChemTHEATER
Pandas apprenant avec la chimioinfomatique
Lecture de données avec TensorFlow
Prévisions de courses de bateaux avec TensorFlow
Apprentissage Scikit-Learn avec la chimioinfomatique
Apprenez avec Chemo Informatics Matplotlib
Apprenez avec Chemo Informatics NumPy
Découvrez Wasserstein GAN avec le modèle Keras et l'optimisation TensorFlow
DCGAN avec TF Learn
Essayez la régression avec TensorFlow
[Comment!] Apprenez et jouez à Super Mario avec Tensorflow !!
Apprenez Pendulum-v0 avec DDPG
Ne pas apprendre avec la séquence TensorFlow ~ Fibonacci de la bibliothèque d'apprentissage automatique
Traduire Premiers pas avec TensorFlow
Apprenez librosa avec un tutoriel 1
Essayez l'apprentissage en profondeur avec TensorFlow
Fonction sinueuse approximative avec TensorFlow
Perte de poids Recherche élastique avec Curator
Apprenez les orbites elliptiques avec Chainer
Apprenez de nouvelles données avec PaintsChainer
Prévision du cours de l'action avec tensorflow