あかすくぱるふぇ

同人サークル「あかすくぱるふぇ」のブログです。

Chainerをインストールし、mnistの実行ができた!
……が、Pythonと機械学習の初心者である私にはmnistの中身を解読することができませんでした。。

そんな私が最初に解読したコードを載せます。
以下のサイトのコードを微修正したものです。
http://qiita.com/carat_yoshizaki/items/bfe559d1bdd434be03ed
import chainer
import math
import numpy as np
import chainer.functions as F
import chainer.links as L

class MyChain(chainer.Chain):
def __init__(self):
super(MyChain, self).__init__(
l1 = L.Linear(1, 100),
l2 = L.Linear(100, 30),
l3 = L.Linear(30, 1)
)

def predict(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
return self.l3(h2)

x, y = [], []
for i in np.linspace(-3,3,100):
x.append([i])
y.append([math.sin(i)])

x = chainer.Variable(np.array(x, dtype=np.float32))
y = chainer.Variable(np.array(y, dtype=np.float32))

model = MyChain()

def forward(x, y, model):
t = model.predict(x)
loss = F.mean_squared_error(t, y)
return loss

optimizer = chainer.optimizers.Adam()
optimizer.setup(model)

for i in range(0,1000):
model.zerograds()
loss = forward(x, y, model)
loss.backward()
optimizer.update()
print loss.data
以下、説明していきます。
import chainer
import math
import numpy as np
import chainer.functions as F
import chainer.links as L
C言語で言うところのinclude文。
asは別名をつける場合に使います。
class MyChain(chainer.Chain):
def __init__(self):
super(MyChain, self).__init__(
l1 = L.Linear(1, 100),
l2 = L.Linear(100, 30),
l3 = L.Linear(30, 1)
)

def predict(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
return self.l3(h2)
ネットワークを定義したクラス。
class MyChain(chainer.Chain)はMyChainクラスがChainクラスを継承していることを表します。
必須ではないが、基本的にはChainクラスを継承してネットワーククラスを定義するようです。

__init__はコンストラクタ。
selfはPythonのメンバ関数では必ず一つ目の引数として指定するものらしいです。C++で言うところのthis。
superは親クラスを表します。

l1~l3はネットワークのリンク構造を表しています。
Linear(a, b)は、a個の入力をb個の出力に変換する1層のネットワークを示しており、数式で表すとu=Wx+bとなります。
ここで、xは入力(a列のベクトル)、Wは重み行列(a列b行の行列)、bはバイアス(b列のベクトル)、uは出力(b列のベクトル)です。
この例は3層のネットワークであり、1個の入力→100個の出力→30個の出力→1個の出力という形で、1個の入力から1個の出力を生成します。

reluは活性化関数LeLU。
x, y = [], []
for i in np.linspace(-3,3,100):
x.append([i])
y.append([math.sin(i)])

x = chainer.Variable(np.array(x, dtype=np.float32))
y = chainer.Variable(np.array(y, dtype=np.float32))
ここでは正解データを生成しています。
linspace(a, b, c)は[a, b]をc分割してサンプリングする関数、append()はリストへの追加を行う関数です。
下2行のようにnumpyのarrayを経由してChainer用のリストを生成するのが作法のようです。
model = MyChain()

def forward(x, y, model):
t = model.predict(x)
loss = F.mean_squared_error(t, y)
return loss

optimizer = chainer.optimizers.Adam()
optimizer.setup(model)
モデル、順伝播関数、最適化モデルの設定をしています。
モデルは先ほど定義したMyChainクラス。
順伝播関数は順伝播を行い、そのコストを返します。コスト関数には平均二乗誤差を用いています。
最適化モデルにはAdamというアルゴリズムを採用しています(説明省略)。
for i in range(0,1000):
model.zerograds()
loss = forward(x, y, model)
loss.backward()
optimizer.update()
print loss.data
学習処理です。
zerograds()で勾配を0に初期化し、forward()で損失関数を計算します。
lossはVariable型であり、backward()を呼ぶと、誤差逆伝搬法によって勾配を計算します。
そして、optimizer.update()によって、重みとバイアスを更新します。
なお、この一連の処理は以下のように置き換えることができます。
optimizer.update(forward, x, y, model)

以上です。

x^Axは二次形式であり、二次形式は固有ベクトルによる基底変換と対角化で標準形にすることができる。
二次形式の標準形が最小値となるのはxがAの最小固有値に対する固有ベクトルの場合である。
なので、x^Axを最小化するxはAの最小固有値に対する固有ベクトルである。

x^Ax
=(Ux')^AUx'
=x'^U^AUx'
=x'^Λx (Λは固有値を対角成分に持つ行列)
=Σλix'i^2
>=Σλ1x'1^2

等号となるときにx^Axは最小となり、これはx'=(1,0,...,0)^の時である。
x=Ux'なので、x=u1の時である。
つまり、x^Axが最小となるのは、x=u1の時であり、これはAの最小固有値に対する固有ベクトルである。

「Ax=0を最小二乗法で解く」というのはこれと同じである。
Ax=0より、x^A^Ax=0となる。
これはx^A^Axを最小化するxを求める問題であり、xはA^Aの最小固有値に対する固有ベクトルとなる。

Unityでオセロ①(http://akasuku.blog.jp/archives/66717359.html)の続きです。
今回は単純なAIを実装しました。

クラス設計は以下のようにしました。
無題

前回の記事では黒番と白番の両方が人間でしたが、今回は人間とAIが交互に打ちます。
なので、PlayerBaseクラスを継承する形でHumanPlayerクラスとMachinePlayerクラスを作り、GameScriptクラスが持っていた「プレイヤーが石を置く」という役割をこれらのクラスに委譲しました。
さらに、これらのクラスからGridScriptクラスにアクセスする必要があったので、GameScriptクラスがBoardScriptクラスを保持する設計に変更し、PlayerBaseクラスからBoardScriptクラスへ関連を持たせました。

・PlayerBaseクラス
using UnityEngine;
using System.Collections;

abstract public class PlayerBase : MonoBehaviour
{
protected BoardScript boardScript_;
protected bool isBlack_;

public void SetBoardScript(BoardScript boardScript)
{
boardScript_ = boardScript;
}

public void IsBlack(bool isBlack)
{
isBlack_ = isBlack;
}

public abstract bool Play();

// Use this for initialization
void Start()
{
}

// Update is called once per frame
void Update()
{
}
}
・HumanPlayerクラス
using UnityEngine;
using System.Collections;

public class HumanPlayer : PlayerBase {

// タップを検知し、GridScriptを取得する
bool DetectTap(out GridScript gridScript)
{
gridScript = null;
if (Input.GetMouseButtonDown(0)) // タップ検知
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit = new RaycastHit();
if (Physics.Raycast(ray, out hit))
{
GameObject obj = hit.collider.gameObject; // タップしたGameObjectを取得
gridScript = obj.GetComponent<GridScript>(); // GridScriptを取得
return true;
}
}
return false;
}

public override bool Play()
{
// タップ検知
GridScript gridScript;
if (DetectTap(out gridScript))
{
// グリッドをタップしており、かつ、そこに石が無いなら
if (gridScript && !gridScript.GetStone())
{
// そこに石を置けるなら
if (gridScript.JudgeStonePutable(isBlack_))
{
// 石を置く
gridScript.PutStone(isBlack_);

// ひっくり返す
gridScript.TurnStone(isBlack_);

return true;
}
}
}
return false;
}

// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

}
}
・MachinePlayerクラス
MachinePlayerクラスは各グリッドに石を置けるか否かを探索し、ラスタ順で最も最初のグリッドに石を置きます。
using UnityEngine;
using System.Collections;

public class MachinePlayer : PlayerBase
{
void SearchPutableGrid(out bool[] isPutableGrid)
{
int colNum = boardScript_.GetColNum();
int rowNum = boardScript_.GetRowNum();
isPutableGrid = new bool[colNum * rowNum];

for (int r = 0; r < rowNum; ++r)
{
for (int c = 0; c < colNum; ++c)
{
GridScript gridScript = boardScript_.GetGrid(c, r).GetComponent<GridScript>();
if (!gridScript.GetStone() && gridScript.JudgeStonePutable(isBlack_))
{
isPutableGrid[r * colNum + c] = true;
}
else
{
isPutableGrid[r * colNum + c] = false;
}
}
}
}

bool SelectGrid(out GridScript gridScript)
{
bool[] isPutableGrid;
SearchPutableGrid(out isPutableGrid);

int colNum = boardScript_.GetColNum();
int rowNum = boardScript_.GetRowNum();
for (int r = 0; r < rowNum; ++r)
{
for (int c = 0; c < colNum; ++c)
{
int index = r * colNum + c;
if (isPutableGrid[index])
{
gridScript = boardScript_.GetGrid(c, r).GetComponent<GridScript>();
return true;
}
}
}
gridScript = null;
return false;
}

public override bool Play()
{
// 石を置くグリッドを選ぶ
GridScript gridScript;
SelectGrid(out gridScript);
if (!gridScript) Application.Quit();

// 石を置く
gridScript.PutStone(isBlack_);

// ひっくり返す
gridScript.TurnStone(isBlack_);

return true;
}

// Use this for initialization
void Start()
{

}

// Update is called once per frame
void Update()
{
}
}
・GameScriptクラス
using UnityEngine;
using System.Collections;

public class GameScript : MonoBehaviour {

public GameObject gridPrefab_, blackStonePrefab_, whiteStonePrefab_;
BoardScript boardScript_;
PlayerBase[] players_;
bool isBlackTurn_;

// Use this for initialization
void Start () {
// BoardScriptクラスの生成
boardScript_ = new BoardScript();
boardScript_.SetGridPrefab(gridPrefab_);
boardScript_.SetBlackStonePrefab(blackStonePrefab_);
boardScript_.SetWhiteStonePrefab(whiteStonePrefab_);
boardScript_.MakeGrids();

// Playerクラスの生成
players_ = new PlayerBase[] {
new HumanPlayer(),
new MachinePlayer(),
};
players_[0].IsBlack(true);
players_[1].IsBlack(false);
for (int i = 0; i < 2; ++i)
{
players_[i].SetBoardScript(boardScript_);
}

isBlackTurn_ = true;
}

// Update is called once per frame
void Update () {
int playerNo = isBlackTurn_ ? 0 : 1;
if (players_[playerNo].Play())
{
isBlackTurn_ = !isBlackTurn_;
}
}
}

現状の実装だと、人間が黒石を押すと、その次の瞬間には白石が置かれてしまいます。
これの修正とAIを賢くするのが次の記事になるかと思います。

↑このページのトップヘ