class_docstr: Translation model that keeps track of vacant positions in the target
  sentence to decide where to place translated words
  
  >>> bitext = []
  >>> bitext.append(AlignedSent(['klein', 'ist', 'das', 'haus'], ['the', 'house', 'is', 'small']))
  >>> bitext.append(AlignedSent(['das', 'haus', 'war', 'ja', 'groß'], ['the', 'house', 'was', 'big']))
  >>> bitext.append(AlignedSent(['das', 'buch', 'ist', 'ja', 'klein'], ['the', 'book', 'is', 'small']))
  >>> bitext.append(AlignedSent(['ein', 'haus', 'ist', 'klein'], ['a', 'house', 'is', 'small']))
  >>> bitext.append(AlignedSent(['das', 'haus'], ['the', 'house']))
  >>> bitext.append(AlignedSent(['das', 'buch'], ['the', 'book']))
  >>> bitext.append(AlignedSent(['ein', 'buch'], ['a', 'book']))
  >>> bitext.append(AlignedSent(['ich', 'fasse', 'das', 'buch', 'zusammen'], ['i', 'summarize', 'the', 'book']))
  >>> bitext.append(AlignedSent(['fasse', 'zusammen'], ['summarize']))
  >>> src_classes = {'the': 0, 'a': 0, 'small': 1, 'big': 1, 'house': 2, 'book': 2, 'is': 3, 'was': 3, 'i': 4, 'summarize': 5 }
  >>> trg_classes = {'das': 0, 'ein': 0, 'haus': 1, 'buch': 1, 'klein': 2, 'groß': 2, 'ist': 3, 'war': 3, 'ja': 4, 'ich': 5, 'fasse': 6, 'zusammen': 6 }
  
  >>> ibm5 = IBMModel5(bitext, 5, src_classes, trg_classes)
  
  >>> print(round(ibm5.head_vacancy_table[1][1][1], 3))
  1.0
  >>> print(round(ibm5.head_vacancy_table[2][1][1], 3))
  0.0
  >>> print(round(ibm5.non_head_vacancy_table[3][3][6], 3))
  1.0
  
  >>> print(round(ibm5.fertility_table[2]['summarize'], 3))
  1.0
  >>> print(round(ibm5.fertility_table[1]['book'], 3))
  1.0
  
  >>> print(round(ibm5.p1, 3))
  0.033
  
  >>> test_sentence = bitext[2]
  >>> test_sentence.words
  ['das', 'buch', 'ist', 'ja', 'klein']
  >>> test_sentence.mots
  ['the', 'book', 'is', 'small']
  >>> test_sentence.alignment
  Alignment([(0, 0), (1, 1), (2, 2), (3, None), (4, 3)])
docstr: Starting from the alignment in ``alignment_info``, look at
  neighboring alignments iteratively for the best one, according
  to Model 4
  
  Note that Model 4 scoring is used instead of Model 5 because the
  latter is too expensive to compute.
  
  There is no guarantee that the best alignment in the alignment
  space will be found, because the algorithm might be stuck in a
  local maximum.
  
  :param j_pegged: If specified, the search will be constrained to
      alignments where ``j_pegged`` remains unchanged
  :type j_pegged: int
  
  :return: The best alignment found from hill climbing
  :rtype: AlignmentInfo
docstr_tok: ["Starting","from","the","alignment","in","``","alignment_info","``",",","look","at","neighboring","alignments","iteratively","for","the","best","one",",","according","to","Model","4","Note","that","Model","4","scoring","is","used","instead","of","Model","5","because","the","latter","is","too","expensive","to","compute",".","There","is","no","guarantee","that","the","best","alignment","in","the","alignment","space","will","be","found",",","because","the","algorithm","might","be","stuck","in","a","local","maximum",".",":","param","j_pegged",":","If","specified",",","the","search","will","be","constrained","to","alignments","where","``","j_pegged","``","remains","unchanged",":","type","j_pegged",":","int",":","return",":","The","best","alignment","found","from","hill","climbing",":","rtype",":","AlignmentInfo"]
code: def hillclimb(self, alignment_info, j_pegged=None):
      """
      Starting from the alignment in ``alignment_info``, look at
      neighboring alignments iteratively for the best one, according
      to Model 4
  
      Note that Model 4 scoring is used instead of Model 5 because the
      latter is too expensive to compute.
  
      There is no guarantee that the best alignment in the alignment
      space will be found, because the algorithm might be stuck in a
      local maximum.
  
      :param j_pegged: If specified, the search will be constrained to
          alignments where ``j_pegged`` remains unchanged
      :type j_pegged: int
  
      :return: The best alignment found from hill climbing
      :rtype: AlignmentInfo
      """
      alignment = alignment_info  # alias with shorter name
      max_probability = IBMModel4.model4_prob_t_a_given_s(
          alignment, self
      )
  
      while True:
          old_alignment = alignment
          for neighbor_alignment in self.neighboring(
              alignment, j_pegged
          ):
              neighbor_probability = IBMModel4.model4_prob_t_a_given_s(
                  neighbor_alignment, self
              )
  
              if neighbor_probability > max_probability:
                  alignment = neighbor_alignment
                  max_probability = neighbor_probability
  
          if alignment == old_alignment:
              # Until there are no better alignments
              break
  
      alignment.score = max_probability
      return alignment
  
imports: import warnings
  from collections import defaultdict
  from math import factorial
  from nltk.translate import AlignedSent, Alignment, IBMModel, IBMModel4
  from nltk.translate.ibm_model import Counts, longest_target_sentence_length
label_desc: Use image node_id 7 for calling the IBMModel5 obj's underlying member method code with example usage: obj.hillclimb(alignment_info, j_pegged) and returns: alignment