class_docstr: Algorithms for learning HMM parameters from training data. These include
  both supervised learning (MLE) and unsupervised learning (Baum-Welch).
  
  Creates an HMM trainer to induce an HMM with the given states and
  output symbol alphabet. A supervised and unsupervised training
  method may be used. If either of the states or symbols are not given,
  these may be derived from supervised training.
  
  :param states:  the set of state labels
  :type states:   sequence of any
  :param symbols: the set of observation symbols
  :type symbols:  sequence of any
docstr: Trains the HMM using the Baum-Welch algorithm to maximise the
  probability of the data sequence. This is a variant of the EM
  algorithm, and is unsupervised in that it doesn't need the state
  sequences for the symbols. The code is based on 'A Tutorial on Hidden
  Markov Models and Selected Applications in Speech Recognition',
  Lawrence Rabiner, IEEE, 1989.
  
  :return: the trained model
  :rtype: HiddenMarkovModelTagger
  :param unlabeled_sequences: the training data, a set of
      sequences of observations
  :type unlabeled_sequences: list
  
  kwargs may include following parameters:
  
  :param model: a HiddenMarkovModelTagger instance used to begin
      the Baum-Welch algorithm
  :param max_iterations: the maximum number of EM iterations
  :param convergence_logprob: the maximum change in log probability to
      allow convergence
docstr_tok: ["Trains","the","HMM","using","the","Baum-Welch","algorithm","to","maximise","the","probability","of","the","data","sequence",".","This","is","a","variant","of","the","EM","algorithm",",","and","is","unsupervised","in","that","it","does","n't","need","the","state","sequences","for","the","symbols",".","The","code","is","based","on","'","A","Tutorial","on","Hidden","Markov","Models","and","Selected","Applications","in","Speech","Recognition","'",",","Lawrence","Rabiner",",","IEEE",",","1989.",":","return",":","the","trained","model",":","rtype",":","HiddenMarkovModelTagger",":","param","unlabeled_sequences",":","the","training","data",",","a","set","of","sequences","of","observations",":","type","unlabeled_sequences",":","list","kwargs","may","include","following","parameters",":",":","param","model",":","a","HiddenMarkovModelTagger","instance","used","to","begin","the","Baum-Welch","algorithm",":","param","max_iterations",":","the","maximum","number","of","EM","iterations",":","param","convergence_logprob",":","the","maximum","change","in","log","probability","to","allow","convergence"]
code: def train_unsupervised(
      self, unlabeled_sequences, update_outputs=True, **kwargs
  ):
      """
      Trains the HMM using the Baum-Welch algorithm to maximise the
      probability of the data sequence. This is a variant of the EM
      algorithm, and is unsupervised in that it doesn't need the state
      sequences for the symbols. The code is based on 'A Tutorial on Hidden
      Markov Models and Selected Applications in Speech Recognition',
      Lawrence Rabiner, IEEE, 1989.
  
      :return: the trained model
      :rtype: HiddenMarkovModelTagger
      :param unlabeled_sequences: the training data, a set of
          sequences of observations
      :type unlabeled_sequences: list
  
      kwargs may include following parameters:
  
      :param model: a HiddenMarkovModelTagger instance used to begin
          the Baum-Welch algorithm
      :param max_iterations: the maximum number of EM iterations
      :param convergence_logprob: the maximum change in log probability to
          allow convergence
      """
  
      # create a uniform HMM, which will be iteratively refined, unless
      # given an existing model
      model = kwargs.get("model")
      if not model:
          priors = RandomProbDist(self._states)
          transitions = DictionaryConditionalProbDist(
              {
                  state: RandomProbDist(self._states)
                  for state in self._states
              }
          )
          outputs = DictionaryConditionalProbDist(
              {
                  state: RandomProbDist(self._symbols)
                  for state in self._states
              }
          )
          model = HiddenMarkovModelTagger(
              self._symbols, self._states, transitions, outputs, priors
          )
  
      self._states = model._states
      self._symbols = model._symbols
  
      N = len(self._states)
      M = len(self._symbols)
      symbol_numbers = {sym: i for i, sym in enumerate(self._symbols)}
  
      # update model prob dists so that they can be modified
      # model._priors = MutableProbDist(model._priors, self._states)
  
      model._transitions = DictionaryConditionalProbDist(
          {
              s: MutableProbDist(model._transitions[s], self._states)
              for s in self._states
          }
      )
  
      if update_outputs:
          model._outputs = DictionaryConditionalProbDist(
              {
                  s: MutableProbDist(model._outputs[s], self._symbols)
                  for s in self._states
              }
          )
  
      model.reset_cache()
  
      # iterate until convergence
      converged = False
      last_logprob = None
      iteration = 0
      max_iterations = kwargs.get("max_iterations", 1000)
      epsilon = kwargs.get("convergence_logprob", 1e-6)
  
      while not converged and iteration < max_iterations:
          A_numer = _ninf_array((N, N))
          B_numer = _ninf_array((N, M))
          A_denom = _ninf_array(N)
          B_denom = _ninf_array(N)
  
          logprob = 0
          for sequence in unlabeled_sequences:
              sequence = list(sequence)
              if not sequence:
                  continue
  
              (
                  lpk,
                  seq_A_numer,
                  seq_A_denom,
                  seq_B_numer,
                  seq_B_denom,
              ) = self._baum_welch_step(sequence, model, symbol_numbers)
  
              # add these sums to the global A and B values
              for i in range(N):
                  A_numer[i] = np.logaddexp2(
                      A_numer[i], seq_A_numer[i] - lpk
                  )
                  B_numer[i] = np.logaddexp2(
                      B_numer[i], seq_B_numer[i] - lpk
                  )
  
              A_denom = np.logaddexp2(A_denom, seq_A_denom - lpk)
              B_denom = np.logaddexp2(B_denom, seq_B_denom - lpk)
  
              logprob += lpk
  
          # use the calculated values to update the transition and output
          # probability values
          for i in range(N):
              logprob_Ai = A_numer[i] - A_denom[i]
              logprob_Bi = B_numer[i] - B_denom[i]
  
              # We should normalize all probabilities (see p.391 Huang et al)
              # Let sum(P) be K.
              # We can divide each Pi by K to make sum(P) == 1.
              #   Pi' = Pi/K
              #   log2(Pi') = log2(Pi) - log2(K)
              logprob_Ai -= logsumexp2(logprob_Ai)
              logprob_Bi -= logsumexp2(logprob_Bi)
  
              # update output and transition probabilities
              si = self._states[i]
  
              for j in range(N):
                  sj = self._states[j]
                  model._transitions[si].update(sj, logprob_Ai[j])
  
              if update_outputs:
                  for k in range(M):
                      ok = self._symbols[k]
                      model._outputs[si].update(ok, logprob_Bi[k])
  
              # Rabiner says the priors don't need to be updated. I don't
              # believe him. FIXME
  
          # test for convergence
          if iteration > 0 and abs(logprob - last_logprob) < epsilon:
              converged = True
  
          print("iteration", iteration, "logprob", logprob)
          iteration += 1
          last_logprob = logprob
  
      return model
  
imports: import itertools
  import re
  from nltk.metrics import accuracy
  from nltk.probability import ConditionalFreqDist, ConditionalProbDist, DictionaryConditionalProbDist, DictionaryProbDist, FreqDist, LidstoneProbDist, MLEProbDist, MutableProbDist, RandomProbDist
  from nltk.tag.api import TaggerI
  from nltk.util import LazyMap, unique_list
label_desc: Use image node_id 4 for calling the HiddenMarkovModelTrainer obj's underlying member method code with example usage: obj.train_unsupervised(unlabeled_sequences, update_outputs) and returns: model