了解 Keras 中语音识别的 CTC 损失
Understanding CTC loss for speech recognition in Keras
我想了解 CTC 损失如何用于语音识别,以及如何在 Keras 中实现它。
- 我认为我明白了(如果我错了请纠正我!)
总的来说,CTC 损失被添加到经典网络之上,以便逐个元素地解码顺序信息元素(文本或语音的字母逐个字母)而不是直接解码元素块(例如单词).
假设我们将一些句子的发音作为 MFCC。
使用 CTC-loss 的目的是学习如何使每个字母在每个时间步都匹配 MFCC。因此,Dense+softmax输出层由与组成句子所需的元素数量一样多的神经元组成:
- 字母表(a、b、...、z)
- 一个空白标记 (-)
- a space (_) 和结束符 (>)
然后,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)
With ALPHABET_LENGTH = 29(字母长度+特殊字符)
并且:
- y_true:包含真值标签的张量(样本,max_string_length)。
- y_pred:张量(样本,time_steps,num_categories)包含预测,或 softmax 的输出。
- input_length:tensor(samples, 1)包含y_pred.
中每个batch item的序列长度
- label_length:tensor(samples, 1)包含y_true.
中每个batch item的序列长度
(source)
现在,我遇到了一些问题:
- 我不明白的地方
- 这种植入是编码和使用 CTC 损失的正确方法吗?
- 具体什么是y_true、input_length和
label_length。有例子吗?
- 我应该以什么形式将标签提供给网络?再一次,有例子吗?
这些是什么?
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 - 如果您关心长度。
这有点复杂,你需要你的模型以某种方式告诉你每个输出句子的长度。
也有几种创造性的方式可以做到这一点:
- 有一个 "end_of_sentence" 字符并检测它在句子中的位置。
- 让你的模型的一个分支来计算这个数字并将它四舍五入为整数。
- (硬核)如果你正在使用有状态的手动训练循环,获取你决定完成一个句子的迭代的索引
我喜欢第一个想法,并将在这里举例说明。
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)
我想了解 CTC 损失如何用于语音识别,以及如何在 Keras 中实现它。
- 我认为我明白了(如果我错了请纠正我!)
总的来说,CTC 损失被添加到经典网络之上,以便逐个元素地解码顺序信息元素(文本或语音的字母逐个字母)而不是直接解码元素块(例如单词).
假设我们将一些句子的发音作为 MFCC。
使用 CTC-loss 的目的是学习如何使每个字母在每个时间步都匹配 MFCC。因此,Dense+softmax输出层由与组成句子所需的元素数量一样多的神经元组成:
- 字母表(a、b、...、z)
- 一个空白标记 (-)
- a space (_) 和结束符 (>)
然后,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)
With ALPHABET_LENGTH = 29(字母长度+特殊字符)
并且:
- y_true:包含真值标签的张量(样本,max_string_length)。
- y_pred:张量(样本,time_steps,num_categories)包含预测,或 softmax 的输出。
- input_length:tensor(samples, 1)包含y_pred. 中每个batch item的序列长度
- label_length:tensor(samples, 1)包含y_true. 中每个batch item的序列长度
(source)
现在,我遇到了一些问题:
- 我不明白的地方
- 这种植入是编码和使用 CTC 损失的正确方法吗?
- 具体什么是y_true、input_length和 label_length。有例子吗?
- 我应该以什么形式将标签提供给网络?再一次,有例子吗?
这些是什么?
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 - 如果您关心长度。
这有点复杂,你需要你的模型以某种方式告诉你每个输出句子的长度。
也有几种创造性的方式可以做到这一点:
- 有一个 "end_of_sentence" 字符并检测它在句子中的位置。
- 让你的模型的一个分支来计算这个数字并将它四舍五入为整数。
- (硬核)如果你正在使用有状态的手动训练循环,获取你决定完成一个句子的迭代的索引
我喜欢第一个想法,并将在这里举例说明。
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)