Zum Inhalt springen

6.5 Dekodierung mit dem Viterbi-Algorithmus

Sobald ein Hidden-Markov-Modell spezifiziert ist, stellt sich eine der ersten Fragen ganz unmittelbar:

Gegeben eine beobachtete Sequenz: Welche verborgene Zustandsfolge hat sie am wahrscheinlichsten erzeugt?

Dies ist das Dekodierungsproblem, und seine klassische Lösung ist der Viterbi-Algorithmus.

Auf den ersten Blick wirkt das Problem einfach. Wenn die beobachtete Sequenz kurz ist und die Zahl der verborgenen Zustände klein, könnte man sich vorstellen, alle möglichen verborgenen Pfade aufzulisten, ihre Wahrscheinlichkeiten zu berechnen und den besten auszuwählen. Dies wird jedoch sehr schnell unpraktikabel. Für eine Sequenz der Länge nn und ein HMM mit kk verborgenen Zuständen gibt es knk^n mögliche Zustandsfolgen. Schon für moderate Werte von nn ist eine erschöpfende Suche unmöglich.

Der Viterbi-Algorithmus löst dieses Problem, indem er eine zentrale strukturelle Eigenschaft von Hidden-Markov-Modellen ausnutzt: Obwohl die Gesamtzahl vollständiger Pfade enorm ist, teilen viele dieser Pfade gemeinsame Präfixe. Daraus entsteht genau jene Struktur überlappender Teilprobleme, die mit dynamischer Programmierung behandelt werden kann.


6.5.1 Das Dekodierungsproblem noch einmal formuliert

Abschnitt betitelt „6.5.1 Das Dekodierungsproblem noch einmal formuliert“

Sei die beobachtete Sequenz

X=(x1,x2,,xn)X = (x_1, x_2, \dots, x_n)

und die verborgene Zustandsfolge

S=(s1,s2,,sn)S = (s_1, s_2, \dots, s_n)

Für ein festes Modell MM besteht das Dekodierungsproblem darin, zu bestimmen:

S=argmaxSP(X,SM)S^* = \arg\max_S P(X, S \mid M)

Das heißt: Unter allen möglichen verborgenen Pfaden suchen wir denjenigen, der die gemeinsame Wahrscheinlichkeit von Beobachtungen und Pfad maximiert.

Diese Formulierung ist wichtig. Wir fragen nicht einfach, welcher Pfad für sich plausibel ist, sondern welcher Pfad unter dem Modell die wahrscheinlichste Erklärung der beobachteten Daten liefert.

Im Promotor-Hintergrund-Beispiel bedeutet dies, dass wir jedem beobachteten Nukleotid jenen Zustand zuordnen möchten, der es am besten erklärt, ohne dabei die Übergangsstruktur des Modells zu verletzen. Ein Pfad mit sehr plausiblen Emissionen, aber extrem unwahrscheinlichen Übergängen, wird nicht optimal sein. Ebenso wenig ein Pfad mit plausiblen Übergängen, aber schlechten Emissionen. Der Viterbi-Algorithmus balanciert beide Beiträge gegeneinander aus.


Um den Wert des Algorithmus zu verstehen, lohnt sich ein Blick auf eine naive Lösung.

Nehmen wir an, unser HMM habe nur zwei verborgene Zustände:

{P,B}\{P, B\}

und die beobachtete Sequenz habe Länge fünf. Dann gibt es bereits

25=322^5 = 32

mögliche verborgene Pfade.

Für eine Länge von zwanzig wären es schon

220=1,048,5762^{20} = 1{,}048{,}576

mögliche Pfade.

Für biologisch realistische Sequenzlängen ist eine erschöpfende Bewertung nicht mehr praktikabel. Zudem wäre ein Großteil der Arbeit redundant. Viele unterschiedliche Pfade teilen dieselben partiellen Präfixe, und die beste Fortsetzung solcher Präfixe wiederholt immer wieder neu zu berechnen, wäre verschwenderisch.

Der Viterbi-Algorithmus vermeidet diese Redundanz, indem er für jede Position und jeden Zustand die Wahrscheinlichkeit des besten Pfades speichert, der dort endet.


Die zentrale Beobachtung ist einfach und zugleich sehr mächtig.

Angenommen, wir wollen an Position ii den besten Pfad kennen, der in Zustand sjs_j endet. Dann muss dieser beste Pfad an Position i1i-1 aus irgendeinem Vorgängerzustand gekommen sein. Unter allen möglichen Vorgängerzuständen gibt es genau einen, der den optimalen Pfad nach sjs_j liefert.

Das bedeutet, dass wir nicht alle vollständigen Pfade gleichzeitig betrachten müssen. Stattdessen können wir die besten partiellen Pfade zu jedem Zustand schrittweise berechnen.

Definieren wir dazu den Viterbi-Score

vi(j)v_i(j)

als die Wahrscheinlichkeit des wahrscheinlichsten Pfades, der die ersten ii Beobachtungen erzeugt und in Zustand sjs_j endet.

Diese Größe fasst genau die Information zusammen, die wir für die weitere dynamische Programmierung benötigen.


Der Algorithmus besteht aus drei Teilen: Initialisierung, Rekursion und Terminierung.

Für die erste Beobachtung x1x_1 ist der beste Pfad, der in Zustand sjs_j endet, einfach die Wahrscheinlichkeit, in diesem Zustand zu starten und das Symbol zu emittieren:

v1(j)=πjej(x1)v_1(j) = \pi_j \, e_j(x_1)

wobei

  • πj\pi_j die Anfangswahrscheinlichkeit von Zustand sjs_j ist
  • ej(x1)e_j(x_1) die Emissionswahrscheinlichkeit des Symbols x1x_1 aus Zustand sjs_j ist

Für jede folgende Position i+1i+1 berechnen wir:

vi+1(j)=maxk(vi(k)tkjej(xi+1))v_{i+1}(j) = \max_{k} \bigl( v_i(k) \, t_{kj} \, e_j(x_{i+1}) \bigr)

Diese Gleichung hat eine sehr natürliche Interpretation.

Um an Position i+1i+1 in Zustand sjs_j zu enden, müssen wir

  1. dem besten Pfad zu einem Vorgängerzustand sks_k an Position ii gefolgt sein
  2. von sks_k nach sjs_j übergehen
  3. das beobachtete Symbol xi+1x_{i+1} aus Zustand sjs_j emittieren

Unter allen Vorgängerzuständen sks_k wählen wir denjenigen, der dieses Produkt maximiert.

Um nicht nur die Wahrscheinlichkeit, sondern auch den eigentlichen Pfad rekonstruieren zu können, müssen wir zusätzlich speichern, welcher Vorgängerzustand das Maximum erreicht hat. Deshalb führen wir parallel eine Backpointer-Tabelle:

bi+1(j)=argmaxk(vi(k)tkjej(xi+1))b_{i+1}(j) = \arg\max_k \bigl( v_i(k) \, t_{kj} \, e_j(x_{i+1}) \bigr)

Diese Tabelle speichert für jede Position und jeden Zustand, aus welchem Vorgängerzustand der optimale Pfad kommt.

Nachdem die letzte Beobachtung verarbeitet wurde, ist der Score des besten vollständigen Pfades

maxjvn(j)\max_j v_n(j)

und der Endzustand dieses Pfades ist

sn=argmaxjvn(j)s_n^* = \arg\max_j v_n(j)

Von diesem Endzustand aus rekonstruieren wir den gesamten Pfad, indem wir den Backpointern rückwärts folgen.


Wir gehen nun ein einfaches Beispiel durch, wie es auch in der Lehrveranstaltung verwendet wird, und betrachten ein Zwei-Zustands-HMM mit einem Promotorzustand PP und einem Hintergrundzustand BB.

Angenommen, die Anfangswahrscheinlichkeiten lauten:

πP=0.1,πB=0.9\pi_P = 0.1, \qquad \pi_B = 0.9

Übergangswahrscheinlichkeiten:

tPP=0.55,tPB=0.45t_{PP} = 0.55, \quad t_{PB} = 0.45 tBP=0.35,tBB=0.65t_{BP} = 0.35, \quad t_{BB} = 0.65

Emissionswahrscheinlichkeiten:

Für den Promotorzustand PP:

eP(A)=0.15,eP(T)=0.12,eP(G)=0.30,eP(C)=0.43e_P(A)=0.15,\quad e_P(T)=0.12,\quad e_P(G)=0.30,\quad e_P(C)=0.43

Für den Hintergrundzustand BB:

eB(A)=0.30,eB(T)=0.30,eB(G)=0.20,eB(C)=0.20e_B(A)=0.30,\quad e_B(T)=0.30,\quad e_B(G)=0.20,\quad e_B(C)=0.20

Betrachten wir nun die beobachtete Sequenz

X=A C C T AX = \text{A C C T A}

Wir berechnen den besten Pfad Schritt für Schritt.


Für das erste Symbol AA gilt:

v1(P)=πPeP(A)=0.10.15=0.015v_1(P) = \pi_P e_P(A) = 0.1 \cdot 0.15 = 0.015 v1(B)=πBeB(A)=0.90.30=0.27v_1(B) = \pi_B e_B(A) = 0.9 \cdot 0.30 = 0.27

Nach dem ersten Symbol hat also der beste Pfad, der in BB endet, eine deutlich höhere Wahrscheinlichkeit als der beste Pfad, der in PP endet. Intuitiv wird das erste AA also eher als Hintergrund als als Promotor erklärt.


Das zweite Symbol ist CC.

Für den Promotorzustand berechnen wir:

v2(P)=max(v1(P)tPPeP(C),v1(B)tBPeP(C))v_2(P) = \max \Bigl( v_1(P)t_{PP}e_P(C), v_1(B)t_{BP}e_P(C) \Bigr)

Einsetzen der Zahlen ergibt:

v2(P)=max(0.0150.550.43,0.270.350.43)v_2(P) = \max \Bigl( 0.015 \cdot 0.55 \cdot 0.43, 0.27 \cdot 0.35 \cdot 0.43 \Bigr) =max(0.00355,0.04064)=0.04064= \max(0.00355, 0.04064) = 0.04064

Der beste Vorgänger ist also BB.

Für den Hintergrundzustand gilt:

v2(B)=max(v1(P)tPBeB(C),v1(B)tBBeB(C))v_2(B) = \max \Bigl( v_1(P)t_{PB}e_B(C), v_1(B)t_{BB}e_B(C) \Bigr) v2(B)=max(0.0150.450.20,0.270.650.20)v_2(B) = \max \Bigl( 0.015 \cdot 0.45 \cdot 0.20, 0.27 \cdot 0.65 \cdot 0.20 \Bigr) =max(0.00135,0.03510)=0.03510= \max(0.00135, 0.03510) = 0.03510

Wieder ist der beste Vorgänger BB.

An diesem Punkt ist der beste Pfad zu PP etwas besser als der beste Pfad zu BB. Nach der Beobachtung von ACAC bevorzugt das Modell also den Promotorzustand.


Das dritte Symbol ist erneut CC.

Für den Promotorzustand:

v3(P)=max(v2(P)tPPeP(C),v2(B)tBPeP(C))v_3(P) = \max \Bigl( v_2(P)t_{PP}e_P(C), v_2(B)t_{BP}e_P(C) \Bigr) =max(0.040640.550.43,0.035100.350.43)= \max \Bigl( 0.04064 \cdot 0.55 \cdot 0.43, 0.03510 \cdot 0.35 \cdot 0.43 \Bigr) =max(0.00961,0.00528)=0.00961= \max(0.00961, 0.00528) = 0.00961

Der beste Vorgänger ist also PP.

Für den Hintergrundzustand:

v3(B)=max(v2(P)tPBeB(C),v2(B)tBBeB(C))v_3(B) = \max \Bigl( v_2(P)t_{PB}e_B(C), v_2(B)t_{BB}e_B(C) \Bigr) =max(0.040640.450.20,0.035100.650.20)= \max \Bigl( 0.04064 \cdot 0.45 \cdot 0.20, 0.03510 \cdot 0.65 \cdot 0.20 \Bigr) =max(0.00366,0.00456)=0.00456= \max(0.00366, 0.00456) = 0.00456

Der beste Vorgänger ist also BB.

Nun ist der Promotorpfad klar im Vorteil.


Das vierte Symbol ist TT.

Für den Promotorzustand:

v4(P)=max(v3(P)tPPeP(T),v3(B)tBPeP(T))v_4(P) = \max \Bigl( v_3(P)t_{PP}e_P(T), v_3(B)t_{BP}e_P(T) \Bigr) =max(0.009610.550.12,0.004560.350.12)= \max \Bigl( 0.00961 \cdot 0.55 \cdot 0.12, 0.00456 \cdot 0.35 \cdot 0.12 \Bigr) =max(0.000634,0.000192)=0.000634= \max(0.000634, 0.000192) = 0.000634

Der beste Vorgänger ist also PP.

Für den Hintergrundzustand:

v4(B)=max(v3(P)tPBeB(T),v3(B)tBBeB(T))v_4(B) = \max \Bigl( v_3(P)t_{PB}e_B(T), v_3(B)t_{BB}e_B(T) \Bigr) =max(0.009610.450.30,0.004560.650.30)= \max \Bigl( 0.00961 \cdot 0.45 \cdot 0.30, 0.00456 \cdot 0.65 \cdot 0.30 \Bigr) =max(0.001297,0.000889)=0.001297= \max(0.001297, 0.000889) = 0.001297

Der beste Vorgänger ist also PP.

Nun ist der beste Pfad zu BB besser als der beste Pfad zu PP, was auf einen Rückübergang in den Hintergrund hinweist.


Das fünfte Symbol ist AA.

Für den Promotorzustand:

v5(P)=max(v4(P)tPPeP(A),v4(B)tBPeP(A))v_5(P) = \max \Bigl( v_4(P)t_{PP}e_P(A), v_4(B)t_{BP}e_P(A) \Bigr) =max(0.0006340.550.15,0.0012970.350.15)= \max \Bigl( 0.000634 \cdot 0.55 \cdot 0.15, 0.001297 \cdot 0.35 \cdot 0.15 \Bigr) =max(0.0000523,0.0000681)=0.0000681= \max(0.0000523, 0.0000681) = 0.0000681

Der beste Vorgänger ist also BB.

Für den Hintergrundzustand:

v5(B)=max(v4(P)tPBeB(A),v4(B)tBBeB(A))v_5(B) = \max \Bigl( v_4(P)t_{PB}e_B(A), v_4(B)t_{BB}e_B(A) \Bigr) =max(0.0006340.450.30,0.0012970.650.30)= \max \Bigl( 0.000634 \cdot 0.45 \cdot 0.30, 0.001297 \cdot 0.65 \cdot 0.30 \Bigr) =max(0.0000856,0.0002529)=0.0002529= \max(0.0000856, 0.0002529) = 0.0002529

Der beste Vorgänger ist also BB.

Der insgesamt beste Endzustand ist damit BB, weil

v5(B)>v5(P)v_5(B) > v_5(P)

Nun rekonstruieren wir den besten Pfad, indem wir den Backpointern rückwärts folgen.

Aus den obigen Berechnungen ergibt sich als bester Endzustand BB. Die Vorgängerentscheidungen lauteten:

  • an Position 5: BBB \leftarrow B
  • an Position 4: BPB \leftarrow P
  • an Position 3: PPP \leftarrow P
  • an Position 2: PBP \leftarrow B
  • an Position 1: Start in BB

Damit lautet der wahrscheinlichste verborgene Pfad:

BPPBBB \quad P \quad P \quad B \quad B

Genau dies erwarten wir von einem Dekodierungsalgorithmus. Die beobachtete Sequenz ACCTAACCTA wird so erklärt, dass sie im Hintergrund beginnt, eine kurze promotorähnliche Region durchläuft und dann wieder in den Hintergrund übergeht.


Dieses Beispiel illustriert mehrere wichtige Punkte.

Erstens hängt der wahrscheinlichste Zustand an einer gegebenen Position nicht nur vom beobachteten Symbol an dieser Position ab. Er hängt auch von der Wahrscheinlichkeit ab, diesen Zustand aus dem vorhergehenden Pfad zu erreichen. Genau deshalb lässt sich der Viterbi-Algorithmus nicht durch eine einfache positionsweise Klassifikationsregel ersetzen.

Zweitens balanciert der Algorithmus Emissionsevidenz gegen Übergangsstruktur aus. Ein Symbol, das mäßig gut mit dem Promotorzustand vereinbar ist, kann dennoch dem Hintergrund zugeordnet werden, wenn der Übergang in den Promotor unwahrscheinlich ist, und umgekehrt.

Drittens ist das Resultat nicht bloß ein Score, sondern eine interpretierbare Segmentierung der Sequenz. Biologisch kann diese Segmentierung Regionen unterschiedlichen funktionellen Charakters entsprechen.


Es lohnt sich, das Prinzip der dynamischen Programmierung an dieser Stelle explizit zu machen.

Warum ist es zulässig, an jeder Zelle der Viterbi-Tabelle nur den besten Pfad zu speichern, der in diesem Zustand endet, und alle anderen partiellen Pfade zu verwerfen?

Weil jede zukünftige Fortsetzung nur vom aktuellen Zustand und vom aktuellen Score abhängt, nicht aber von der detaillierten Geschichte, wie dieser Zustand erreicht wurde. Wenn ein partieller Pfad zu Zustand PP schlechter ist als ein anderer, kann er ihn später nie mehr überholen, da beide dieselben zukünftigen Übergangs- und Emissionsoptionen sehen.

Dies ist genau die Eigenschaft optimaler Teilstruktur, die den Viterbi-Algorithmus möglich macht.


Angenommen, ein HMM habe NN Zustände und die beobachtete Sequenz Länge LL.

An jeder Position betrachten wir für jeden Zustand alle möglichen Vorgängerzustände. Daraus ergibt sich eine Zeitkomplexität von

O(N2L)O(N^2 L)

Dies ist dramatisch besser als die exponentielle Komplexität erschöpfender Enumeration.

Die Speicherkosten betragen typischerweise

O(NL)O(NL)

wenn die vollständige Tabelle und die Backpointer gespeichert werden, was für moderate Zustandsräume in der Regel akzeptabel ist.


Der Viterbi-Algorithmus löst das Dekodierungsproblem, indem er drei Ideen verbindet:

  • zustandsspezifische Emissionen
  • Übergänge zwischen Zuständen
  • dynamische Programmierung

Sein Output ist der einzelne wahrscheinlichste verborgene Pfad, der die beobachtete Sequenz erklärt.

Für die biologische Sequenzanalyse macht ihn dies zu einem leistungsfähigen Werkzeug für:

  • Promotorerkennung
  • Vorhersage von Genstruktur
  • Annotation von Proteinsekundärstruktur
  • profilbasierte Sequenzklassifikation

In all diesen Anwendungen geht es nicht nur darum, eine Sequenz zu bewerten, sondern die verborgene biologische Organisation zu rekonstruieren, die ihr zugrunde liegt.


  1. Warum ist eine erschöpfende Suche über alle verborgenen Pfade nicht praktikabel?
  2. Was repräsentiert die Größe vi(j)v_i(j) im Viterbi-Algorithmus?
  3. Warum werden Backpointer benötigt?
  4. In welchem Sinn verwendet der Viterbi-Algorithmus dynamische Programmierung?
  5. Warum kann der wahrscheinlichste Zustand an einer Position von früheren Positionen abhängen?