Przejdź do treści
coden

Ukryte modele Markowa: algorytm Viterbiego i nieuczciwe kasyno

Ukryty model Markowa, trzy zadania według Rabinera, algorytm w przód, algorytm Viterbiego, dekodowanie a posteriori i algorytm Bauma–Welcha — na kasynie, które od czasu do czasu potajemnie zamienia kostkę na fałszywą.

14 minut Matěj Žalmánek 2026-10-15

Seria Podstawy AI Część 39 z 52
  1. Mapa sztucznej inteligencji: co jest czym i jak to do siebie pasuje
  2. Krótka historia sztucznej inteligencji: od Turinga do modeli językowych
  3. Dane, model, trening, inferencja: podstawowe pojęcia na jednym przykładzie
  4. Jak czytać zapis matematyczny: symbole, indeksy, sumy i kwantyfikatory
  5. Zbiory i relacje: klocki, z których składają się definicje w AI
  6. Funkcje i odwzorowania: dlaczego każdy model AI jest funkcją
  7. Logika i dowody: reguły, według których rozumuje AI
  8. Grafy i drzewa: struktura, na której opierają się przeszukiwanie i sieci neuronowe
  9. Algebra liniowa dla uczenia maszynowego: wektory, macierze i dlaczego AI głównie mnoży
  10. Pochodne, gradient i reguła łańcuchowa: jak model ustala, w którą stronę się uczyć
  11. Spadek gradientu: jak uczy się model
  12. Czym jest prawdopodobieństwo: od rzutu kostką do aksjomatów Kołmogorowa
  13. Prawdopodobieństwo warunkowe i twierdzenie Bayesa: jak zmienić ocenę, gdy dowiesz się czegoś nowego
  14. Zmienna losowa i dystrybuanta: jedna funkcja, która mówi o rozkładzie wszystko
  15. Funkcja gęstości i funkcja prawdopodobieństwa: dlaczego zmienna ciągła ma w punkcie prawdopodobieństwo zero
  16. Wartość oczekiwana, wariancja i kowariancja: gdzie leży rozkład, jak jest szeroki i co z czym się wiąże
  17. Przegląd rozkładów prawdopodobieństwa: Bernoulliego, dwumianowy, Poissona, jednostajny, normalny i wykładniczy
  18. Prawo wielkich liczb i centralne twierdzenie graniczne: dlaczego średnie zachowują się tak przewidywalnie
  19. Statystyka i estymacja parametrów: największa wiarygodność, MAP i przedziały ufności
  20. Teoria informacji: entropia, entropia krzyżowa i dywergencja KL — skąd się biorą funkcje straty
  21. Przeszukiwanie przestrzeni stanów: BFS, DFS, UCS i A* z animacjami
  22. Gry i przeciwnik: minimax, przycinanie alfa-beta i Monte Carlo tree search
  23. Spełnianie ograniczeń: backtracking, propagacja ograniczeń i AC-3 na sudoku i mapie czeskich krajów
  24. Logika i systemy z bazą wiedzy: wnioskowanie w przód i wstecz, rezolucja i systemy ekspertowe
  25. Planowanie: STRIPS, świat klocków, heurystyki z relaksacji i planowanie w agentach AI
  26. Przeszukiwanie lokalne i symulowane wyżarzanie: komiwojażer przez czeskie miasta wojewódzkie
  27. Algorytmy genetyczne: populacja, krzyżowanie, mutacja i selekcja na plecaku na wycieczkę
  28. Strategie ewolucyjne i programowanie genetyczne: od reguły jednej piątej do prawa Keplera
  29. Optymalizacja kolonią mrówek: jak ślad feromonowy znajduje krótką drogę
  30. Optymalizacja rojem cząstek: bezwładność, własne doświadczenie i doświadczenie roju
  31. Neuroewolucja: ewolucja wag i kształtu sieci neuronowych oraz algorytm NEAT
  32. Zbiory rozmyte i funkcje przynależności: jak zapisać „trochę ciepło”
  33. Logika rozmyta a prawdopodobieństwo: dlaczego „trochę ciepło” nie znaczy „prawdopodobnie ciepło”
  34. Logika rozmyta: t-normy, implikacje i reguły JEŻELI–TO
  35. Sterowanie rozmyte: Mamdani i Sugeno od wentylatora po wahadło na wózku
  36. Klasteryzacja rozmyta: algorytm fuzzy c-means i punkty między skupieniami
  37. Systemy neuro-rozmyte (ANFIS): reguły rozmyte, które uczą się z danych
  38. Łańcuchy Markowa: macierz przejścia, rozkład stacjonarny i PageRank
  39. Ukryte modele Markowa: algorytm Viterbiego i nieuczciwe kasyno
  40. Sieci bayesowskie: dlaczego trawa jest mokra i co z tego wynika dla zraszacza
  41. Filtr Kalmana: jak z niedokładnych pomiarów oszacować, gdzie jest samochód i jak szybko jedzie
  42. Rodzaje uczenia maszynowego: nadzorowane, nienadzorowane, półnadzorowane i inne
  43. Prompty, kontekst i RAG: jak dać modelowi właściwe informacje
  44. Wariacyjne autoenkodery (VAE): nowe obrazki z ciągłej przestrzeni latentnej
  45. Generatywne sieci przeciwstawne (GAN): fałszerz kontra detektyw
  46. Modele dyfuzyjne: jak z czystego szumu powstaje obraz
  47. Procesy decyzyjne Markowa: stany, akcje, nagrody i równania Bellmana
  48. Q-learning i DQN: uczenie się wartości akcji od tabeli po sieć neuronową
  49. Policy gradient i PPO: uczenie się bezpośrednio polityki
  50. Jak działa agent AI: model językowy w pętli z narzędziami
  51. Programowanie z agentami AI: zadanie, testy i kontrola
  52. Ocena i bezpieczeństwo systemów AI: jak mierzyć i jak pilnować

Pełny program serii Gotowe: 52 z 83 tematów

Czego się dowiesz
  • Czym jest ukryty model Markowa i jak obliczyć prawdopodobieństwo obserwacji bez przechodzenia wszystkich ścieżek.
  • Jak algorytm Viterbiego znajduje najbardziej prawdopodobne ukryte stany i gdzie się myli.
  • Jak model uczy się parametrów wyłącznie z obserwacji i ile danych do tego potrzebuje.

Warto wiedzieć wcześniej: Łańcuchy Markowa: macierz przejścia, rozkład stacjonarny i PageRank

W poprzedniej części stan łańcucha widzieliśmy: dziś świeci słońce, jutro pada. Często jednak widzimy tylko jego przejawy. Mikrofon rejestruje falę dźwiękową, a nie słowo, które ją wytworzyło. Sekwenator odczytuje litery DNA, a nie to, gdzie zaczyna się gen. A w kasynie widzimy, co wypadło, a nie to, którą kostką rzuca krupier. Ukryte modele Markowa to łańcuchy, których stanów nie widzimy — i algorytmy, którymi mimo to da się ustalić, co dzieje się w środku. Przez dziesięciolecia opierało się na nich rozpoznawanie mowy.

Nawiązuje do części Łańcuchy Markowa.

1. Nieuczciwe kasyno

Klasyczny podręcznikowy przykład (Durbin i in., 1998): krupier rzuca albo uczciwą kostką, albo fałszywą, na której szóstka wypada z prawdopodobieństwem 1/2, a pozostałe liczby z 1/10. Po każdym rzucie z prawdopodobieństwem 0,05 zamienia uczciwą na fałszywą, a z prawdopodobieństwem 0,1 fałszywą z powrotem na uczciwą. Zamiany nie widzimy. Widzimy tylko ciąg liczb — w naszym eksperymencie 300 rzutów, które zaczynają się tak:

163665521623524236522511663566613666222461626262162666626516…

Pytamy: czy gra się tu uczciwie? A jeśli nie, to kiedy dokładnie w grze była fałszywa kostka?

2. Ukryty model Markowa

Definicja — ukryty model Markowa

Ukryty model Markowa (HMM) λ = (π, A, B) ma ukryte stany 1, …, N i obserwowalne symbole 1, …, M:

  • ukryte stany X1, X2, … tworzą łańcuch Markowa z rozkładem początkowym π i macierzą przejścia A, aij = P(Xt+1 = j | Xt = i);
  • w każdej chwili stan Xt = i generuje obserwację Ot według prawdopodobieństw emisji bi(o) = P(Ot = o | Xt = i), niezależnie od wszystkiego innego.
Mówiąc prościej

Za kurtyną działa zwykły łańcuch Markowa — kostka zmienia się według macierzy A. My widzimy tylko to, co z każdego stanu „wypada”: rzut według prawdopodobieństw B. Szóstka jest dla fałszywej kostki pięć razy bardziej prawdopodobna niż jakakolwiek inna liczba, ale może wypaść także na uczciwej. Pojedynczy rzut niczego więc nie zdradzi — zdradzi to dopiero wzór w całym ciągu.

ukryte widzimy 0,05 0,1 0,95 uczciwa 1 2 3 4 5 6 wyrzucona liczba (oczka) P(rzut) (0–1) 1/6 0,9 fałszywa 1 2 3 4 5 6 wyrzucona liczba (oczka) P(rzut) (0–1) 6: 0,5
Model kasyna: ukryte stany u góry, prawdopodobieństwa rzutów u dołu

Z definicji wynika, jak prawdopodobna jest jedna konkretna „historia”: ciąg kostek x = (x1, …, xT) razem z rzutami O = (O1, …, OT).

P(x, O | λ) = πx1 bx1(O1) · ∏t=2T axt−1 xt bxt(Ot)

πx1
pierwsza kostka nie ma poprzednika, z którego by się na nią przeszło — jej prawdopodobieństwo daje rozkład początkowy
axt−1 xt
każda kolejna kostka zależy tylko od poprzedniej (własność Markowa), więc na każdy krok wystarczy jedna liczba z macierzy A
bxt(Ot)
rzut zależy tylko od kostki, którą właśnie się rzuca — bierzemy prawdopodobieństwo liczby, która naprawdę wypadła
∏t=2T
w ogólności mnożyłoby się prawdopodobieństwa warunkowane całą przeszłością (reguła łańcuchowa); oba założenia niezależności z definicji skracają je do tych lokalnych liczb, i dlatego całość jest zwykłym iloczynem
Każdy krok dodaje dwa czynniki: jak (nie) zmieniła się kostka i co na niej wypadło.
Przykład — dwie historie pierwszych trzech rzutów

Nasz ciąg zaczyna się rzutami 1, 6, 3. Oznaczmy przez U uczciwą, a przez F fałszywą kostkę. Historia „trzy razy fałszywa”:

P(FFF, 1, 6, 3) = 0,5 · 0,1 · 0,9 · 0,5 · 0,9 · 0,1 = 0,002025,

historia „trzy razy uczciwa”:

P(UUU, 1, 6, 3) = 0,5 · 1/6 · 0,95 · 1/6 · 0,95 · 1/6 = 0,002089.

Uczciwa historia wychodzi o włos bardziej prawdopodobna: szóstka na fałszywej kostce wprawdzie dostaje 0,5 zamiast 1/6, ale jedynka i trójka tylko 0,1 zamiast 1/6. (W symulacji przy tym wszystkie trzy rzuty wykonano fałszywą kostką — z trzech rzutów nie da się tego poznać).

Lawrence Rabiner w słynnym przeglądzie z 1989 roku streścił to, co można zrobić z takim modelem, w trzech zadaniach: ocena (jak prawdopodobny jest zaobserwowany ciąg?), dekodowanie (jakie ukryte stany najpewniej za nim stały?) i uczenie (jakie parametry ma model, gdy znamy tylko obserwacje?). Na każde istnieje elegancki algorytm.

3. Jak prawdopodobny jest ciąg: algorytm w przód

Prawdopodobieństwo zaobserwowanego ciągu to suma po wszystkich możliwych ciągach ukrytych stanów. Dla 300 rzutów i dwóch kostek jest ich 2300 ≈ 2 · 1090 — więcej, niż jest atomów we wszechświecie. Wprost tego zsumować się nie da. Własność Markowa pozwala jednak liczyć stopniowo.

Przykład — prawdopodobieństwo trzech rzutów jako suma po wszystkich historiach

Prawdopodobieństwo obserwacji to suma prawdopodobieństwa łącznego po wszystkich możliwych historiach: P(O | λ) = ∑x P(x, O | λ). Dla rzutów 1, 6, 3 jest ich 23 = 8:

HistoriaP(x, O)
UUU0,002089
UUF0,000066
UFU0,000035
UFF0,000188
FUU0,000132
FUF0,000004
FFU0,000375
FFF0,002025
suma0,004913

Każda historia kosztuje 2T − 1 = 5 mnożeń, razem 8 · 5 = 40. Dla T rzutów to (2T − 1) · 2T — przy 300 rzutach niewyobrażalnie dużo.

Algorytm — algorytm w przód

Oznaczmy αt(j) = P(O1, …, Ot, Xt = j). Wtedy

α1(j) = πj bj(O1),   αt+1(j) = [∑i αt(i) aij] bj(Ot+1),   P(O | λ) = ∑j αT(j).

Obliczenie trwa O(N2 T) zamiast O(NT). Żeby liczby nie uległy niedomiarowi, αt normalizuje się w każdym kroku, a sumuje się logarytmy stałych normalizujących.

Mówiąc prościej

Nie musisz śledzić każdej możliwej historii osobno. Wystarczy po każdym rzucie pamiętać dwie liczby: „jak prawdopodobne jest wszystko, co do tej pory widziałem, i to, że teraz w grze jest uczciwa kostka” — i to samo dla fałszywej. Z nich oblicza się liczby dla następnego rzutu. Wszystkie historie, które kończą się tym samym stanem, zlewają się w jedną liczbę.

αt+1(j) = ∑i αt(i) aij · bj(Ot+1)

αt(i)
wszystko, co wiemy o przeszłości: prawdopodobieństwo dotychczasowych rzutów i tego, że teraz w grze jest kostka i; w tej jednej liczbie zlane są wszystkie historie kończące się w i
aij
krok łańcucha z i do j; dzięki własności Markowa nie ma znaczenia, jak dotarliśmy do i
∑i
nie wiemy, którą kostką rzucano ostatnio, a do j da się dojść z każdej — dlatego sumujemy po wszystkich możliwościach (historie są zdarzeniami wzajemnie rozłącznymi)
bj(Ot+1)
nowy rzut musi naprawdę wygenerować kostka j; nie zależy od i, więc da się go wyciągnąć przed sumę
Suma po 2T historiach rozpada się na T małych sum po N składników.
Przykład — algorytm w przód na rzutach 1, 6, 3

Start: α1(U) = 0,5 · 1/6 = 0,0833, α1(F) = 0,5 · 0,1 = 0,0500. Drugi rzut to szóstka:

α2(U) = (0,0833 · 0,95 + 0,0500 · 0,1) · 1/6 = 0,0140,
α2(F) = (0,0833 · 0,05 + 0,0500 · 0,9) · 0,5 = 0,0246.

trzutαt(U)αt(F)suma = P(O1, …, Ot)ct
110,08330,05000,1333330,1333
260,01400,02460,0386110,2896
330,00260,00230,0049130,1273

Suma w ostatnim wierszu to dokładnie suma ośmiu historii z poprzedniego przykładu, tylko zamiast 40 mnożeń wystarczyło 2 + 2 · 6 = 14. Ostatnia kolumna ct = P(Ot | O1, …, Ot−1) to stała normalizująca ze skalowania: jak prawdopodobny był rzut t przy znanych poprzednich. Ich iloczyn to P(O | λ), więc log P(O | λ) = ∑t log ct.

Dlaczego logarytmy? Przy 3 000 rzutów P(O | λ) wychodzi około 10−2 278, a najmniejsza dodatnia liczba, jaką komputer zapisze w zwykłej podwójnej precyzji (IEEE 754), to około 4,9 · 10−324. Bez skalowania α spadłoby do zera.

def dopredu(hody, pi, A, B):
    alfa = pi * B[:, hody[0]]
    logP = 0.0
    for t in range(len(hody)):
        if t > 0:
            alfa = (alfa @ A) * B[:, hody[t]]       # suma po i, potem emisja
        c = alfa.sum()                              # c_t = P(O_t | O_1, …, O_t−1)
        alfa = alfa / c                             # skalowanie: α_t sumuje się teraz do 1
        logP += np.log(c)
    return logP                                     # log P(O | λ)

Dla naszych 300 rzutów wychodzi P(rzuty | model kasyna) ≈ 10−230,03. Gdyby przez cały czas grano uczciwie, prawdopodobieństwo wyniosłoby (1/6)300 ≈ 10−233,45. Model z okazjonalną fałszywą kostką wyjaśnia zaobserwowane rzuty mniej więcej 2 600 razy lepiej — mocny dowód, że ktoś tu oszukuje, choć żaden pojedynczy rzut oszustwa nie zdradza.

Definicja — iloraz wiarygodności (czynnik Bayesa)

Dla dwóch modeli M1, M2 i danych O czynnik Bayesa to K = P(O | M1) / P(O | M2). Z twierdzenia Bayesa (zapisanego dla obu modeli i podzielonego) wynika:

P(M1 | O) / P(M2 | O) = P(O | M1) / P(O | M2) · P(M1) / P(M2)

P(M1 | O) / P(M2 | O)
iloraz szans po obserwacji — to, co nas interesuje: o ile bardziej wierzymy w oszustwo niż w uczciwą grę, gdy już widzieliśmy rzuty
P(O | M1) / P(O | M2)
czynnik Bayesa — jedyne miejsce, którym do wzoru wchodzą dane; obie wiarygodności oblicza algorytm w przód. Mianownik P(O) z twierdzenia Bayesa przy dzieleniu się skraca, dlatego nie trzeba go znać
P(M1) / P(M2)
iloraz szans z góry — kto z góry ufa kasynu, potrzebuje mocniejszego dowodu; dane tylko go przemnożą

Obliczenie. log10 K = −230,03 − (−233,45) = 3,42 (z niezaokrąglonych wartości 3,413), czyli K ≈ 103,413 ≈ 2 590, w zaokrągleniu 2 600. Kto z góry uważał oszustwo i uczciwą grę za jednakowo prawdopodobne, po 300 rzutach ma szanse około 2 600 : 1 na korzyść oszustwa. Według powszechnie używanej skali Kassa i Raftery’ego (1995) czynnik Bayesa powyżej 150 to „bardzo mocny” dowód.

4. Kiedy oszukiwano: algorytm Viterbiego

Algorytm — algorytm Viterbiego (1967)

Szukamy najbardziej prawdopodobnego ciągu stanów argmaxx P(x, O | λ). Oznaczmy przez δt(j) prawdopodobieństwo najlepszej ścieżki, która w chwili t kończy się w stanie j:

δ1(j) = πj bj(O1),   δt+1(j) = maxi [δt(i) aij] · bj(Ot+1),

a przy każdym δt+1(j) zapamiętaj, z którego i przyszło. Na końcu wybierz najlepszy stan i według zapamiętanych odsyłaczy idź z powrotem. Liczy się w logarytmach; czas znów wynosi O(N2 T).

Mówiąc prościej

Ta sama idea co w algorytmie w przód, tylko zamiast sumy po wszystkich ścieżkach bierze się najlepszą. Do każdego stanu w każdej chwili prowadzi jedna „zwycięska” ścieżka — gdyby najlepsza ścieżka w całości szła tędy, i tak dotarłaby do tego punktu właśnie nią. To programowanie dynamiczne, spokrewnione z szukaniem najkrótszej ścieżki z części o przeszukiwaniu przestrzeni stanów.

δt+1(j) = maxi δt(i) aij · bj(Ot+1),   ψt+1(j) = argmaxi δt(i) aij

δt(i)
prawdopodobieństwo najlepszej historii, która w chwili t kończy się kostką i; gorszych historii prowadzących do i już nie potrzebujemy — gdyby najlepsza pełna ścieżka prowadziła przez i, dalej szłaby tą najlepszą
aij
koszt przejścia z i do j; małe aUF = 0,05 to powód, dla którego Viterbiemu nie chce się przełączać z powodu jednej szóstki
maxi, ψt+1(j)
w odróżnieniu od algorytmu w przód nie sumujemy, lecz wybieramy najlepszego poprzednika — chcemy jednej ścieżki, a nie prawdopodobieństwa wszystkich; ψ pamięta, kto wygrał (na kracie bursztynowe strzałki), żeby ścieżkę dało się na końcu odtworzyć wstecz
bj(Ot+1)
jak dobrze kostka j wyjaśnia nowy rzut — tak samo jak przy α
Ten sam wzór co dla α, tylko suma ∑ jest zastąpiona maksimum.
uczciwa fałszywa rzut 5 −1,1 −1,3 2 −1,9 −2,3 2 −2,7 −3,4 5 −3,5 −4,4 1 −4,3 −5,5 1 −5,1 −6,5 6 −5,9 −6,7 6 −6,7 −7,0 3 −7,5 −8,1 5 −8,3 −9,1 6 −9,1 −9,5 6 −9,9 −9,8
Krata Viterbiego dla rzutów 19–30: liczby to log₁₀ prawdopodobieństwa najlepszej ścieżki

Na dwunastu rzutach widać, jak Viterbi podejmuje decyzję. Pierwsze sześć rzutów (5, 2, 2, 5, 1, 1) wygląda uczciwie. Potem wypadają dwie szóstki z rzędu i kolejne dwie kawałek dalej — i najlepsza ścieżka przełącza się na fałszywą kostkę. W rzeczywistości krupier wymienił kostkę dopiero cztery rzuty później; dwie szóstki z rzędu wypadają też na uczciwej kostce.

Przykład — Viterbi krok po kroku na rzutach 1, 6, 3

Start jest taki sam jak dla α: δ1(U) = 0,0833, δ1(F) = 0,0500. Dla szóstki w drugim rzucie:

δ2(U) = max(0,0833 · 0,95; 0,0500 · 0,1) · 1/6 = 0,0792 · 1/6 = 0,0132   (wygrało U),
δ2(F) = max(0,0833 · 0,05; 0,0500 · 0,9) · 0,5 = 0,0450 · 0,5 = 0,0225   (wygrało F).

trzutδt(U)ψt(U)δt(F)ψt(F)
110,083333—0,050000—
260,013194U0,022500F
330,002089U0,002025F

Na końcu wygrywa U (0,002089 > 0,002025) i wstecz po odsyłaczach ψ wychodzi ścieżka UUU — dokładnie ta historia z pierwszego przykładu, która była najbardziej prawdopodobna ze wszystkich ośmiu. Algorytm w przód daje jednak dla trzeciego rzutu P(F | O1, O2, O3) = 0,002283 / 0,004913 = 0,465. Najlepsza ścieżka i najbardziej prawdopodobny stan są tu zgodne (U), ale „zwycięstwo” jest nieznaczne, a trzy rzuty nie wystarczą do pewności. W ogólności zgodne być nie muszą — wrócimy do tego przy dekodowaniu a posteriori.

import numpy as np

def viterbi(hody, pi, A, B):
    T, N = len(hody), len(pi)
    delta = np.log(pi) + np.log(B[:, hody[0]])
    odkud = np.zeros((T, N), dtype=int)
    for t in range(1, T):
        kandidati = delta[:, None] + np.log(A)       # z każdego i do każdego j
        odkud[t] = kandidati.argmax(axis=0)
        delta = kandidati.max(axis=0) + np.log(B[:, hody[t]])
    cesta = [int(delta.argmax())]
    for t in range(T - 1, 0, -1):                     # wstecz po zwycięskich odsyłaczach
        cesta.append(odkud[t][cesta[-1]])
    return cesta[::-1]

5. Wszystkie 300 rzutów

rzeczywistośćrzuty (oczka)ViterbiP(fałszywa) (0–1) 01 0100200300 rzut (kolejność w symulacji)
Rzeczywistość, rzuty, ścieżka Viterbiego i prawdopodobieństwo fałszywej kostki

Fałszywa kostka była w grze 88 razy, w dziewięciu odcinkach. Viterbi oznaczył jako fałszywe 62 rzuty, z czego 56 poprawnie, i łącznie poprawnie określił kostkę dla 87,3 % rzutów. Długie odcinki oszustwa znalazł, krótkie jednak przeoczył albo zlał ze sobą — zamiast dziewięciu odcinków widzi dwa. Na krótkim odcinku po prostu nie wypada dość szóstek, żeby opłacała się „droga” zamiana kostki (prawdopodobieństwo zamiany to tylko 0,05).

Definicja — prawdopodobieństwo a posteriori stanu

Algorytm w przód i wstecz oblicza dodatkowo βt(i) = P(Ot+1, …, OT | Xt = i) od końca i z obu dostaje γt(i) = P(Xt = i | O) ∝ αt(i) βt(i).

Mówiąc prościej

Viterbi daje jedną odpowiedź: „to była ta kostka”. Prawdopodobieństwo a posteriori mówi, na ile jesteśmy pewni — dla każdego rzutu osobno, z uwzględnieniem wszystkiego, co wypadło przedtem i potem. Dolna krzywa na obrazku: tam, gdzie wznosi się ponad połowę, raczej się oszukuje.

γt(i) = αt(i) · βt(i) / P(O | λ),   βt(i) = ∑j aij bj(Ot+1) βt+1(j),   βT(i) = 1

γt(i)
prawdopodobieństwo, że w chwili t w grze była kostka i, gdy znamy wszystkie rzuty — dla fałszywej kostki to czerwona krzywa na obrazku
αt(i)
dowody z przeszłości: rzuty od 1 do t i stan i w chwili t
βt(i)
dowody z przyszłości: jak dobrze stan i wyjaśnia rzuty od t + 1 do T. Liczy się od końca tym samym trikiem co α — jeden krok łańcucha, emisja, reszta przyszłości. Mnożyć α i β wolno nam, bo przy znanym stanie Xt przeszłe i przyszłe rzuty są niezależne
P(O | λ)
iloczyn α·β to prawdopodobieństwo łączne P(Xt = i, O); dzieląc przez prawdopodobieństwo wszystkich rzutów, robimy z niego warunkowe. To ta sama liczba, którą dał algorytm w przód, i w każdym t zachodzi ∑i αt(i) βt(i) = P(O | λ)
Przykład — drugi rzut: co dodaje przyszłość

Zostańmy przy rzutach 1, 6, 3 i zapytajmy o drugi rzut (szóstkę). Krok wstecz od β3 = 1 z trzecim rzutem (trójką):

β2(U) = 0,95 · 1/6 + 0,05 · 0,1 = 0,1633,   β2(F) = 0,1 · 1/6 + 0,9 · 0,1 = 0,1067.

γ2(F) = 0,024583 · 0,106667 / 0,004913 = 0,534.

Gdybyśmy znali tylko pierwsze dwa rzuty, byłoby P(F | O1, O2) = α2(F) / (α2(U) + α2(F)) = 0,637. Trójka, która wypadła potem, jest na fałszywej kostce mniej prawdopodobna, więc wstecznie obniża podejrzenie co do drugiego rzutu do 0,534.

Definicja — dekodowanie a posteriori i dokładność

Dekodowanie a posteriori wybiera w każdej chwili osobno x̂t = argmaxi γt(i). Dokładność dekodowania x̂ względem prawdziwych stanów x to odsetek zgodności (1/T) ∑t [x̂t = xt], gdzie [·] wynosi 1, gdy stwierdzenie jest prawdziwe, w przeciwnym razie 0.

Dekodowanie a posteriori maksymalizuje oczekiwaną liczbę poprawnie określonych rzutów, Viterbi prawdopodobieństwo, że poprawna jest cała ścieżka naraz. Dlatego ciąg a posteriori może zawierać nawet przejście, które ma w modelu prawdopodobieństwo zero — w kasynie takiego nie ma, ale w ogólności może się to zdarzyć.

Gdybyśmy przy każdym rzucie wybrali kostkę z większym prawdopodobieństwem a posteriori, trafilibyśmy w 87,7 % — o włos więcej niż Viterbi (263 rzuty z 300 wobec 262, różnica jednego rzutu). (Viterbi szuka najlepszej całej ścieżki, dekodowanie a posteriori najlepszego oszacowania każdego rzutu osobno; to nie to samo). I uwaga na pojedynczą symulację: w dwunastu różnych eksperymentach dokładność algorytmu Viterbiego wahała się od 73,3 do 90,0 %, średnio 83,4 %. Zależnie od tego, jak długie odcinki oszustwa wytworzy przypadek.

6. Uczenie: algorytm Bauma–Welcha

Do tej pory parametry kasyna znaliśmy. Co, jeśli wiemy tylko tyle, że kostki są dwie, a wszystko inne trzeba wyczytać z rzutów?

Algorytm — algorytm Bauma–Welcha

To algorytm EM (Baum i in., około 1970 roku). Powtarzaj:

  1. krok E: przy bieżących parametrach oblicz algorytmem w przód i wstecz γt(i) oraz oczekiwane liczby przejść ξt(i, j) = P(Xt = i, Xt+1 = j | O);
  2. krok M: nowe aij = ∑t ξt(i, j) / ∑t γt(i), nowe bi(o) = udział γ w chwilach, gdy wypadło o.

Wiarygodność P(O | λ) w żadnej iteracji nie maleje; algorytm zbiega do maksimum lokalnego.

Mówiąc prościej

Gdybyśmy wiedzieli, kiedy w grze była która kostka, obliczylibyśmy parametry zwykłym liczeniem. Nie wiemy tego — ale mając jakieś oszacowanie parametrów, umiemy obliczyć, z jakim prawdopodobieństwem która kostka była gdzie. Obliczamy więc „miękkie” liczności, z nich lepsze parametry, z nich lepsze liczności… Każda runda ulepsza model.

Oczekiwaną liczbę przejść oblicza się z tych samych α i β co γ, tylko wstawia się między nie jeden krok łańcucha:

ξt(i, j) = αt(i) aij bj(Ot+1) βt+1(j) / P(O | λ)

αt(i)
rzuty do chwili t i kostka i w chwili t — lewy koniec przejścia
aij
samo przejście i → j, którego częstość szacujemy
bj(Ot+1)
rzut t + 1 musi wygenerować nowa kostka j
βt+1(j)
reszta rzutów od t + 2 dalej, wyjaśniona z kostki j — prawy koniec przejścia
P(O | λ)
dzieląc, robimy z łącznego prawdopodobieństwa „przejście i wszystkie rzuty” warunkowe „przejście, gdy znamy rzuty”

âij = ∑t=1T−1 ξt(i, j) / ∑t=1T−1 γt(i)

âij
nowe oszacowanie prawdopodobieństwa przejścia — częstość względna, jakbyśmy liczyli kreski
∑ ξt(i, j)
oczekiwana liczba przejść i → j; zamiast „przeszło się albo nie” (1 lub 0) sumujemy prawdopodobieństwa, bo stanów nie widzimy
∑ γt(i)
oczekiwana liczba wyjść ze stanu i; sumuje się tylko do T − 1, bo z ostatniego stanu żadne przejście już nie prowadzi. Dzięki temu wiersz nowej macierzy sumuje się do 1
Gdybyśmy stany widzieli, ξ i γ byłyby zerami i jedynkami, a wzór byłby zwykłym estymatorem największej wiarygodności z liczności.
powtarzaj (u nas 100 razy):
    α, β, P(O) ← algorytm w przód i wstecz z bieżącymi π, A, B
    # krok E: miękkie liczności
    dla każdego t, i:         γ[t][i]    ← α[t][i] · β[t][i] / P(O)
    dla każdego t < T, i, j:  ξ[t][i][j] ← α[t][i] · a[i][j] · b[j](O[t+1]) · β[t+1][j] / P(O)
    # krok M: częstości względne
    π[i]    ← γ[1][i]
    a[i][j] ← ∑_{t<T} ξ[t][i][j] / ∑_{t<T} γ[t][i]
    b[i](o) ← ∑_{t: O[t]=o} γ[t][i] / ∑_t γ[t][i]

Zaczęliśmy od złego oszacowania: kostki prawie się nie różnią i wymieniają się często. Z 300 rzutów algorytm Bauma–Welcha doszedł do modelu, który kostek prawie wcale nie wymienia, a fałszywej kostce daje szóstkę tylko z prawdopodobieństwem 0,39 — do wiarygodnego oszacowania 300 rzutów to za mało. Z 3 000 rzutów (innej symulacji tego samego kasyna) wyszło już bardzo blisko prawdy:

ParametrRzeczywistośćOszacowanie z 3 000 rzutów
uczciwa → fałszywa0,050,039
fałszywa → uczciwa0,100,090
szóstka na fałszywej kostce0,500,491
szóstka na uczciwej kostce0,1670,163

Dlaczego 300 rzutów nie wystarcza, pokazuje wiarygodność. W ciągu 100 iteracji na naszych 300 rzutach wzrosła z 10−231,26 do 10−227,14 — a to więcej niż 10−230,03 prawdziwego modelu kasyna. Wyuczony model wyjaśnia więc właśnie te rzuty lepiej niż prawda: dopasował się nawet do przypadkowych wahań konkretnych danych. Estymator największej wiarygodności zbliża się do prawdy dopiero wraz z przybywaniem danych.

7. Ukryte modele Markowa w praktyce

  • Rozpoznawanie mowy. Od lat osiemdziesiątych do nadejścia głębokich sieci około 2010 roku niemal wszystkie systemy opierały się na HMM: ukryte stany to części głosek, obserwacje to krótkie fragmenty widma dźwięku, a Viterbi szuka najbardziej prawdopodobnego ciągu słów.
  • Język. Rozpoznawanie części mowy: ukryty stan to część mowy, obserwacja to słowo. „Piec” może być rzeczownikiem albo czasownikiem — rozstrzyga kontekst.
  • Bioinformatyka. Szukanie genów w DNA (odcinki kodujące i niekodujące to ukryte stany) albo profilowe HMM dla rodzin białek w bazie Pfam.
  • Stany ciągłe. Gdy ukryty stan to nie „kostka A albo B”, lecz położenie i prędkość, zamiast HMM używa się filtru Kalmana — ma w programie własną część.

8. Podsumowanie

  • Ukryty model Markowa to łańcuch Markowa ukrytych stanów, z których widzimy tylko emisje; wyznaczają go π, A i B.
  • Algorytm w przód oblicza prawdopodobieństwo obserwacji w czasie O(N2T) zamiast sumy po 2300 ścieżkach; kasyno z oszustwem wyjaśnia nasze rzuty mniej więcej 2 600 razy lepiej niż uczciwa gra.
  • Algorytm Viterbiego znajduje najbardziej prawdopodobną ścieżkę ukrytych stanów programowaniem dynamicznym — w naszej symulacji poprawnie określił kostkę dla 87,3 % rzutów, krótkie odcinki jednak przeoczył.
  • Algorytm w przód i wstecz daje prawdopodobieństwo a posteriori stanu w każdej chwili.
  • Algorytm Bauma–Welcha uczy się parametrów tylko z obserwacji — ale potrzebuje dość danych: 300 rzutów nie wystarczyło, 3 000 tak.

Chcesz to wypróbować? Niech agent AI nauczy HMM z dwoma stanami na dziennych stopach zwrotu indeksu giełdowego — i sprawdź, czy sam znajdzie „spokojne” i „burzliwe” okresy na rynku.

Masz inne zdanie? Napisz na support@coden-app.com.

Agent na twoim komputerze, sterowany z telefonu