surolog

AI・機械学習・データ分析 と 本 など

AlphaGoが見せてくれた「大局観」という可能性

囲碁において、世界トップ棋士に勝利したことで有名になった、DeepMindのAlphaGoがまたまた進化したようですね。

 

gigazine.net

 記事には以下のようにあります。

アルファ碁ゼロは、棋譜のデータに頼らず、人間の初心者以下の状態から強化学習だけで上達する。

 アルファ碁ゼロっていうんですね。やっぱり教師データとなる棋譜を全く使わないからゼロがつくんですかね。。。

アルファ碁は、アルゴリズムとして教師あり機械学習ディープラーニング、強化学習を素晴らしくうまく組み合わせてパフォーマンスを発揮していましたが、アルファ碁ゼロはディープラーニングと強化学習のみで囲碁を学習していくようですね。

 同じDeepMindがかつて発表した、ゲームを画像のみから強化学習で学習し強くなっていくDQNに近しいものになったと言えそうです。

 

かつてアルファ碁がイ・セドル九段に勝利したのを知った時、私が一番気になったのは、定石にはない打ち手の斬新さ、といったことより、「アルゴリズムが、囲碁というゲームにおいて人間を超える大局観を手に入れたのではないか」という仮説です。

 

ディープラーニングなどのアルゴリズムの特色として、判断のスピードと精度がよく注目されることがあります。顔認識や、不良品の検知などを「一瞬」で人間より「正確」に判断することができる、などの特性です。

 

こと判断スピードに関しては、早いことばかりもてはやされていますが、本質的な価値は「人間がうまく対応できないタイムスケールに対応できる」という点にあります。

 

人が反応できないミリ秒単位で認識できることには価値があります。

同様に、より長い時間での判断でも同じように価値があります。

なぜなら世の中には、より長期的な視点に基づく意思決定が必要な場合があります。そういう場合は、短期的に見たときと長期的に見たときで、とるべき判断が食い違うといった矛盾が生じることがあります(短期的には損益が悪化するけども、将来を見越して投資する、といったように)。この場合、短期的に見た最善手と長期的に見た最善手が矛盾し、将来のリターンに対して最適化するなら、短期的に不合理な選択をする必要が出てきます。

そのような判断ができることを私は「大局観がある」と表現しています。

 

 イ・セドル九段との対戦の際、アルファ碁は序盤に人間から見て不可解な、定石を外れた手を多く打っていたようです。

以下は山本教授の「人工知能はどのようにして「名人」を超えたか?」よりの抜粋です。

 

そうそう20手先までいくと「これおかしいんじゃない?」と思っていた手が、好手だったとわかる。アルファ碁はこれがやりたかったんだ、と。

 

 

 これは、囲碁というゲームにおいて、アルゴリズムが「勝利」という長期的なゴールを目指して戦略的行動を取ったと言えます。

このアルゴリズムは、他のいろいろな戦略的ゲームに応用できます。実際に、アルファ碁を開発したDeepMindでは、スタークラフトという戦略シミュレーションゲームを次の舞台として検証を進めています。

wired.jp

囲碁という抽象的な戦略ゲームから、種族間の戦争をシミュレートするゲームへ。DeepMindのアルゴリズムがその先に現実の戦争をコントロールしようとしているとは思いたくないですが、もしかしたらそうなった場合、人間より「効率的に」戦争を終わらせることができるかもしれません。おそろしや。。。

 

 

よなよなエール 350ml×24本

よなよなエール 350ml×24本

 

 

ベクトルの導入

NATUREofCODE

processingを用いたプログラミングの書籍として、「NATUREofCODE」というとてもおもしろいものがあります。ざっくりいうとprocessingを使って物理シミュレーションを行う内容なのですが、高校の物理の教科書に出てくるような簡単な物理法則の式を実装・適用するだけで、驚くほど生き生きと物体をシミュレートすることができます。
勉強をかねて、コードを実装していきます。

Nature of Code -Processingではじめる自然現象のシミュレーション-

Nature of Code -Processingではじめる自然現象のシミュレーション-

ベクトル

今後、コードで物理シミュレーションを実現するにあたって、重要な要素である「ベクトル」を導入していきます。
ベクトルという言葉は、いろいろなシーンでいろいろな意味で使われています。プログラムでも配列データ構造をベクトルと呼んだりしますが、ここで使う場合のベクトルはユークリッドベクトルを指します。意味するところは、「大きさと方向の両方を持つ実体」であるということです。

ベクトルなしで運動を表現

ベクトルは今後、物理シミュレーションとして「運動」を表現するにあたり必要になってきます。
ですが、ひとまずベクトルなしで運動をコードで表現してみます。シンプルなバウンドし続けるボールを描いてみます。

Fig1.バウンドするボール
f:id:sator926:20160225212129g:plain

コードはこちらになります。

float x=100;
float y=100;
float xspeed=1;
float yspeed=3;

void setup(){
  size(500,300);
  background(255);
}

void draw(){
  fill(255,100);
  rect(0,0,width,height);

  //update x,y  
  x=x+xspeed;
  y=y+yspeed;
  
  //collision detection 
  if((x>width) || (x<0)){
    xspeed=xspeed*-1;
  }
  if((y>height) || (y<0)){
    yspeed=yspeed*-1;
  }
  noStroke();
  fill(125);
  ellipse(x,y,25,25);
}

上記のコードの中で、ボールの運動は直前の位置(x,y)に速度(xspeed,yspeed)を加えたものになります。
つまり速度は、現在の位置と直前の位置の差になります。そこには、「方向」があり、移動距離という「大きさ」があるので、ベクトルとして表現することができそうです。一方位置は、空間上の1点を指しているように見えます。しかし、どこかに原点があると考えると(実際、processingの平面上ではデフォルトで左上隅が座標(0,0)の原点です)、位置も原点からその位置までのベクトルと考えることができます。そこで、位置と速度をベクトルとして再定義してみます。

class PVector{
 float x;
 float y;

 PVector(float x_, float y_){
  x=x_;
  y=y_;
 }

 //ベクトル加算の表現
 void add(PVector v){
  x=x+v.x;
  y=y+v.y;
 }
}

PVector location = new PVector(100,100);
PVector velocity = new PVector(1,3);

それでは、このベクトル表現を使ってバウンドするボールのコードを書き直します。

PVector location;
PVector velocity;

void setup(){
  size(500,300);
  background(255);
  
  location = new PVector(100,100);
  velocity = new PVector(1,3);
}

void draw(){
  fill(255,100);
  rect(0,0,width,height);

  //update location
  location.add(velocity);
  
  //collision detection
  if((location.x>width) || (location.x<0)){
    velocity.x=velocity.x*-1;
  }
  if((location.y>height) || (location.y<0)){
    velocity.y=velocity.y*-1;
  }
  noStroke();
  fill(125);
  ellipse(location.x,location.y,25,25);
}

一見するとスッキリしたわけではないですが、これから先、相互作用しあう複数の物体を表現するときに、ベクトル表現が大きな力を発揮します。

パーリンノイズを可視化する

NATUREofCODE

processingを用いたプログラミングの書籍として、「NATUREofCODE」というとてもおもしろいものがあります。ざっくりいうとprocessingを使って物理シミュレーションを行う内容なのですが、高校の物理の教科書に出てくるような簡単な物理法則の式を実装・適用するだけで、驚くほど生き生きと物体をシミュレートすることができます。
勉強をかねて、コードを実装していきます。

Nature of Code -Processingではじめる自然現象のシミュレーション-

Nature of Code -Processingではじめる自然現象のシミュレーション-


パーリンノイズ

物理現象をシミュレーションする上で、ランダム性を取り入れることは有効です。
ランダムな変化は自然を表現する上で活用されており、例えば最近だとMinecraftなどはランダムな地形生成でマップを作成しているようです。
このとき、数学的に正しいランダム性を使って地形を生み出すと、あまりに脈絡のないものになり「自然に」感じられません。自然の中のランダム性は、ランダムでありながら連続性があり有機的なものになります。これを表現するのに用いられるのがパーリンノイズです。processingではデフォルトのnoise関数として使用する事ができます。

ランダムノイズとパーリンノイズの時間変化を描画したものが下のグラフになります。パーリンノイズはランダムですが滑らかに変化していきます。

Fig1.パーリンノイズ
f:id:sator926:20160209213328p:plain
Fig2.ランダムノイズ
f:id:sator926:20160209213335p:plain

このノイズをx,y座標に組み込んで平面上を移動する円を描画すると以下のようになります。

f:id:sator926:20160209214211g:plain

ランダムな動きですが、なんとなく生物的な有機的な動きに感じられます。
次に、全てのピクセルにこのノイズを適用し、連続的に明度を変更すると、以下のような表現になります。

f:id:sator926:20160209221018p:plain

どんよりした曇り空、煙、あるいは鉱物の表面のようにも見えます。ノイズの変化率や色合いを調整することで、様々な質感を表現できます。今後は、このパーリンノイズを用いて自然現象を表現していきます。


せば


以下ソースコードです

  • 平面上をパーリンノイズに従いランダムに移動する円
import gifAnimation.*;
gifAnimation.GifMaker gm;
int fl=300; //framelimit

Walker w;

void setup(){
  size(500,300);
  w = new Walker();
  background(255);
  
  frameRate(50);
  gm = new gifAnimation.GifMaker(this,"perlinnoise_1D.gif");
  gm.setRepeat(0);
  gm.setDelay(20); //animetion
}

void draw(){
  w.step();
  w.display();
  
  gm.addFrame();
  if(frameCount >= fl){
    gm.finish();
    exit();
  }
}

class Walker{
  float x,y;
  float tx,ty;
  
  Walker(){
    tx=0;
    ty=10000;
  }
  
  void step(){
    x = map(noise(tx), 0,1,0,width);
    y = map(noise(ty), 0,1,0,height);
    
    tx+=0.01;
    ty+=0.01;
  }
  
  void display(){
    fill(200);
    ellipse(x,y,16,16);
  }
}
  • パーリンノイズで生成された二次元テクスチャ
float increment=0.02;

void setup(){
  size(500,300);
}

void draw(){
  background(0);
  loadPixels();
  
  float xoff=0.0;
  for(int x=0; x<width; x++){
    float yoff=0.0;
    for(int y=0; y<height; y++){
      float bright=map(noise(xoff,yoff),0,1,0,255);
      pixels[x+y*width]=color(bright);
      yoff+=increment;
    }
    xoff+=increment;
  }
  
  updatePixels();
}

サイコロシミュレーションでガウス分布と中心極限定理を理解する

前回ガウス分布をシンプルに可視化しましたが、サイコロを例にもう少しガウス分布を試してみます。

surolog.hatenablog.com


ガウス分布は、統計学や自然科学の分野でとても特別な存在です。
Wikipediaには以下のようにあります。

正規分布統計学や自然科学、社会科学の様々な場面で複雑な現象を簡単に表すモデルとして用いられている。たとえば実験における測定の誤差は正規分布に従って分布すると仮定され、不確かさの評価が計算されている。


生物の体長や植物の花弁サイズなど様々なものの数量の分布として正規分布が表れるらしいです。
(教科書にはそう書いてありました。)

で、なぜそんなに表れやすいのかという理由を説明するのが、中心極限定理というものの存在です。中心極限定理について再びWikipediaを参照しますと

独立な同一の分布に従う確率変数の算術平均(確率変数の合計を変数の数で割ったもの)の分布は、もとの確率変数に標準偏差が存在するならば、もとの分布の形状に関係なく、変数の数が多数になったとき、正規分布に収束する。

と言うことなんです。ややこしい。

これをサイコロで例えてみましょう。
「独立な同一の分布に従う確率変数」を「サイコロ」と言い換えます。
あるサイコロの出目は、他のサイコロの出目に影響しないので、「独立」です。
サイコロを超完璧に作れば、サイコロの目の出方は1から6まで同じになり、「同一の分布に従う」と言えます。
ちなみにそんな出方は「一様分布」と言います。

ということで、上記の中心極限定理を超意訳すると

サイコロ1個の目の出方は一様分布だけど、たくさんのサイコロ同時に振ってその目の和(や平均)を出すと、その出方は正規分布に近いよ。

という感じです。(ガチ勢の人見てたらすいません。。)

・・・本当ですかね?

ということで確かめてみます。
processingを使って、簡単にシミュレートしてみましょう。

まずは、1から6までの数字をランダムに発生させて、擬似的なサイコロとします。
「サイコロ1個を振る」のを繰り返して、その出目をカウントしていきます。


コードは後で載せるとして、結果を可視化すると以下のようになります。

f:id:sator926:20160204214541g:plain

まあ当然、どの出目も一様に増えていきますね。まさに一様分布。
では次に、サイコロ2個で試してみます。
「サイコロ2個を同時に振る」のを繰り返して、その合計値をカウントしていきます。

f:id:sator926:20160204215245g:plain

どうですかね。明らかに一様じゃないですね。中央の値(サイコロ2個の場合は7)の出現回数が明らかに増えて、分布が変わってきています。
では、サイコロ10個でいってみましょう。

f:id:sator926:20160204221142g:plain

より分布が滑らかになってきて、例の釣鐘型がなんとなく見えてきます。このあとしばらく放置すると、以下のような分布なりました。

f:id:sator926:20160204221423p:plain

だいぶ正規分布に近づいていますね。もっと数を増やせば、より顕著にこの傾向が表れます。これが、中心極限定理の示すものということになります。

この定理はとても強力です。その強力さは、元の定理文をもう一度見返すと表れています。

独立な同一の分布に従う確率変数の算術平均(確率変数の合計を変数の数で割ったもの)の分布は、もとの確率変数に標準偏差が存在するならば、もとの分布の形状に関係なく、変数の数が多数になったとき、正規分布に収束する。

「もとの分布形状に関係なく」とあります。すごいです。
このサイコロがイカサマサイコロで、1から5の目の出る確率が0.1%ずつ、6の目の出る確率が99.5%だったとしても、十分多い数のイカサマサイコロを振るのを繰り返してその分布を取れば、普通のサイコロと同じ正規分布に行き着くということです。

この強力な性質により、正規分布統計学、数学、物理学などで、重要な役割を担っているのです。

int[] randomCounts;
int dices=10; //サイコロの数を入力
int pt=1+5*dices;
int fl=400; //framelimit

void setup(){
  size(500, 300);
  randomCounts = new int[pt];
  frameRate(50);
  textAlign(CENTER);
}

void draw(){
  background(255);
  textSize(15);
  text("dice="+dices, width/2, 20);
  int index=0;
  for(int a=0 ; a<dices ; a++){
    index += int(random(6));
  }
  println(index);
  randomCounts[index]++;
  stroke(0);
  fill(175);
  int w = width/randomCounts.length;
  for(int x=0 ; x < randomCounts.length ; x++){
    rect(x*w, height-randomCounts[x]-20, w-1, randomCounts[x]);
    textSize(7);
    text(x+dices,x*w+w/2, height-5);
  }
}

(メモ)processingでガウス分布を可視化

NATUREofCODE

processingを用いたプログラミングの書籍として、「NATUREofCODE」というとてもおもしろいものがあります。ざっくりいうとprocessingを使って物理シミュレーションを行う内容なのですが、高校の物理の教科書に出てくるような簡単な物理法則の式を実装・適用するだけで、驚くほど生き生きと物体をシミュレートすることができます。
勉強をかねて、コードを実装していきます。

ガウス分布

正規分布とも言いますし、ラプラス分布とも言います。
正規分布という堅苦しい感じですが、「ありふれた」ぐらいの意味と思っていいと思います。
自然界にありふれた分布ということです。
それでは可視化してみます。
以下がコードになります。

//ガウス分布
import java.util.Random;
Random generator;

void setup(){
  size(640,360);
  generator = new Random();
  frameRate(50);
}

void draw(){
  float num=(float)generator.nextGaussian();
  float sd=60;
  float mean=320;
  float x=sd*num+mean;
  noStroke();
  fill(255,10);
  ellipse(x,180,25,25);
}


nextGaussian()で標準正規分布(平均0,標準偏差1)に従うお行儀の良い分布を発生させています。
そのあと、画面に合わせて中心が320pixel、左右の標準偏差が60pixelの分布にしています。
この結果、下のようなガウス分布の軌跡が残ります。


f:id:sator926:20160201214912g:plain


分布の中心(320pixel)付近に多く円が描かれていますが、左右の離れたところにも時折円が描画されています。この円の出現位置をカウントしてグラフにすれば、よく見る釣鐘型曲線になります。

せば

(メモ1)Processingでランダムウォークを可視化

NATUREofCODE

processingを用いたプログラミングの書籍として、「NATUREofCODE」というとてもおもしろいものがあります。ざっくりいうとprocessingを使って物理シミュレーションを行う内容なのですが、高校の物理の教科書に出てくるような簡単な物理法則の式を実装・適用するだけで、驚くほど生き生きと物体をシミュレートすることができます。
勉強をかねて、コードを実装していきます。

ランダムウォーク

まずは様々な方向にランダムに移動する粒子を実装します。
私が思うこの本のおもしろいところは、プログラムに積極的にランダム性を取り入れて
その結果生まれる現象を考察している(あるいはただ楽しんでいる)点だと思います。
実際、ランダムな動きから生まれるビジュアルはとても美しく、飽きずに見て入られます。
ランダムウォークはこのランダム性の基本となるものです。早速コードを見ていきます。

//ランダムウォーカークラス
class walker {
  int x;
  int y;
  
  walker(){
    x = width/2;
    y = height/2;
  }
  
  void display(){
    stroke(0);
    point(x,y);
  }
  
  void step(){
    int choice = int(random(4));
    
    if(choice == 0){
      x++;
    } else if(choice == 1){
      x--;
    } else if(choice == 2){
      y++;
    } else {
      y--;
    }
  }
}

walker w;

void setup(){
  size(640,360);
  w = new walker();
  background(255);
}

void draw(){
  w.step();
  w.display();
}

記述はこれだけです。

  1. ランダムウォーカークラスを定義
  2. setup()で画面中央に配置
  3. update()で座標をランダムに1point変化
  4. display()で描画

あとは3.4をdraw()で繰り返す

この結果、下のようにランダムウォークの軌跡が残ります。

f:id:sator926:20141226160135g:plain

これだけでなんだかおもしろいですよね。
おまけとして、乱数の生成に使用したrandom()関数の挙動を見てみます。
random()関数は指定した個数の乱数を一様に発生させますが、その様子が見て取れると思います。

int[] randomCounts;

void setup(){
  size(500, 300);    
  randomCounts = new int[20];
}

void draw(){
  background(255);
  int index = int(random(randomCounts.length));
  randomCounts[index]++;
  
  stroke(0);
  fill(175);
  int w = width/randomCounts.length;
  for(int x=0 ; x < randomCounts.length ; x++){
    rect(x*w, height-randomCounts[x], w-1, randomCounts[x]);
  }
}

f:id:sator926:20150114003644g:plain

せば

Pythonで情報利得を計算してみる

ジニ係数に引き続き、情報利得の関数も作ってみました。
ジニ係数については以下を参照ください、


[Pythonでジニ係数を計算してみる - surolog

以下の流れでご紹介

  • 情報利得の簡単な説明
  • 情報利得の実装

情報利得って

wikiによれば

カルバック・ライブラー情報量 - Wikipedia


カルバック・ライブラー情報量(カルバック・ライブラーじょうほうりょう、英: Kullback–Leibler divergence、カルバック・ライブラー・ダイバージェンス)とは、確率論と情報理論における2つの確率分布の差異を計る尺度である。情報ダイバージェンス(Information divergence)、情報利得(Information gain)、相対エントロピー(Relative entropy)とも呼ばれる。

http://ja.wikipedia.org/wiki/カルバック・ライブラー情報量

wiki冒頭にあるように、いろいろな呼び方があります。私はたまたま情報利得という呼び方を最初に知ったので、その呼び方に慣れていますが、内容を一番わかりやすく表現しているのは相対エントロピーではないかと思います。

Pythonで実装

まずは単一の状態でのエントロピーを計算する関数を実装します。
こいつをある変数に対して分割前の状態と分割後の各状態で計算して、その差分を見るのがデータ分析での情報利得の扱いですね。

# -*- encoding=utf-8 -*-

import numpy
import pandas
from collections import Counter

def entropy(vec):
    entropys    = list()
    count       = Counter(vec)
    countall    = float(numpy.sum(count.values()))
    for item in count.items():
        counteach       = item[1]
        prob            = counteach/countall
        entropyeach     = -prob * numpy.log(prob)
        # print  "%s : %f" %(item[0],entropyeach)
        entropys.append(entropyeach)
    entropy = numpy.sum(entropys)
    print "%s : %f" % (vec.name, entropy)
    return entropy


続いて、このentropyを呼び出して分割前の目的変数yのエントロピーと、あるカテゴリカル変数xで分割した後のエントロピーを比較します。この差分が正の方向に大きいほど、「xによる分割で各サブセット内のyに秩序が生じた」ことになるので、xは影響力のありそうな特徴量かな、となります。

def informationGain(x,y):
    # x,yともにカテゴリカル変数
    root_ent        = entropy(y)
    grouped         = y.groupby(x)
    leaf_ent_0      = grouped.apply(entropy)
    leaf_weight     = grouped.apply(len) / float(len(y))
    leaf_ent        = leaf_ent_0 * leaf_weight

    ig  = root_ent - leaf_ent.sum()
    print "information gain : %f" %ig
    return ig


ついでにxが連続量の場合についても作ってみました。
厳密にはxについて積分が必要な気がしますが、簡易版として100分割して
離散化しています。

def informationGainNum(x,y, bins=100):
    # xが連続変数、yがカテゴリカル変数
    root_ent        = entropy(y)
    xname = x.name
    yname = y.name

    xy      = pandas.concat([x,y], axis=1)
    xbins = pandas.cut(xy[xname], bins)
    # xbins   = pandas.qcut(xy[xname], 10)

    grouped         = xy[yname].groupby(xbins)
    leaf_ent_0      = grouped.apply(entropy)
    leaf_weight     = grouped.apply(len) / float(len(y))
    leaf_ent        = leaf_ent_0 * leaf_weight

    ig  = root_ent - leaf_ent.sum()
    print "information gain : %f" %ig
    return ig


では実際に使ってみます。

x = numpy.random.choice(["A","B","C"], 100)
x = pandas.Series(x)
y = numpy.random.choice([0,1], 100)
y = pandas.Series(y)
y.name = "testy"

informationGain(x,y)

これを実行すると、以下のようになります。

testy : 0.692947
A : 0.693147
B : 0.690186
C : 0.692627
information gain : 0.001116

せば