二、ctc基础--待完善

ctc 基础

1. 什么是ctc

WeNet 的核心结构是 U2(Unified Two-pass):

复制代码
            ┌──────────┐
  音频 → →  │ Encoder │ → → CTC 分支(无条件解码)
            └────┬────┘
                 │
                 ▼
            Attention Decoder(第二步)
环节 CTC 的作用
训练阶段 加强对齐、加速收敛、提升 encoder 稳定性
解码阶段(第一遍) 快速输出文本(无 Decoder)
解码阶段(第二遍) 通过"CTC 对齐约束"提升 attention 解码精度
可选融合 Prefix Beam + external LM 提升语言模型效果

2. 说下语音识别的整体流程。

2.1 音频特征提取

fbank做音频提取。步骤中存在着预加重、分帧、加窗、fft、mel滤波器卷积、取log,整个具体流程在本专栏第一个里面有概述。

2.2 将fbank提取的特征输入encoder(Conformer/Transformer)

Encoder 负责把"声学特征"变成"语音内容表征"。

在encoder之前会先做下采样

卷积 subsampling (2× 或 4×)

作用:

降低序列长度

让 T 减少

提高速度

如何理解:

假设fbank出来的矩阵是个[1000,80] 的矩阵,Transformer / Conformer 的注意力复杂度是 O(T²)!,那么这个矩阵经过encoder后的是[1000000,80],这个矩阵运算会特别慢,这一步可以减少训练或者推理的速度。

py 复制代码
class ConvSubsampling(nn.Module):
    def __init__(self, in_dim=80, out_dim=256):
        super().__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, out_dim, kernel_size=3, stride=2),
            nn.ReLU(),
            nn.Conv2d(out_dim, out_dim, kernel_size=3, stride=2),
            nn.ReLU(),
        )
        self.linear = nn.Linear(out_dim * ((in_dim - 3*2) // 4), out_dim)

    def forward(self, x):
        # x: [B, T, F]
        x = x.unsqueeze(1)       # [B, 1, T, F]
        x = self.conv(x)         # [B, D, T/4, F']
        
        B, D, T2, F2 = x.size()
        x = x.transpose(1, 2)    # [B, T/4, D, F2]
        x = x.reshape(B, T2, D * F2)
        return self.linear(x)    # [B, T/4, D]

效果:

复制代码
Conv1:  [B, T, 80] → [B, T/2, C]
Conv2:  [B, T/2, C] → [B, T/4, D]  ← 通常 D = Encoder dim = 256 或 512

在这一步后会用到相对位置编码。中间有些复杂,不赘诉,主要是为了区分哪个编码对应哪个位置。也可以理解为解码出来的东西的顺序前后。

复制代码
fbank input: [T, 80]
↓
Conv subsampling 4×
→ 提取局部结构
→ 降低序列长度
output: [T/4, D]
↓
Positional encoding
↓
N 层 Conformer blocks
→ 提取语音内容表征
→ 音素级建模
→ 远程依赖和局部模式
output: [T/4, D]
↓
CTC projection(全连接)

Encoder 会学习:

哪些帧属于元音 / 辅音
音素边界
声学模式
说话人变化
噪声鲁棒性

2.3 CTC 分支(Connectionist Temporal Classification)

从 Encoder 输出接一个全连接层 + Softmax:

复制代码
logit = Linear(D → V)      (V=字典大小+blank)

结果:

复制代码
frame0: p(blank)=0.6, p("a")=0.2, p("t")=0.1 ...
frame1: p(blank)=0.7, p("a")=0.1, p("t")=0.05 ...

CTC 的三个核心:

① blank 机制

保证模型可以 "不发音"

避免强制每帧输出字。

② 同字合并(aa → a)

重复字符自动压缩。

③ forward-backward 概率

无需帧级标注,也能训练。

ctc输出是:
一个每帧的 token 分布序列 P(t)[token]

2.4 第一遍解码(CTC 解码)

WeNet 的第一遍解码是 CTC Prefix Beam Search。 相当于上一步得到的是每个字典中元素的概率,就每一帧上,所有元素的概率和为1,选取出概率最高的元素。在这个字典里面,是允许 有blank的,就我可以在那一帧静音,啥都没说。

流程:

  1. 从 CTC logit 序列取概率最高的前 N 个 prefix

  2. 保持前缀概率

  3. blank / non-blank 分开计算概率

  4. 最终选择概率最高的前 K 个候选句子(beam)

2.5 decoder 第二遍解码(Attention Rescoring)

把每个候选序列送入 Decoder(Transformer Decoder)再打分

相当于去算一下你上一步选取出来的前k个候选句子里面中最有可能的那一个,Attention 可以看到 Encoder embedding,Decoder 模拟语言模型特性,提升输出流畅性、减少 CTC 的"省略字"问题

复制代码
score = λ * CTC_prefix_score + (1 - λ) * decoder_score

但其实这块基本跟上一步ctc出来的差不太多,至少从我做的项目来看的话。

3.ctc中某些概念的理解

3.1 ctc中blank的作用

CTC 需要在 帧序列(很多) → 文字(较少) 之间建立柔性对齐。

  1. 让模型有"无输出/延迟输出"的能力(最重要)
    音频 1000 帧,但文本可能只有 20 字。
    如果没有 blank,每帧必须输出一个字符,就会:
    强制每帧都产生字(不现实)
    没有地方表示"这帧不对应任何字"
    blank = "这帧暂时不输出字"。

  2. 允许同一个字按多帧持续存在
    例如 "AAA" 音频帧很长,但文本只有 "A"。
    如果没有 blank:

    A A A → 会认为是 "AAA"

有了 blank:

复制代码
(blank) A A (blank) A (blank) → 合并成 "A"
  1. CTC 的 collapse rule(合并重复 + 去 blank)需要 blank 才能正常工作

    1. 去掉所有 blank
    2. 合并相邻重复字符

    [blank, A, A, blank, A] → collapse → "AA"
    [blank, A, blank, A] → collapse → "AA"

py 复制代码
for t in frames:
    for each prefix in current_beam:
        for each token in vocab:
            if token == blank:
                // blank transition
                p_b[new_prefix] += (p_b[old] + p_nb[old]) * prob(blank)
            else:
                if token == last_char_of_prefix:
                    // repeat character
                    p_nb[prefix] += p_nb[old] * prob(token)
                else:
                    // new character
                    new_prefix = prefix + token
                    p_nb[new_prefix] += (p_b[old] + p_nb[old]) * prob(token)

3.2 forward--backward 原理

声学模型输出帧序列 X(T 帧)

文本目标序列 Y(长度 U,例如 "ni hao")

CTC 不需要逐帧标注,而是计算:
所有可能的对齐路径(alignment)概率之和

这两个公式属于核心
P(Y∣X)=∑π∈align(Y)P(π∣X) P(Y|X) = \sum_{\pi \in \text{align}(Y)} P(\pi|X) P(Y∣X)=π∈align(Y)∑P(π∣X)

Loss=−log⁡P(Y∣X) \text{Loss} = -\log P(Y|X) Loss=−logP(Y∣X)

例如:

Y = "HI"

其中 H=1, I=2,blank=0

可能对齐:

复制代码
0 H H H 0 I I
H 0 H 0 I I 0
0 H 0 H 0 I 0

你会看到 1000 帧对应 2 个字,会有巨量路径。
CTC 要做的,就是高效计算这个巨量路径概率之和。

核心作用:计算CTC损失函数

为什么需要计算所有路径? 因为单一路径很难直接获得,且训练目标是让模型所有正确的对齐路径概率之和最大。

如何高效计算? 直接枚举所有路径是指数级的,不可行。前向-后向算法利用动态规划,将复杂度降低到 O(T * U),其中 U = 2L + 1,是扩展后的序列长度(在目标标签间插入了空白符 blank)。

计算损失函数的梯度:

在神经网络训练中,我们不仅需要损失函数的值(通常为负对数似然:-log P(Y|X)),更需要该损失相对于模型每一个输出(在每一个时间步 t 对每一个标签 k 的概率)的梯度。

前向-后向算法在计算过程中,会自然地得到每个时间步、每个标签上的后验概率。这个后验概率 P(π_t = k | X, Y) 直接给出了"在已知输入和目标序列的条件下,在时间步t输出标签k"的概率期望。这个期望值正是计算梯度所需的关键中间量。

梯度可以简洁地表示为:
∂LCTC∂ytk=eytk∑jeytj−1P(Y∣X)∑uZu=kαt(u)βt(u) \frac{\partial \mathcal{L}{\text{CTC}}}{\partial y_t^k} = \frac{e^{y_t^k}}{\sum_j e^{y_t^j}} - \frac{1}{P(Y|X)} \sum{\substack{u \\ Z_u = k}} \alpha_t(u) \beta_t(u) ∂ytk∂LCTC=∑jeytjeytk−P(Y∣X)1uZu=k∑αt(u)βt(u)

(模型在t时刻预测k的概率) - (前向-后向算出的期望概率)**

这使得梯度计算非常高效,可以轻松地通过反向传播更新网络权重。

作用:
用途:仅用于训练阶段
目的:计算CTC损失函数的梯度

py 复制代码
# 训练时:需要前向-后向算法
loss = ctc_loss(logits, labels, input_lengths, label_lengths)
loss.backward()  # 内部使用前向-后向计算梯度

3.3 CTC 输出如何合并重复字符

假设以下是每一帧的概率分布

复制代码
frame 0: [blank=0.6, A=0.3, B=0.1]
frame 1: [blank=0.1, A=0.8, B=0.1]
frame 2: [blank=0.05, A=0.9, B=0.05]
frame 3: [blank=0.8, A=0.1, B=0.1]
frame 4: [blank=0.1, A=0.1, B=0.8]

合并重复字符的规则(collapse rule)

复制代码
假设原始出来的字符是:[blank, A, A, blank, B, B, B, blank, C]
1. 去掉 blank
	[A, A, B, B, B, C]
2. 合并重复字符
   [A,B,C]

总结来说就是:如果当前字符与前一个非 blank 字相同,则只保留一个;blank 可以分隔重复字符。

未完.

相关推荐
yuhaiqun19892 小时前
SQL+VSCode实战指南:AI赋能高效数据库操作
数据库·人工智能·经验分享·vscode·sql·学习·学习方法
Elias不吃糖2 小时前
Markdown 基础语法学习笔记
笔记·学习·markdown
棒棒的皮皮2 小时前
【深度学习】YOLO学习资源之官方文档&Darknet文档
深度学习·学习·yolo·计算机视觉
Qhumaing2 小时前
Java学习——第五章 异常处理与输入输出流笔记
java·笔记·学习
世人万千丶2 小时前
鸿蒙跨端框架 Flutter 学习 iverpod 实战:超越 Provider 的响应式状态管理
学习·flutter·华为·交互·harmonyos·鸿蒙
旖旎夜光2 小时前
Linux(11)(上)
linux·学习
猛扇赵四那边好嘴.2 小时前
Flutter 框架跨平台鸿蒙开发 - 学习打卡助手应用开发教程
学习·flutter·华为·harmonyos
好奇龙猫2 小时前
【日语学习-日语知识点小记-日本語体系構造-JLPT-N2前期阶段-第一阶段(5):单词语法】
学习
魔芋红茶2 小时前
Spring Security 学习笔记 3:认证模型
笔记·学习·spring