手書き数字診断士、まずは動くようにしました。
ただ、初っ端から間違えています・・・!
動画
作り方
データ通信方法
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の学習モデルを保存し、テストする
まとめ
やってることは至って簡単です。
ただ少し気になることが多いですね。