我试图了解CTC丢失如何用于语音识别,以及如何在Keras中实现它。

  • 我认为我了解的内容(如果我错了,请指正!)

  • 总的来说,CTC损失被添加到经典网络的顶部,以便逐个元素地解码顺序的信息元素(文本或语音的逐个字母的字母),而不是直接对元素块直接解码(例如,一个单词)。

    假设我们正在以MFCC的形式提供某些句子的发音。

    使用CTC损失的目的是学习如何使每个字母在每个时间步都与MFCC相匹配。因此,Dense + softmax输出层由与组成句子所需的元素数量一样多的神经元组成:
  • 字母(a,b,...,z)
  • 空白 token (-)
  • 空格(_)和结束字符(>)

  • 然后,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 :包含真值标签的张量(样本,max_string_length)。
  • y_pred :包含预测或softmax输出的张量(样本,time_steps,num_categories)。
  • input_length :张量(样本,1),包含y_pred中每个批处理项目的序列长度。
  • label_length :张量(样本,1),包含y_true中每个批处理项目的序列长度。

  • (source)

    现在,我面临一些问题:
  • 我不了解的内容
  • 这是编写和使用CTC损失的正确方法吗?
  • 我不明白具体是什么 y_true input_length
    label_length 。有什么例子吗?
  • 我应该以哪种形式给网络标签?再次,有什么例子吗?
  • 最佳答案

    这些是什么?

  • y_true您的地面真实数据。您将要在训练中将要与模型输出进行比较的数据。 (另一方面,y_pred是模型的计算输出)
  • input_lengthy_pred张量(如here)中每个样本(句子)的长度(分步,或在这种情况下为chars)
  • label_length,即y_true(或标签)张量中每个样本(句子)的长度(分步,在此情况下为char)。

  • 似乎这种损失期望您模型的输出(y_pred)具有不同的长度,以及地面真实数据(y_true)。这可能是为了避免在句子结束后计算垃圾字符的损失(因为您需要固定大小的张量才能一次处理大量句子)

    标签形式:

    由于函数的文档要求提供(samples, length)的形状,因此格式为...每个句子中每个char的char索引。

    如何使用呢?

    有一些可能性。

    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-如果您在乎长度。

    这有点复杂,您需要模型以某种方式告诉您每个输出语句的长度。
    再次有几种创造性的形式可以做到这一点:
  • 拥有一个“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)
    

    10-06 07:14