Reconnaissance vocale et Graph Transformer Network II

$$\gdef \sam #1 {\mathrm{softargmax}(#1)}$$ $$\gdef \vect #1 {\boldsymbol{#1}} $$ $$\gdef \matr #1 {\boldsymbol{#1}} $$ $$\gdef \E {\mathbb{E}} $$ $$\gdef \V {\mathbb{V}} $$ $$\gdef \R {\mathbb{R}} $$ $$\gdef \N {\mathbb{N}} $$ $$\gdef \relu #1 {\texttt{ReLU}(#1)} $$ $$\gdef \D {\,\mathrm{d}} $$ $$\gdef \deriv #1 #2 {\frac{\D #1}{\D #2}}$$ $$\gdef \pd #1 #2 {\frac{\partial #1}{\partial #2}}$$ $$\gdef \set #1 {\left\lbrace #1 \right\rbrace} $$ % My colours $$\gdef \aqua #1 {\textcolor{8dd3c7}{#1}} $$ $$\gdef \yellow #1 {\textcolor{ffffb3}{#1}} $$ $$\gdef \lavender #1 {\textcolor{bebada}{#1}} $$ $$\gdef \red #1 {\textcolor{fb8072}{#1}} $$ $$\gdef \blue #1 {\textcolor{80b1d3}{#1}} $$ $$\gdef \orange #1 {\textcolor{fdb462}{#1}} $$ $$\gdef \green #1 {\textcolor{b3de69}{#1}} $$ $$\gdef \pink #1 {\textcolor{fccde5}{#1}} $$ $$\gdef \vgrey #1 {\textcolor{d9d9d9}{#1}} $$ $$\gdef \violet #1 {\textcolor{bc80bd}{#1}} $$ $$\gdef \unka #1 {\textcolor{ccebc5}{#1}} $$ $$\gdef \unkb #1 {\textcolor{ffed6f}{#1}} $$ % Vectors $$\gdef \vx {\pink{\vect{x }}} $$ $$\gdef \vy {\blue{\vect{y }}} $$ $$\gdef \vb {\vect{b}} $$ $$\gdef \vz {\orange{\vect{z }}} $$ $$\gdef \vtheta {\vect{\theta }} $$ $$\gdef \vh {\green{\vect{h }}} $$ $$\gdef \vq {\aqua{\vect{q }}} $$ $$\gdef \vk {\yellow{\vect{k }}} $$ $$\gdef \vv {\green{\vect{v }}} $$ $$\gdef \vytilde {\violet{\tilde{\vect{y}}}} $$ $$\gdef \vyhat {\red{\hat{\vect{y}}}} $$ $$\gdef \vycheck {\blue{\check{\vect{y}}}} $$ $$\gdef \vzcheck {\blue{\check{\vect{z}}}} $$ $$\gdef \vztilde {\green{\tilde{\vect{z}}}} $$ $$\gdef \vmu {\green{\vect{\mu}}} $$ $$\gdef \vu {\orange{\vect{u}}} $$ % Matrices $$\gdef \mW {\matr{W}} $$ $$\gdef \mA {\matr{A}} $$ $$\gdef \mX {\pink{\matr{X}}} $$ $$\gdef \mY {\blue{\matr{Y}}} $$ $$\gdef \mQ {\aqua{\matr{Q }}} $$ $$\gdef \mK {\yellow{\matr{K }}} $$ $$\gdef \mV {\lavender{\matr{V }}} $$ $$\gdef \mH {\green{\matr{H }}} $$ % Coloured math $$\gdef \cx {\pink{x}} $$ $$\gdef \ctheta {\orange{\theta}} $$ $$\gdef \cz {\orange{z}} $$ $$\gdef \Enc {\lavender{\text{Enc}}} $$ $$\gdef \Dec {\aqua{\text{Dec}}}$$
🎙️ Awni Hannun

Temps d’inférence

L’inférence pour obtenir la transcription d’un signal audio donné peut être formulée en utilisant deux distributions :

  • Le modèle acoustique (de l’audio à la transcription), représenté par $P(\mY \mid \mX)$.
  • Le modèle linguistique, représenté par $P(\mY)$.

L’inférence finale est obtenue en prenant la somme des probabilités logarithmiques de ces deux modèles, c’est-à-dire :

\[\vect{\mY*} = \underset{\mY}{\operatorname{argmax}} \log P(\mY \mid \mX) + \log P(\mY)\]

Nous utilisons le terme supplémentaire pour nous assurer que la transcription est conforme aux règles de la langue. Sinon, nous risquons d’obtenir une transcription grammaticalement incorrecte.

Recherche en faisceau

Lors de la génération d’une séquence, nous pouvons suivre l’approche gloutonne où nous prenons la valeur maximale de $P(\vy_{t} \mid \vy_{t-1}…\vy_{1})$.

Cependant, nous pouvons manquer une bonne séquence qui peut ne pas avoir la valeur maximale de $P(\vy_{t} \mid …)$ comme l’illustre l’exemple ci-dessous.


Figure 1 : Approche gloutonne (en bleue) : une solution moins optimale

Pour remédier à cela, nous employons la recherche en faisceau. Grossièrement, nous considérons les jetons maximum $k$ en termes de probabilité à chaque étape $t$ de la séquence. Pour chacun de ces n-grammes, nous poursuivons la recherche et trouvons le maximum.

L’illustration ci-dessous montre comment la recherche en faisceau peut conduire à une meilleure séquence.


Figure 2 : Recherche en faisceau : étape 1


Figure 3 : Recherche en faisceau : étape 2


Figure 4 : Recherche en faisceau : étape 3

Graph Transformer Networks

Nous avons vu précédemment le Weighted Finite State Acceptor (WFSA) pour représenter les alginements dans un graphe.

Les Graph Transformer Networks (GTNs) sont essentiellement des WSFAs avec une différenciation automatique.

Examinons les principales différences entre les réseaux de neurones (NNs) et les GTNs.

  NN GTN
Structure de données de base Tenseur Graphe/WFSA
  Multiplémentation matricielle Compose
Opérations de base Opérations de réduction (Somme, Produit) Distance la plus courte (Viterbi, Forward)
  Négation, Addition, Soustraction, … Fermeture, Union, Concaténation, …

Pourquoi des WSFAs différentiables ?

  • Encodage des a priori : facile d’encoder les a priori dans un graphe.
  • Bout en bout : permet de faire le pont entre le processus d’entrâinement et de test
  • Faciliter la recherche académique : lorsque nous séparons les données du code, il est plus facile d’explorer différentes idées. Dans notre cas, le graphe est la donnée et le code est l’opération, ce qui nous permet d’explorer différentes idées en modifiant le graphe.

Critères de séquence avec WFSAs

Il s’avère que de nombreux critères de séquence peuvent être spécifiés comme la différence entre deux scores forward.

  • Graphes A : fonction de l’entrée $\mX$ (parole) et de la sortie cible $\mY$ (transcription).
  • Graphe Z : fonction de l’entrée $\mX$, qui sert de normalisation.
  • Perte : $\log P(\mY \mid \mX) = \text{forwardScore}(A_{\mX,\mY}) - \text{forwardScore}(Z_{\mX})$.

Des fonctions de perte courantes en reconnaissance automatique de la parole :

  • Critère de segmentation automatique (ASG pour Automatic Segmentations Criterion)
  • Classification Temporale Connectioniste (CTC pour Connectionist Temporal Classification)
  • MMI sans treillis (LF-MMI pour Lattice Free Maximum Mutual Information)

Nombre de lignes de code pour implémenter la CTC

  Lignes
Warp-CTC 9,742
wav2letter 2,859
PyTorch 1,161
gtn 30*

*Le même code fonctionne pour le décodage.

Différents types d’états finis pondérés

Accepteur d’états finis pondérés (WFSA pour Weighted Finite-State Acceptor)

Le cercle en gras est l’état de départ et l’état d’acceptation est celui avec les cercles concentriques.

Exemple

Un WFSA simple reconnaît les séquences aa et ab avec comme score :

  • Le score de aa est $0 + 2 = 2$
  • Le score de ba est $1 + 2 = 3$

Figure 5 : Accepteur d'états finis pondérés (WFSA) à trois nœuds

Transducteur à états finis pondérés (WFST pour Weighted Finite-State Transducer)

Ces graphes sont appelés transducteurs car ils transforment des séquences d’entrée en séquences de sortie.

Exemple

Un WFST simple reconnaît ab à xz et bb à yz avec comme score :

  • Le score de ab $\to$ xz est $1,1 + 3,3 = 4,4$
  • Le score de bb $\to$ yz est de $2,0 + 3,3 = 5,3$

Figure 6 : Transducteur d'états finis pondérés (WFST) à trois nœuds

Plus de WFSAs et WFSTs

  • Les cycles et les boucles sont autorisés.
  • Les nœuds de départ et d’arrivée multiples sont autorisés.
  • Les transitions de type $\epsilon$ (c’est-à-dire faisant rien) sont autorisées dans les WFSAs et WFSTs.

Les différentes opérations

Union

L’union accepte une séquence si elle est acceptée par l’un des graphes d’entrée.


Figure 7 : Opération d'union entre trois graphes

Etoile de Kleene

Accepte toute séquence acceptée par le graphe d’entrée répétée 0 fois ou plus.


Figure 8 : Opération de l'étoile de Kleene

Intersection

Similaire à la multiplication matricielle ou à la convolution. Cette opération est probablement la plus importante dans les GTNs.

  1. Tout chemin qui est accepté par les deux WFSA est accepté par le produit de l’intersection.
  2. Le score du chemin instersecté est la somme des scores des chemins des graphes d’entrée.

Figure 9 : Opération d'intersection entre deux graphes

Composition

C’est essentiellement la même chose que l’intersection mais pour les transducteurs au lieu des accepteurs.


Figure 10 : Opération de composition entre deux graphes

La principale chose à propos de la composition est qu’elle permet de faire des correspondances à partir de différents domaines. Par exemple, si nous avons un graphe qui fait correspondre des lettres aux mots et un autre graphe qui fait correspondre des mots aux phrases, alors lorsque nous composons ces deux graphes nous faisons correspondre des lettres aux phrases.

Score forward

Supposons que le graphe est DAG (c’est-à-dire dirigé et sans boucles) et avons un algorithme de programmation dynamique efficace.


Figure 11 : Le score forward est la SoftMax des chemins

Les graphes acceptent trois chemins :

  • aca avec comme score $1,1 + 1,4 + 2,1$
  • ba avec comme score $3,2 + 2,1$
  • ca avec commescore $1,4 + 2,1$

Le ForwardScore(g) est la SoftMax réelle des chemins.

Séquence de critères avec WFSTs

Graphe cible $\mY$


Figure 12 : Graphe cible Y

Graphe des émissions E

Entre deux nœuds nous avons des logits.


Figure 13 : Graphe des émissions E

Graphe contraint par la cible A par intersection ($\mY$, E)


Figure 14 : Graphe contraint par la cible

Perte

\[\ell = - (\text{forwardScore}(A) - \text{forwardScore}(E))\]

Ce n’est pas la CTC mais ça s’en approche.

Exemples de code

Créer le graphe cible

Similaire à la figure 8.

import gtn

# On crée le graphe
target = gtn.Graph(calc_grad=False)
# Ajouts des noeuds
target.add_node(start=True)
target.add_node()
target.add_node(accept=True)
# Ajouts des arcs
target.add_arc(src_node=0, dst_node=1, label=0)
target.add_arc(src_node=1, dst_node=1, label=0)
target.add_arc(src_node=1, dst_node=2, label=1)
target.add_arc(src_node=2, dst_node=2, label=1)
# On dessine le graphe
label_map = {0: 'a', 1: 'b'}
gtn.draw(target, "target.pdf", label_map)

Faire le graphe des émissions

Similaire à la figure 9.

import gtn

# Tableau des émissions (logits)
emissions_array = np.random.randn(4, 3)
# Créer le graphe 
emissions = gtn.linear_graph(4, 3, calc_grad=True)
# Définir les poids
emissions.set_weights()

L’ASG dans GTN

Étapes

  1. Calculer les graphes
  2. Calculer la perte
  3. Gradients automatiques
from gtn import *

def ASG(emissions, target):
    # Etape 1
    # Calculs des graphes :
    A = intersect(target, emissions)
    Z = emissions

    # Etape 2
    # Forward both.graphs : 
    A_score = forward(A)
    Z_score = forward(Z)
    
    # Compute loss:
    loss = negate(subtract(A_score, Z_score))
    
    # Etape 3 
    # Nettoyage des gradients précédents
    emissions.zero_grad()
    
    # Calcul des gradients
    backward(loss, retain_graph=False)
    return loss.item(), emissions.grad()

CTC dans GTN

from gtn import *

def CTC(émissions, "target") :
    # Etape 1
    # Calculs des graphes :
    A = intersect(target, emissions)
    Z = emissions

    # Etape 2
    # Forward both.graphs : 
    A_score = forward(A)
    Z_score = forward(Z)
    
    # Compute loss:
    loss = negate(subtract(A_score, Z_score))
    
    # Etape 3 
    # Nettoyage des gradients précédents
    emissions.zero_grad()
    
    # Calcul des gradients
    backward(loss, retain_graph=False)
    return loss.item(), emissions.grad()

La seule différence est la cible ici. Elle rend très facile l’essai de différents algorithmes et gtn est censé être similaire à PyTorch avec une structure de données et des opérations différentes.

Lectures supplémentaires


📝 Gyanesh Gupta, Abed Qaddoumi
Loïck Bourdois
14th April 2021