$yuzu->log();

技術ネタなど。

【機械学習】ニューラルネットワークを利用して自分の好みの女性を学習させる

ニューラルネットワークとは?

脳内には多数のニューロンと呼ばれる神経細胞があります。それぞれのニューロンは、他のニューロンから信号を受け付け,他のニューロンへ信号を受け渡しています。脳は、この信号の流れによって、様々な情報処理を行っています。 この仕組みをコンピュータ内に実現しようとしたものがニューラルネットワークです。

たとえば、入力層に学習させたいデータの特徴を入力します。 すると、入力層、中間層、出力層を通って、結果が出力されます。

http://cdn-ak.f.st-hatena.com/images/fotolife/u/ura_ra/20111026/20111026235506.png

ニューロンのモデル化

ニューロンの基本的な働きは信号の入力と出力だけです。 ここで重要になるのが、入力された信号をそのまま出力するのではなく一定の閾値を超えたときのみ出力(発火)されます。

ニューロンにはそれぞれ信号伝達の効率がバラバラです。そこでそれぞれの入力に対し結合荷重を設定します。 そしてその重み付きの入力の総和が各ニューロンに設定されている閾値を超えたとき、発火したとして、他ニューロンに信号を送ります。 コンピュータ上での実現のため、簡単化のため0を信号が無い、1を信号がある状態とします。

数式で表してみます。

 \displaystyle

\( i \)番目のニューロンからの入力信号を\( x_i \)、それぞれの荷重を\( w_i \)とすると他ニューロンからの入力信号の総和は、

{ \displaystyle
 \sum_{i=1}^{n} X_iW_i
}

となります。 入力信号を受けっとったニューロンは、その入力値が一定の閾値\( θ \)を超えたときに発火するので、次のように表せます。

{ \displaystyle
 y = f_k(\sum_{i=1}^{n} X_iW_i - θ)
}

ここで関数 \( f_k \)は入力値に対し発火するかしないかなので1か0を返却する関数です。このような関数は、ステップ関数またはヘビサイド関数と呼ばれています。関数は次のようになります。

{ \displaystyle
 y = f_k(x) = 1 or 0
}       

これがニューロンモデルです。

FANNについて

PHPニューラルネットワークを扱うためにはFANNを使う必要があります。

Fast Artificial Neural Network Library (FANN)

FANNはC言語で書かれていて主に次の特徴があります。

  • 誤差逆伝播法(BackProp)による学習を主にサポート
  • 動作がかなり高速
  • 最短3ステップで簡単に機械学習(リソース作成、学習、実行)
  • 英語ドキュメントは豊富である

MacOSXへFANNをインストール

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

php.iniに下記を追記

extension=fann.so

確認

php -i |grep fann

では次にFANNを使って自分の好みの女性を学習させてみましょう

自分の好みの女性かそうでないかを分類する

学習データを集める

まず教師データとして、大量の女性の画像が必要になります。 集め方はここでは触れませんが、僕は某ユニコーン企業のサイトから拝借させていただきました。

それぞれlikeとnopeというフォルダを用意し、手動で分類してください。それぞれ200枚ぐらい用意してみると良いかも。 更にテスト用にlike-testとnope-testというフォルダも用意しテスト用の画像としてそこにも20枚ぐらいいれておいてください。

学習データの作成

画像の分類はカラーヒストグラムを作成し、ニューラルネットワークで判定したいと思います。 カラーヒストグラムについては下記の記事を参照してください。

yuzurus.hatenablog.jp

カラーヒストグラムを作成するプログラム(histogram-ib.inc.php)

<?php
function make_histogram($path, $debug = true) {
  if ($debug) {
    echo "histogram: $path\n";
  }
  $im_big = imagecreatefromjpeg($path);
  $sx_big = imagesx($im_big);
  $sy_big = imagesy($im_big);
  $sx = 256;
  $sy = 192;
  $im = imagecreatetruecolor($sx, $sy);
  imagecopyresampled($im, $im_big, 0, 0, 0, 0, $sx, $sy, $sx_big, $sy_big);
  $his = array_fill(0, 64, 0);
  for ($y = 0;$y < $sy;$y++) {
    for ($x = 0;$x < $sx;$x++) {
      $rgb = imagecolorat($im, $x, $y);
      $no = rgb2no($rgb);
      $his[$no]++;
    }
  }

  $pixels = $sx * $sy;
  for ($i = 0; $i < 64; $i++) {
    $his[$i] = $his[$i] / $pixels;
  }
  imagedestroy($im_big);
  imagedestroy($im);
  return $his;
}

function rgb2no($rgb) {
  $r = ($rgb >> 16) & 0xFF;
  $g = ($rgb >> 8) & 0xFF;
  $b = $rgb & 0xFF;
  $rn = floor($r / 64);
  $gn = floor($g / 64);
  $bn = floor($b / 64);
  return 16 * $rn + 4 * $gn + $bn;
}

ヒストグラムデータからFANNの学習用データを生成するプログラム(gen-data.php)

<?php
require_once 'histogram-lib.inc.php';

$favorite_type = [
  "like" => "1 0",
  "nope" => "0 1",
];

gen_data("", 40);
gen_data("-test", 14);

echo "ok\n";

function gen_data($dir_type, $count) {
  $data = '';
  $types = ['like', 'nope'];
  $cnt = 0;
  foreach ($types as $type) {
    $type_list = glob("{$type}{$dir_type}/*jpg");
    shuffle($type_list);
    $type_list = array_slice($type_list, 0, $count);
    $cnt += count($type_list);
    $data .= gen_fann_data($type_list, $type);
  }
  $data = "$cnt 64 2\n" . $data;
  file_put_contents("type{$dir_type}.dat", $data);
}

function gen_fann_data($list, $type) {
  global $favorite_type;
  $out = $favorite_type[$type];
  $data = '';
  foreach ($list as $f) {
    $his = make_histogram($f);
    $data .= implode(' ', $his) . "\n";
    $data .= $out . "\n";
  }
  return $data;
}

これを実行するとtype.datとtype-test.datというデータが生成されます。

$ php gen-data.php
histogram: like/0228.jpg
histogram: like/0060.jpg
histogram: like/0295.jpg
*
*
*
ok

生成された学習用データを実際にFANNで学習して、テストデータで判定してみます(train.php)

<?php
$num_layers = 3;
$num_input = 64;
$num_neuros_hidden = 3;
$num_output = 2;
$ann = fann_create_standard(
  $num_layers, $num_input,
  $num_neuros_hidden, $num_output
);

if (!$ann) {
  die("FANNの初期化に失敗");
}

fann_set_activation_function_hidden($ann, FANN_SIGMOID_SYMMETRIC);
fann_set_activation_function_output($ann, FANN_SIGMOID_SYMMETRIC);

echo "学習します\n";
$desired_error = 0.0001;
$max_epochs = 500000;
$epochs_between_reports = 1000;
fann_train_on_file($ann, "type.dat", $max_epochs, $epochs_between_reports, $desired_error);
fann_save($ann, 'type.net');

echo "テストします\n";
$favorite_data = [
  "1 0" => 'like',
  "0 1" => 'nope',
];
$favorite_index = ["like", "nope"];
$testdata = explode("\n", file_get_contents("type-test.dat"));
array_shift($testdata);
$total = $ok = 0;
while ($testdata) {
  $s = array_shift($testdata);
  if ($s == "") continue;
  $data = explode(" ", $s);
  $label = array_shift($testdata);
  $label_desc = $favorite_data[$label];
  $r = fann_run($ann, $data);
  $v = $favorite_index[array_max_index($r)];
  echo "- $label_desc = $v\n";
  if ($label_desc == $v) $ok++;
  $total++;
}

$pre = floor($ok / $total * 100);
echo "結果: $ok/$total = $pre%\n";

function array_max_index($a) {
  $mv = -1;
  $mi = -1;
  foreach ($a as $i => $v) {
    if ($mv < $v) {
      $mv = $v;
      $mi = $i;
    }
  }
  return $mi;
}

実行してみましょう

$ php train.php
学習します
テストします
- like = like
- like = like
- like = like
- like = like
*
*
*
結果: 21/28 = 75%

ってことでわりとまずまずな数値が出たのではないでしょうか?

簡単にWEBのインターフェイスも作ってみました。

f:id:yuzurus:20161006004626p:plain

f:id:yuzurus:20161006004638p:plain

なるほどな結果になりましたね!