ProcessingでAIしてみる

Processing Advent Calendar 2016、23日目の記事です。

今年は2回目の記事になります。kitaoです。よろしくお願いします。

今年はとにかく人工知能(AI)関連のニュースが多かったですね。やはりここは流行に乗ってProcessingでもAIすることにトライしてみたいと思います。

AIとは何か

AIの定義は人によって異なるのですが、おおよそ共通する意味としては、「人間にしかできない知的処理を機械に行わせる」といったあたりかと思います。

面白いのは「人間にしかできないこと」というのが時代によって変化していることで、かつてはAIと呼ばれていた手書き文字の読み取り(OCR)やエアコンの温度の自動調整などは、その技術が一般化するのに伴い、わざわざAIと呼ばれることはなくなっています。

そんな中でも、このところ特に"AI"として注目されているのは、複雑なイメージの認識や、絵・音楽・文章の創作といった、従来は難しかったけれど深層学習によって最近急激に発展した技術領域なのではないかと思います。

ん、機械による絵の創作? つまりプログラムによるアートの作成?

f:id:tkitao:20161222220017g:plain

ということで、いつのまにか流行に乗っていたようです。めでたしめでたし。

これだけではあんまりなので、もう少しそれっぽいAIをProcessingで作る方法を紹介してみたいと思います。

ルールベースによるAI実現

残念ながらProcessing用に作られたAIライブラリはほとんどないのですが、AI for 2D Gamesというライブラリを使うことでAIを作ることができます。

AI for 2D GamesはProcessingのライブラリのインポートメニューからインストールできます。

f:id:tkitao:20161222220920p:plain

AI for 2D Gamesがサポートしているのは、ルールベースのAI技術です。ルールベースのAIとは、ざっくり言うと「IF文の集合を効率よく表現する手法」のことで、ゲームのキャラクターのように何らかの法則に基づいて対象を自動で動かしたい時などに向いています。

AI for 2D Gamesに付属してくるサンプルの画像がこちらです。

f:id:tkitao:20161222221514p:plain

機械学習によるAI実現

続いてもう少しトレンドっぽい、機械学習によるAIの実現方法を調べてみます。

機械学習によるAIとは、ざっくり言うと「データを解析することでIF文を自動的に作成する手法」で、画像や音声の認識など、参考になるデータはあるけど条件が複雑すぎてどうプログラムを書いたらよいかわからないようなものに向いています。

残念ながら、Processing用の手軽な機械学習ライブラリは見当たらなかったので、REINFORCEjsというJavaScriptから簡単に使える強化学習のライブラリをp5.jsから呼んで利用してみることにします。

まず、こちらからダウンロードしたrl.jsをp5スケッチのlibrariesフォルダに配置して、index.htmlから以下のように読み込みます。

<html>
 <head> 
  <meta charset="UTF-8" /> 
  <!-- PLEASE NO CHANGES BELOW THIS LINE (UNTIL I SAY SO) --> 
  <script language="javascript" type="text/javascript" src="libraries/p5.js"></script> 
  <script language="javascript" type="text/javascript" src="lib/rl.js"></script>
  <script language="javascript" type="text/javascript" src="sketch_161222c.js"></script> 
  <!-- OK, YOU CAN MAKE CHANGES BELOW THIS LINE AGAIN --> 
  <!-- this line removes any default padding and style. you might only need one of these values set. --> 
  <style> body {padding: 0; margin: 0;} </style> 
 </head> 
 <body>  
 </body>
</html>

今回は試しに、円が四角の中に入ると報酬を与えるシンプルなAIを作成してみます。

var env = {};
env.getNumStates = function() { return 4; }
env.getMaxNumActions = function() { return 4; }

var agent = new RL.DQNAgent(env, {}); 

var x, y;
var vx, vy;

function setup() {
  createCanvas(400, 300);
  
  x = 200;
  y = 150;
  vx = 0;
  vy = 0;
}

function update() {
  for (var i = 0; i < 20; i++) {
    var action = agent.act([x, y, vx, vy]);
  
    switch (action) {
      case 0:
        vx += 0.02;
        break;
      case 1:
        vx -= 0.02;
        break;
      case 2:
        vy += 0.02;
        break;
      case 3:
        vy -= 0.02;
        break;
    }
  
    x += vx;
    y += vy;
      
    if (x < 0) { x = 0; vx = 0; }
    if (x > width) { x = width; vx = 0; }
    if (y < 0) { y = 0; vy = 0; }
    if (y > height) { y = height; vy = 0; }
  
    var reward = (x > 50 && x < 350 && y > 50 && y < 250) ? 1 : -1;
    agent.learn(reward);
  }
}

function draw() {
  update();

  noStroke();
  fill(0, 20);
  rect(0, 0, width, height);
  
  stroke(200);
  noFill();
  rect(50, 50, 300, 200);

  noStroke();
  fill(0, 255, 0);
  text(frameCount, 60, 70);
  
  fill(255, 0, 0);
  ellipse(x, y, 30);
}

env.getNumStatesで入力として与える状態の数を、env.getMaxNumActionsで行動の数を指定します。

今回は、入力として円の座標(x,y)と速度(vx,vy)の4つをagent.act([x, y, vx, vy])で渡し、返ってきた行動を移動方向(上下左右の4方向)として扱っています。

最後に、reward = (x > 50 && x < 350 && y > 50 && y < 250) ? 1 : -1で枠内に入っているときは+1、枠外の時は-1の報酬を決定し、agent.learn(reward)でその値を与えています。

実際の動作の様子がこちら。

ずっと見ているといずれ円が常に枠内に入るようになるはずなのですが、時間の都合もあり実はまだ確認できていません。いずれ生暖かく見守ってみようと思います。