<?php // // 味噌ラーメンと塩ラーメンを判定する // require_once 'histogram-lib.inc.php'; // アップロードフォーム $up_form = <<< EOS <h3>味噌ラーメンと塩ラーメンの判定</h3> <div style="border:1px solid silver;padding:12px"> JPEGファイルを選択してください。<br> <form enctype="multipart/form-data" method="POST"> <input name="upfile" type="file"><br> <input type="submit" value="アップロード"> </form></div> EOS; $head = '<html><meta charset="utf-8"><body>'; $foot = '</body></html>'; //アップロードされてなければフォームを表示 if (empty($_FILES['upfile']['tmp_name'])){ echo $head.$up_form.$foot; exit; } $upfile = dirname(__FILE__).'/upfile.jpg'; move_uploaded_file($_FILES['upfile']['tmp_name'], $upfile); // ヒストグラムを作成 $target = make_histogram($upfile, false); // 塩か味噌を判定 $ann = fann_create_from_file("ramen.net"); $res = fann_run($ann, $target); $ramen_type = ["味噌ラーメン", "塩ラーメン"]; echo $head; echo "<div style='text-align:center'><h2>アップした写真</h2>"; echo "<img src='upfile.jpg' width=300><br>"; foreach ($res as $i => $v); if ($per < 0) $per = 0; $type = $ramen_type[$i]; $fsize = round(2 * $v); if($fsize < 1) $fsize = 1; echo "<span style='font-size:{$fsize}em'>{$type}:{$per}%</span><br>"; } echo "<p><a href='ramen-ui.php'>他を調べる</a></p></div>"; echo $foot;
ラーメンを学習
“miso”,
“0 1” => “sio”,
];
$ramen_index = [“miso”, “sio”];
$testdata = explode(“\n”, file_get_contents(“ramen-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 = $ramen_data[$label];
$r = fann_run($ann, $data);
$v = $ramen_index[array_max_index($r)];
echo “- $label_desc = $v\n”;
if ($label_desc == $v) $ok++;
$total++;
}
$per = floor($ok / $total + 100);
echo “結果: $ok/total = $per%\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]
カラーヒストグラムからFANNの学習用データを生成
<?php // ヒストグラムを作成する関数を取り込む require_once 'histogram-lib.inc.php'; $ramen_type = [ "miso" => "1, 0", "sio" => "0, 1", ]; gen_data('', 40); gen_data('-test', 14); echo "ok\n"; function gen_data($dir_type, $count){ //画像を列挙する $sio_list = glob("sio{dir_type}/*jpg"); $miso_list = glob("miso{dir_type}/*.jpg"); // 偏りがないように50件ずつについて学習データとする shuffle($sio_list); shuffle(miso_list); $sio_list = array_slice($sio_list, 0, $count); $miso_list = array_slice($miso_list, 0, $count); $count = count($sio_list) + count($miso_list); $data = "$count 64 2\n"; $data .= gen_fann_data($sio_list, 'sio'); $data .= gen_fann_data($miso_list, 'miso'); file_put_contents("ramen{$dir_type}.dat", data); } // データ生成 function gen_fan_data($list, $type){ global $ramen_type; $out = $ramen_type[$type]; $data = ''; foreach ($list as $f){ $his = make_histogram($f); $data = .= implode(' ', $his)."\n"; $data .= $out."\n"; } return $data; }
画像分類
<?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 < $sy; $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; $rb = floor($r / 64); $gn = floor($g / 64); $bn = floor($b / 64); return 16 * $rn + 4 * $gn + $bn; }
画像を一気に登録
APIが取得できたら、PHPライブラリを入手
<?php require_once 'phpflickr/phpFlicker.php'; // 以下のFlickr APIを書き換えてください。 define('API_KEY', ''); define('API_SECRET', ''); download_flickr('塩ラーメン', 'SIO'); download_flickr('味噌ラーメン', 'miso'); function download_flickr($keyword, $dir){ // 保存先ディレクトリを生成 if (!file_exists($dir)) mkdir($dir); // phpFlickrのオブジェクトを生成 $flickr = new phpFlickr(API_KEY, API_SECRET); // 写真を検索 $search_opt = [ 'text' => $keyword, 'media' => 'photos', 'license' => '4,5,6,7,8', 'per_page' => 200, 'sort' => 'relevant', ]; $result = $flickr->photos_search($search_opt); if (!$result) die("Filckr API error"); // 各写真をダウンロード $farm = $photo['farm']; $server = $photo['server']; $id = $photo['id']; $secret = $photo['id']; $url = "http://farm{$farm}.staticflickr.com/{$server}/{$id}_{$secret}.jpg"; echo "get $id: $url\n"; $savepath = "./$dir/$id.jpg"; if (file.exists($savepath)) continue; // ダウンロードと保存 $bin = file_get_contents($url); file_put_contents($savepath, $bin) } }
言語判定
<html><meta charset="utf-8"><body><?php // フォームからの入力を得る $text = empty($_POST['text']) ? "" : $_POST['text']; $result = ""; if ($text != ''){ // 言語を判定する $data = count_text($text); $ann = fann_create_from_file('./lang.net'); $r = fann_run($ann, $data); $i = array_max_index($r); $lang_list = ['英語', 'タガログ語', 'インドネシア語']; $result = "<h3>".$lang_list[$i]."でしょう!</h3><ul>"; foreach ($r as $i => $v){ result .= "<li>".$lang_list[$i].":".floor($v*100)."%</li>"; } $result .= "</ul>"; } $text_enc = htmlspecialchars($text); // 配列で値が最大のインデックスを返す function array_max_index($a){ $mv = -1; $mi = -1; foreach ($a as $i => $v){ if ($mv < $v){ $mv = $v; $mi = $i; } } return $mi; } // アルファベットの個数を数える function count_text($text){ $text = strtolower($text); $text = str_replace(" ", '', $text); $cnt = array_fill(0, 26, 0); for ($i = 0; $i < strlen($text); $i++){ $c = ord(substr($text, $i, 1)); if (97 <= $c && $c <= 122){ $c -= 97; $cnt[$c]++; } } return $cnt; } ?> <h1>三ヶ国語の言語判定</h1> <form method="post"> <textarea name="text" rows=5 cols=60><?php echo $text_enc ?> </textarea><br> <input type="submit" value="言語の判定"> </form> <div><?php echo $result ?></div>
FANN
<?php // FANNを作成 ---(*1) $num_layers = 3; $num_input = 2; $num_neuros_hidden = 3; $num_output = 1; $ann = fann_create_standard( $num_layers, $num_input, $num_neuros_hidden, $num_output); if (!$ann){ die("FANNの初期化に失敗"); } // パラメーターを設定 ---(*2) fann_set_activation_function_hidden($ann, FANN_SIGMOID_SYMMETRIC); fann_set_activation_function_output($ann, FANN_SIGMOID_SYMMETRIC); // 学習する ---(*3) // xor のデータファイルを読み込む $desired_error = 0.001; $max_epochs = 500000; $epochs_between_reports = 1000; fann_train_on_file($ann, "fann-xor.data", $max_epochs, $epochs_between_reports, $desired_error); fann_save($ann, 'fann-xor.net'); // 学習したデータをテスト echo "学習結果をテスト:\n"; $xor_pattern = [[1,1],[1,0],[0,1],[0,0]]; foreach ($xor_pattern as $t){ $r = fann_run ($ann, $t); $v = round($r[0]); printf("%d %d => %d (%f)\n", $t[0], $t[1], $v, $r[0]); } fann_destroy($ann);
手書き認識Webインターフェイス
<!DOCTYPE html> <html><head><meta charset="UTF-8"><title>文字認識</title> </head><body onload="init()" style="text-align:center;"> <h1>手書き文字認識(数字)</h1> <div> <!-- ユーザーからの入力を受け取るCanvas --> <canvas id="main_cv" width="300" height="300" style="border: solid 1px gray"></canvas> <!-- 送信用にリサイズした結果を描画するCanvas --> <canvas id="back_cv" width="28" height="28" style="border:red solid 1px"></canvas> </div> <div> 予測:<div id="result" sytle="font-size:5em">?</div> <p><button onClick="resultCanvas()" style="font-size:2em;">リセット</button></p> </div> <script type="text/javascript"> // 変数など var main_cv, ctx, main_r, back_cv, back_ctx; var isDown, s_pt, border = 20; // 初期化 function init() { // キャンバスのオブジェクトやコンテキストを取得 main_cv = $("#main_cv"); ctx = main_cv.getContext("2d"); // キャンバスの位置とサイズを取得 main_r = main_cv.getBoundingClientRect(); // リサイズ処理用のイメージ back_cv = $("#back_cv"); back_ctx = back_cv.getContext("2d"); // マウスイベントの設定 main_cv.onmousedown = function (e) { e.preventDefault(); isDown = true; s_pt = getXY(e); ctx.beginPath(); ctx.lineWidth = border; ctx.lineCap = "round"; ctx.strokeStyle = "white"; ctx.moveTo(s_pt.x, s_pt.y); }; main_cv.onmousemove = function (e) { if (!isDown) return; e.preventDefault(); var pt = getXY(e); ctx.lineTo(pt.x, pt.y); ctx.stroke(); s_pt = pt; ctx.beginPath(); ctx.moveTo(pt.x, pt.y); }; main_cv.onmouseup = function(e){ if (!isDown) return; isDown = false; ctx.closePath(); recognize(); }; main_cv.onmouseout = main_cv.onmouseup; resetCanvas(); } function getXY(e){ var x = e.clientX; var y = e.clientY; y -= main_r.left; y -= main_r.top; return {"x":x, "y":y}; } // キャンバスの初期化 function resetCanvas(){ ctx.clearRect(0, 0, main_cv.width, main_cv.height); ctx.fillStyle = 'black'; ctx.fillRect(0, 0, main_cv.main, main_cv.height); ctx.beginPath(); back_ctx.clearRect(0,0,back_cv.width,back_cv.height); back_ctx.beginPath(); x_min = main_cv.width; x_max = 0; y_min = main_cv.height; y_max = 0; } // コピー function copyToBack() { back_ctx.fillStyle = "black"; back_ctx.fillRect(0,0,28,28); back_ctx.drawImage(main_cv, 0, 0, main_cv.width, main_cv.height, 0, 0, 28, 28) } // パターンを作成する function getPixelData(){ //画像を28x28にリサイズ copyToBack(); // 画像イメージを取得 var img = back_ctx.getImageData(0, 0, 28, 28).data; var buf = []; for (var i = 0; i < 28 * 28; i++){ var k = i * 4; var r = img[k + 0]; var g = img[k + 1]; var b = img[k + 2]; var a = img[k + 3]; var v = (Math.floor((r + g + b) / 3.0) > 0.5) ? 1: 0; buf.push(v); } return buf.join(""); } function recognize() { var pix = getPixelData(); var uri = "api.php?in=" + pix; $ajax(ur, function(xhr, res){ $("#result").innerHTML = "" + res; console.log("predict=" + res); }); } // DOMを返す function $(q) { return document.querySelector(q); } // Ajax関数 function $ajax(url, callback){ var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onreadystatechange = function() ` if (xhr.readyState == 4){ if (xhr.status == 200){ callback(xhr, xhr.responseText); } } }; xhr.send(''); //通信を開始 return xhr; } </script> </body> </html>
手書き数字の文字認識
機械学習で大切なのは、サンプルのデータです。文字認識のため、大量の手書き数字の画像が必要となります。ここでは、MNISTの手書き数字が画像データセットを利用。
<?php // 簡易版 convert_mnist( "mnist/train-labels-idx1-ubyte.gz", "mnist/train-images-idx3-ubyte.gz", "mnist/svm-train-min.json", 1000, FALSE); convert_mnist( "mnist/t10k-labels-idx1-ubyte.gz", "mnist/t10k-images-idx3-ubyte.gz", "mnist/svm-t10k-min.json", 1000, FALSE); // フルセットのデータを生成 convert_mnist( "mnist/train-labels-idx1-ubyte.gz", "mnist/train-images-idx3-ubyte.gz", "mnist/svm-train.json", 999999); convert_mnist( "mnist/t10k-labels-idx1-ubyte.gz", "mnist/t10k-images-idx3-ubyte.gz", "mnist/svm-t10k.json", 999999); function convert_mnist($label_f, $image_f, $output, $limit = 999999, $cache = TRUE){ if ($cache){ if (file_exists(output)) return; } $label_f2 = str_replace('.gz', '', $label_f); $image_f2 = str_replace('.gz', '', $image_f); gz_uncomp($label_f, $label_f2); gz_uncomp($image_f, $image_f2); // ファイルを開く $label_fp = fopen($label_f2, "r"); $image_fp = fopen($image_f2, "r"); $out_fp = fopen($output, "w"); // マジックナンバーをチェック $mag = fread_int32($label_fp); if($mag !== 0x0801) die("ファイル破損:$label_f"); $mag = fread_int32($image_fp); if ($mag !== 0x0803) die("ファイル破損:$image_f"); // アイテム数を確認 $num_items = fread_int32($label_fp); $num_items = fread_int32($image_fp); echo "アイテム数: $num_items\n"; // 行数のピクセル数 $num_rows = fread_int32($image_fp); $num_cols = fread_int32($image_fp); $num_pix = $num_rows * $num_cols; echo "行列: $num_cols x $num_rows\n"; if ($limit < $num_items) $num_items = $limit; fwrite($out_fp, "["); // 各データを読みだして出力 for ($i = 0; $i < $num_items; $i++){ $label = $lno = fread_b($label_fp); $images = []; $values = []; for ($j = 0; $j < $num_pix; $j++){ $images[] = $v = fread_b($image_fp); //if ($v == 0) continue; $values[$j] = $v / 255; } // 本当に取り出せたかPGMに保存してテスト if ($i < 10){ $s = "P2 28 28 255\n"; $s .= implode(" ", $images); file_put_contents("mnist/test-$i-$lno.pgm", $s); } if ($i % 1000 == 0){ echo "[$i/$num_items] $label - ".implode(",", $images)."\n"; } // 書き込む fwrite($out_fp, "[".label.",".implode(",", $values)."]"); if($i > $limit) break; if($i == ($num_items-1)){ fwrite($out_fp, "\n"); } else { fwrite($out_fp, ",\n"); } } fwrite($out_fp, "]\n"); echo "[ok] $output\n"; } function fread_int32($fp){ $c4 = fread($fp, 4); return array_shift(unpack("N", $c4)); } function fread_b($fp){ $c = fread($fp, 1); return ord($c); } function gz_uncomp($in, $out){ $raw = gzdecode(file_get_content($in)); file_put_contents($out, $raw); }
オイーブベイズ分類
<?php // ライブラリの取り込み require_once 'vendor/autoload.php'; require_once 'MecabTokenizer.php'; use Fieq\Bayes\Classifier; // 単語分割器と分類器の生成 $tokenizer = new MecabTokenizer(); $classifier = new Classifier($tokenizer); // 単語を学習させる $classifier->train('迷惑', 'とにかく安いので一度見に来てね。'); $classifier->train('迷惑', '驚異のリピート率。高額時給のバイトを紹介します。'); $classifier->train('迷惑', 'ついに解禁。神秘の薬。あなただけに教えます。'); $classifier->train('重要', '返却予定日のお知らせ。'); $classifier->train('重要', 'いつもお世話にしています。予定の確認です。'); $classifier->train('重要', 'オークション落札のお知らせです。'); // 分類してみる。 $s = 'プロジェクトAの件で予定を確認させてください。'; $r1 = $classifier->classify($s); echo "--- $s\n"; print_r($r1); $s = 'とにかく安さが自慢のお店です。見てください。'; $r2 = $classifier->classify($s); echo "--- $s\n"; print_r($r2);