ホーム DoRuby 肥大化するAIと対峙する時に覚えておきたいこと
 

肥大化するAIと対峙する時に覚えておきたいこと

この記事はアピリッツの技術ブログ「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式を使った方が良さそうな気がします。

話はちょっと変わりますが、醜いアホの子が実は人工知能だったら面白くないですか???
暇と余裕とゆとりがあれば、そのうち機械学習をアホの子に突っ込んでみたいなぁって思いました。

記事を共有
モバイルバージョンを終了