Понимание потери CTC для распознавания речи в Keras
Я пытаюсь понять, как потеря CTC работает для распознавания речи и как она может быть реализована в Keras.
- Что я думаю, что понял (пожалуйста, поправьте меня, если я ошибаюсь!)
В общем, потери CTC добавляются поверх классической сети, чтобы декодировать последовательный информационный элемент за элементом (буква за буквой для текста или речи), а не напрямую декодировать блок элемента напрямую (например, словом).
Допустим, мы подаем высказывания некоторых предложений как MFCC.
Цель использования CTC-loss - узнать, как заставить каждую букву соответствовать MFCC на каждом временном шаге. Таким образом, выходной слой Dense+softmax состоит из такого количества нейронов, которое соответствует количеству элементов, необходимых для составления предложений:
- алфавит (a, b, ..., z)
- пустой токен (-)
- пробел (_) и конечный символ (>)
Затем слой softmax имеет 29 нейронов (26 для алфавита + некоторые специальные символы).
Чтобы реализовать это, я обнаружил, что могу сделать что-то вроде этого:
# CTC implementation from Keras example found at https://github.com/keras-
# team/keras/blob/master/examples/image_ocr.py
def ctc_lambda_func(args):
y_pred, labels, input_length, label_length = args
# the 2 is critical here since the first couple outputs of the RNN
# tend to be garbage:
# print "y_pred_shape: ", y_pred.shape
y_pred = y_pred[:, 2:, :]
# print "y_pred_shape: ", y_pred.shape
return K.ctc_batch_cost(labels, y_pred, input_length, label_length)
input_data = Input(shape=(1000, 20))
#let's say each MFCC is (1000 timestamps x 20 features)
x = Bidirectional(lstm(...,return_sequences=True))(input_data)
x = Bidirectional(lstm(...,return_sequences=True))(x)
y_pred = TimeDistributed(Dense(units=ALPHABET_LENGTH, activation='softmax'))(x)
loss_out = Lambda(function=ctc_lambda_func, name='ctc', output_shape=(1,))(
[y_pred, y_true, input_length, label_length])
model = Model(inputs=[input_data, y_true, input_length,label_length],
outputs=loss_out)
С ALPHABET_LENGTH = 29 (длина алфавита + специальные символы)
А также:
- y_true: тензор (samples, max_string_length), содержащий метки истинности.
- y_pred: тензор (samples, time_steps, num_categories), содержащий прогноз или вывод softmax.
- input_length: тензор (samples, 1), содержащий длину последовательности для каждого элемента пакета в y_pred.
- label_length: тензор (samples, 1), содержащий длину последовательности для каждого элемента пакета в y_true.
( источник)
Теперь я столкнулся с некоторыми проблемами:
- Что я не понимаю
- Является ли эта имплантация правильным способом кодирования и использования потери CTC?
- Я не понимаю, что конкретно y_true, input_length и label_length. Есть примеры?
- В каком виде я должен отдавать метки в сеть? Опять же, есть примеры?
1 ответ
Что это?
y_true
ваши наземные данные правды. Данные, которые вы собираетесь сравнить с результатами модели при обучении. (С другой стороны,y_pred
это расчетный результат модели)input_length
длина (в шагах, или символы в этом случае) каждого образца (предложения) вy_pred
тензор (как здесь сказано)label_length
длина (в шагах, или символы в этом случае) каждого образца (предложения) вy_true
(или метки) тензор.
Кажется, эта потеря ожидает, что выходные данные вашей модели (y_pred
) имеют разную длину, а также ваши основные данные правды (y_true
). Это, вероятно, позволит избежать подсчета потерь для мусорных символов после конца предложений (поскольку вам понадобится тензор фиксированного размера для работы с большим количеством предложений одновременно)
Форма этикетки:
Так как документация функции запрашивает форму (samples, length)
, формат таков... индекс символа для каждого символа в каждом предложении.
Как это использовать?
Есть несколько возможностей.
1- Если вам не важны длины:
Если все длины одинаковы, вы можете легко использовать его как обычную потерю:
def ctc_loss(y_true, y_pred):
return K.ctc_batch_cost(y_true, y_pred, input_length, label_length)
#where input_length and label_length are constants you created previously
#the easiest way here is to have a fixed batch size in training
#the lengths should have the same batch size (see shapes in the link for ctc_cost)
model.compile(loss=ctc_loss, ...)
#here is how you pass the labels for training
model.fit(input_data_X_train, ground_truth_data_Y_train, ....)
2 - Если вы заботитесь о длине.
Это немного сложнее, вам нужно, чтобы ваша модель как-то сообщала вам длину каждого выходного предложения.
Есть снова несколько творческих форм выполнения этого:
- Имейте char "end_of_sentence" и определяйте, где в предложении он находится.
- Есть ветвь вашей модели, чтобы вычислить это число и округлить его до целого числа.
- (Hardcore) Если вы используете цикл ручного обучения с сохранением состояния, получите индекс итерации, которую вы решили закончить предложение
Мне нравится первая идея, и я проиллюстрирую ее здесь.
def ctc_find_eos(y_true, y_pred):
#convert y_pred from one-hot to label indices
y_pred_ind = K.argmax(y_pred, axis=-1)
#to make sure y_pred has one end_of_sentence (to avoid errors)
y_pred_end = K.concatenate([
y_pred_ind[:,:-1],
eos_index * K.ones_like(y_pred_ind[:,-1:])
], axis = 1)
#to make sure the first occurrence of the char is more important than subsequent ones
occurrence_weights = K.arange(start = max_length, stop=0, dtype=K.floatx())
#is eos?
is_eos_true = K.cast_to_floatx(K.equal(y_true, eos_index))
is_eos_pred = K.cast_to_floatx(K.equal(y_pred_end, eos_index))
#lengths
true_lengths = 1 + K.argmax(occurrence_weights * is_eos_true, axis=1)
pred_lengths = 1 + K.argmax(occurrence_weights * is_eos_pred, axis=1)
#reshape
true_lengths = K.reshape(true_lengths, (-1,1))
pred_lengths = K.reshape(pred_lengths, (-1,1))
return K.ctc_batch_cost(y_true, y_pred, pred_lengths, true_lengths)
model.compile(loss=ctc_find_eos, ....)
Если вы используете другую опцию, используйте ветвь модели, чтобы вычислить длины, объединить их с первым или последним шагом вывода и убедиться, что вы делаете то же самое с истинными длинами в ваших данных истинности. Затем, в функции потерь, просто возьмите секцию для длин:
def ctc_concatenated_length(y_true, y_pred):
#assuming you concatenated the length in the first step
true_lengths = y_true[:,:1] #may need to cast to int
y_true = y_true[:, 1:]
#since y_pred uses one-hot, you will need to concatenate to full size of the last axis,
#thus the 0 here
pred_lengths = K.cast(y_pred[:, :1, 0], "int32")
y_pred = y_pred[:, 1:]
return K.ctc_batch_cost(y_true, y_pred, pred_lengths, true_lengths)