Weakly Supervised Transducer (WST):
Weakly supervised transducer is a training technique introduced in this paper: https://arxiv.org/pdf/2511.04035. It introduces skip paths in the transducer’s lattice to tolerate errors in the training transcripts. This document summarises its mathematical formulation and gives the pseudocode.
1. Mathematical formulation
Notation
- $
\mathbf{x} = (x_1, \dots, x_T)$: acoustic input, length $T$. - $
\mathbf{y} = (y_1, \dots, y_U)$: label sequence, vocabulary $V$, $|V|$ symbols. - $
\phi$: blank symbol. - $
\star$: garbage/uncertainty token, not a real vocabulary index. - $
\lambda_\phi, \lambda_y \ge 0$: fixed bypass-arc penalties, see parametrisation. - At lattice node $
(t, u)$, the joint network produces logits $h_{t,u}$ with softmax outputs:- $
S_\phi(t, u) = \mathrm{softmax}(h_{t,u})[\phi]$ - $
S_y(t, u) = \mathrm{softmax}(h_{t,u})[y_{u+1}]$ - $
S_k(t, u) = \mathrm{softmax}(h_{t,u})[k]$
- $
- $
\star$-arc base weight:
S_\star(t, u) = \frac{1 - S_\phi(t,u)}{|V| - 1}
- Aggregated transition weights:
W_\rightarrow(t,u) = S_\phi(t,u) + \lambda_\phi\, S_\star(t,u) \qquad\text{(blank + blank-bypass)}
W_\uparrow(t,u) = S_y(t,u) + \lambda_y\, S_\star(t,u) \qquad\text{(label + token-bypass)}
- $
P = P(\mathbf{y} \mid \mathbf{x})$, $\mathcal{L} = -\log P$.
Transitions out of $(t, u)$
| From | Symbol | Weight | To |
|---|---|---|---|
$(t, u)$ | $\phi$ | $S_\phi$ | $(t+1, u)$ |
$(t, u)$ | $\star$ (blank-bypass) | $\lambda_\phi S_\star$ | $(t+1, u)$ |
$(t, u)$ | $y_{u+1}$ | $S_y$ | $(t, u+1)$ |
$(t, u)$ | $\star$ (token-bypass) | $\lambda_y S_\star$ | $(t, u+1)$ |
Forward-backward recurrences
\alpha(t, u) = \alpha(t{-}1, u)\, W_\rightarrow(t{-}1, u) \;+\; \alpha(t, u{-}1)\, W_\uparrow(t, u{-}1)
\beta(t, u) = \beta(t{+}1, u)\, W_\rightarrow(t, u) \;+\; \beta(t, u{+}1)\, W_\uparrow(t, u)
P = \sum_{(t,u):\, t+u = c} \alpha(t, u)\, \beta(t, u) \quad \text{for any constant } c
Gradient
Define the bypass leak at node $(t,u)$:
B_\lambda(t,u) \;=\; \frac{\lambda_\phi\,\beta(t{+}1,u) + \lambda_y\,\beta(t,u{+}1)}{|V|-1}
Then:
\frac{\partial \mathcal{L}}{\partial x_{t,u,k}} = \frac{\alpha(t,u)}{P} \Big[\,
S_k\big(\beta(t, u) - B_\lambda(t,u)\big)
\;-\; \delta_{k,\,y_{u+1}}\, S_y\, \beta(t, u{+}1)
\;-\; \delta_{k,\phi}\,S_\phi\big(\beta(t{+}1, u) - B_\lambda(t,u)\big)
\,\Big]
Structural comparison to RNN-T:
| Term | RNN-T | WST |
|---|---|---|
Everywhere $k$ | $S_k\,\beta(t,u)$ | $S_k\,(\beta(t,u) - B_\lambda)$ |
$\delta_{k,\,y_{u+1}}$ | $-S_y\,\beta(t,u{+}1)$ | $-S_y\,\beta(t,u{+}1)$ (unchanged) |
$\delta_{k,\phi}$ | $-S_\phi\,\beta(t{+}1,u)$ | $-S_\phi\,(\beta(t{+}1,u) - B_\lambda)$ |
WST = RNN-T with $\beta$ replaced by $(\beta - B_\lambda)$ in the everywhere-$k$ and blank-delta terms. Reduces to RNN-T exactly when $\lambda_\phi = \lambda_y = 0$ (then $B_\lambda = 0$).
Numerical stability notes
- $
\log S_\star$ is needed in the forward to form $\log W_\rightarrow$ and $\log W_\uparrow$. Computed as $\log S_\star = \mathrm{log1mexp}(\log S_\phi) - \log(|V|-1)$, where $\mathrm{log1mexp}(a) = \log(1 - e^a)$ for $a \le 0$ (use the standard two-branch stable form). - Subtractions $
\beta(t,u) - B_\lambda$ and $S_\phi\,\beta(t{+}1,u) - B_\lambda$ are done in linear space at the very last step of the gradient assembly. Each subtracted pair shares a common log-prefactor (alpha/P * loss_grad * S_koralpha/P * loss_grad * S_phi), which is multiplied in via log-addition before the finalexp. This is the same numerical discipline as the existing RNN-T backward.
2. Pseudocode (log-space)
Conventions
- All probabilities and partial sums in log-space.
lse(a, b) = log(exp(a) + exp(b)), computed stably asmax(a,b) + log1p(exp(-|a-b|)).log1mexp(a) = log(1 - exp(a))fora <= 0, standard two-branch form.log_lam_blank,log_lam_emit=log(lam_phi),log(lam_y);-infif the corresponding lam is 0.log_Vm1=log(|V| - 1).denom[t,u] = logsumexp(x[t,u,:])is precomputed.
Per-cell log helpers
def lbx(t, u): return x[t, u, blank_idx] - denom[t, u] # log S_phi
def lsx(v): return log1mexp(v) - log_Vm1 # log S_star, given log S_phi
def lex(t, u): return x[t, u, label[u]] - denom[t, u] # log S_y (label[u] = y_{u+1})
2.1 Forward: alpha and beta
Structurally identical to RNN-T; only the transition weights change.
def forward(x, denom, label, T, U):
alpha = full((T, U), -inf); alpha[0, 0] = 0.0
for step in range(1, T + U - 1):
for u in valid_u_on_diagonal(step, T, U):
t = step - u
lbx_ = lbx(t-1, u)
log_W_blank = lse(lbx_, log_lam_blank + lsx(lbx_))
from_blank = alpha[t-1, u] + log_W_blank if t > 0 else -inf
lbx_ = lbx(t, u-1)
log_W_emit = lse(lex(t, u-1), log_lam_emit + lsx(lbx_))
from_emit = alpha[t, u-1] + log_W_emit if u > 0 else -inf
alpha[t, u] = lse(from_blank, from_emit)
beta = full((T, U), -inf); beta[T-1, U-1] = lbx(T-1, U-1)
for step in range(T + U - 3, -1, -1):
for u in valid_u_on_diagonal(step, T, U):
t = step - u
lbx_ = lbx(t, u); lsx_ = lsx(lbx_)
log_W_blank = lse(lbx_, log_lam_blank + lsx_)
from_blank = log_W_blank + beta[t+1, u] if t < T-1 else -inf
log_W_emit = lse(lex(t, u), log_lam_emit + lsx_)
from_emit = log_W_emit + beta[t, u+1] if u < U-1 else -inf
beta[t, u] = lse(from_blank, from_emit)
return alpha, beta, loss=-beta[0, 0]
2.2 Backward: gradient w.r.t. logits
def backward(x, denom, alpha, beta, label, T, U, loss_grad):
x_grad = zeros_like(x)
log_P = beta[0, 0]
for t in range(T):
for u in range(U):
lbx_TU = lbx(t, u)
common = log(loss_grad) + alpha[t, u] - log_P
beta_TU = beta[t, u]
beta_TUp1 = beta[t, u+1] if u < U-1 else -inf
beta_Tp1U = beta[t+1, u] if t < T-1 else -inf
from_skip_blank = log_lam_blank + beta_Tp1U
from_skip_emit = log_lam_emit + beta_TUp1
log_B_lam = -log_Vm1 + lse(from_skip_blank, from_skip_emit)
for k in range(V):
grad = common + x[t, u, k] - denom[t, u]
my_grad = exp(grad + beta_TU) - exp(grad + log_B_lam)
if u < U-1 and k == label[u]:
my_grad -= exp(grad + beta_TUp1)
if k == blank_idx:
if t == T-1 and u == U-1: # terminal
my_grad -= exp(grad)
else:
my_grad -= (exp(common + lbx_TU + beta_Tp1U)
- exp(common + lbx_TU + log_B_lam))
x_grad[t, u, k] = my_grad
return x_grad
2.3 Reduction to RNN-T
When lam_blank = lam_emit = 0, log_lam_blank = log_lam_emit = -inf, so log_B_lam = -inf
and exp(log_B_lam) = 0. log_W_blank collapses to lbx; log_W_emit collapses to lex.
Both forward and backward reduce exactly to standard RNN-T.
3. Parametrisation
$\lambda_\phi$ and $\lambda_y$ are set by --wst_skip_blank and --wst_skip_emit,
in two ways that differ from the paper.
Vocab-normalized. The bypass weight $\lambda S_\star = \lambda (1 - S_\phi)/(|V|-1)$
shrinks with $1/|V|$ at fixed $\lambda$, while nothing else in the lattice depends on
$|V|$, so a tuned $\lambda$ does not transfer between the testing (1024), base (8704)
and large (17408) vocabularies. The CLI takes $\tilde\lambda$ and the loss is given
$\lambda = \tilde\lambda |V|$, so $\tilde\lambda$ is approximately the fraction of
the non-blank mass carried by the bypass arc. Tuned on the testing model:
$\tilde\lambda_\phi = 0.05$ (English-only), $0.03$ (multilingual). This does not
correct for tokenization granularity — a larger vocabulary gives fewer tokens, hence
fewer bypass opportunities, per utterance — so expect to retune somewhat per model.
Ramped. Bypass arcs on an untrained model just license the model to skip everything,
so each $\tilde\lambda$ starts at 0 and is released only once validation WER drops
below --wst_{sb,se}_wer_threshold (default 0.6), then ramps linearly to its final value
over --wst_{sb,se}_ramp_steps steps (0, the default, jumps straight there).