RのWeb制作

Webサービス制作のための技術情報を。データ分析(Python、機械学習コンペ他)や自作野球ゲームMeisyoのこと中心。

Web制作 Python

手書き数字診断士(機械学習)ver 0.0

投稿日:

手書き数字診断士、まずは動くようにしました。

ただ、初っ端から間違えています・・・!

動画

作り方

データ通信方法

canvas → 画像データ化 → JavaScript(Json化)→ POSTで送信 → python、postの[data]のみ受信 → データ整形 → 予測 → 結果表示

使うファイル

pythonのflaskを使いました。
・consultant-app.py 本体
・consultant-pred.py 学習モデル生成用
・consultant.pkl 学習モデル
/template
・index.html 表示、データ通信用
・message.html 判定用

問題点

・判定能力が甘い
 MNISTだから割とデータが整っている=数字の中心がずれたら判定できない

 ・・・その結果ほぼ4に!

・pythonのPostで何でもデータ受け取っている。
 セキュリティがザル。公開するには攻撃対策が必要だろう。

・データ整形に不安感
 JavaScript(Json化)→ POSTで送信 → python、postの[data]のみ受信
 この間で、28×28のデータを送っているはずが・・・56×56に?
 無理やり整形していますが、これでいいのか。

コード

consultant-app.py

from flask import Flask, render_template, request, flash
from wtforms import Form, FloatField, SubmitField, validators, ValidationError
import numpy as np
from sklearn.externals import joblib
import json

# 学習済みモデルを読み込み
def predict(parameters):
    model = joblib.load('./consultant.pkl')
    params = parameters.reshape(1,-1)
    pred = model.predict(params)
    return pred

app = Flask(__name__)
app.config.from_object(__name__)
app.config['SECRET_KEY'] = 'zJe09C5c3tMf5FnNL09C5d6SAzZoY'

@app.route('/', methods = ['GET', 'POST'])
def index():
    return render_template('index.html')

@app.route('/message', methods = ['GET', 'POST'])
def message():
    if request.method == 'POST':
        # フォームの取得、辞書化
        form = request.form
        data = json.loads(form['data'])
        dict = data['data']
        # 辞書→np配列化
        array = np.array(list(dict.values()))
        # 28x28の多次元配列化 なぜかarray[3::4]で出力?
        array_ex = np.reshape(array[3::4], (28, 28))
        #print(*array_ex, sep='\n')
        # 28x28 → 対応する8x8へ(meanで補正)
        d = 8
        z = 28
        array_last = np.zeros((d, d))
        n = int(z / d)
        y = x = int(0)
        for y in range(d):
            for x in range(d):
                yb = n * y
                yf = n * y + n
                xb = n * x
                xf = n * x + n
                data = np.mean(array_ex[yb:yf, xb:xf])
                #print(str(y) + ':' + str(x));
                #print('?' + str(yb) + '-' + str(yf) + 'x' + str(xb) + '-' + str(xf) + ':' + str(data))
                array_last[y,x] = data
        array_last = array_last / np.amax(array_last) # 補正
        pred = predict(array_last)
        print(array_last)
    else:
        pred = 'None'

    return render_template('message.html', pred=pred)

if __name__ == "__main__":
    app.run()

index.html

<html>
<head>
<meta charset='utf-8'>
<title>手書き数字診断士「う~ん、これは0!w」</title>
</head>
<body>
<h1>手書き数字診断士</h1>
<input type=button id='button' onclick="push();" value="診断!" disabled>
<input type=button onclick="delete_canvas();" value="もう1度"><br />
手書き数字診断士:<span id="comment">「」</span><br />
<canvas id="canvas" name="canvas" width="28" height="28" style="width: 280px; height: 280px; border: 1px solid #eee;"></canvas><br />
0~9の数字を判定してくれる人工知能です。
<script type="text/javascript">
<!--
	var drawing = false;
	// 前回の座標を記録する(初期値:0)
	var before_x = 0;
	var before_y = 0;

	var canvas = document.getElementById('canvas');
	var ctx = canvas.getContext('2d');

	canvas.addEventListener('mousemove', draw_canvas);
	// マウスをクリックしてる時
	canvas.addEventListener('mousedown', function(e) {
		drawing = true;
		var rect = e.target.getBoundingClientRect();
		before_x = e.clientX - rect.left;
		before_y = e.clientY - rect.top;
	});
	// マウスをクリックしてない時
	canvas.addEventListener('mouseup', function() {
		drawing = false;
	});

// 描画の処理
function draw_canvas(e) {
	// drawingがtrueじゃなかったら返す
	if (!drawing){
		return
	};
	var rect = e.target.getBoundingClientRect();
	var x = e.clientX - rect.left;
	var y = e.clientY - rect.top;

	// 描画
	ctx.lineCap = 'round';
  ctx.strokeStyle = 'black';
	ctx.lineWidth = 1;
	ctx.beginPath();
	ctx.moveTo(before_x/10, before_y/10);
	ctx.lineTo(x/10, y/10);
	ctx.stroke();
	ctx.closePath();
	// 描画最後の座標を前回の座標に代入する
	before_x = x;
	before_y = y;

	// ボタン使用可能
	document.getElementById('button').disabled = false;
}

// canvas内削除
function delete_canvas() {
	ctx.clearRect(0, 0, 28, 28);
	// ボタン使用不可
	document.getElementById('button').disabled = true;
	document.getElementById('comment').innerHTML = '「」';
}

// 出力
function push() {
	var imgData = ctx.getImageData(0, 0, 28, 28);
	var Data = JSON.stringify(imgData, null);
	var xhr = new XMLHttpRequest();
	xhr.open('POST', '/message');
	xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
	xhr.send('data=' + Data);
	xhr.onreadystatechange = function() {
		if(xhr.readyState === 4 && xhr.status === 200) {
			document.getElementById('comment').innerHTML = xhr.responseText;
		}
	}
}
//-->
</script>
</body>
</html>

message.html

{% if pred == 'None' %}
お客さんデータが無いアルよ
{% else %}
う~ん、これは{{ pred }}!w
{% endif %}

consultant-pred.py
MNISTの学習モデルを保存し、テストする

まとめ

やってることは至って簡単です。
ただ少し気になることが多いですね。

-Web制作, Python

執筆者:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事

【教材紹介】機械学習を解釈する技術

多くの企業で導入されるようになってきた機械学習。 その機械の判断基準、本当にわかっていますか? 今回は、実務に利用するために最低限の技術として、変数(特徴量)の重要度や変数と予測値の関係性を求める方法 …

no image

PHPのコーディング規則、PSRを学ぶ(4)

PHPのコーディング規則、PSRを学ぶ(3)の続きです。 今回は実際にクラス名定義を自宅サーバーで試してみます。 1.「SplClassLoader.php」を手に入れる 「ダウンロード」または「直接 …

手書き数字診断士(機械学習)ver 0.1 K近傍法を使ってみた

手書き文字の判定精度が全然上がらないので、他の手法を試してみました。 sklearnの開発元によると、以下の方法が良いらしい・・・。 なるほど!SVCで上手くいかない → K近傍法だな! 早速実装 p …

(VPSでつくる) Python(Flask)でMariaDB(MySQL)へ接続できるアプリをもっと読みやすく改良してみよう

連載第十二回目です。 前回の記事で、Python3.6.8+FlaskでMariaDBに接続・データベースを編集するアプリを動作させる設定を行い、動作確認しました。 今回は、機能は前回と全く同じアプリ …

ゲームのつくり方

何で書こうとしたの? ゲーム制作者が自由な記事を作る Advent Calendar 2021があったのでつい… 昔からゲーム開発の方法論を簡単にまとめたいと思っていましたが、いいきっかけだと思って書 …