RのWeb制作

Webサービス制作のための技術情報を。

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

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

関連記事

[Meisyo]第7回公式戦結果

平成最後の公式戦がありました。 これまで優勝2回の強豪、ジャガ畑 vs 並みいる強豪を倒してきたダークホース、ふたもじ その結果はこちら 優勝おめでとうございます!

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

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

[Meisyo]練習試合にレーティング制を導入

練習試合にレーティング制を導入しました。 レートはメイン画面に下記のように表示されます。 平均レートは1000です。 レーティングの変動⊿Rは下記の式で表されます。 a(レートRa)がb(レートRb) …

Google Driveを使ってXAMPP+CakePHPのドキュメントルートを共有する in Windows

どこでも同じように作業がしたい。 デスクトップでも、ノートでも。 無論、サーバー(XAMPP)も同じように動かしたいな ・・・ということで、Google Driveを使って環境を構築していきます。 今 …

久々にWeb更新

ミニ四駆ブログまとめ http://mini4wd.rei-farms.jp/ とかを更新してました。珍しいですね。 仕事でいろいろとあってるので、疲労感が半端ないです。 上司が壊し屋(多数実績あり) …