![](https://habrastorage.org/webt/sa/fh/en/safhenqnyltoiatl2x-83trc6lc.gif)
В данном материале предлагается, приложив небольшие усилия, соединить python 3.7+flask+tensorflow 2.0+keras+небольшие вкрапления js и вывести на web-страницу определенный интерактив. Пользователь, рисуя на холсте, будет отправлять на распознавание цифры, а ранее обученная модель, использующая архитектуру CNN, будет распознавать полученный рисунок и выводить результат. Модель обучена на известном наборе рукописных цифр MNIST, поэтому и распознавать будет только цифры от 0 до 9 включительно. В качестве системы, на которой все это будет крутиться, используется windows 7.
Небольшое вступление
Чем печальны книги по машинному обучению, так, пожалуй, тем, что код устаревает почти с выходом самой книги. И хорошо, если автор издания поддерживает свое дитя, сопровождая и обновляя код, но, зачастую все ограничивается тем, что пишут — вот вам requirements.txt, ставьте устаревшие пакеты, и все заработает.
Так вышло и в этот раз. Читая «Hands-On Python Deep Learning for the Web» авторства Anubhav Singh, Sayak Paul, сначала все шло хорошо. Однако, после первой главы праздник закончился. Самое неприятное было то, что заявленные требования в requirements в целом соблюдались.
Масло в огонь подлили и сами разработчики пакетов tensorflow и keras. Один пакет работает только с определенным другим и, либо даунгрейд одного из них либо бубен шамана.
Но и это еще не все. Оказывается, что некоторые пакеты еще и зависимы от архитектуры используемого железа!
Так, за неимением алтернативы железа, устанавливался tensorflow 2.0 на платформу с Celeron j1900 и, как оказалось, там нет инструкции AVX2:
![](https://habrastorage.org/webt/9k/u8/n1/9ku8n1syambgz02eiokf2a2qv1y.png)
И вариант через pip install tensorflow не работал.
Но не все так грустно при наличии желания и интернета!
Вариант с tensorflow 2.0 удалось реализовать через wheel — github.com/fo40225/tensorflow-windows-wheel/tree/master/2.0.0/py37/CPU/sse2 и установку x86: vc_redist.x86.exe, x64: vc_redist.x64.exe (https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads).
Keras был установлен с минимальной версией, с которой он «стал совместим» с tensorflow — Keras==2.3.0.
Поэтому
pip install tensorflow-2.0.0-cp37-cp37m-win_amd64.whl
и
pip install keras==2.3.0
Основное приложение
Рассмотрим код основной программы.
flask_app.py
#code work with scipy==1.6.1, tensorflow @ file:///D:/python64/tensorflow-2.0.0-cp37-cp37m-win_amd64.whl,
#Keras==2.3.0
from flask import Flask, render_template, request
import imageio
#https://imageio.readthedocs.io/en/stable/examples.html
#from scipy.misc import imread, imresize
#from matplotlib.pyplot import imread
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import model_from_json
from skimage import transform,io
json_file = open('model.json','r')
model_json = json_file.read()
json_file.close()
model = model_from_json(model_json)
model.load_weights("weights.h5")
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
#graph = tf.get_default_graph()
graph = tf.compat.v1.get_default_graph()
app = Flask(__name__)
@app.route('/')
def index():
return render_template("index.html")
import re
import base64
def convertImage(imgData1):
imgstr = re.search(r'base64,(.*)', str(imgData1)).group(1)
with open('output.png', 'wb') as output:
output.write(base64.b64decode(imgstr))
@app.route('/predict/', methods=['GET', 'POST'])
def predict():
global model, graph
imgData = request.get_data()
convertImage(imgData)
#print(imgData)
#x = imread('output.png', mode='L')
#x.shape
#(280, 280)
x = imageio.imread('output.png',pilmode='L')
#x = imresize(x, (28, 28))
#x = x.resize(x, (28, 28))
x = transform.resize(x, (28,28), mode='symmetric', preserve_range=True)
#(28, 28)
#type(x)
#<class 'numpy.ndarray'>
x = x.reshape(1, 28, 28, 1)
#(1, 28, 28, 1)
x = tf.cast(x, tf.float32)
# perform the prediction
out = model.predict(x)
#print(np.argmax(out, axis=1))
# convert the response to a string
response = np.argmax(out, axis=1)
return str(response[0])
if __name__ == "__main__":
# run the app locally on the given port
app.run(host='0.0.0.0', port=80)
# optional if we want to run in debugging mode
app.run(debug=True)
Подгрузили пакеты:
from flask import Flask, render_template, request
import imageio
#https://imageio.readthedocs.io/en/stable/examples.html
#from scipy.misc import imread, imresize
#from matplotlib.pyplot import imread
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import model_from_json
from skimage import transform,io
Как выяснилось imread, imresize устарели еще со времен scipy==1.0. Непонятно, как у автора все работало, учитывая, что книга относительно нова (2019). С современной scipy==1.6.1 книжный вариант кода не работал.
Загружаем с диска, компилируем модель нейросети:
json_file = open('model.json','r')
model_json = json_file.read()
json_file.close()
model = model_from_json(model_json)
model.load_weights("weights.h5")
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
#graph = tf.get_default_graph()
graph = tf.compat.v1.get_default_graph()
Здесь произведена замена на tf.compat.v1.get_default_graph() в виду несовместимости.
Далее часть, относящаяся к серверу на flask. «Прорисовка» шаблона страницы:
@app.route('/')
def index():
return render_template("index.html")
Часть, преобразующая картинку в числовой массив:
import re
import base64
def convertImage(imgData1):
imgstr = re.search(r'base64,(.*)', str(imgData1)).group(1)
with open('output.png', 'wb') as output:
output.write(base64.b64decode(imgstr))
Основная функция предсказания:
def predict():
global model, graph
imgData = request.get_data()
convertImage(imgData)
#print(imgData)
#x = imread('output.png', mode='L')
#x.shape
#(280, 280)
x = imageio.imread('output.png',pilmode='L')
#x = imresize(x, (28, 28))
#x = x.resize(x, (28, 28))
x = transform.resize(x, (28,28), mode='symmetric', preserve_range=True)
#(28, 28)
#type(x)
#<class 'numpy.ndarray'>
x = x.reshape(1, 28, 28, 1)
#(1, 28, 28, 1)
x = tf.cast(x, tf.float32)
# perform the prediction
out = model.predict(x)
#print(np.argmax(out, axis=1))
# convert the response to a string
response = np.argmax(out, axis=1)
return str(response[0])
Закоментированы строки, которые были заменены на рабочие, а также оставлены выводы отдельных строк для наглядности.
Как все работает
После запуска командой python flask_app.py запускается локальный flask-сервер, который выводит index.html с вкраплением js.
Пользователь рисует на холсте цифру, нажимает «predict». Картинка «улетает» на сервер, где сохраняется и преобразуется в цифровой массив. Далее в бой вступает CNN, распознающая цифру и возвращающая ответ в виде цифры.
Сеть не всегда дает верный ответ, т.к. обучалась всего на 10 эпохах. Это можно наблюдать, если нарисовать «спорную» цифру, которая может трактоваться по-разному.
*Можно покрутить слайдер, увеличивая или уменьшая толщину начертания цифры для целей распознавания.
Второй вариант программы — через API,curl
Поользователь загружает на сервер свое изображение с цифрой для распознавания и нажимает «отправить»:
![](https://habrastorage.org/webt/sd/p3/uw/sdp3uwmo6xpir30xx7vtkzp98ac.gif)
Заменим index.js на следующий:
index.js:
$("form").submit(function(evt){
evt.preventDefault();
var formData = new FormData($(this)[0]);
$.ajax({
url: '/predict/',
type: 'POST',
data: formData,
async: false,
cache: false,
contentType: false,
enctype: 'multipart/form-data',
processData: false,
success: function (response) {
$('#result').empty().append(response);
}
});
return false;
});
Шаблон страницы также изменится:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>MNIST CNN</title>
</head>
<body>
<h1>MNIST Handwritten Digits Prediction</h1>
<form>
<input type="file" name="img"></input>
<input type="submit"></input>
</form>
<hr>
<h3>Prediction: <span id="result"></span></h3>
<script
src='https://code.jquery.com/jquery-3.6.0.min.js'></script>
<script src="{{ url_for('static',filename='index.js') }}"></script>
</body>
</html>
Немного изменится и основная программа:
flask_app2.py
#code work with scipy==1.6.1, tensorflow @ file:///D:/python64/tensorflow-2.0.0-cp37-cp37m-win_amd64.whl,
#Keras==2.3.0
from flask import Flask, render_template, request
import imageio
#https://imageio.readthedocs.io/en/stable/examples.html
#from scipy.misc import imread, imresize
#from matplotlib.pyplot import imread
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import model_from_json
from skimage import transform,io
json_file = open('model.json','r')
model_json = json_file.read()
json_file.close()
model = model_from_json(model_json)
model.load_weights("weights.h5")
model.compile(loss='categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
#graph = tf.get_default_graph()
graph = tf.compat.v1.get_default_graph()
app = Flask(__name__)
@app.route('/')
def index():
return render_template("index.html")
import re
import base64
def convertImage(imgData1):
imgstr = re.search(r'base64,(.*)', str(imgData1)).group(1)
with open('output.png', 'wb') as output:
output.write(base64.b64decode(imgstr))
@app.route('/predict/', methods=['POST'])
def predict():
global model, graph
imgData = request.get_data()
try:
stringToImage(imgData)
except:
f = request.files['img']
f.save('image.png')
#x = imread('output.png', mode='L')
#x.shape
#(280, 280)
x = imageio.imread('image.png',pilmode='L')
#x = imresize(x, (28, 28))
#x = x.resize(x, (28, 28))
x = transform.resize(x, (28,28), mode='symmetric', preserve_range=True)
#(28, 28)
#type(x)
#<class 'numpy.ndarray'>
x = x.reshape(1, 28, 28, 1)
#(1, 28, 28, 1)
x = tf.cast(x, tf.float32)
# perform the prediction
out = model.predict(x)
#print(np.argmax(out, axis=1))
# convert the response to a string
response = np.argmax(out, axis=1)
return str(response[0])
if __name__ == "__main__":
# run the app locally on the given port
app.run(host='0.0.0.0', port=80)
# optional if we want to run in debugging mode
app.run(debug=True)
Запускается все похоже — python flask_app2.py
Вариант с curl (для windows)
Скачиваем curl
В командной строке windows отправляем команду:
curl -X POST -F img=@1.png http://localhost/predict/
где 1.png — картинка с цифрой (или она же с путем к ней).
В ответ прилетит распознанная цифра.
Файлы для скачивания — скачать.