マヤ・キチェ語の人工無脳プログラムTzijonik作成も大分進んできた。今回はユーザーが入力した文章が単語リストに載っている名詞を含んでいる場合、テンプレートとして保存する仕様にしてみた。変更点は次の3つのコードに殆ど含まれている。
Responder.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
require 'csv' class Responder def initialize(dictionary) @dictionary = dictionary end def response(input, mood) return '' end attr_reader :name end class WhatResponder < Responder def response(input, mood) "Jas #{input}?" end end class RandomResponder < Responder def response(input, mood) return randomSelect(@dictionary.random) end end class PatternResponder < Responder def response(input, mood) @dictionary.pattern.each do |patternItems| if m = patternItems.match(input) resp = patternItems.choice(mood) next if resp.nil? return resp.gsub(/%match%/, m.to_s) end end return randomSelect(@dictionary.random) end end class TemplateResponder < Responder def response(input, mood) vocabularyList = CSV.read('./dictionaries/VocabularyList.csv') keywords = [] fragments = input.split fragments.each do |word| lowerCase = word.downcase vocabularyList.each do |c1, c2| if lowerCase == c1 && c2 == "noun" then keywords.push(lowerCase) end end end count = keywords.size if count > 0 template = randomSelect(@dictionary.template[count]) return template.gsub(/%NOUN%/){keywords.shift} end return randomSelect(@dictionary.random) end end |
Dictionary.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
require 'csv' # added 20170523 class Dictionary def initialize @random = [] open('dictionaries/list02.txt') do |f| f.each do |line| line.chomp! next if line.empty? @random.push(line) end end @pattern = [] @phrases = [] open('dictionaries/patterns02.txt') do |f| f.each do |line| pattern, phrases = line.chomp.split("\t") #split by tab next if pattern.nil? or phrases.nil? @pattern.push(PatternItem.new(pattern, phrases)) @phrases << [pattern, phrases] end end @vocabularyList = CSV.read('./dictionaries/VocabularyList.csv')# added 20170523 ################################################################ @template = [] open('dictionaries/template.txt') do |f| f.each do |line| count, template = line.chomp.split(/\t/) next if count.nil? or pattern.nil? count = count.to_i @template[count] = [] unless @template[count] @template[count].push(template) end end ################################################################ end def study(input) return if @random.include?(input) @random.push(input) end def save open('dictionaries/list02.txt', 'w') do |f| f.puts(@random) end end ################################################################ def analyze(input) flag = false inputSmall = "" @phrases.each do |c1, c2| c1 = c1.to_s c1 = c1.gsub(/[^A-Za-z']/, "") inputSmall = input.downcase c1 = c1.downcase c2 = c2.downcase if c2.include?(inputSmall) && c2.include?(inputSmall) flag = true break end end unless flag @vocabularyList.each do |t1, t2| if inputSmall == t1 && t2.match("noun") then ## conditions to avoid a duplicate entry @pattern.push(PatternItem.new(t1, input)) end end end end def savePatterns open('dictionaries/patterns02.txt', 'w') do |f| @pattern.each{|patternItem| f.puts(patternItem.makeLine)} end end ################################################################# ################################################################# def studyTemplate(input) template = '' count = 0 vocabularyList = CSV.read('./dictionaries/VocabularyList.csv') keywords = [] fragments = input.split fragments.each do |word| lowerCase = word.downcase vocabularyList.each do |c1, c2| lowerCase = lowerCase.gsub(/[^a-z']/, "") if lowerCase == c1 && c2 == "noun" then word = lowerCase.gsub(c1, "\%NOUN\%") count += 1 end end if template == "" then template += word else template += " " + word end end return unless count > 0 @template[count] = [] unless @template[count] unless @template[count].include?(template) @template[count].push(template) end end def saveTemplates open('dictionaries/template.txt', 'w') do |f| @template.each_with_index do |templates, i| next if templates.nil? templates.each do |template| f.puts(i.to_s + "\t" + template) end end end end ################################################################# attr_reader :random, :pattern, :template end class PatternItem SEPARATOR = /^((-?\d+)##)?(.*)$/ def initialize(pattern, phrases) SEPARATOR =~ pattern @modify, @pattern = $2.to_i, $3 @phrases = [] phrases.split('|').each do |phrase| SEPARATOR =~ phrase @phrases.push({'need'=>$2.to_i, 'phrase'=>$3}) end end def match(str) return str.match(@pattern) end def choice(mood) choices = [] @phrases.each do |p| choices.push(p['phrase']) if suitable?(p['need'], mood) end return (choices.empty?)? nil : randomSelect(choices) end def suitable?(need, mood) return true if need == 0 if need > 0 return mood > need else return mood < need end end ################################################################ def makeLine pattern = @modify.to_s + "##" + @pattern phrases = @phrases.map{|p| p['need'].to_s + "##" + p['phrase']} return pattern + "\t" + phrases.join('|') end ################################################################ attr_reader :modify, :pattern, :phrases end |
Kotzij.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
require './Responder' require './Dictionary' class Kotzij def initialize @dictionary = Dictionary.new @emocion = Emocion.new(@dictionary) @responderWhat = WhatResponder.new(@dictionary) @responderRandom = RandomResponder.new(@dictionary) @responderPatterns = PatternResponder.new(@dictionary) @responderTemplate = TemplateResponder.new(@dictionary) @responder = @responderPatterns end def dialogue(input) @emocion.update(input) case rand(10) when 0 @responder = @responderWhat when 1..2 @responder = @responderRandom when 3..4 @responder = @responderPatterns else @responder = @responderTemplate end response = @responder.response(input, @emocion.mood) @dictionary.analyze(input) @dictionary.study(input) @dictionary.studyTemplate(input) return response end def responderName return @responder.name end def mood return @emocion.mood end def save @dictionary.save end def savePatterns @dictionary.savePatterns end def saveTemplates @dictionary.saveTemplates end attr_reader :name end def randomSelect(ary) return ary[rand(ary.size)] end |
会話らしくなってきている様な気がする。今後、単語リストの語彙数を増やしつつ、根気強く会話していけばどんどん良くなる筈。
さて、次はマルコフ連鎖を用いた辞書と返答方法の作成だけど、こちらは今後Javaへの移植も考えると本通りには出来ない。配列の組み方は大体想像出来るので自分なりのフォーマットで作成していくしかないかなぁ。そんなに大変そうでもないけど。そもそも今回も判定方法や基準は結構修正する必要があったし。
しかし、ベイズ統計で触れたマルコフ連鎖にこの分野で出会うことになるとは。。