【问题标题】:Python Implementation of Viterbi Algorithm维特比算法的Python实现
【发布时间】:2012-04-01 13:34:05
【问题描述】:

我正在做一个 Python 项目,我想在其中使用 Viterbi 算法。有人知道维特比算法的完整 Python 实现吗?维基百科上的正确性似乎在讨论页上存在问题。有没有人指点一下?

【问题讨论】:

    标签: python viterbi


    【解决方案1】:

    这是我的。它直接来自psuedocode implemenation from wikipedia。它使用numpy 来表达他们的ndarray,但它是纯python3 实现。

    import numpy as np
    
    def viterbi(y, A, B, Pi=None):
        """
        Return the MAP estimate of state trajectory of Hidden Markov Model.
    
        Parameters
        ----------
        y : array (T,)
            Observation state sequence. int dtype.
        A : array (K, K)
            State transition matrix. See HiddenMarkovModel.state_transition  for
            details.
        B : array (K, M)
            Emission matrix. See HiddenMarkovModel.emission for details.
        Pi: optional, (K,)
            Initial state probabilities: Pi[i] is the probability x[0] == i. If
            None, uniform initial distribution is assumed (Pi[:] == 1/K).
    
        Returns
        -------
        x : array (T,)
            Maximum a posteriori probability estimate of hidden state trajectory,
            conditioned on observation sequence y under the model parameters A, B,
            Pi.
        T1: array (K, T)
            the probability of the most likely path so far
        T2: array (K, T)
            the x_j-1 of the most likely path so far
        """
        # Cardinality of the state space
        K = A.shape[0]
        # Initialize the priors with default (uniform dist) if not given by caller
        Pi = Pi if Pi is not None else np.full(K, 1 / K)
        T = len(y)
        T1 = np.empty((K, T), 'd')
        T2 = np.empty((K, T), 'B')
    
        # Initilaize the tracking tables from first observation
        T1[:, 0] = Pi * B[:, y[0]]
        T2[:, 0] = 0
    
        # Iterate throught the observations updating the tracking tables
        for i in range(1, T):
            T1[:, i] = np.max(T1[:, i - 1] * A.T * B[np.newaxis, :, y[i]].T, 1)
            T2[:, i] = np.argmax(T1[:, i - 1] * A.T, 1)
    
        # Build the output, optimal model trajectory
        x = np.empty(T, 'B')
        x[-1] = np.argmax(T1[:, T - 1])
        for i in reversed(range(1, T)):
            x[i - 1] = T2[x[i], i]
    
        return x, T1, T2
    

    【讨论】:

    • 在包含 4 个可能类别的 12 个值的 np.array 上运行此算法时出现错误
    • 您遇到什么错误?以及您是如何尝试调用该函数的?
    【解决方案2】:

    我已经修改了@Rhubarb 对于边际概率已知的情况的答案(例如通过计算前向后向算法)。

    def viterbi (transition_probabilities, conditional_probabilities):
        # Initialise everything
        num_samples = conditional_probabilities.shape[1]
        num_states = transition_probabilities.shape[0] # number of states
    
        c = np.zeros(num_samples) #scale factors (necessary to prevent underflow)
        viterbi = np.zeros((num_states,num_samples)) # initialise viterbi table
        best_path_table = np.zeros((num_states,num_samples)) # initialise the best path table
        best_path = np.zeros(num_samples).astype(np.int32) # this will be your output
    
        # B- appoint initial values for viterbi and best path (bp) tables - Eq (32a-32b)
        viterbi[:,0] = conditional_probabilities[:,0]
        c[0] = 1.0/np.sum(viterbi[:,0])
        viterbi[:,0] = c[0] * viterbi[:,0] # apply the scaling factor
    
        # C- Do the iterations for viterbi and psi for time>0 until T
        for t in range(1, num_samples): # loop through time
            for s in range (0,num_states): # loop through the states @(t-1)
                trans_p = viterbi[:, t-1] * transition_probabilities[:,s] # transition probs of each state transitioning
                best_path_table[s,t], viterbi[s,t] = max(enumerate(trans_p), key=operator.itemgetter(1))
                viterbi[s,t] = viterbi[s,t] * conditional_probabilities[s][t]
    
            c[t] = 1.0/np.sum(viterbi[:,t]) # scaling factor
            viterbi[:,t] = c[t] * viterbi[:,t]
    
        ## D - Back-tracking
        best_path[num_samples-1] =  viterbi[:,num_samples-1].argmax() # last state
        for t in range(num_samples-1,0,-1): # states of (last-1)th to 0th time step
            best_path[t-1] = best_path_table[best_path[t],t]
        return best_path
    

    【讨论】:

      【解决方案3】:

      这是一个老问题,但其他答案都不是我所需要的相当,因为我的应用程序没有观察到的特定状态。

      继 @Rhubarb 之后,我还重新实现了 Kevin Murphey 的 Matlab implementation(参见 viterbi_path.m),但我让它更接近原版。我还包含了一个简单的测试用例。

      import numpy as np
      
      
      def viterbi_path(prior, transmat, obslik, scaled=True, ret_loglik=False):
          '''Finds the most-probable (Viterbi) path through the HMM state trellis
          Notation:
              Z[t] := Observation at time t
              Q[t] := Hidden state at time t
          Inputs:
              prior: np.array(num_hid)
                  prior[i] := Pr(Q[0] == i)
              transmat: np.ndarray((num_hid,num_hid))
                  transmat[i,j] := Pr(Q[t+1] == j | Q[t] == i)
              obslik: np.ndarray((num_hid,num_obs))
                  obslik[i,t] := Pr(Z[t] | Q[t] == i)
              scaled: bool
                  whether or not to normalize the probability trellis along the way
                  doing so prevents underflow by repeated multiplications of probabilities
              ret_loglik: bool
                  whether or not to return the log-likelihood of the best path
          Outputs:
              path: np.array(num_obs)
                  path[t] := Q[t]
          '''
          num_hid = obslik.shape[0] # number of hidden states
          num_obs = obslik.shape[1] # number of observations (not observation *states*)
      
          # trellis_prob[i,t] := Pr((best sequence of length t-1 goes to state i), Z[1:(t+1)])
          trellis_prob = np.zeros((num_hid,num_obs))
          # trellis_state[i,t] := best predecessor state given that we ended up in state i at t
          trellis_state = np.zeros((num_hid,num_obs), dtype=int) # int because its elements will be used as indicies
          path = np.zeros(num_obs, dtype=int) # int because its elements will be used as indicies
      
          trellis_prob[:,0] = prior * obslik[:,0] # element-wise mult
          if scaled:
              scale = np.ones(num_obs) # only instantiated if necessary to save memory
              scale[0] = 1.0 / np.sum(trellis_prob[:,0])
              trellis_prob[:,0] *= scale[0]
      
          trellis_state[:,0] = 0 # arbitrary value since t == 0 has no predecessor
          for t in xrange(1, num_obs):
              for j in xrange(num_hid):
                  trans_probs = trellis_prob[:,t-1] * transmat[:,j] # element-wise mult
                  trellis_state[j,t] = trans_probs.argmax()
                  trellis_prob[j,t] = trans_probs[trellis_state[j,t]] # max of trans_probs
                  trellis_prob[j,t] *= obslik[j,t]
              if scaled:
                  scale[t] = 1.0 / np.sum(trellis_prob[:,t])
                  trellis_prob[:,t] *= scale[t]
      
          path[-1] = trellis_prob[:,-1].argmax()
          for t in range(num_obs-2, -1, -1):
              path[t] = trellis_state[(path[t+1]), t+1]
      
          if not ret_loglik:
              return path
          else:
              if scaled:
                  loglik = -np.sum(np.log(scale))
              else:
                  p = trellis_prob[path[-1],-1]
                  loglik = np.log(p)
              return path, loglik
      
      
      if __name__=='__main__':
          # Assume there are 3 observation states, 2 hidden states, and 5 observations
          priors = np.array([0.5, 0.5])
          transmat = np.array([
              [0.75, 0.25],
              [0.32, 0.68]])
          emmat = np.array([
              [0.8, 0.1, 0.1],
              [0.1, 0.2, 0.7]])
          observations = np.array([0, 1, 2, 1, 0], dtype=int)
          obslik = np.array([emmat[:,z] for z in observations]).T
          print viterbi_path(priors, transmat, obslik)                                #=> [0 1 1 1 0]
          print viterbi_path(priors, transmat, obslik, scaled=False)                  #=> [0 1 1 1 0]
          print viterbi_path(priors, transmat, obslik, ret_loglik=True)               #=> (array([0, 1, 1, 1, 0]), -7.776472586614755)
          print viterbi_path(priors, transmat, obslik, scaled=False, ret_loglik=True) #=> (array([0, 1, 1, 1, 0]), -8.0120386579275227)
      

      请注意,此实现不直接使用发射概率,而是使用变量obslik。通常,emissions[i,j] := Pr(observed_state == j | hidden_state == i) 用于特定的观察状态i,使得emissions.shape == (num_hidden_states, num_obs_states)

      然而,给定一个序列observations[t] := observation at time t,维特比算法所需要的只是观察每个隐藏状态的可能性。因此,obslik[i,t] := Pr(observations[t] | hidden_state == i)。观察状态的实际值不是必需的。

      【讨论】:

        【解决方案4】:

        我刚刚更正了Viterbi in Wikipedia 的伪实现。从最初的(不正确的)版本开始,我花了一段时间才弄清楚我哪里出了问题,但我终于成功了,部分归功于 Kevin Murphy 在MatLab HMM toolbox 中实现了viterbi_path.m

        在带有变量的 HMM 对象的上下文中:

        hmm = HMM()
        hmm.priors = np.array([0.5, 0.5]) # pi = prior probs
        hmm.transition = np.array([[0.75, 0.25], # A = transition probs. / 2 states
                                   [0.32, 0.68]])
        hmm.emission = np.array([[0.8, 0.1, 0.1], # B = emission (observation) probs. / 3 obs modes
                                 [0.1, 0.2, 0.7]])
        

        运行 Viterbi(最佳路径)算法的 Python 函数如下:

        def viterbi (self,observations):
            """Return the best path, given an HMM model and a sequence of observations"""
            # A - initialise stuff
            nSamples = len(observations[0])
            nStates = self.transition.shape[0] # number of states
            c = np.zeros(nSamples) #scale factors (necessary to prevent underflow)
            viterbi = np.zeros((nStates,nSamples)) # initialise viterbi table
            psi = np.zeros((nStates,nSamples)) # initialise the best path table
            best_path = np.zeros(nSamples); # this will be your output
        
            # B- appoint initial values for viterbi and best path (bp) tables - Eq (32a-32b)
            viterbi[:,0] = self.priors.T * self.emission[:,observations(0)]
            c[0] = 1.0/np.sum(viterbi[:,0])
            viterbi[:,0] = c[0] * viterbi[:,0] # apply the scaling factor
            psi[0] = 0;
        
            # C- Do the iterations for viterbi and psi for time>0 until T
            for t in range(1,nSamples): # loop through time
                for s in range (0,nStates): # loop through the states @(t-1)
                    trans_p = viterbi[:,t-1] * self.transition[:,s]
                    psi[s,t], viterbi[s,t] = max(enumerate(trans_p), key=operator.itemgetter(1))
                    viterbi[s,t] = viterbi[s,t]*self.emission[s,observations(t)]
        
                c[t] = 1.0/np.sum(viterbi[:,t]) # scaling factor
                viterbi[:,t] = c[t] * viterbi[:,t]
        
            # D - Back-tracking
            best_path[nSamples-1] =  viterbi[:,nSamples-1].argmax() # last state
            for t in range(nSamples-1,0,-1): # states of (last-1)th to 0th time step
                best_path[t-1] = psi[best_path[t],t]
        
            return best_path
        

        【讨论】:

        • jahrulesoverall 的评论在答案中发布不正确,observations(0) 是错误的吧?应该是观察[0]和观察[t]?
        • 我不明白您在执行psi[best_path[t],t] 时如何不会出错,因为best_path 是浮点类型,您只能使用整数进行索引?
        • @MikeVella 我添加了:bp = np.zeros(nSamples).astype(int)
        【解决方案5】:

        我在Artificial Intelligence: A Modern Approach 的示例存储库中找到了以下code。您正在寻找类似的东西吗?

        def viterbi_segment(text, P):
            """Find the best segmentation of the string of characters, given the
            UnigramTextModel P."""
            # best[i] = best probability for text[0:i]
            # words[i] = best word ending at position i
            n = len(text)
            words = [''] + list(text)
            best = [1.0] + [0.0] * n
            ## Fill in the vectors best, words via dynamic programming
            for i in range(n+1):
                for j in range(0, i):
                    w = text[j:i]
                    if P[w] * best[i - len(w)] >= best[i]:
                        best[i] = P[w] * best[i - len(w)]
                        words[i] = w
            ## Now recover the sequence of best words
            sequence = []; i = len(words)-1
            while i > 0:
                sequence[0:0] = [words[i]]
                i = i - len(words[i])
            ## Return sequence of best words and overall probability
            return sequence, best[-1]
        

        【讨论】:

          【解决方案6】:

          嗯,我可以发布我的。虽然它并不漂亮,如果您需要澄清,请告诉我。我最近专门为词性标注写了这篇文章。

          class Trellis:
              trell = []
              def __init__(self, hmm, words):
                  self.trell = []
                  temp = {}
                  for label in hmm.labels:
                     temp[label] = [0,None]
                  for word in words:
                      self.trell.append([word,copy.deepcopy(temp)])
                  self.fill_in(hmm)
          
              def fill_in(self,hmm):
                  for i in range(len(self.trell)):
                      for token in self.trell[i][1]:
                          word = self.trell[i][0]
                          if i == 0:
                              self.trell[i][1][token][0] = hmm.e(token,word)
                          else:
                              max = None
                              guess = None
                              c = None
                              for k in self.trell[i-1][1]:
                                  c = self.trell[i-1][1][k][0] + hmm.t(k,token)
                                  if max == None or c > max:
                                      max = c
                                      guess = k
                              max += hmm.e(token,word)
                              self.trell[i][1][token][0] = max
                              self.trell[i][1][token][1] = guess
          
              def return_max(self):
                  tokens = []
                  token = None
                  for i in range(len(self.trell)-1,-1,-1):
                      if token == None:
                          max = None
                          guess = None
                          for k in self.trell[i][1]:
                              if max == None or self.trell[i][1][k][0] > max:
                                  max = self.trell[i][1][k][0]
                                  token = self.trell[i][1][k][1]
                                  guess = k
                          tokens.append(guess)
                      else:
                          tokens.append(token)
                          token = self.trell[i][1][token][1]
                  tokens.reverse()
                  return tokens
          

          【讨论】:

          • 我有点困惑,为什么这比 NLTK 帖子高,他们的实现不正确吗? OP 你觉得我完全没有文档的代码令人满意吗?
          • 这可能是因为它比 NLTK 代码更容易破解以适应自己的需求。
          • @placeybordeaux 这个函数hmm.t(k,token) 有什么作用?我试图复制代码,但我不知道hmm.t(k,token) 做了什么。你能举个例子吗?
          • @Mohammed hmm 在这里可以追溯到很远,但我很确定hmm.t(k, token) 是从状态 k 转换到令牌的概率,hmm.e(token, word) 是在给定令牌时发出单词的概率。查看 NLTK 代码也可能会有所帮助。老实说,我的帖子不是特别漂亮或可读性。
          猜你喜欢
          • 1970-01-01
          • 2011-12-22
          • 2013-02-25
          • 2011-05-02
          • 2014-06-27
          • 2022-06-15
          • 2018-05-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多