/// <summary> /// 特化版的求解HMM模型 /// </summary> /// <param name="vertices"></param> /// <param name="transMaxtrix">转移矩阵</param> public static void Compute(List <Vertex> vertices, TransformMatrixDictionary <Nature> transMaxtrix) { //int length = vertices.Count - 1; // 去掉首节点之后的数量(包括了尾节点) double[][] cost = new double[2][]; // 滚动数组,用于保存最近两个节点的各状态对应的概率值 var start = vertices[0]; // vertices包含了首尾节点,故 start 是辅助首节点 Nature pre = start.attr.natures[0]; // start 节点的nature 肯定是 Nature.begin // 第二个节点计算 Vertex item = vertices[1]; cost[0] = new double[item.attr.natures.Length]; for (int i = 0; i < item.attr.natures.Length; i++) // 遍历第二个节点的所有可能的状态 { var cur = item.attr.natures[i]; cost[0][i] = transMaxtrix.Trans_Prob[(int)pre][(int)cur] - // 从首节点状态转移到第二个节点状态的概率 乘以 第二个节点状态下观测到第二个节点值的发射概率(取对数并取相反数) Math.Log((item.attr.freqs[i] + 1e-8) / transMaxtrix.GetFreq(cur)); } Vertex preItem = item; Nature[] preTagSet = item.attr.natures; for (int i = 2; i < vertices.Count; i++) { int index_i_1 = i % 2; // i = even 时 为 0,表示上一个节点各状态的概率 int index_i = 1 - index_i_1; // i = even 时 为 1,表示当前节点各状态的概率 item = vertices[i]; cost[index_i] = new double[item.attr.natures.Length]; // 用于保存当前节点在各状态下的概率 double perfect_cost_line = double.MaxValue; // 保存 截止到当前时刻 i 为止,已确定的最优路径的概率 var curTagSet = item.attr.natures; for (int k = 0; k < curTagSet.Length; k++) // 遍历当前节点的所有可能状态(标签) { var cur = curTagSet[k]; // 当前状态 for (int n = 0; n < preTagSet.Length; n++) // 遍历上一个节点的所有可能状态 { var p = preTagSet[n]; double now = cost[index_i_1][n] + transMaxtrix.Trans_Prob[(int)p][(int)cur] - // 上一节点状态n的概率 乘以 从上一节点转移到当前节点状态的概率 乘以 当前节点状态的发射概率 Math.Log((item.attr.freqs[k] + 1e-8) / transMaxtrix.GetFreq(cur)); if (now < cost[index_i][k]) { cost[index_i][k] = now; if (now < perfect_cost_line) { perfect_cost_line = now; pre = p; } } } } preItem.ConfirmNature(pre); //! 在当前时刻 i 为止确定的最优路径,并确定这是来自上一节点的哪个状态,从而确定上个节点的状态,然而这种方法确定的每个节点的状态是否属于同一条路径? preTagSet = curTagSet; preItem = item; } }
/// <summary> /// 仅仅利用了转移矩阵的 Viterbi 算法 /// </summary> /// <typeparam name="E">标签(状态)类型</typeparam> /// <param name="roleTagList">观测序列</param> /// <param name="transMatrix">转移矩阵</param> /// <returns></returns> public static List <E> ComputeSimply <E>(List <TagFreqItem <E> > roleTagList, TransformMatrixDictionary <E> transMatrix) where E : IConvertible { var start = roleTagList[0]; // 首节点 E pre = start.labelMap.First().Key; // 首节点标签 E perfect_tag = pre; var list = new List <E>() { pre }; for (int i = 1; i < roleTagList.Count; i++) { double perfect_cost = double.MaxValue; var item = roleTagList[i]; foreach (var cur in item.labelMap.Keys) { double now = transMatrix.Trans_Prob[Convert.ToInt32(pre)][Convert.ToInt32(cur)] - Math.Log((item.GetFreqOrDefault(cur) + 1e-8) / transMatrix.GetFreq(cur)); if (perfect_cost > now) { perfect_cost = now; perfect_tag = cur; } } pre = perfect_tag; list.Add(pre); } return(list); }
/// <summary> /// 标准版的Viterbi算法,查询准确率高,效率稍低 /// </summary> /// <typeparam name="E"></typeparam> /// <param name="roleTagList">观测序列</param> /// <param name="transMatrix">转移矩阵</param> /// <returns></returns> public static List <E> Compute <E>(List <TagFreqItem <E> > roleTagList, TransformMatrixDictionary <E> transMatrix) where E : IConvertible { var list = new List <E>(roleTagList.Count); // 标签序列 var cost = new double[2][]; // 滚动数组,作用与上一个方法类似 var start = roleTagList[0]; // 首节点,这是一个辅助节点(另外一个辅助节点是尾节点) E pre = start.labelMap.First().Key; // 首节点的 标签是确定的 list.Add(pre); // 第二个节点的标签也是可以很容易算出来的 var item = roleTagList[1]; cost[0] = new double[item.labelMap.Count]; // 第二个节点所有可能的标签分别对应的概率 int j = 0; foreach (var p in item.labelMap) { cost[0][j] = transMatrix.Trans_Prob[Convert.ToInt32(pre)][Convert.ToInt32(p.Key)] - // transMatrix中所有概率均作了取对数并取相反数处理 Math.Log((item.GetFreqOrDefault(p.Key) + 1e-8) / transMatrix.GetFreq(p.Key)); // 状态转移概率乘以发射概率(频次相除,做了为 0 处理) j++; } var preTagSet = item.labelMap.Keys; // for (int i = 2; i < roleTagList.Count; i++) { int index_i_1 = i % 2; int index_i = 1 - index_i_1; item = roleTagList[i]; cost[index_i] = new double[item.labelMap.Count]; double perfect_cost_line = double.MaxValue; int k = 0; var curTagSet = item.labelMap.Keys; foreach (var cur in curTagSet) // 遍历当前节点的所有可能标签 { cost[index_i][k] = double.MaxValue; j = 0; foreach (var p in preTagSet) // 遍历前一节点的所有标签 { double now = cost[index_i_1][j] // 上一节点某个状态的概率 + transMatrix.Trans_Prob[Convert.ToInt32(p)][Convert.ToInt32(cur)] // 上一节点那个状态转移到此节点当前状态的概率 - Math.Log((item.GetFreqOrDefault(cur) + 1e-8) / transMatrix.GetFreq(cur)); // 此节点当前状态的发射概率 j++; if (now < cost[index_i][k]) // 对此节点的当前状态来说,如果发现来自上一节点某个状态的路径对应概率更高(取了相反数,即更小) { cost[index_i][k] = now; // 记录此节点当前状态的最优路径的概率 if (now < perfect_cost_line) { perfect_cost_line = now; // 记录此节点所有状态中的最优路径的概率 pre = p; // 记录到达此节点时最优路径中上个节点的标签 } } } k++; } list.Add(pre); //! 在当前时刻 i 为止确定的最优路径,并确定这是来自上一节点的哪个状态,从而确定上个节点的状态,然而这种方法确定的每个节点的状态是否属于同一条路径? preTagSet = curTagSet; } list.Add(list[0]); // 尾节点(##末##)对应的标签 return(list); }