Im vorherigen Artikel habe ich geschrieben, wie man CTC-Verlust (Connectionist Temporal Classification) verwendet, um ein Modell (RNN) zu lernen, das Daten variabler Länge für die Eingabe und Ausgabe mit TensorFlow 2.x verwendet. [\ TensorFlow 2 ] Lernen Sie RNN mit CTC Loss-Qiita
Es war jedoch noch eine entladen. ** Wie man mit CTC-Verlust gut mit Keras umgeht **. Ich habe es im vorherigen Artikel versucht, aber das Ergebnis war unbemerkt, mit vielen zweifelhaften Hacks und langsamer Verarbeitung. Dieses Mal habe ich die Lösung gefunden, notieren Sie sie sich.
Ich denke, dass die hier beschriebene Methode nicht nur auf CTC-Verluste angewendet werden kann, sondern auch, wenn Sie eine spezielle Verlustfunktion definieren und erlernen möchten.
Ich denke, der Grund für die letzte Niederlage war schließlich, dass ich beim Versuch, CTC-Verlust mit Model.compile ()
von Keras zu definieren, nicht weiterkommen konnte.
Tatsächlich gab es jedoch eine andere Möglichkeit, eine Verlustfunktion und eine Bewertungsskala (korrekte Antwortrate usw.) als "Model.compile ()" hinzuzufügen.
Train and evaluate with Keras | TensorFlow Core
The overwhelming majority of losses and metrics can be computed from y_true and y_pred, where y_pred is an output of your model. But not all of them. For instance, a regularization loss may only require the activation of a layer (there are no targets in this case), and this activation may not be a model output.
In such cases, you can call self.add_loss(loss_value) from inside the call method of a custom layer. Here's a simple example that adds activity regularization (note that activity regularization is built-in in all Keras layers -- this layer is just for the sake of providing a concrete example): (Weggelassen) You can do the same for logging metric values: (Weggelassen)
** Wenn Sie Ihre eigene Ebene definieren und add_loss ()
verwenden, können Sie die Verlustfunktion unabhängig vom Prototyp von (y_true, y_pred)
definieren! ** **.
Nein, ich muss das Tutorial richtig lesen ... orz
Die API-Beschreibung von add_loss ()
, die die Verlustfunktion definiert, und add_metric ()
, die die Bewertungsskala definiert, finden Sie auf der folgenden Seite.
tf.keras.layers.Layer | TensorFlow Core v2.1.0
Wie Sie dem Beispielcode im Tutorial entnehmen können, ** sind die Verlustfunktion und die Bewertungsskala "Tensor" und werden durch die Operation "Tensor" zusammengesetzt. ** Das im Beispielcode enthaltene "x1" ist ein "Tensor", der die Ausgabe der Schicht darstellt und zur Darstellung der Verlustfunktion verwendet werden kann.
inputs = keras.Input(shape=(784,), name='digits')
x1 = layers.Dense(64, activation='relu', name='dense_1')(inputs)
x2 = layers.Dense(64, activation='relu', name='dense_2')(x1)
outputs = layers.Dense(10, name='predictions')(x2)
model = keras.Model(inputs=inputs, outputs=outputs)
model.add_loss(tf.reduce_sum(x1) * 0.1)
model.add_metric(keras.backend.std(x1),
name='std_of_activation',
aggregation='mean')
In diesem Fall sind die Informationen zur Länge der Merkmalsmengenreihen / Etikettenreihen sowie die Etikettenreihen selbst notwendige Informationen für die Berechnung des CTC-Verlusts, daher müssen sie als "Tensor" gespeichert werden. Mit anderen Worten, diese müssen auch als Eingaben in das Modell angegeben werden (als x, nicht als y). Mit anderen Worten, ** erstellen Sie ein Modell mit mehreren Eingaben **.
Der ursprüngliche Quellcode ist GitHub - igormq/ctc_tensorflow_example: CTC + Tensorflow Example for ASR ist.
ctc_tensorflow_example_tf2_keras.py
# Compatibility imports
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import time
import tensorflow as tf
import scipy.io.wavfile as wav
import numpy as np
from six.moves import xrange as range
try:
from python_speech_features import mfcc
except ImportError:
print("Failed to import python_speech_features.\n Try pip install python_speech_features.")
raise ImportError
from utils import maybe_download as maybe_download
from utils import sparse_tuple_from as sparse_tuple_from
# Constants
SPACE_TOKEN = '<space>'
SPACE_INDEX = 0
FIRST_INDEX = ord('a') - 1 # 0 is reserved to space
FEAT_MASK_VALUE = 1e+10
# Some configs
num_features = 13
num_units = 50 # Number of units in the LSTM cell
# Accounting the 0th indice + space + blank label = 28 characters
num_classes = ord('z') - ord('a') + 1 + 1 + 1
# Hyper-parameters
num_epochs = 400
num_layers = 1
batch_size = 2
initial_learning_rate = 0.005
momentum = 0.9
# Loading the data
audio_filename = maybe_download('LDC93S1.wav', 93638)
target_filename = maybe_download('LDC93S1.txt', 62)
fs, audio = wav.read(audio_filename)
# create a dataset composed of data with variable lengths
inputs = mfcc(audio, samplerate=fs)
inputs = (inputs - np.mean(inputs))/np.std(inputs)
inputs_short = mfcc(audio[fs*8//10:fs*20//10], samplerate=fs)
inputs_short = (inputs_short - np.mean(inputs_short))/np.std(inputs_short)
# Transform in 3D array
train_inputs = tf.ragged.constant([inputs, inputs_short], dtype=np.float32)
train_seq_len = tf.cast(train_inputs.row_lengths(), tf.int32)
train_inputs = train_inputs.to_tensor(default_value=FEAT_MASK_VALUE)
# Reading targets
with open(target_filename, 'r') as f:
#Only the last line is necessary
line = f.readlines()[-1]
# Get only the words between [a-z] and replace period for none
original = ' '.join(line.strip().lower().split(' ')[2:]).replace('.', '')
targets = original.replace(' ', ' ')
targets = targets.split(' ')
# Adding blank label
targets = np.hstack([SPACE_TOKEN if x == '' else list(x) for x in targets])
# Transform char into index
targets = np.asarray([SPACE_INDEX if x == SPACE_TOKEN else ord(x) - FIRST_INDEX
for x in targets])
# Creating sparse representation to feed the placeholder
train_targets = tf.ragged.constant([targets, targets[13:32]], dtype=np.int32)
train_targets_len = tf.cast(train_targets.row_lengths(), tf.int32)
train_targets = train_targets.to_sparse()
# We don't have a validation dataset :(
val_inputs, val_targets, val_seq_len, val_targets_len = train_inputs, train_targets, \
train_seq_len, train_targets_len
# THE MAIN CODE!
# add loss and metrics with a custom layer
class CTCLossLayer(tf.keras.layers.Layer):
def call(self, inputs):
labels = inputs[0]
logits = inputs[1]
label_len = inputs[2]
logit_len = inputs[3]
logits_trans = tf.transpose(logits, (1, 0, 2))
label_len = tf.reshape(label_len, (-1,))
logit_len = tf.reshape(logit_len, (-1,))
loss = tf.reduce_mean(tf.nn.ctc_loss(labels, logits_trans, label_len, logit_len, blank_index=-1))
# define loss here instead of compile()
self.add_loss(loss)
# decode
decoded, _ = tf.nn.ctc_greedy_decoder(logits_trans, logit_len)
# Inaccuracy: label error rate
ler = tf.reduce_mean(tf.edit_distance(tf.cast(decoded[0], tf.int32),
labels))
self.add_metric(ler, name="ler", aggregation="mean")
return logits # Pass-through layer.
# Defining the cell
# Can be:
# tf.nn.rnn_cell.RNNCell
# tf.nn.rnn_cell.GRUCell
cells = []
for _ in range(num_layers):
cell = tf.keras.layers.LSTMCell(num_units) # Or LSTMCell(num_units)
cells.append(cell)
stack = tf.keras.layers.StackedRNNCells(cells)
input_feature = tf.keras.layers.Input((None, num_features), name="input_feature")
input_label = tf.keras.layers.Input((None,), dtype=tf.int32, sparse=True, name="input_label")
input_feature_len = tf.keras.layers.Input((1,), dtype=tf.int32, name="input_feature_len")
input_label_len = tf.keras.layers.Input((1,), dtype=tf.int32, name="input_label_len")
layer_masking = tf.keras.layers.Masking(FEAT_MASK_VALUE)(input_feature)
layer_rnn = tf.keras.layers.RNN(stack, return_sequences=True)(layer_masking)
layer_output = tf.keras.layers.Dense(
num_classes,
kernel_initializer=tf.keras.initializers.TruncatedNormal(0.0, 0.1),
bias_initializer="zeros",
name="logit")(layer_rnn)
layer_loss = CTCLossLayer()([input_label, layer_output, input_label_len, input_feature_len])
# create models for training and prediction (sharing weights)
model_train = tf.keras.models.Model(
inputs=[input_feature, input_label, input_feature_len, input_label_len],
outputs=layer_loss)
model_predict = tf.keras.models.Model(inputs=input_feature, outputs=layer_output)
optimizer = tf.keras.optimizers.SGD(initial_learning_rate, momentum)
# adding no loss: we have already defined with a custom layer
model_train.compile(optimizer=optimizer)
# training: y is dummy!
model_train.fit(x=[train_inputs, train_targets, train_seq_len, train_targets_len], y=None,
validation_data=([val_inputs, val_targets, val_seq_len, val_targets_len], None),
epochs=num_epochs)
# Decoding
print('Original:')
print(original)
print(original[13:32])
print('Decoded:')
decoded, _ = tf.nn.ctc_greedy_decoder(tf.transpose(model_predict.predict(train_inputs), (1, 0, 2)), train_seq_len)
d = tf.sparse.to_dense(decoded[0], default_value=-1).numpy()
str_decoded = [''.join([chr(x + FIRST_INDEX) for x in np.asarray(row) if x != -1]) for row in d]
for s in str_decoded:
# Replacing blank label to none
s = s.replace(chr(ord('z') + 1), '')
# Replacing space label to space
s = s.replace(chr(ord('a') - 1), ' ')
print(s)
Das Ausführungsergebnis ist wie folgt.
Train on 2 samples, validate on 2 samples
Epoch 1/400
2/2 [==============================] - 2s 991ms/sample - loss: 546.3565 - ler: 1.0668 - val_loss: 464.2611 - val_ler: 0.8801
Epoch 2/400
2/2 [==============================] - 0s 136ms/sample - loss: 464.2611 - ler: 0.8801 - val_loss: 179.9780 - val_ler: 1.0000
(Weggelassen)
Epoch 400/400
2/2 [==============================] - 0s 135ms/sample - loss: 1.6670 - ler: 0.0000e+00 - val_loss: 1.6565 - val_ler: 0.0000e+00
Original:
she had your dark suit in greasy wash water all year
dark suit in greasy
Decoded:
she had your dark suit in greasy wash water all year
dark suit in greasy
Es scheint kein Problem mit der Verarbeitungszeit und dem Wert der Fehlerrate zu geben, und es scheint, dass es endlich richtig funktioniert hat ...! (Da die Verarbeitungszeit der Wert geteilt durch die Anzahl der Abtastwerte ist, ist die tatsächliche Zeit doppelt so groß wie der angezeigte Wert. Wenn sie jedoch für 2 Abtastwerte 300 ms oder weniger beträgt, kann gesagt werden, dass sie mit der vorherigen Zeit identisch ist.)
Wie eingangs erwähnt, können Sie den modellbezogenen Tensor
verwenden, um die Verlustfunktion mithilfe von Layer.add_loss ()
zu definieren. Der obige Code definiert eine Ebene namens "CTCLossLayer" und in "call ()" die Version TensorFlow 2.x (vorheriger Artikel). Siehe #% E3% 82% B5% E3% 83% B3% E3% 83% 97% E3% 83% AB% E3% 82% B3% E3% 83% BC% E3% 83% 892))) Ich schreibe fast den gleichen Prozess. Schließlich wird die Eingabe "logits" so ausgegeben, wie sie ist.
Hier hat call ()
vier Argumente außer self
. Mit diesen vier Informationen können Sie CTC-Verluste verursachen und dekodieren. Wenn Sie ein Modell erstellen, benötigen Sie außerdem vier Eingaben für die Ebene, wie unten gezeigt.
layer_loss = CTCLossLayer()(input_label, layer_output, input_label_len, input_feature_len)
Die im vorherigen Argument angegebenen Informationen müssen "Tensor" sein. layer_output
ist dasselbe wie das normale Keras-Modell, aber input_label, input_label_len, input_feature_len
werden durch Hinzufügen einer Eingabeebene unterstützt.
input_feature = tf.keras.layers.Input((None, num_features), name="input_feature")
input_label = tf.keras.layers.Input((None,), dtype=tf.int32, sparse=True, name="input_label")
input_feature_len = tf.keras.layers.Input((1,), dtype=tf.int32, name="input_feature_len")
input_label_len = tf.keras.layers.Input((1,), dtype=tf.int32, name="input_label_len")
Wie Sie sehen können, erstellen wir eine Input
-Ebene mit der entsprechenden Form und dtype
. Der dtype
des Layers außer dem Feature-Betrag sollte int32
sein.
In Anbetracht der Spezifikation des Arguments von "tf.nn.ctc_loss" möchte ich die Form von "input_feature_len" und "input_label_len" wirklich in "()" ändern, erhalte aber später eine Fehlermeldung. Ich konnte es nicht gut bewegen. Daher wird die Form als "(1,)" geschrieben und "Umformen" in "CTCLossLayer" ausgeführt.
Eine andere Sache, die ich hinzugefügt habe, ist "sparse = True" beim Erstellen von "input_label". Wenn dies angegeben ist, wird der Tensor
, der dem input_label
entspricht, zu SparseTensor
.
tf.keras.Input | TensorFlow Core v2.1.0
Dieses sparse = True
ist ein Maß dafür, dass bei der Berechnung der Fehlerrate des Decodierungsergebnisses (tf.nn.ctc_loss, das bei der Berechnung des CTC-Verlusts verwendet wird) das richtige Antwortetikett mit
SparseTensorübergeben werden muss.
kann auch SparseTensor
) empfangen. Die von "Model.fit ()" usw. angegebenen Daten werden ebenfalls von "SparseTensor" erstellt.
tf.nn.ctc_loss | TensorFlow Core v2.1.0
tf.edit_distance | TensorFlow Core v2.1.0
In ähnlicher Weise scheint es, wenn man die Eingabe von "RaggedTensor" annimmt, "ragged = True" zu geben.
Die Modelle für Training und Vorhersage (Inferenz) werden wie unten gezeigt separat erstellt.
model_train = tf.keras.models.Model(
inputs=[input_feature, input_label, input_feature_len, input_label_len],
outputs=layer_loss)
model_predict = tf.keras.models.Model(inputs=input_feature, outputs=layer_output)
Während des Trainings waren vier Eingaben erforderlich, aber nur Logits (dh die Ausgabe von "Dense") sind für die Vorhersage (Decodierung) erforderlich, sodass es ausreicht, Funktionen als Eingaben zu haben. Daher arbeitet das Vorhersagemodell mit nur einer Eingabe. Natürlich kann der Verlust nicht berechnet werden, aber er ist nicht nur für Decodierungszwecke erforderlich. Geben Sie daher "layer_output" an, bevor Sie "CTCLossLayer" als Ausgabe durchlaufen.
Wenn Sie es in einem Diagramm zeichnen, wird es so sein.
Da die Ebenen mit Gewichten erstellt werden, um gemeinsam genutzt zu werden, ist es möglich, mit dem Vorhersagemodell wie nach dem Training mit dem Trainingsmodell zu schließen.
Da CTC Loss in einer eigenen Ebene definiert wurde, muss in compile ()
keine Verlustfunktion definiert werden. In diesem Fall müssen Sie nicht einfach das Argument "Verlust" angeben.
model_train.compile(optimizer=optimizer)
Die korrekten Beschriftungs- und Längeninformationen, die zur Berechnung der Verlustfunktion verwendet werden, sind die Informationen, die an die Eingabeebene gesendet werden sollen. Sie müssen daher auf der x-Seite des Arguments von Model.fit () angegeben werden. Für y
gibt es nichts zu spezifizieren, also schreibe None
.
In ähnlicher Weise schreiben Sie für "validation_data" "None" als zweiten Teil des Tapples.
model_train.fit(x=[train_inputs, train_targets, train_seq_len, train_targets_len], y=None,
validation_data=([val_inputs, val_targets, val_seq_len, val_targets_len], None),
epochs=num_epochs)
Um ehrlich zu sein, ich weiß nicht, ob dies die Verwendung ist, die die Formel erwartet, aber wenn Sie in compile ()
nicht loss
angeben, funktioniert y = None
einwandfrei ( Wenn "Verlust" angegeben wird, ist eine Bezeichnung erforderlich, die an das Argument "y_true" der Verlustfunktion übergeben werden soll. Daher tritt natürlich ein Fehler auf, sofern nicht einige Daten an "y" übergeben werden.
Wie oben erwähnt, verwenden Sie model_predict
, wenn Sie darauf schließen. Es ist in Ordnung, dem Argument von "vorhersagen ()" nur die Merkmalsmengenreihen zuzuweisen.
decoded, _ = tf.nn.ctc_greedy_decoder(tf.transpose(model_predict.predict(train_inputs), (1, 0, 2)), train_seq_len)
―― Masking
und input_feature_len
haben ähnliche Funktionen, daher fühlt es sich etwas überflüssig an ...
Nachdem Keras das Tutorial richtig gelesen hatte, konnte es mit CTC Loss lernen. Überraschenderweise hat Keras auch eine kleine Wendung. Es tut mir Leid.
Recommended Posts