算法简介:
Viterbi 算法又叫维特比算法,其是HMM模型中在给出观测序列O,以及状态转移举证M 和 状态-观测矩阵Q之后,求解能够最佳解释序列O的状态序列S的一种动态规划算法。
具体如下图所示:

其中:
?
假设我们已知观测序列O,即图片Bottom位置的0、1序列,则图片Top位置的用红框框起来的0、1序列就是对应的S序列,即我们所说的在给定矩阵M和Q之后,能最佳地解释O的序列。
?
算法实现:
算法的实现也很简单,主要包含两步:
1.?从前到后遍历O序列,根据观测生成的方式,计算每个观测的最大生成概率。
? ? 在计算过程中,当前观测只依赖于上一个观测的结果,即马氏性。
? ? 同时记录当前状态最有可能的上一个状态。
2. 从后向前回溯,得到序列S
? ? 根据1中在每个观测点记录的上一个最佳状态进行回溯。
?
具体实现如下:
/* ========================================================
* Copyright (C) 2014 All rights reserved.
*
* filename : viterbi.c
* author : ***
* date : 2014-12-19
* info :
* ======================================================== */
#include <math.h>
#include <string.h>
#include "viterbi.h"
/* ****************************************
* @param l : input 0|1 vector
* @param o : output[smoothed] 0|1 vector
* @param n : vector length
* @param r_t : rate for stat <--> stat
* @param r_o : rate for stat <--> observ
* ****************************************/
int viterbi(int * l, int * o, int n, int r_t, int r_o){
// check params
if (!l || !o || r_t < 1 || r_o < 1){
// return fail
return -1;
}
int i = 0;
double r_t_ = log(r_t);
double r_o_ = log(r_o);
double ls0 = 0.0, ls1 = 0.0, cs0 = 0.0, cs1 = 0.0;
double ps0 = 0.0, ps1 = 0.0;
// prev_state for trace back
int (*pre_s)[2] = (int(*)[2])malloc(sizeof(int[2]) * n);
// init pre_state and output vector
memset(pre_s, 0, sizeof(int[2]) * n);
memset(o, 0, sizeof(int) * n);
// init the begin stat distribute
ls0 = (l[0] == 0 ? r_o_ : 0.0);
ls1 = (l[0] == 1 ? r_o_ : 0.0);
// pass through to calculate max prob
for (i = 1; i < n ; i++){
ps0 = ls0 + r_t_ + (l[i] == 0 ? r_o_ : 0.0);
ps1 = ls1 + (l[i] == 0 ? r_o_ : 0.0);
if (ps0 >= ps1){
cs0 = ps0; pre_s[i][0] = 0;
}
else{
cs0 = ps1; pre_s[i][0] = 1;
}
ps0 = ls0 + (l[i] == 1 ? r_o_ : 0.0);
ps1 = ls1 + r_t_ + (l[i] == 1 ? r_o_ : 0.0);
if (ps0 >= ps1){
cs1 = ps0; pre_s[i][1] = 0;
}
else{
cs1 = ps1; pre_s[i][1] = 1;
}
ls0 = cs0; ls1 = cs1;
}
// end state with the max prob
o[n - 1] = cs0 >= cs1 ? 0 : 1;
// trace back for prev state
for (i = n - 2; i >= 0; i--){
o[i] = pre_s[i + 1][o[i + 1]];
}
// free the trace pre states
free(pre_s);
pre_s = NULL;
// return success
return 0;
}
?
测试代码如下:
int main(){
int a[15] = {0,0,1,0,0,1,1,1,0,1,1,0,0,0,0};
int b[15] = {0,0,1,0,0,1,1,1,0,1,1,0,0,0,0};
int c = viterbi(a,b,15,10,5);
int i = 0;
printf("before smooth:\n");
for (; i < 15; i++){
printf("%d ", a[i]);
}
printf("\n");
printf("after smooth:\n");
for (i = 0; i < 15; i++){
printf("%d ", b[i]);
}
printf("\n");
}
?
结果如下:
before smooth: 0 0 1 0 0 1 1 1 0 1 1 0 0 0 0 after smooth: 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0
?
结果中显示原始序列中第3位置的“1”被平滑成“0”,原始序列中第9位置的“0”被平滑成"1"。
?
该算法在处理时序异常检测结果中的异常序列十分有用。
可以将平滑后的连续“1” 看成一个异常事件,而不用针对每个检测到的异常点单独处理。
原文:http://liuzhiqiangruc.iteye.com/blog/2169147