忍び歩く男 - SLYWALKER

大阪のこっそりPHPer

PHP FANN (Fast Artificial Neural Network) を使って儲けてみる

f:id:slywalker:20150224124759j:plain

データ解析を勉強するにあたって、何かモチベーションになるものはないかと思っていました。そんなとき、「儲かったらいいんじゃね?」との考えにいたりました。

そこでお題にえらんだものが「競馬予想」です。とんでもねーなと思っていたところ、データ解析のお題としては「金融」とならんでベタのお題ということが判明。入力となるデータと答えの値がはっきりしているので、お題として適切なんだそうです。

今回は、かれこれ20年前に大学でかじったニューラルネットワークを使ってのディープラーニング(かっこいい響き)で予測してみました。

前置き

ニューラルネットワークに詳しくありません。学生のとき、まじめに勉強していればよかったと後悔してる状態です。

根っからのPHPerなのでPHPを使います。PHPにはPHP-FANNというのがPECLにあるのでそれを使います。

PHP: FANN - Manual

概要はこのスライドが参考になりました。

PHP による機械学習

FANNについての日本語ドキュメントはこちらをみさせていただきました。

Momma's Wiki: FANN - Fast Artificial Neural Network Librar...

インストール

HomeBrewを使ってインストールします。PHPは5.6がインストールされている前提です。

$ brew install homebrew/science/fann
$ pecl install fann

サンプルで動作テスト

PHP: XOR training - Manual

こちらのサンプルコードをコピペして、まず動作するかを確認します。

入力データ

幸い競馬では自力で指数化しなくても、世の中に「なんちゃら指数」というものがあふれています。これをかき集めて、勝ち馬のパターンを抽出しようと思いました。(競馬新聞のデータから、おっさんが勝ち馬を赤ペンで印をつけるイメージ)

データ集めにはこれ

fabpot/goutte - Packagist

$ php composer.phar require fabpot/goutte:~2.0

PHPに優秀なクローラーが登場しています。

入力データの規格化

今回の仕様では、あるレースに出馬する1頭の各指数のパターンを解析して、3着以内の確率を求めます。

指数データというのは、JRAに登録されているすべての馬の中でどのくらいの能力値といったものなので、各レース毎にそのレースでどのくらいの能力値かといったように規格化します。規格化のためには偏差値を使用しました。

PHPで偏差値を算出するには、以下の拡張をインストールします。

$ brew install php56-stats

偏差値の算出例

<?php

$values = [1, 2, 3, 4, 5];

$std = stats_standard_deviation($values);
$avg = array_sum($values) / count($values);

$results = [];
foreach ($values as $value) {
    $results[] = 10 * ($value - $avg) / $std + 50;
}

var_export($results);

?>

array (
  0 => 35.857864376269049,
  1 => 42.928932188134524,
  2 => 50,
  3 => 57.071067811865476,
  4 => 64.142135623730951,
)

過去データの学習

ニューラルネットワークに過去データを学習させます。試行錯誤の結果、評価関数は FANN_ELLIOT_SYMMETRIC がよかったです。一般的には FANN_SIGMOID_SYMMETRIC が使われることが多いみたいです。

入力データの範囲は一般的に[-1から1]らしく、今回は[0から1]となっています。また答えは[-1,0,1]を与えることが多く、今回は[0,1](5着以内は1、それ以外は0)としました。

学習回数は、MSEの推移をみながら決定しました。

<?php

$layers = [3, 2, 1];
$ann = fann_create_standard_array(count($layers), $layers);

if ($ann) {
    fann_set_activation_function_hidden($ann, FANN_ELLIOT_SYMMETRIC);
    fann_set_activation_function_output($ann, FANN_ELLIOT_SYMMETRIC);

    $sql = 'SELECT index1, index2, index3, order FROM keiba_indexes';
    for ($i = 0; $i < 10; $i++) { 
        fann_reset_MSE($ann);

        $query = $this->query($sql);
        while ($row = $query->fetch()) {
            $order = $row['order'];
            unset($row['order']);

            $out = ($order <= 5) ? [1] : [0];
            fann_train($ann, array_values($row), $out);
        }

        $mse = fann_get_MSE($ann);
        echo $mse . PHP_EOL;

        $query->closeCursor();
        fann_save($ann, 'keiba_float.net');
    }
}

?>

予想データの解析

金曜の夜、土曜の夜になると次の日のレースの出馬表が発表されます。その後指数データも公開されるためので入手して規格化しておきます。

あとは、学習によって得られた関数で数値を算出します。

<?php

$ann = fann_create_from_file('keiba_float.net');

if ($ann) {
    $results = [];
    $sql = 'SELECT index1, index2, index3 FROM keiba_indexes';
    $query = $this->query($sql);
    while ($row = $query->fetch()) {
        $results[] = fann_run($ann, array_values($data));
    }

    var_export($results);
}

?>

算出された数値を降順で並び替えれば、勝つ確率の高い順になります。

気になる成果は?

まぁ、単純に数値を出しただけでは儲かるわけがありません。 購入レースのフィルタリングや、購入券種の検討、資金管理ルールが必要でした。

では、ジャーン!

日程 的中率 回収率
2/14,2/15 54.17% 207.28%
2/21,2/22 40.00% 122.70%

とりあえず、競馬には勝ってます。なぜか残高が増えてませんが…

欲望駆動開発でモノはできたのですが、購入の際は機械的に欲望を断ち切って行ったほうがよいようですね(涙)