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.
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
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.
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
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))
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.
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)
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.
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
# 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à. ..
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
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
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 ...
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 ...
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
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>
Recommended Posts