--- tags: HMM, python, Bioinformatics --- # Baum-Welch Algorithm >本篇只會介紹程式碼,原理與更多解釋請參考[這裡](https://hackmd.io/@XibGbVrGSw6szTtbuQpNBw/HJdmQtqmj) ```python= def BaumWelch(O, a, b, pi, n_iter=100): N = a.shape[0] T = len(O) # total time for i in range(n_iter): alpha = Forward(O, a, b, pi, True) beta = Backward(O, a, b) xi = np.zeros((N, N, T - 1)) # init xi # compute xi # scaled alpha and beta wont effect xi # xi: probability of being in state i at time t and state j at time t + 1 for t in range(T - 1): denominator = np.dot( np.dot(alpha[t, :].T, a) * b[:, O[t + 1]].T, beta[t + 1, :]) for i in range(N): numerator = alpha[t, i] * a[i, :] * \ b[:, O[t + 1]].T * beta[t + 1, :].T xi[i, :, t] = numerator / denominator # use xi to compute gamma # scaled alpha and beta wont effect xi # gamma : probability of being in state i at time t (fix i and t then sum all j) gamma = np.sum(xi, axis=1) # use xi and gamma to update a # sum xi and gamma over time t # new a = sum(xi) / sum(gamma) # because sum(xi, 2) is two demension and sum(gamma, axis=1) is one demension, gamma need to be expand to two demension # fix row, reshape column a = np.sum(xi, 2) / np.sum(gamma, axis=1).reshape((-1, 1)) # Add additional T'th element in gamma (because xi is T-1) # hsack: stack arrays in sequence horizontally (column wise) # use the origin gamma formula to compute the last gamma denominator = alpha[T - 1] @ beta[T - 1] gamma = np.hstack((gamma, np.expand_dims( (alpha[T - 1] * beta[T - 1]) / denominator, axis=1))) # use gamma to update b # K is the number of observation types (four in this case) K = b.shape[1] denominator = np.sum(gamma, axis=1) for l in range(K): b[:, l] = np.sum(gamma[:, O == l], axis=1) b = np.divide(b, denominator.reshape((-1, 1))) return a, b ``` ## 參數宣告與初始化 ```python= N = a.shape[0] T = len(O) # total time ``` N: 狀態數量,T: 序列長度(時間長度) ## 取得$\alpha$和$\beta$ ```python= alpha = Forward(O, a, b, pi, True) beta = Backward(O, a, b) ``` ## 計算$\xi$ ```python= for t in range(T - 1): denominator = np.dot( np.dot(alpha[t, :].T, a) * b[:, O[t + 1]].T, beta[t + 1, :]) for i in range(N): numerator = alpha[t, i] * a[i, :] * \ b[:, O[t + 1]].T * beta[t + 1, :].T xi[i, :, t] = numerator / denominator ``` * 分母: 因為矩陣型狀的關係,alpha在和a做內積前要先轉方向,之後乘的b也要轉方向(別忘了我們的a跟b都是直的,跟原本解說中畫的表格不一樣。),最後和beta內積。 * 分子: 分子也一樣要做轉置,而且要把分子分母調整成一樣的方向。 * **特別注意$\xi$只會計算到T-1,因為T沒有下一個狀態可以計算了** ## 計算$\gamma$ ```python= gamma = np.sum(xi, axis=1) ``` 這裡是利用$\xi$和$\gamma$的特殊關係,把$\xi$加起來就是了。不過要注意因為$\xi$只能算到T - 1,所以最後一個$\gamma$要另外補上。 ```python= denominator = alpha[T - 1] @ beta[T - 1] gamma = np.hstack((gamma, np.expand_dims( (alpha[T - 1] * beta[T - 1]) / denominator, axis=1))) ``` 這裡就用回$\gamma$原本的公式,因為是幫陣列多一個值,要用hstack插入。 ## 更新 a 和 b ```python= a = np.sum(xi, 2) / np.sum(gamma, axis=1).reshape((-1, 1)) ``` 套用公式,但因為$\xi$是三維矩陣,$\gamma$要用reshape來把自己的維度調整成和$\xi$一樣才可以計算。 ```python= K = b.shape[1] denominator = np.sum(gamma, axis=1) for l in range(K): b[:, l] = np.sum(gamma[:, O == l], axis=1) b = np.divide(b, denominator.reshape((-1, 1))) ``` K是序列種類,一樣套用公式計算,利用迴圈找出序列中當前所要計算的第K種結果的數量,最後除上$xi$(一樣因為維度關係要reshape)。