lib/megahal/megahal.rb
def _learn(puncs, norms, words)
    return if words.length == 0

    # Convert the three lists of strings into three lists of symbols so that we
    # can use the Sooth::Predictor. This is done by finding the ID of each of
    # the strings in the @dictionary, allowing us to easily rewrite each symbol
    # back to a string later.
    punc_symbols = puncs.map { |punc| @dictionary[punc] ||= @dictionary.length }
    norm_symbols = norms.map { |norm| @dictionary[norm] ||= @dictionary.length }
    word_symbols = words.map { |word| @dictionary[word] ||= @dictionary.length }

    # The @seed model is used to start the forwards-backwards reply generation.
    # Given a keyword, we want to find a word that has been observed adjacent to
    # it. Each context here is a bigram where one symbol is the keyword and the
    # other is the special <blank> symbol (which has ID 2). The model learns
    # which words can fill the blank.
    prev = 1
    (norm_symbols + [1]).each do |norm|
      context = [prev, 2]
      id = @brain[context.dup] ||= @brain.length
      @seed.observe(id, norm)  
      context = [2, norm]
      id = @brain[context.dup] ||= @brain.length
      @seed.observe(id, prev)  
      prev = norm
    end

    # The @fore model is a classic second-order Markov model that can be used to
    # generate an utterance in a random-walk fashion. For each adjacent pair of
    # symbols the model learns which symbols can come next. Note that the
    # special <fence> symbol (which has ID 1) is used to delimit the utterance.
    context = [1, 1]
    norm_symbols.each do |norm|
      id = @brain[context.dup] ||= @brain.length
      @fore.observe(id, norm)  
      context << norm
      context.shift
    end
    id = @brain[context.dup] ||= @brain.length
    @fore.observe(id, 1)

    # The @back model is similar to the @fore model; it simply operates in the
    # opposite direction. This is how the original MegaHAL was able to generate
    # a random sentence guaranteed to contain a keyword; the @fore model filled
    # in the gaps towards the end of the sentence, and the @back model filled in
    # the gaps towards the beginning of the sentence.
    context = [1, 1]
    norm_symbols.reverse.each do |norm|
      id = @brain[context.dup] ||= @brain.length
      @back.observe(id, norm)  
      context << norm
      context.shift
    end
    id = @brain[context.dup] ||= @brain.length
    @back.observe(id, 1)

    # The previous three models were all learning the sequence of norms, which
    # are capitalised words. When we generate a reply, we want to rewrite it so
    # MegaHAL doesn't speak in ALL CAPS. The @case model achieves this. For the
    # previous word and the current norm it learns what the next word should be.
    context = [1, 1]
    word_symbols.zip(norm_symbols).each do |word, norm|
      context[1] = norm
      id = @brain[context.dup] ||= @brain.length
      @case.observe(id, word)  
      context[0] = word
    end

    # After generating a list of words, we need to join them together with
    # word-separators (whitespace and punctuation) in-between. The @punc model
    # is used to do this; here it learns for two adjacent words which
    # word-separators can be used to join them together.
    context = [1, 1]
    punc_symbols.zip(word_symbols + [1]).each do |punc, word|
      context << word
      context.shift
      id = @brain[context.dup] ||= @brain.length
      @punc.observe(id, punc)  
    end
  end
# Train each of the five models based on a sentence decomposed into a list of
# word separators (puncs), capitalised words (norms) and words as they were
# observed (in mixed case).
Train each of the five models based on a sentence decomposed into a list of
word separators (puncs), capitalised words (norms) and words as they were
observed (in mixed case).
Train each of the five models based on a sentence decomposed into a list of word separators ( puncs ) capitalised words ( norms ) and words as they were observed ( in mixed case ) .
def _learn(puncs, norms, words)
    return if words.length == 0
    punc_symbols = puncs.map { |punc| @dictionary[punc] ||= @dictionary.length }
    norm_symbols = norms.map { |norm| @dictionary[norm] ||= @dictionary.length }
    word_symbols = words.map { |word| @dictionary[word] ||= @dictionary.length }
    prev = 1
    (norm_symbols + [1]).each do |norm|
      context = [prev, 2]
      id = @brain[context.dup] ||= @brain.length
      @seed.observe(id, norm)  
      context = [2, norm]
      id = @brain[context.dup] ||= @brain.length
      @seed.observe(id, prev)  
      prev = norm
    end
    context = [1, 1]
    norm_symbols.each do |norm|
      id = @brain[context.dup] ||= @brain.length
      @fore.observe(id, norm)  
      context << norm
      context.shift
    end
    id = @brain[context.dup] ||= @brain.length
    @fore.observe(id, 1)
    context = [1, 1]
    norm_symbols.reverse.each do |norm|
      id = @brain[context.dup] ||= @brain.length
      @back.observe(id, norm)  
      context << norm
      context.shift
    end
    id = @brain[context.dup] ||= @brain.length
    @back.observe(id, 1)
    context = [1, 1]
    word_symbols.zip(norm_symbols).each do |word, norm|
      context[1] = norm
      id = @brain[context.dup] ||= @brain.length
      @case.observe(id, word)  
      context[0] = word
    end
    context = [1, 1]
    punc_symbols.zip(word_symbols + [1]).each do |punc, word|
      context << word
      context.shift
      id = @brain[context.dup] ||= @brain.length
      @punc.observe(id, punc)  
    end
  end
def _learn ( puncs , norms , words ) return if words . length == 0 punc_symbols = puncs . map { | punc | @dictionary [ punc ] ||= @dictionary . length } norm_symbols = norms . map { | norm | @dictionary [ norm ] ||= @dictionary . length } word_symbols = words . map { | word | @dictionary [ word ] ||= @dictionary . length } prev = 1 ( norm_symbols + [ 1 ] ) . each do | norm | context = [ prev , 2 ] id = @brain [ context . dup ] ||= @brain . length @seed . observe ( id , norm ) context = [ 2 , norm ] id = @brain [ context . dup ] ||= @brain . length @seed . observe ( id , prev ) prev = norm end context = [ 1 , 1 ] norm_symbols . each do | norm | id = @brain [ context . dup ] ||= @brain . length @fore . observe ( id , norm ) context << norm context . shift end id = @brain [ context . dup ] ||= @brain . length @fore . observe ( id , 1 ) context = [ 1 , 1 ] norm_symbols . reverse . each do | norm | id = @brain [ context . dup ] ||= @brain . length @back . observe ( id , norm ) context << norm context . shift end id = @brain [ context . dup ] ||= @brain . length @back . observe ( id , 1 ) context = [ 1 , 1 ] word_symbols . zip ( norm_symbols ) . each do | word , norm | context [ 1 ] = norm id = @brain [ context . dup ] ||= @brain . length @case . observe ( id , word ) context [ 0 ] = word end context = [ 1 , 1 ] punc_symbols . zip ( word_symbols + [ 1 ] ) . each do | punc , word | context << word context . shift id = @brain [ context . dup ] ||= @brain . length @punc . observe ( id , punc ) end end
Train each of the five models based on a sentence decomposed into a list of
word separators (puncs), capitalised words (norms) and words as they were
observed (in mixed case).
Train each of the five models based on a sentence decomposed into a list of word separators ( puncs ) capitalised words ( norms ) and words as they were observed ( in mixed case ) .
# Convert the three lists of strings into three lists of symbols so that we # can use the Sooth::Predictor. This is done by finding the ID of each of # the strings in the @dictionary, allowing us to easily rewrite each symbol # back to a string later. # The @seed model is used to start the forwards-backwards reply generation. # Given a keyword, we want to find a word that has been observed adjacent to # it. Each context here is a bigram where one symbol is the keyword and the # other is the special <blank> symbol (which has ID 2). The model learns # which words can fill the blank. # The @fore model is a classic second-order Markov model that can be used to # generate an utterance in a random-walk fashion. For each adjacent pair of # symbols the model learns which symbols can come next. Note that the # special <fence> symbol (which has ID 1) is used to delimit the utterance. # The @back model is similar to the @fore model; it simply operates in the # opposite direction. This is how the original MegaHAL was able to generate # a random sentence guaranteed to contain a keyword; the @fore model filled # in the gaps towards the end of the sentence, and the @back model filled in # the gaps towards the beginning of the sentence. # The previous three models were all learning the sequence of norms, which # are capitalised words. When we generate a reply, we want to rewrite it so # MegaHAL doesn't speak in ALL CAPS. The @case model achieves this. For the # previous word and the current norm it learns what the next word should be. # After generating a list of words, we need to join them together with # word-separators (whitespace and punctuation) in-between. The @punc model # is used to do this; here it learns for two adjacent words which # word-separators can be used to join them together.
puncs
norms
words