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

