この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
戦略的に切り崩していかなければ、立ち向かえない可能性もあります。
はじめに
みなさん、お久しぶりです。
新卒エンジニアのくろすです。
さて、残念ながらこの記事には最近話題のAIの話はほとんどありません。
言い訳
いや本当はMNISTのDataSetから手書き数字認識をやってみたって話を書こうと思ってたんです。今月半ばまでは。
弊社はrubyistが多いので、rubyでNNを書いて簡単に説明してみたってやるつもりだったんです。
昔MATLABで書いたプログラムがどっかにあるから、ピャーって書いちゃおうって。
9割遊びだからお家で作業を進めてるんですよ。
rubyでMNISTのデータセット取ってきて、バイナリデータだからそれを画像ファイルに変換してですね。
そして、こう、アニメがですね、アニメを見てるとですね……
rubyでNN作るやつはそのうち記事にはします。
そもそもAIとは
AI流行ってますよね。
映画でも機械学習がーって話があったり、ギークがリア充にボコボコにされたり、出身大学が悪の巣窟扱いされたり。
じゃあそもそもAIってなんですかって話です。
Artificial Intelligence
コンピュータ上で知能を実現しようとする試みやそれに関連する技術がそう呼ばれているよう思います。
人工知能学会の人工知能って何?には以下のように書かれています。
「人工知能」とは何だと思うでしょうか?まるで人間のようにふるまう機械を想像するのではないでしょうか?これは正しいとも,間違っているともいえます.なぜなら,人工知能の研究には二つの立場があるからです.一つは,人間の知能そのものをもつ機械を作ろうとする立場,もう一つは,人間が知能を使ってすることを機械にさせようとする立場です(注1).そして,実際の研究のほとんどは後者の立場にたっています.ですので,人工知能の研究といっても,人間のような機械を作っているわけではありません.
学習や推論をするゲームのAIだと囲碁や将棋が有名ですよね。
ニューラルネットワークなどの手法で学習を行なっているようです。
ゲームのAIって???
学習や推論等を行わないゲームのAIの場合は、なんらかの単語をトリガーに会話を返す人工無脳と似たような存在です。
条件分岐に従った行動しか取れない、要するにただ設定されているだけの戦略と言い換えてもいいかもしれません。
かなり適当ですがこんな感じです。
class AI
def initialize(code)
@code = code
end
def select_action(code)
send("select_action_#{code}")
end
# ここからAI
def select_action_1
# 1の倍数のターンだけアホになる
end
def select_action_2
# 2の倍数のターンだけアホになる
end
alias :select_action_4 :select_action_2
# code:4のAIはやっぱり2の倍数の時もアホになった方がいい感じ
def select_action_3
# 3の倍数のターンだけアホになる
end
end
sendメソッドで動的にメソッドを呼び出すことで大量のcase文が並ぶことは避けられていますが、このクラス自身が全ての戦略を知っているということは様々な問題に繋がります。
AIが増えるにつれ条件分岐が増え、空は堕ち、大地は割れ、海が涸れます。
こんなところで死ぬわけにはいかないので大問題です。
生存戦略
さて、この肥大化する戦略群と向き合うとしましょう。
うまいこと戦略を切り分けてあげないと、とてもじゃないけど生き残れる気がしません。
取替え可能な戦略をうまいこと使いたいなぁ……
戦略を切り替えたいなぁ……
そうStrategy Pattern
の出番です。
module AIManager
@codes_to_ai = {}
def self.set_code_to_ai(klass, codes)
codes.each do |code|
@codes_to_ai.merge!({code => klass})
end
end
class NotIntelligence < StandardError; end
def self.get_ai(code)
# ai = AIManager.get_ai(code).new(code)のように呼ばれた時の大域脱出で使う
raise NotIntelligence unless @codes_to_ai.keys.include?(code)
@codes_to_ai[code]
end
module StrategyInterface
OVERRIDE_PROHIBITION = [:initialize].freeze
def self.included(klass)
klass.define_singleton_method(:method_added) do |symbol|
if OVERRIDE_PROHIBITON.include?(symbol)
raise "Do not override : #{symbol}"
end
end
AIManager.set_code_to_ai(klass, klass::CODES)
end
def initialize(code)
@code = code
end
def select_action
raise "Called abstract method : select_action"
end
end
end
このようなInterfaceを作り、AIManager.get_ai(code)を介して各AIクラスにアクセスすることで、codeさえわかれば欲しいAIを引っ張り出すことが可能です。
StrategyInterface 内で定義されているincluded
というのはモジュールがincludeされた時にrubyが呼び出すメソッドで、引数にはclassが自動的に入ります。
Interfaceにせずにabstractなクラスを作っても良いのですが、同じことをやろうとしても、クラスが継承された時に呼ばれるinherited
メソッドは
class Hoge < SuperClass
のような一文を解釈した時に呼ばれてしまうためうまくcodeを管理者に渡せません。
module AIManager
class AlwaysAho
CODES = [1].freeze
include StrategyInterface
def select_action
# 1の倍数のターンにアホになる
end
end
class UsuallyAho
CODES = [2, 4].freeze
include StrategyInterface
def select_action
# 2の倍数のターンにアホになる
end
end
class SometimesAho
CODES = [3].freeze
include StrategyInterface
def select_action
# 3の倍数のターンにアホになる
end
end
end
アホになる頻度でAIのクラスの名前を変更することに成功しました。
実際に使う側からはこのAIクラスが本当にAIかはどうでもよく、select_actionが呼べてたまにアホになれば、それはAIだというふうに捉えていきます。
アヒルのように歩きアヒルのように鳴くものはアヒルに違いない。ということです。
終わりに
アホになれないcodeを割り振られた子たちはNotIntelligenceエラーを返されるという、なんとも分かりにくい説明コードを書いてしまいました。
こんな書き方はしてますがrubyでのStrategyパターンではconcreat strategyはlambda式を使った方が良さそうな気がします。
話はちょっと変わりますが、醜いアホの子が実は人工知能だったら面白くないですか???
暇と余裕とゆとりがあれば、そのうち機械学習をアホの子に突っ込んでみたいなぁって思いました。