Понимание потери CTC для распознавания речи в Keras

Я пытаюсь понять, как потеря CTC работает для распознавания речи и как она может быть реализована в Keras.

  1. Что я думаю, что понял (пожалуйста, поправьте меня, если я ошибаюсь!)

В общем, потери 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.

( источник)

Теперь я столкнулся с некоторыми проблемами:

  1. Что я не понимаю
    • Является ли эта имплантация правильным способом кодирования и использования потери 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)
Другие вопросы по тегам