Étude

Nous allons voir dans cette étude comment définir/calculer une distance entre 2 chaînes de caractères. Nous utiliserons la distance d'édition, très utilisée.

Distance entre chaines ?

Soient $a$ et $b$ deux chaines de caractères définies sur un alphabet $\mathcal{A}$. Commençons par supposer qu'elles sont de même longueur (disons $n$). On peut compter les différences entre $a$ et $b$ :

$$ H(a, b) = \vert \{i \mid a[i] \neq b[i], 0 \leq i < n \} \vert $$

La distance entre "MISO" et "SILO" est de 2 différences.

Cette distance est très utilisée dans de nombreux domaines. Son nom commun est Distance de Hamming ou distance L1.

Montrez que la distance de Hamming est une vraie distance :

  • elle est symétrique et positive
  • elle vaut 0 si $a=b$
  • elle respecte l'inégalité triangulaire

corrigé

Les deux premières propriétés sont évidentes. La seule chose à démontrer est l'inégalité triangulaire.

Soient $a$, $b$ et $c$ trois chaînes de caractères de même longueur. Si $a[i] \neq c[i]$ alors soit $a[i] \neq b[i]$, soit $b[i] \neq c[i]$. Donc :

$$ \{i \mid a[i] \neq c[i], 0 \leq i < n \} \subseteq \{i \mid a[i] \neq b[i], 0 \leq i < n \} \cup \{i \mid b[i] \neq c[i], 0 \leq i < n \} $$

Ce qui implique :

$$ \vert \{i \mid a[i] \neq c[i], 0 \leq i < n \} \vert \leq \vert \{i \mid a[i] \neq b[i], 0 \leq i < n \} \cup \{i \mid b[i] \neq c[i], 0 \leq i < n \} \vert $$

Et donc :

$$ \vert \{i \mid a[i] \neq c[i], 0 \leq i < n \} \vert \leq \vert \{i \mid a[i] \neq b[i], 0 \leq i < n \} \vert + \vert \{i \mid b[i] \neq c[i], 0 \leq i < n \} \vert $$

Ce qui est égal à :

$$ H(a, c) \leq H(a, b) + H(b, c) $$

Cette définition de distance est cependant un peu frustre puisque qu'elle ne permet de comparer que deux mots ayant le même nombre de caractères. Il faut donc généraliser pour permettre de comparer deux chaînes de longueur différentes.

Pour cela, on va ajouter un caractère noté - qui correspond à un caractère vide et dont le but est d'allonger artificiellement une chaîne. Par exemple : MEROU et ME-R-OU correspondent aux même chaînes, mais l'une est de longueur 6 et la seconde de longueur 8.

On peut donc maintenant comparer MEROU et MARLOU via un allongement de MEROU. Par exemple comparer MERO-U et MARLOU, ce qui donne une distance de 3. Le dessin ci-dessous représente cette distance. On a mis des | entre les lettres identiques :

MERO-U
| |  |
MARLOU

La distance est donc égale :

Ceci pose cependant deux (gros) problèmes :

  1. selon l'allongement choisi, la distance n'est pas la même :

    MER-OU
    | | ||
    MARLOU
  2. on peut utiliser l'allongement pour changer la distance de 2 chaînes de mêmes longueurs :

    MEROUS       MER-OUS
    | |      ≠   | | ||
    MARLOU       MARLOU-

Il faut donc tout refaire... Une solution pour unifier les deux approches est de formaliser la notion d'alignement entre séquences, puis d'utiliser cet outil pour définir la distance d'édition entre deux séquences.

Alignement

Un alignement entre la chaîne $a =a_0\dots a_{n-1}$ et $b = b_0\dots b_{m-1}$ est un couple $(a^\star, b^\star)$ tel que :

  • $a^\star =a^\star_0\dots a^\star_{L-1}$
  • $b^\star =b^\star_0\dots b^\star_{L-1}$
  • chaque caractère de $a^\star$ et $b^\star$ est soit - soit un caractère de la chaîne initiale
  • $(a^\star_i, b^\star_i) \neq (-, -)$ pour tout $0 \leq i < L$
  • $a^\star$ (respectivement $b^\star$) privé des caractères - est égal à $a$ (resp. $b$)

On remarque que : $\max(n, m) \leq L < n + m$.

Distance

Étant donné un alignement $(a^\star, b^\star)$, on peut alors définir sa distance de Hamming :

$$ H(a^\star, b^\star) = \sum_{i=0}^{L-1} \delta(a^\star_i, b^\star_i) $$

Avec la distance élémentaire $\delta$ :

$$ \delta(a^\star_i, b^\star_i) = \begin{cases} 0 & \text{si } a^\star_i = b^\star_i\\ 1 & \text{sinon}\end{cases} $$

Evolution d'une séquence en l'autre

Un alignement permet de simuler le passage d'une séquence à l'autre. Par exemple en considérant l'alignement suivant :

MER-OUS
| | ||
MARLOU-

En allant de gauche à droite on passe de MEROUS à MARLOU :

  1. MEROUS (mot initial)
  2. MEROUS (identité)
  3. MAROUS (substitution de E en A)
  4. MAROUS (identité)
  5. MARLOUS (insertion de L)
  6. MARLOUS (identité)
  7. MARLOUS (identité)
  8. MARLOU (suppression de S)

Nombre d'alignements

Formule

Il peut y avoir beaucoup (beaucoup) d'alignements possibles entre 2 séquences.

Trouver au moins 3 alignements différents entre les séquences ACTGC et ACGTC

solution

ACTG-C
|  |
A-CGTC
ACTGC
||  |
ACGTC
ACTG-C
|| | |
AC-GTC

et bien d'autres encore sont possibles.

On peut remarquer que ce nombre ne dépend que de la longueur des chaines $a$ et $b$, pas de leur contenu. On note alors $f(n, m)$ le nombre possible d'alignements entre une chaîne de longueur $n$ et une chaîne de longueur $m$.

Comme un alignement ne peut finir sur $(-, -)$, on ne peut qu'avoir 3 possibilités :

Aligner $a$ et $b$ revient alors soit à aligner :

Ce qui donne l'équation de récurrence générale suivante :

$$ f(n, m) = f(n − 1, m − 1) + f(n − 1, m) + f(n, m − 1) $$

Avec les conditions aux limites :

Notez que $f(0, 0)$ ne peut exister puisque l'alignement $(-, -)$ n'existe pas.

Ce nombre est gigantesque. On peut en effet prouver, si $n=m$ :

$$ f(n, n) \sim \frac{(1 + \sqrt{2})^{2n + 1}}{\sqrt{n}} $$

Ce qui donne :

Il n'y a qu'environ $10^{80}$ particules dans l'univers.

Calcul

Comme il est impossible d'énumérer tous les alignements car il y en a trop, il faut ruser pour calculer le nombre $f(n, m)$ pour $1 \leq n, m$.

On remarque que pour calculer $f(n, m)$ on a besoin que de $f(n-1, m-1)$, $f(n-1, m)$ et $f(n, m-1)$. Si l'on a stocké ces 3 valeurs, le calcul de $f(n, m)$ devient aisé.

On peut représenter ces dépendances de façon matricielle :

0 1 ... j-1 j ... m
0 $1$ $1$ $1$ $1$ $1$
1 $1$ $3$
...
i-1 $1$ $f(i-1, j-1)$ $f(i-1, j)$
i $1$ $f(i, j-1)$ $f(i, j)$
...
n $1$ $f(n,m)$

En notant $F$ cette matrice on a alors :

$$ F[i][j] = F[i-1][j] + F[i][j-1] + F[i-1][j-1] $$

Remplir la matrice $F$ nous donne le nombre d'alignements, ce qui se fait aisément en remarquant que l'on peut construire $F$ ligne à ligne :

F = []
for i in range(n+1):  # matrice de 1
    ligne = [1] * (m + 1)
    F.append(ligne)

for i in range(1, n + 1):  # ligne après ligne àn partir de la seconde
    for j in range(1, m + 1):
        F[i][j] = F[i - 1][j] + F[i][j - 1] + F[i - 1][j - 1]

On a utilisé l'astuce de placer $F[0][0] = 1$ pour que $F[1][1]$ puisse être calculé sans cas particulier.

M pour n=m=10

[[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
 [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21],
 [1, 5, 13, 25, 41, 61, 85, 113, 145, 181, 221],
 [1, 7, 25, 63, 129, 231, 377, 575, 833, 1159, 1561],
 [1, 9, 41, 129, 321, 681, 1289, 2241, 3649, 5641, 8361],
 [1, 11, 61, 231, 681, 1683, 3653, 7183, 13073, 22363, 36365],
 [1, 13, 85, 377, 1289, 3653, 8989, 19825, 40081, 75517, 134245],
 [1, 15, 113, 575, 2241, 7183, 19825, 48639, 108545, 224143, 433905],
 [1, 17, 145, 833, 3649, 13073, 40081, 108545, 265729, 598417, 1256465],
 [1, 19, 181, 1159, 5641, 22363, 75517, 224143, 598417, 1462563, 3317445],
 [1, 21, 221, 1561, 8361, 36365, 134245, 433905, 1256465, 3317445, 8097453]]

On voit que l'approximation précédente n'est pas encore trop valide lorsque $n$ est petit. Elle le devient de plus en plus à mesure que $n$ augmente.

La complexité de l'algorithme de calcul précédent :

Proposez un algorithme nécessitant uniquement une complexité spatiale de $\mathcal{O}(m)$ pour calculer $f(n, m)$.

corrigé

On remarque que l'algorithme n'a uniquement besoin que de la ligne précédente à chaque itération. On peut alors le modifier :

ligne_précédente = [-1] + [1] * m
ligne_actuelle = [1]

for i in range(1, n + 1):
    print(ligne_précédente)
    for j in range(1, m + 1):
        f_ij =  ligne_précédente[j] + ligne_actuelle[j - 1] + ligne_précédente[j - 1]
        ligne_actuelle.append(f_ij)
    ligne_précédente = ligne_actuelle
    ligne_actuelle = [1]

f_nm = ligne_précédente[-1]

Distance d'édition

L'alignement entre 2 séquences nous permet de définir, à partir d'une distance pour alignement, une distance pour séquences :

$$ D(a, b) = \min \{ H(a^\star, b^\star) \mid \text{pour tous les alignements } (a^\star, b^\star) \text{ entre } a \text{ et } b\} $$

Comme il est impossible de trouver tous les alignements possibles, il faut ruser pour calculer cette distance sans faire exploser la complexité.

Calcul pour la distance élémentaire

Reprenons le calcul de la distance pour un alignement donné :

$$ H(a^\star, b^\star) = \sum_{i=0}^{L-1} \delta(a^\star_i, b^\star_i) = \sum_{i=0}^{L-2} \delta(a^\star_i, b^\star_i) + \delta(a^\star_{L-1}, b^\star_{L-1}) $$

Ce qui, pour la distance élémentaire, donne :

$$ H(a^\star, b^\star) = \sum_{i=0}^{L-2} \delta(a^\star_i, b^\star_i) + \begin{cases} 0 & \text{si } a^\star_{L-1} = b^\star_{L-1}\\ 1 & \text{sinon} \end{cases} $$

De la même façon que l'on a fait pour établir l'équation de récurrence pour déterminer le nombre d'alignements, on a que $(a^\star_{L-1}, b^\star_{L-1})$ peut être égal à :

De là, si l'alignement $(a^\star, b^\star)$ est celui réalisant la distance ($D(a, b) = H(a^\star, b^\star)$), le cas réalisant le minimum est forcément également une distance.

Par exemple, si $H(a^\star_0\dots a^\star_{L-2}, b^\star_0\dots b^\star_{L-2}) + \delta(a_{n-1}, b_{m-1})$ est plus petit que $H(a^\star_0\dots a^\star_{L-2}, b^\star_0\dots b^\star_{L-1}) + \delta(a_{n-1}, -)$ et que $H(a^\star_0\dots a^\star_{L-1}, b^\star_0\dots b^\star_{L-2}) + \delta(-, b_{m-1})$, alors non seulement :

$$ H(a^\star, b^\star) = H(a^\star_0\dots a^\star_{L-2}, b^\star_0\dots b^\star_{L-2}) + \delta(a_{n-1}, b_{m-1}) $$

Mais on en déduit également que :

$$ D(a[:-1], b[:-1]) = H(a^\star_0\dots a^\star_{L-2}, b^\star_0\dots b^\star_{L-2}) $$

Car si $D(a[:-1], b[:-1])$ était strictement plus petite, on aurait pu passer par l'alignement utilisé pour avoir un alignement entre $a$ et $b$ plus petit que $H$, ce qui est impossible.

Les deux autres cas, se traitent de la même manière : un alignement optimal est composé de sous-alignements eux aussi optimaux !

C'est le principe de la programmation dynamique : un chemin optimal est constitué de sous-chemins eux-mêmes optimaux.

Donc si l'on connaît :

on a :

$$ D(a, b) = \min \begin{cases} D(a[:-1], b[:-1]) + \delta(a_{n-1}, b_{m-1}) & \\ D(a[:-1], b) + 1 & \\ D(a, b[:-1]) + 1 &\\ \end{cases} $$

Ceci se généralise pour tout $i$ et $j$ :

$$ D(a[:i+1], b[:j+1]) = \min \begin{cases} D(a[:i], b[:j]) + \delta(a[i], b[j]) & \\ D(a[:i], b[:j + 1]) + 1 & \\ D(a[:i + 1], b[:j]) + 1 & \\ \end{cases} $$

Et nous permet – tout comme on l'a fait pour le calcul du nombre d'alignements – de créer une représentation matricielle de l'alignement et de la distance d'édition, appelée matrice d'édition :

$-$ $a[0]$ ... $a[i-1]$ $a[i]$ $a[n-1]$
$-$ 0 $1$ $i$ $i+1$ $n$
$b[0]$ $1$
...
$b[j-1]$ $j$ $D(a[:i],b[:j])$ $D(a[:i+1],b[:j])$
$b[j]$ $j+1$ $D(a[:i],b[:j+1])$ $D(a[:i+1],b[:j+1])$
...
$b[m-1]$ $m$ $D(a,b)$

Et nous donne un algorithme très facile pour la calculer, puisqu'il suffit de remplir la première ligne et la première colonne, puis de progresser ligne à ligne avec la formule:

$$ M[i + 1][j + 1] = \min \begin{cases} M[i][j] + 0 & \text{si } a[j] = b[i]\\ M[i][j] + 1 & \text{si } a[j] \neq b[i]\\ M[i + 1][j] + 1 &\\ M[i][j + 1] + 1&\\ \end{cases} $$

La distance entre $a$ et $b$ qui correspond à un alignement de distance minimale est alors à la dernière ligne et dernière colonne de la matrice (en $M[-1][-1]$).

Exemple pour la distance élémentaire

Distance de ACTGATT (horizontal) à GCTAATCG (vertical).

Créez la matrice d'édition vierge à utiliser

solution

- A C T G A T T
-
G
C
T
A
A
T
C
G

Remplissez la première ligne et la première colonne

solution

- A C T G A T T
- 0 1 2 3 4 5 6 7
G 1
C 2
T 3
A 4
A 5
T 6
C 7
G 8

Remplissez le reste de la matrice ligne à ligne

solution

- A C T G A T T
- 0 1 2 3 4 5 6 7
G 1 1 2 3 3 4 5 6
C 2 2 1 2 3 4 5 6
T 3 3 2 1 2 3 4 5
A 4 3 3 2 2 2 3 4
A 5 4 4 3 3 2 3 4
T 6 5 5 4 4 3 2 3
C 7 6 5 5 5 4 3 3
G 8 7 6 6 5 5 4 4

Donnez la distance obtenue

solution

C'est $M[-1][-1]$ et cela vaut 4

Alignement et distance d'édition

Avec la matrice d'édition, il est facile de retrouver un alignement qui a réalisé la distance minimale en remontant dans la matrice.

  1. on pose $i=-1$ et $j=-1$
  2. on pose $A = []$, c'est le tableau qui va contenir notre alignement
  3. on considère $M[i][j]$ qui est la valeur courante de la matrice
  4. on cherche le minimum parmi les 4 possibilités :
    1. $M[i-1][j-1]$ si $a[j] = b[i]$
    2. $M[i-1][j-1] + 1$ si $a[j] \neq b[i]$
    3. $M[i-1][j] + 1$
    4. $M[i][j-1] + 1$
  5. le minimum de l'étape 4 nous donne une partie de l'alignement à ajouter :
    1. on ajoute $(a[j], b[i])$ au début de $A$ et on pose $i=i-1$ et $j=j-1$
    2. on ajoute $(a[j], b[i])$ au début de $A$ et on pose $i=i-1$ et $j=j-1$
    3. on ajoute $(-, b[i])$ au début de $A$ et on pose $i=i-1$
    4. on ajoute $(a[j], -)$ au début de $A$ et on pose $j=j-1$
  6. si $i> -m$ ou $j>-n$ on retourne en 3.

L'algorithme précédent est une idée de l'algorithme. Il faudra ajouter les conditions au bord de la matrice (lorsque $i=-m$ et $j > -n$ par exemple) pour ne pas déborder.

En reprenant l'exemple précédent, donner un alignement donnant le coût minimum

solution

Le chemin dans la matrice est donné en gras :

- A C T G A T T
- 0 1 2 3 4 5 6 7
G 1 1 2 3 3 4 5 6
C 2 2 1 2 3 4 5 6
T 3 3 2 1 2 3 4 5
A 4 4 3 2 2 2 3 4
A 5 4 4 3 3 2 3 4
T 6 5 4 4 4 3 2 3
C 7 6 5 5 5 4 3 3
G 8 7 6 6 5 5 4 4

On obtient alors l'alignement :

ACTGAT-T
 || ||
GCTAATCG

Notez qu'il y a un autre alignement possible :

- A C T G A T T
- 0 1 2 3 4 5 6 7
G 1 1 2 3 3 4 5 6
C 2 2 1 2 3 4 5 6
T 3 3 2 1 2 3 4 5
A 4 4 3 2 2 2 3 4
A 5 4 4 3 3 2 3 4
T 6 5 4 4 4 3 2 3
C 7 6 5 5 5 4 3 3
G 8 7 6 6 5 5 4 4

Qui donne l'alignement :

ACTGATT-
 || ||
GCTAATCG

cas général

Pour l'instant notre distance élémentaire est définie telle que :

Avec la distance élémentaire $\delta$ :

$$ \delta(a^\star_i, b^\star_i) = \begin{cases} 0 & \text{si } a^\star_i = b^\star_i\\ 1 & \text{sinon}\end{cases} $$

Comme $a^\star_i$ et $b^\star_j$ sont soit des caractères de l'alphabet soit le caractère vide $-$, on peut définir $\delta$ tel que :

Il peut parfois être intéressant d'affiner un peu cette distance. Par exemple, si l'on cherche à trouver les erreurs de frappe, on pourra tenter de supposer que deux mots sont proches si les lettres qui les composent sont proches sur le clavier.

"ORNE" sera plus proche de "ORBE" que de "URNE" si l'on compte l'éloignement des touches sur le clavier.

On pourra alors utiliser la distance :

De façon général, on définit alors un coût entre caractères défini tel que

définition du cas général

Tout ce qu'on a fait précédemment est toujours applicable !

Sachant un coût $d$, on peut définir la distance $S_d$ pour un alignement $(a^\star, b^\star)$:

$$ S_d(a^\star, b^\star) = \sum_{i=0}^{L-1} d(a^\star_i, b^\star_i) $$

Ce qui nous permet de définir la distance entre deux séquences pour un coût $d$ :

$$ D_d(a, b) = \min \{ S_d(a^\star, b^\star) \mid \text{pour tous les alignements } (a^\star, b^\star) \text{ entre } a \text{ et } b\} $$

On en déduit une méthode itérative pour trouver cette distance grâce à l'équation :

$$ D_d(a[:i+1], b[:j+1]) = \min \begin{cases} D_d(a[:i], b[:j]) + d(a[i], b[j]) & \\ D_d(a[:i], b[:j + 1]) + d(a[i], -) & \\ D_d(a[:i + 1], b[:j]) + d(-, b[j]) & \\ \end{cases} $$

Et le terme général de la matrice d'édition :

M[i + 1][j + 1] = min(M[i][j] + d(a[j], b[i]),
                      M[i + 1][j] + d(-, b[i]),
                      M[i][j + 1] + d(a[j], -))

exemple du cas général

Considérons le coût :

A C G T
A 0
C 2 0
G 2 2 0
T 2 2 2 0
- 1 1 1 1

Aller de ACTGATT (horizontal) à GCTAATCG (vertical).

Créez la matrice d'édition vierge à utiliser

solution

- A C T G A T T
-
G
C
T
A
A
T
C
G

Remplissez la première ligne et la première colonne

solution

- A C T G A T T
- 0 1 2 3 4 5 6 7
G 1
C 2
T 3
A 4
A 5
T 6
C 7
G 8

Remplissez le reste de la matrice ligne à ligne

solution

- A C T G A T T
- 0 1 2 3 4 5 6 7
G 1 2 3 4 3 4 5 6
C 2 3 2 3 4 5 6 7
T 3 4 3 2 3 4 5 6
A 4 3 4 3 4 3 4 5
A 5 4 5 4 5 4 5 6
T 6 5 6 5 6 5 4 5
C 7 6 5 6 7 6 5 6
G 8 7 6 7 6 7 6 7

Quel est la distance entre les deux séquences ?

solution

7