[PYTHON] [TensorFlow 2] Lernen Sie RNN mit CTC-Verlust

Einführung

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.

Überprüfungsumgebung

Basierend auf Beispielcode

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

In Code für TensorFlow 2 konvertieren

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.

  1. Effective TensorFlow 2 | TensorFlow Core
  2. TensorFlow 2.0 Alpha: Konvertieren Sie vorhandenen Code in TensorFlow 2.0 - TensorFlow 2.x
  3. [TensorFlow 2.0-Hauptänderungen - S-Analyse](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 /)

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

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

Erläuterung der Änderungen

Abschaffung von tf.Session und tf.placeholder

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

Änderung des Lernteils

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.

Andere

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)

Verbessert, um Daten mit variabler Länge zu verarbeiten

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.

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

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

Kommentar

Vorbereitung von Daten variabler Länge

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

Geben Sie Daten variabler Länge in das Modell ein

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

Kannst du nicht alles mit Keras machen?

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

Beispielcode (3)

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

Verdächtiger Punkt

Umgang mit spärlichen Etiketten

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

Verlustfunktionsverhalten

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

Die Ausführungsgeschwindigkeit ist langsam

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>

Zusammenfassung

  • Ich habe erklärt, wie man Parameter mit CTC Loss in TensorFlow 2.x lernt. Es scheint vorerst zu funktionieren. ――Es sieht so aus, als würde eine Lernschleife im TensorFlow-Stil mit eingemischtem Keras-Code geschrieben, aber Ich empfehle nicht, sie vollständig in Keras zu schreiben, da dies ziemlich mühsam ist. </ del>
  • ** (Hinzugefügt am 27.04.2020) Informationen zum Erlernen im Keras-Stil finden Sie unter Separater Artikel. ** ** **

Recommended Posts

[TensorFlow 2] Lernen Sie RNN mit CTC-Verlust
Versuchen Sie TensorFlow MNIST mit RNN
[TensorFlow 2 / Keras] Ausführen des Lernens mit CTC Loss in Keras
Lernen Sie mit TensorFlow Y = 2X verteilte Daten
Probieren Sie TensorFlows RNN mit einem Basismodell aus
Übe RNN TensorFlow
Zundokokiyoshi mit TensorFlow
Brechen Sie Blöcke mit Tensorflow
Lerne Python mit ChemTHEATER
Pandas lernen mit Chemoinfomatik
Daten mit TensorFlow lesen
Bootsrennen Vorhersage mit TensorFlow
Scikit-Lernen mit Chemoinfomatik
Lernen Sie mit Chemo Informatics Matplotlib
Lernen Sie mit Chemo Informatics NumPy
Lernen Sie Wasserstein GAN mit Keras-Modell und TensorFlow-Optimierung
DCGAN mit TF Learn
Versuchen Sie eine Regression mit TensorFlow
[How to!] Lerne und spiele Super Mario mit Tensorflow !!
Lernen Sie Pendulum-v0 mit DDPG
Lernen Sie nicht mit der TensorFlow ~ Fibonacci-Sequenz der Bibliothek für maschinelles Lernen
Übersetzen Erste Schritte mit TensorFlow
Lernen Sie Librosa mit einem Tutorial 1
Versuchen Sie es mit TensorFlow
Ungefähre Sinusfunktion mit TensorFlow
Gewichtsverlust Elasticsearch mit Kurator
Lernen Sie mit Chainer elliptische Bahnen
Lernen Sie neue Daten mit PaintsChainer
Aktienkursprognose mit Tensorflow