1. 程式人生 > >HMM學習最佳範例五:前向演算法5

HMM學習最佳範例五:前向演算法5

  在HMM這個翻譯系列的原文中,作者舉了一個前向演算法的互動例子,這也是這個系列中比較出彩的地方,但是,在具體執行這個例子的時候,卻發現其似乎有點問題。
  先說一下如何使用這個互動例子,執行時需要瀏覽器支援java,我用的是firefox。首先在Set按鈕前面的對話方塊裡上觀察序列,如“Dry,Damp, Soggy” 或“Dry Damp Soggy”,觀察符號間用逗號或空格隔開;然後再點選Set按鈕,這樣就初始化了觀察矩陣;如果想得到一個總的結果,即Pr(觀察序列|隱馬爾科夫模型),就點旁邊的Run按鈕;如果想一步一步觀察計算過程,即每個節點的區域性概率,就單擊旁邊的Step按鈕。
  原文互動例子(即天氣這個例子)中所定義的已知隱馬爾科夫模型如下:
  1、隱藏狀態 (天氣):Sunny,Cloudy,Rainy;
  2、觀察狀態(海藻溼度):Dry,Dryish,Damp,Soggy;
  3、初始狀態概率: Sunny(0.63), Cloudy(0.17), Rainy(0.20);
  4、狀態轉移矩陣:

             weather today
             Sunny Cloudy Rainy
     weather Sunny 0.500 0.375 0.125
    yesterday Cloudy 0.250 0.125 0.625
          Rainy  0.250 0.375 0.375

  5、混淆矩陣:

            observed states
           Dry Dryish Damp Soggy
         Sunny 0.60 0.20 0.15 0.05
    hidden  Cloudy 0.25 0.25 0.25 0.25
    states  Rainy 0.05 0.10 0.35 0.50

  為了UMDHMM也能執行這個例子,我們將上述天氣例子中的隱馬爾科夫模型轉化為如下的UMDHMM可讀的HMM檔案weather.hmm:
——————————————————————–
    M= 4
    N= 3 
    A:
    0.500 0.375 0.125
    0.250 0.125 0.625
    0.250 0.375 0.375
    B:
    0.60 0.20 0.15 0.05
    0.25 0.25 0.25 0.25
    0.05 0.10 0.35 0.50
    pi:
    0.63 0.17 0.20
——————————————————————–
  在執行例子之前,如果讀者也想觀察每一步的運算結果,可以將umdhmm-v1.02目錄下forward.c中的void Forward(…)函式替換如下:
——————————————————————–
void Forward(HMM *phmm, int T, int *O, double **alpha, double *pprob)
{
  int i, j; /* state indices */
  int t; /* time index */
  double sum; /* partial sum */
  
  /* 1. Initialization */
  for (i = 1; i <= phmm->N; i++)
  {
    alpha[1][i] = phmm->pi[i]* phmm->B[i][O[1]];
    printf( “a[1][%d] = pi[%d] * b[%d][%d] = %f * %f = %f\\n”,i, i, i, O[i], phmm->pi[i], phmm->B[i][O[1]], alpha[1][i] );
  }
  
  /* 2. Induction */
  for (t = 1; t < T; t++)
  {
    for (j = 1; j <= phmm->N; j++)
    {
      sum = 0.0;
      for (i = 1; i <= phmm->N; i++)
      {
        sum += alpha[t][i]* (phmm->A[i][j]);
        printf( “a[%d][%d] * A[%d][%d] = %f * %f = %f\\n”, t, i, i, j, alpha[t][i], phmm->A[i][j], alpha[t][i]* (phmm->A[i][j]));
        printf( “sum = %f\\n”, sum );
      }
      alpha[t+1][j] = sum*(phmm->B[j][O[t+1]]);
      printf( “a[%d][%d] = sum * b[%d][%d]] = %f * %f = %f\\n”,t+1, j, j, O[t+1], sum, phmm->B[j][O[t+1]], alpha[t+1][j] );
    }
  }

  /* 3. Termination */
  *pprob = 0.0;
  for (i = 1; i <= phmm->N; i++)
  {
    *pprob += alpha[T][i];
    printf( “alpha[%d][%d] = %f\\n”, T, i, alpha[T][i] );
    printf( “pprob = %f\\n”, *pprob );
  }
}
——————————————————————–
  替換完畢之後,重新“make clean”,“make all”,這樣新的testfor可執行程式就可以輸出前向演算法每一步的計算結果。
  現在我們就用testfor來執行原文中預設給出的觀察序列“Dry,Damp,Soggy”,其所對應的UMDHMM可讀的觀察序列檔案test1.seq:
——————————————————————–
    T=3
    1 3 4
——————————————————————–
  好了,一切準備工作就緒,現在就輸入如下命令:
    testfor weather.hmm test1.seq > result1
  result1就包含了所有的結果細節:
——————————————————————–
Forward without scaling
a[1][1] = pi[1] * b[1][1] = 0.630000 * 0.600000 = 0.378000
a[1][2] = pi[2] * b[2][3] = 0.170000 * 0.250000 = 0.042500
a[1][3] = pi[3] * b[3][4] = 0.200000 * 0.050000 = 0.010000

pprob = 0.026901
log prob(O| model) = -3.615577E+00
prob(O| model) = 0.026901


——————————————————————–
  黑體部分是最終的觀察序列的概率結果,即本例中的Pr(觀察序列|HMM) = 0.026901。
  但是,在原文中點Run按鈕後,結果卻是:Probability of this model = 0.027386915。
  這其中的差別到底在哪裡?我們來仔細觀察一下中間執行過程:
  在初始化亦t=1時刻的區域性概率計算兩個是一致的,沒有問題。但是,t=2時,在隱藏狀態“Sunny”的區域性概率是不一致的。英文原文給出的例子的執行結果是:
  Alpha = (((0.37800002*0.5) + (0.0425*0.375) + (0.010000001*0.125)) * 0.15) = 0.03092813
  而UMDHMM給出的結果是:
——————————————————————–
  a[1][1] * A[1][1] = 0.378000 * 0.500000 = 0.189000
  sum = 0.189000
  a[1][2] * A[2][1] = 0.042500 * 0.250000 = 0.010625
  sum = 0.199625
  a[1][3] * A[3][1] = 0.010000 * 0.250000 = 0.002500
  sum = 0.202125
  a[2][1] = sum * b[1][3]] = 0.202125 * 0.150000 = 0.030319
——————————————————————–
  區別就在於狀態轉移概率的選擇上,原文選擇的是狀態轉移矩陣中的第一行,而UMDHMM選擇的則是狀態轉移矩陣中的第一列。如果從原文給出的狀態轉移矩陣來看,第一行代表的是從前一時刻的狀態“Sunny”分別到當前時刻的狀態“Sunny”,“Cloudy”,“Rainy”的概率;而第一列代表的是從前一時刻的狀態“Sunny”,“Cloudy”,“Rainy”分別到當前時刻狀態“Sunny”的概率。這樣看來似乎原文的計算過程有誤,讀者不妨多試幾個例子看看,前向演算法這一章就到此為止了。