手書き数字の文字認識

機械学習で大切なのは、サンプルのデータです。文字認識のため、大量の手書き数字の画像が必要となります。ここでは、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, "&#91;");
        
        // 各データを読みだして出力
        for ($i = 0; $i < $num_items; $i++){
            $label = $lno = fread_b($label_fp);
            $images = &#91;&#93;; $values = &#91;&#93;;
            for ($j = 0; $j < $num_pix; $j++){
                $images&#91;&#93; = $v = fread_b($image_fp);
                //if ($v == 0) continue;
                $values&#91;$j&#93; = $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 "&#91;$i/$num_items&#93; $label - ".implode(",", $images)."\n";
            }
            // 書き込む
            fwrite($out_fp,
                   "&#91;".label.",".implode(",", $values)."&#93;");
            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);

average hashで類似画像を検索

<?php
    include_once 'ahash-common.inc.php';

    define("PHOTO_PATH", "images");

    $up_form = <<< EOS
    <h3>類似画像検索(Average Hash)</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 = makeAHash($upfile, false);
    $files = enumJpeg(PHOTO_PATH);
    $result = checkHash($target, $files);
    $top6 = array_slice($result, 0, 6);
    echo $head;
    echo "<div style='text-align:center'><h2>アップした写真</h2>";
    echo "<img src='upfile.jpg' width=300>";
    echo "<h2>以下類似する順番に表示</h2>";
    foreach($top6 as $f){
        $path = $f['path'];
        $value = $f['value'];
        echo "<img src='path' width=300 alt='$value:$path'>";
    }
    echo "</div>".$foot;

    // hashから類似度を算出
    function checkHash($target, $files){
        $list = [];
        foreach ($files as $file){
            $hash = makeAHash($file);
            $match = 0;
            for ($i = 0; $i < strlen($target); $i++){
                if (substr($target,$i,1) == substr($hash,$i,1)) $match++;
            }
            $list&#91;&#93; = &#91;
            "path" => $file,
            "value" => $match,
            ];
        }
        usort($list, function ($a, $b){
              return $b['value'] - $a['value'];
              });
        return $list;
    }

average hashを作成

<?php
// Average Hashを調べるライブラリ
    function makeAHash($file, $cache = TRUE){
        $hashfile = preg_replace('/\.(jpg|jpeg)$/', '.hash', $file);
        if($cach) {
            if (file_exists($hashfile)){
                $v = file_get_contents($hashfile);
                return $v;
            }
        }
        // (1) 16x16にリサイズ
        $sz = 16;
        $src = imagecreatefromjpeg($file);
        $sx = imagesx($src); $sy = imagesy($src);
        $des = imagecreatetruecolor($sz, $sz);
        imagecopyresized($des, $src, 0, 0, 0, 0, $sz, $sz, $sx, $sy);
        imagefilter($des, IMG_FILTER_GRAYSCALE);
        // (3)平均値を得つつ、配列に入れておく
        $pix = &#91;&#93;; $sum = 0;
        for ($y = 0; $y < $sz; $y++){
            for ($x = 0; $x < $sz; $x++){
                $rgb = imagecolorat($des, $x, $y);
                $b = $rgb & 0xFF;
                $sum += $b;
                $pix&#91;&#93; = $b;
            }
        }
        $ave = floor($sum / ($sz * $sz));
        // (4)2値化する
        $hash = '';
        foreach ($pix as $i => $v){
            $hash .= ($v >= $ave) ? '1' : '0';
            if ($i % 16 == 15) $hash .= "\n";
        }
    }

    function enumJpeg($path){
        $files = [];
        $fs = scandir($path);
        foreach ($fs as $f){
            if (substr($f, 0, 1) == ".") continue;
            $fullpath = $path.'/'.$f;
            if (is_dir($fullpath)){
                $files = array_merge($files, enumJpeg($fullpath));
                continue;
            }
            if (!preg_match('/\.(jpg|jpeg)$/i', $f)) continue;
            $files[] = $fullpath;
        }
        return $files;
    }

ヒストグラム

<?php
    include_one 'histogram-common.inc.php';

    define("PHOTO_PATH", "images");

    $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="upload">
    </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['tmp_name'], $upfile);

    $target = makeHistogram($upfile, false);
    $files = enumJpeg(PHOTO_PATH);
    $result = calcIntersection($target, $files);
    $top6 = array_slice($result, 0, 6);
    echo $head;
    echo "<div style='text-align:center'><h2>アップした写真</h2>";
    echo "<img src='upfile.jpg' width=300>";
    echo "<h2>以下類似する順番に表示</h2>";
    foreach ($top6 as $f){
        $path = $f['path'];
        $value = $f['value'];
        echo "<img src='path' width=300 alt='$value:$path'>";
    }
    echo "<div>".$foot;

    // ヒストグラムから類似度を計算
    function calcIntersection($target, $files){
        $histlist = [];
        foreach ($files as $file) {
            $hist = makeHistogram($file);
            $value = 0;
            for ($i = 0; $i < count($target); $i++){
                $value += min(intval($target&#91;$fi&#93;), intval($hist&#91;$i&#93;));
            }
            $histlist&#91;&#93; = &#91;
            "path" => $file,
            "value" => $value,
            ];
        }
        usort($histlist, function ($a, $b){
              return $b['value'] - $a['value'];
              });
        return $histlist;
    }

CAPTCHA

<?php
    session_start();

// ユーザーからの入力を取得
    $input = isset($_GET&#91;'input'&#93; ? $_GET&#91;'input'&#93;: "";
// CAPTCHAと入力フォームのタグを定義
    $captch = "<img src='genImage.php'>";
    $msg = "5文字のひらがなを入力してください。";
    $form = <<< END_OF_FORM
<form method="GET">
    <input type="text" name="input">
    <input type="submit" value="OK">
</form>
    END_OF_FORM;

//CAPTCHAのコードが入力されたときの処理
                   if (isset($_SESSION["CAPTCH"]) && $_SESSION["CAPTCH"] === $input) {
    $msg = "<h3>正解です!</h3><a href='form.php'>もう一度試す</a>";
    $captch = $form = "-";
                   } else {
                   if ($input != ""){
                   $msg = "間違い!もう一度、{$msg}";
            }
    }

// HTMLを出力
echo <<< END_OF_HTML
<html><head><meta charset="UTF-8"></head>
<body>
    <h1>CAPTCHA TEST</h1>
    <p>$captcha</p>
    <p>$msg</p>
    <p>$form</p>
    END_OF_HTML;

imageMagicを使って反転と回転を行う

<?php
// コマンドラインから画像を取得
    if (count($argv) <= 1){
        echo "no input"; exit;
    }
    $input = $argv&#91;1&#93;;
    $outfile = preg_replace('/\.(jpeg|jpg)$/', '-out.jpeg', $input);
    $cmpfile = preg_replace('/\.(jpeg|jpg)$/', '-cmp.jpeg', $input);

    $i = new Imagick();
    $i->readImage($input);
    $i->stripImage();
    $i->writeImage($cmpfile);
    echo " 回転前: $outfile\n";

    // 回転用に画像を処理
    $img = new Imagick();
    $img->readImage($input);
    // 画像の向きを自動回転
    img_auto_rotate($img);
    // 画像を保存
    $img->writeImage($outfile);
    echo " 回転後: $outfile\n";

    // 画像の向きを自動回転
    function img_auto_rotate(Imagick $img){
        switch ($img->getImageOrientation()){
            case Imagick::ORIENTATION_TOPLEFT:
                break;
            case Imagick::ORIENTATION_TOPRIGHT:
                $img->flopImage();
                break;
            case Imagick::ORIENTATION_BOTTOMRIGHT:
                $img->rotateImage("#000, 180");
                break;
            case Imagick::ORIENTATION_LEFTTOP:
                $img->flopImage();
                $img->rotateImage("#000", -90);
                break;
            case Imagick::ORIENTATION_RIGHTTOP:
                $img->rotateImage("#000", 90);
                break;
            case Imagick::ORIENTATION_RIGHTBOTTOM:
                $img->flopImage();
                $img->rotateImage("#000", 90);
                break;
            case Imagick::ORIENTATION_LEFTBOTTOM:
                $img->rotateImage("#000", -90);
                break;
            default:
                break;
        }
        $img->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
        return $img;
    }

マルコフ連鎖の実装

形態素解析をしてそれを辞書に登録し、辞書を元に作文を行います。

 3) array_shift($tmp);
            set_word($dic, $tmp);
            if ($w == "。 "){
                $tmp = ["@"]; continue;
            }
        }
        return $dic;
    }

// 辞書に単語を登録
    function set_word(&$dic, $tmp){
        $w1 = $tmp[0];
        $w2 = $tmp[1];
        $w3 = $tmp[2];
        if (empty($dic[$w1])) $dic[$w1] = [];
        if (empty($dic[$w1][$w2])) $dic[$w1][$w2] = [];
        if (empty($dic[$w1][$w2][$w3])) $dic[$w1][$w2][$w3] = 0;
        $dic[$w1][$w2][$w3]++;
    }

// 辞書から作文する
    function make_sentence($dic, $count){
        $ret = [];
        for ($i = 0; $i < $count; $i++){
            $top = $dic["@"];
            if (!$top) break;
            $w1 = choice_word($top);
            $w2 = choice_word($top[$w1]);
            $ret[] = $w1;
            $ret[] = $w2;
            for (;;) {
                $w3 = choice_word($dic[$w1][$w2]);
                $ret[] = $w3;
                if ($w3 == "。")break;
                $w1 = $w2;
                $w2 = $w3;
            }
        }
        return implode("", $ret);
    }

    function choice_word($o){
    if (!is_array($o)) return "。 ";
        $ks = array_keys($o);
        return $ks[mt_rand(0, count($ks)-1)];
    }

ASCIIコード表

<?php
// 特殊記号
$sp = &#91;'NULL','SOH','STX','ETX','EOT','ENQ','ACK','BEL','BS','HT','LF','VT',
        'FF','CR','SO','SI','DLE','DC1','DC2','DC3','DC4','NAL','SYN','ETV','CAN'm
    'EM','SUB','ESC','FS','GS','RS','US','SPC'&#93;;
    $sp&#91;127&#93; = 'DEL';

// header
    $html = "<tr><th></th>";
    for($j = 0; $j < 16; $j++){
    $html .= "<th>".sprintf("%X",$j)."</th>";
    }
    $html .= "</tr>";

// characters
    for ($i = 0; $i <= 7; $i++){
    $html .= "<tr><th>{$i}x</th>";
        for ($j = 0; $j < 16; $j++){
            $code = ($i * 16) + $j;
            $ch = isset($sp&#91;$code&#93;) ? $sp&#91;$code&#93; : chr($code);
            $html .= "<td>$ch</td>";
        }
        $html .= "</tr>";
    }

echo <<< EOS
<style>
    * { margin:0; padding:0; }
    table { margin: 12px; }
    td, th {
        font-size: 12px, padding: 4px;
    width: 32px; text-align: center;
        border-bottom: 1px solid silver;
        border-right: 1px solid silver;
    }
    th { color: white; background-color: blue; }
</style>
<table>
$html
</table>
    EOS;

月間カレンダーの描画

<html><body>
<style>
.cal td { border-bottom: 1px solid silver; padding: 4px; }
.cur { font-size: 14px; color: black; }
.oth { font-size: 9px; color: silver; }
.sun { color: red; }
</style>
<?php
// カレンダーの描写
    echo make_calendar(2018,2);

    function make_calendar($year, $month){
    // 月初めをセット
        $t = new DateTime();
        $t->setDate($year, $month, 1);
        // その週の月曜日(カレンダー左上の日)を得る
        $t->modify("Mon this week");
        // 月終わりをセット
        $end_t = new DateTime();
        $end_t->setDate($year,$month, 1);
        $end_t->modify("Sun");
        $html = "<p>{$year}年 {$month}月</p>";
        $html .= "<table class='cal'>";
        for (;;){
            $d = $t->format("d");
            $w = $t->format("w");
            $c_week = ($w == 0) ? "sun" : (($w == 6) ? "sat" : "");
            $c_mon = ((int)$t->format("m") == $month) ? 'cur' : 'oth';
            if ($w == 1){
                $html .= "<tr>";
            }
            $html .= "<td class='$c_mon $c_week'>$d</td>";
            if ($w == 0){
                $html .= "</tr>";
            }
            $diff = $end_t->diff($t);
            if ($diff->days == 0) break;
            $t->modify("+1 days");
        }
        $html .= "</table>";
        return $html;
    }
?></body></html>