Ich habe TensorFlow 2.x ausprobiert, um die Parameter von RNN (Recurrent Neural Network) zu lernen, das eine Serie mit CTC (Connectionist Temporal Classification) Loss zurückgibt. Ich machte mir eine Notiz, weil es nur wenige Proben gab und ich es schwer hatte, sie zu bewegen.
Der CTC-Verlust ist auf den folgenden Seiten zusammengefasst.
GitHub - igormq/ctc_tensorflow_example: CTC + Tensorflow Example for ASR
Dies ist ein Beispiel, das in TensorFlow 1.x ohne Verwendung der Keras-API implementiert wurde. Die Entsprechung zwischen Merkmalsmengenreihen und Etiketten- (Zeichen-) Reihen wird von LSTM wie ein Ende-zu-Ende-Spracherkennungsmuster gelernt.
Es ist eine 1.x-Version des Codes, aber es ist nicht schwierig, mit TensorFlow 2.x zu arbeiten.
#Installieren Sie die erforderlichen Pakete
pip3 install python_speech_features --user
#Holen Sie sich den Code
git clone https://github.com/igormq/ctc_tensorflow_example.git
Wenn Sie ctc_tensorflow_example.py
wie unten gezeigt um 3 Zeilen ändern, funktioniert dies mit 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
(Weggelassen)
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
Wenn Sie TensorFlow 2 mit viel Aufwand verwenden, verbessert das Schreiben für TensorFlow 2 (wahrscheinlich) die Verarbeitungseffizienz und ist für eine spätere Wartung geeignet. Also werde ich versuchen, den Beispielcode neu zu schreiben, aber ich kann kein Beispiel dafür finden, wie man ihn schreibt ...
Es funktionierte schließlich, als hätte ich den Code an verschiedenen Stellen ausgeschnitten und eingefügt. Die Hauptreferenzstellen sind wie folgt.
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)
Nur die erste Epoche braucht Zeit, aber danach scheint sie etwa 30% schneller zu sein.
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
(Weggelassen)
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
Der ursprüngliche Code basierte auf "tf.Session" von TensorFlow 1.x, daher funktioniert er nicht mit TensorFlow 2.x (ohne die API "tf.compat.v1"). .. Der tf.placeholder
ist weg und Sie können einfach den Code schreiben, der den eingegebenen Tensor
direkt manipuliert.
Grundsätzlich ist die Kombination von "tf.Session" und "tf.placeholder" wie in Effective TensorFlow 2 beschrieben. Schreiben Sie wie folgt um.
# TensorFlow 1.X
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TensorFlow 2.0
outputs = f(input)
Fügen Sie zu diesem Zeitpunkt einen Dekorator "@ tf.function" hinzu, um "f" im Grafikmodus [^ 1] zu verschieben.
[^ 1]: Es funktioniert ohne @ tf.function
, aber es ist langsam, weil es Eager Execution ist. Eager Execution ist nützlich beim Debuggen, daher halte ich es für eine gute Idee, @ tf.function
zu entfernen (zu kommentieren) und @ tf.function
hinzuzufügen, wenn es funktioniert.
Also der Originalcode
# 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
Ist eine Funktion (mit "@ tf.function"), die "train_inputs, train_targets, train_seq_len" als Argumente angibt und "cost, optimizer" als Rückgabewert zurückgibt, wenn Sie im Prinzip denken. Es wird neu geschrieben.
Optimizer
muss jedoch nur ausgeführt werden und muss keinen Wert zurückgeben. Außerdem wird derselbe "Feed" unmittelbar nach "session.run" angegeben, um "ler" zu berechnen, und "decoded" wird im Decodierungsprozess verwendet, nachdem das Lernen abgeschlossen ist. Ich werde sie zusammen zurückgeben (ich habe "decodieren" nur das letzte Mal verwendet, aber trotzdem "decodiert" intern für die Berechnung von "ler") Es ist keine Verschwendung von Verarbeitung, da es berechnet wird (wahrscheinlich ...)).
# TensorFlow 2.0
@tf.function
def step(inputs, targets, seq_len, targets_len, flag_training):
(Weggelassen)
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
Um den größten Teil der Verarbeitung für Überprüfungszwecke wiederzuverwenden, habe ich den Funktionsnamen "step" geschrieben, um zu wechseln, ob mit dem zusätzlichen Argument "flag_training" trainiert werden soll.
Darüber hinaus wurde das Argument target_len
erhöht. Dies liegt jedoch daran, dass sich das Argument für tf.nn.ctc_loss
in TensorFlow 2.x geändert hat und nicht direkt mit Eager Executionization zusammenhängen sollte.
Der tf.sparse_placeholder
, der verwendet wurde, um die korrekte Bezeichnung mit variabler Länge zu erhalten, ist ebenfalls verschwunden. Ich habe ein Taple von "(Indizes, Werte, Form)" vorbereitet, um "tf.sparse_placeholder" Daten zu geben, aber jetzt kann ich "tf.SparseTensor" direkt von außen angeben. Also würde ich "tf.SparseTensor" selbst erstellen. dtype
entspricht dem Typ des ursprünglichen tf.sparse_placeholder
, aber beachten Sie, dass es np.int32
anstelle von tf.int32
ist (subtil abgefangen) Punkt).
# TensorFlow 1.X
train_targets = sparse_tuple_from([targets])
# TensorFlow 2.0
train_targets = tf.sparse.SparseTensor(*sparse_tuple_from([targets], dtype=np.int32))
In TensorFlow 2.x wurde Optimizer
geändert, um das von Keras zu verwenden. In Übereinstimmung damit bisher
Als ich "Optimizer.minimize ()" verwendet habe, habe ich es geändert, um "GradientTape" von TensorFlow 2.x zu verwenden. Dieser Prozess befindet sich in dem zuvor definierten Schritt ().
##### 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:
(Unten weggelassen)
Hier müssen wir eine Liste von Gewichten angeben, die (gelernt) werden sollen, um den Gradienten zu berechnen, aber es ist mühsam, manuell definierte "tf.Variable" zu erfassen, sodass wir selbst ein Berechnungsdiagramm des Modells erstellen. Ich habe den Teil, den ich schrieb, in "tf.keras.Model" geändert. Dies macht es einfach, eine Liste von Gewichten zu erhalten, die mit model.trainable_variables
trainiert werden sollen. Es vereinfacht auch die Erstellung von Berechnungsdiagrammen.
##### 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"))
Wenn ein Tensor im 3. Stock oder höher einschließlich der Probenabmessung in "tf.keras.layers.Dense" eingegeben wird
Es wird die Operation sein. Im ursprünglichen Code habe ich die Formoperation vor und nach dem Schreiben des Gewichts selbst geschrieben, aber es ist auch sehr einfach, da es auf Keras geworfen werden kann.
Ich habe dtype
angegeben und den Feature-Typ auf float32
gesetzt. Es funktioniert auch, wenn es nicht angegeben ist, aber WARNUNG auftritt.
train_inputs = np.asarray(inputs[np.newaxis, :], dtype=np.float32)
Im obigen Beispielcode (1) gab es nur eine Trainingsdaten, aber in Wirklichkeit möchten wir natürlich mehrere Daten für das Training in einem Mini-Batch speichern. Sowohl die Eingabedaten als auch die richtigen Etikettenserien haben unterschiedliche Längen, daher müssen Sie gut damit umgehen.
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)
Das Ausführungsergebnis ist beispielsweise wie folgt.
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
(Weggelassen)
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]
Ich habe einen Teil der im Originalcode verwendeten Daten vorbereitet und die Daten auf zwei erhöht. Am Ende sind die Daten "SparseTensor", aber wenn Sie zuerst "RaggedTensor" mit "tf.ragged.constant ()" erstellen und dann von dort konvertieren, ist es einfacher zu erstellen. ..
Wie ich in einem anderen Artikel erwähnt habe, verwende ich die Ebene "Maskierung", um Eingaben mit variabler Länge darzustellen. Probieren Sie Basic RNN (LSTM) mit Keras-Qiita aus
model.add(tf.keras.layers.Masking(FEAT_MASK_VALUE, input_shape=(None, num_features)))
Da die Form des Mini-Batches zum Zeitpunkt der Eingabe mit der maximalen Datenlänge erstellt wird, geben Sie bei der Eingabe kurzer Daten "FEAT_MASK_VALUE" in den fehlenden Teil der Länge ein.
@tf.function
def step(inputs, targets, seq_len, targets_len, flag_training):
inputs = tf.sparse.to_dense(inputs, default_value=FEAT_MASK_VALUE)
Ich habe die Menge der Eingabefunktionen erklärt, aber das Gleiche gilt auf der Etikettenseite.
Ziele [13:32]
ruft nur das Etikett ab, das dem abgeschnittenen Audioabschnitt entspricht (magische Zahl ...).
# 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()
Erstellen Sie während des Trainings einen "Datensatz", der die erforderlichen Daten zusammenfasst, und erstellen Sie mit "batch ()" einen Mini-Batch. Sie können Mini-Batch nacheinander in einer for
-Schleife abrufen.
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):
(Weggelassen)
for batch_inputs, batch_targets, batch_seq_len, batch_targets_len in ds:
(Weggelassen)
In Wirklichkeit denke ich, dass die Trainingsdaten im Voraus in eine Datei im TFRecord-Format geschrieben werden und daraus ein "Datensatz" erstellt und verwendet wird. Wenn Sie tf.io.VarLenFeature
verwenden, um den Feature-Betrag zum Zeitpunkt des Ladens als SparseTensor
abzurufen, können Sie die Verarbeitung des Inhalts der aktuellen Schleife so verwenden, wie sie ist (wahrscheinlich).
[\ TensorFlow 2 ] Es wird empfohlen, die Funktionsmenge von TFRecord in Batch-Einheiten zu lesen - Qiita
Es ist gut zu sagen, dass es mit der TensorFlow 2.x-basierten Verarbeitung funktioniert. Da das Modell jedoch am Ende in Keras konvertiert wurde, sollten wir uns überlegen, ob der Lernteil auch mit der Keras-API ausgeführt werden kann. Ich werde.
** Dies war der Beginn von Shura no Michi ...
Aus der Schlussfolgerung geht hervor, dass Sie sich nicht anstrengen sollten. </ del> **
** (Hinzugefügt am 27.04.2020) Ich habe einen Weg gefunden, gut mit Keras zusammenzuarbeiten. Weitere Informationen finden Sie in einem anderen Artikel. ** ** ** [\ TensorFlow 2 / Keras ] Ausführen des Lernens mit CTC-Verlust in Keras-Qiita
Es basiert auf dem Beispielcode (1), der mit einem Daten gelernt werden soll.
ctc_tensorflow_example_tf2_keras.py
(Da es sich um die TF2-Version handelt, wird die erste Hälfte weggelassen.)
# Creating sparse representation to feed the placeholder
train_targets = tf.sparse.to_dense(tf.sparse.SparseTensor(*sparse_tuple_from([targets], dtype=np.int32)))
(Weggelassen)
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)
Es sieht so aus, als wäre es so geschrieben. Allerdings ist das Verhalten eigentlich ziemlich verdächtig ...
Wenn Sie ein Modell normalerweise mit Keras erstellen, können Sie keine Sparse-Labels mit Model.fit ()
oder Model.train_on_batch ()
verwenden. Ich konnte nicht anders, also habe ich es in einen normalen "Tensor" umgewandelt.
Da das Etikett bei der Berechnung der Etikettenfehlerrate sparsam sein muss
targets = tf.sparse.from_dense(tf.cast(tf.reshape(y_true, (-1, targets_len)), tf.int32))
Ich kehre wieder zu Sparse zurück, aber dadurch wird das ID: 0-Symbol entfernt, das dem Leerzeichen entspricht (ja, das ist richtig, da es sich um eine spärliche Matrix handelt, die ursprünglich keine 0 hat ...). Daher wird die Fehlerrate mit Leerzeichen berechnet, die aus der richtigen Antwortetikettenspalte entfernt wurden, und die Fehlerrate wird nicht für immer 0 (Einfügefehler treten so oft auf wie die Anzahl der Leerzeichen). Ich werde. Die neueste Lösung besteht darin, das ID-System so zu ändern, dass ID: 0 zu einem leeren Symbol (≠ Leerzeichen) wird. Es ist jedoch besser, den Punkt zu lösen, an dem Sie Sparse auf Dense setzen und erneut zurückgeben ...
Geben Sie beim Schreiben in Keras die Verlustfunktion in Model.compile ()
an. Sie können auch Ihr eigenes "Callable" angeben
def loss(y_true, y_pred):
Da nur 2 Argumente von verwendet werden können, werden wir diesmal die Längeninformationen aus der globalen Variablen abrufen. Bis zu diesem Punkt ist es immer noch gut.
def loss(y_true, y_pred):
#print(y_true) # Tensor("dense_target:0", shape=(None, None, None), dtype=float32) ???
(Weggelassen)
targets = tf.sparse.from_dense(tf.cast(tf.reshape(y_true, (-1, targets_len)), tf.int32))
Sind nicht "y_true" die Daten, die vom richtigen Label stammen (dh "train_targets" und "val_targets")? Diese Dimensionen sollen zwei Dimensionen von "(Probe, Zeit)" sein, aber aus irgendeinem Grund sind sie drei Dimensionen von "Tensor" ... Außerdem sollte das Originaletikett mit "int32" erstellt worden sein, aber aus irgendeinem Grund ist es "float32" ...
Deshalb weiß ich nicht, was zu "y_true" kommt
targets = tf.sparse.from_dense(tf.cast(tf.reshape(y_true, (-1, targets_len)), tf.int32))
Es wurde in zwei Dimensionen umgewandelt und typkonvertiert. Es ist zu verdächtig. Aber es scheint, dass das Lernen richtig gemacht wird?
Dies kann die Spezifikation (Designkonzept?) Von Keras sein, und die Beschreibung des folgenden Dokuments ist auch 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]
Es kann gelesen werden, als ob es die gleiche Form haben soll. Es gibt kein Problem bei der Verwendung von Kreuzentropieverlust usw. in einem normalen Klassifizierungsproblem, aber im Fall von "die Länge des richtigen Antwortetiketts und das Vorhersageergebnis sind unterschiedlich" wie bei CTC-Verlust wird es sofort verwirrt.
… Übrigens hat sparse_categorical_crossentropy
verschiedene Formen von y_true
und y_pred
, richtig? Wie wird das erreicht?
-- y_true
: Kategorievariable (batch_size,)
-- y_pred
: Ausgabewert für jede Kategorie (batch_size, num_classes)
Mit anderen Worten, Sie sollten in der Lage sein, diese Implementierung nachzuahmen. Wenn Sie sich die folgende Implementierung ansehen, sind Transformation und Typkonvertierung enthalten, sodass sie möglicherweise in etwa der aktuellen Implementierung entspricht. (Aber immer noch verdächtig) 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
(Weggelassen)
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
Es dauert ungefähr dreimal länger als vor dem Umschreiben auf die Keras-Version (TensorFlow 2.x-Version) ... [^ 2] Darüber hinaus werden aus den oben genannten Gründen die Werte von "train_ler" und "val_ler" nicht korrekt ausgegeben.
[^ 2]: Da die Datenmenge gering ist, ist das Lernen selbst nicht langsam, aber es kann sein, dass die Keras-Konvertierung einen Overhead verursacht, der nicht von der Datenmenge abhängt. Eine andere mögliche Ursache ist, dass die Etiketten zwischen Dense und Sparse hin und her verschoben werden.
Ich habe mein Bestes versucht, den Lernteil im Keras-Stil zu schreiben, aber am Ende hatte ich verdächtige Hacks, und im Moment gibt es nichts Gutes. </ strong> Es kann mit dem Versions-Upgrade von TensorFlow und Keras gelöst werden, aber wie wäre es damit? </ del>
Recommended Posts