You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TensorFlow搭建的ASR模型训练100批次后CTC Loss突变为无穷

问题:基于Mozilla Common Voice 11的ASR模型训练中CTC Loss突变为无穷大

我正在用TensorFlow搭建基于Hugging Face Mozilla Common Voice 11数据集的自动语音识别(ASR)神经网络,但模型训练约100批次后,CTC Loss突然变为无穷大。

数据预处理代码

dataset = datasets.load_dataset("mozilla-foundation/common_voice_11_0", "en")
dataset = dataset.remove_columns(['client_id', 'audio', 'up_votes', 'down_votes', 'age', 'gender', 'accent', 'locale', 'segment'])

def prepare_dataset(batch):
    wav_file = batch['path']
    # Remove file name
    split = wav_file.split("\\")
    joined = "\\".join(split[:-1]) + "\\"
    # Get the train number
    complete_path = glob.glob(joined + "*")
    # Combine all the parts
    file = complete_path[0] + "\\" + split[-1]
    batch['path'] = file
    return batch

train_dataset = dataset['train'].map(prepare_dataset).shuffle(len(dataset['train']))
val_dataset = dataset['validation'].map(prepare_dataset).shuffle(len(dataset['validation']))

frame_length = 256
frame_step = 160
fft_length = 384

def load_mp3(wav_file):
    audio = tfio.audio.AudioIOTensor(wav_file, dtype=tf.float32)
    sample_rate = tf.cast(audio.rate, dtype=tf.int64)
    audio = tf.squeeze(audio.to_tensor())
    audio = tfio.audio.resample(audio, rate_in=sample_rate, rate_out=8000)
    audio = tfio.audio.fade(audio, fade_in=1000, fade_out=2000, mode="logarithmic")
    return audio
    
def convert_to_spect(audio):
    spectrogram = tf.signal.stft(
        audio, frame_length=frame_length, frame_step=frame_step, fft_length=fft_length
    )
    spectrogram = tf.abs(spectrogram)
    spectrogram = tf.math.pow(spectrogram, 0.5)
    
    spectrogram = tfio.audio.freq_mask(spectrogram, param=25)
    spectrogram = tfio.audio.time_mask(spectrogram, param=25)
    spectrogram = tfio.audio.freq_mask(spectrogram, param=25)
    spectrogram = tfio.audio.time_mask(spectrogram, param=25)
    
    means = tf.math.reduce_mean(spectrogram, 1, keepdims=True)
    stddevs = tf.math.reduce_std(spectrogram, 1, keepdims=True)
    spectrogram = (spectrogram - means) / (stddevs + 1e-10)
        
    return spectrogram

def process_text(label):
    label = tf.strings.lower(label)
    label = tf.strings.unicode_split(label, input_encoding="UTF-8")
    label = char_to_num(label)
    
    return label

def encode_mozilla_sample(wav_file, label):
    audio = load_mp3(wav_file)
    spectrogram = convert_to_spect(audio)
    label = process_text(label)
    return spectrogram, label

模型代码

def CTCLoss(y_true, y_pred):
    # Compute the training-time loss value
    batch_len = tf.cast(tf.shape(y_true)[0], dtype="int64")
    input_length = tf.cast(tf.shape(y_pred)[1], dtype="int64")
    label_length = tf.cast(tf.shape(y_true)[1], dtype="int64")

    input_length = input_length * tf.ones(shape=(batch_len, 1), dtype="int64")
    label_length = label_length * tf.ones(shape=(batch_len, 1), dtype="int64")

    loss = tf.keras.backend.ctc_batch_cost(y_true, y_pred, input_length, label_length)
    return loss


def build_model(input_dim, output_dim, rnn_layers=5, conv_units=128, rnn_units=128, dropout=0.5):
    input_spectrogram = tf.keras.layers.Input((None, input_dim), name="input")
    
    x = tf.keras.layers.Reshape((-1, input_dim, 1), name="expand_dim")(input_spectrogram)
    # Conv layers
    x = tf.keras.layers.Conv2D(
        filters=conv_units,
        kernel_size=[11, 41],
        strides=[2, 2],
        padding="same",
        use_bias=False,
        name="conv_1",
    )(x)
    x = tf.keras.layers.BatchNormalization(name="conv_1_bn")(x)
    x = tf.keras.layers.ReLU(name="conv_1_relu")(x)
    
    x = tf.keras.layers.Conv2D(
        filters=conv_units,
        kernel_size=[11, 21],
        strides=[1, 2],
        padding="same",
        use_bias=False,
        name="conv_2",
    )(x)
    x = tf.keras.layers.BatchNormalization(name="conv_2_bn")(x)
    x = tf.keras.layers.ReLU(name="conv_2_relu")(x)
    
    x = tf.keras.layers.Reshape((-1, x.shape[-2] * x.shape[-1]))(x)
    # RNN layers
    for i in range(1, rnn_layers + 1):
        recurrent = tf.keras.layers.GRU(
            units=rnn_units,
            activation="tanh",
            recurrent_activation="sigmoid",
            use_bias=True,
            return_sequences=True,
            reset_after=True,
            name=f"gru_{i}",
        )
        x = tf.keras.layers.Bidirectional(
            recurrent, name=f"bidirectional_{i}", merge_mode="concat"
        )(x)
        x = tf.keras.layers.BatchNormalization(name=f"rnn_{i}_bn")(x)
        if i < rnn_layers:
            x = tf.keras.layers.Dropout(rate=dropout)(x)
    # Dense layer
    x = tf.keras.layers.Dense(units=rnn_units * 2, activation="gelu", name="dense_1")(x)
    x = tf.keras.layers.Dropout(rate=dropout)(x)
    # Classification layer
    output = tf.keras.layers.Dense(units=output_dim + 1, activation="softmax", name="output_layer")(x)
    # Model
    model = tf.keras.Model(input_spectrogram, output, name="DeepSpeech_2")
    # Optimizer
    opt = tf.keras.optimizers.Adam(learning_rate=0.01)
    # Compile the model and return
    model.compile(optimizer=opt, loss=CTCLoss)
    return model


# Get the model
model = build_model(
    input_dim=fft_length // 2 + 1,
    output_dim=char_to_num.vocabulary_size(),
    rnn_units=32,
    conv_units=32,
    rnn_layers=5,
    dropout=0.5
)

环境配置

  • tensorflow: 2.10.1
  • python: 3.9.12
  • gpu: Nvidia GeForce RTX 3080
  • OS: Windows 11
  • cuDNN: 8.1
  • CUDA: 11.2

已尝试的解决方案

  • 调整batch size:256及以上会GPU内存不足,128及以下仍出现Loss突变为无穷的问题
  • 减少训练数据量、缩小模型规模
  • 更换激活函数:ReLU、LeakyReLU、GELU
  • 更换优化器:SGD、Adam、AdamW
  • 调整RNN/Conv层数
  • 替换为1D Conv层、取消GRU双向设置、换回AdamW(更新后尝试)

补充疑问

我希望从零实现ASR,用现有RTX 3080和Mozilla Common Voice 11数据集能否得到尚可的结果,还是必须使用wav2vec?


解答

1. CTC Loss突变为无穷大的原因及解决办法

CTC Loss出现无穷大,核心原因通常是模型输出的概率分布中,对应真实标签的路径概率趋近于0,导致计算时触发log(0)的情况,进而引发无穷大。结合代码和尝试的操作,给出具体排查方向:

  • 修正CTC Loss的长度计算逻辑
    当前Loss函数里,直接用tf.shape(y_pred)[1]作为所有样本的输入长度,tf.shape(y_true)[1]作为所有样本的标签长度,但实际每个样本的输入(频谱图的时间步长)和标签长度各不相同——强制让一个batch内所有样本共享相同长度,会导致部分样本的输入长度小于标签长度,触发CTC的无效路径计算,直接导致Loss爆无穷。

    解决办法:数据预处理时单独记录每个样本的输入长度(频谱图的时间维度)和标签长度,训练时将这两个作为额外输入传入Loss函数,而非使用batch的统一长度。

  • 降低初始学习率
    目前使用的Adam初始学习率0.01对于ASR模型来说过高,容易导致训练后期权重更新幅度过大,模型输出出现极端值(比如某类概率趋近于0)。建议将学习率降到1e-4甚至1e-5,配合学习率衰减策略(如ReduceLROnPlateau)使用。

  • 过滤预处理中的异常值
    convert_to_spect函数中的掩码操作可能导致部分频谱图的标准差趋近于0,虽然添加了1e-10的偏移,但如果数据本身存在问题(比如静音片段过长),归一化后仍可能出现NaN或极端值。可以在预处理后添加数值限制:

    spectrogram = tf.clip_by_value(spectrogram, -10, 10)
    

    把数值约束在合理范围,避免梯度爆炸。

  • 检查标签处理逻辑
    确认char_to_num的词汇表包含所有需要的字符,且标签中没有超出词汇表的内容。另外,CTC要求标签不能有连续重复字符(除非中间有空白符,但当前预处理未处理此情况),这虽不会直接导致Loss无穷,但会影响模型收敛效率。

2. 从零实现ASR的可行性

用RTX 3080和Mozilla Common Voice 11完全可以得到尚可的结果,不需要依赖wav2vec。当前模型框架参考DeepSpeech结构是合理的,解决Loss爆无穷问题后,再调整训练策略即可:

  • 使用更小的学习率,延长训练周期
  • 增加数据增强手段(如速度扰动、音量扰动)
  • 过滤过长/过短的音频和标签数据
  • 启用梯度裁剪(tf.keras.optimizers.Adam(clipnorm=1.0))防止梯度爆炸

训练完成后,在Common Voice测试集上达到80%左右的词错误率(WER)是完全可行的,进一步调优后WER可降至60%以下。

内容的提问来源于stack exchange,提问作者Nathan Montanez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 00:25:51