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

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

関連記事

[社内コンペ] 間取り予測

これは何? 某社で行われている社内コンペのメモです。私の備忘録でもあります。 結果:3位 精度:60.7% 今回は学習データが12,000件ほどあり、ある程度多いデータ量での学習ができました。 学習デ …

[Meisyo]左投右打が出過ぎで気持ち悪い件の分析

本日フリーゲーム夢現に初コメントが付いたのでワクワクして見ました。 #—————————&# …

[Meisyo]練習難易度の不均衡是正への分析的アプローチ2

[Meisyo]練習難易度の不均衡是正への分析的アプローチから早3か月。 「練習ごとに難易度が違いすぎるんですけど!!」という不満は少しは解消されたかなー・・・ 効果測定してないのにわかるの?エスパー …

(VPSでつくる) セキュリティ設定

連載第十四回目です。 セキュリティ設定について何もわからん状態なので色々と漁っていました。 PythonやFlaskなどの個別のモノに対してのセキュリティ設定の書籍は中々多くないので、Linuxなどの …

for内で選手個人データをSQLで呼び出すより、辞書型を利用した方が読み込みは早い

for内でSQLを呼び出すと遅い…。 自作野球ゲームMeisyoでは、明示的にデータを呼び出すためにfor内でSQLを利用していました。 CDs = {} # Cardデータ(選手id, 選手データ) …