Пусть ваша модель глубокого обучения летает
Развертывание вашей первой модели глубокого обучения: MNIST в производственной среде
Как вы можете развернуть свою модель MNIST в производственной среде
Набор данных MNIST - это набор данных hello world для большинства любителей машинного обучения, которые любят нас. В какой-то момент каждый, кто начал свой путь в этой области или желает начать, наткнется на этот набор данных и наверняка получит в свои руки.
Это хороший набор данных для людей, которые хотят опробовать методы обучения и методы распознавания образов на реальных данных, затрачивая минимальные усилия на предварительную обработку и форматирование. - Янн ЛеКун
Что мы строим?
В этом посте я собираюсь написать о том, как каждый, кто прошел MNIST, может развернуть там обученную модель как красивое веб-приложение в производственной среде с использованием Django и Heroku.
Предпосылки
Вы должны иметь базовые знания о:
- Язык программирования Python
- Django - фреймворк веб-приложений
- Heroku - Платформа как услуга (Необязательно: вы узнаете, как ее использовать в этом посте)
и у вас должен быть файл модели MNIST; на основе Keras или вы можете начать работу с MNIST прямо сейчас с этот файл записной книжки Jupyter.
Подготовка бэкэнда
Первым делом давайте установим Django с помощью терминала CMD или bash; если вы еще этого не сделали.
Если у вас нет опыта работы с Django, в Интернете доступно множество бесплатных ресурсов. Пожалуйста, подумайте о том, чтобы посмотреть на это. Это отличная платформа для создания веб-приложений с использованием Python. Нечего терять.
Стартовый проект
pip install django
Это установит для вас Django, и у вас будет доступ к Django CLI для создания папки проекта.
django-admin startproject digitrecognizer
Я собираюсь назвать свой проект распознавателем цифр, вы можете называть его как хотите. Как только вы это сделаете, вам будет представлена папка с несколькими файлами внутри.
давайте создадим наше новое приложение main внутри этой папки с помощью mange.py cli.
python manage.py startapp main
Это создаст для вас новое приложение с именем main. Теперь мы можем записать наши основные коды в файл views.py.
Часть кода
Напишем код в файле views.py:
## Views.py from django.shortcuts import render from scipy.misc.pilutil import imread, imresize import numpy as np import re import sys import os sys.path.append(os.path.abspath("./model")) from .utils import * from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt global model, graph model, graph = init() import base64 OUTPUT = os.path.join(os.path.dirname(__file__), 'output.png') from PIL import Image from io import BytesIO def getI420FromBase64(codec): base64_data = re.sub('^data:image/.+;base64,', '', codec) byte_data = base64.b64decode(base64_data) image_data = BytesIO(byte_data) img = Image.open(image_data) img.save(OUTPUT) def convertImage(imgData): getI420FromBase64(imgData) @csrf_exempt def predict(request): imgData = request.POST.get('img') convertImage(imgData) x = imread(OUTPUT, mode='L') x = np.invert(x) x = imresize(x, (28, 28)) x = x.reshape(1, 28, 28, 1) with graph.as_default(): out = model.predict(x) print(out) print(np.argmax(out, axis=1)) response = np.array_str(np.argmax(out, axis=1)) return JsonResponse({"output": response})
Вроде много, но это не так! 😂 поверьте мне.
Давайте разберемся
В самом начале кода мы импортируем все необходимые библиотеки и модули.
Импорт
Каждый импорт не требует пояснений, а также я прокомментировал важные разделы, подумайте о том, чтобы взглянуть на него.
from django.shortcuts import render from scipy.misc.pilutil import imread, imresize import numpy as np import re import sys ## Apending MNIST model path import os sys.path.append(os.path.abspath("./model")) ## custom utils file create for writing some helper func from .utils import * from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt ## Declaring global variable global model, graph ## initializing MNIST model file (It comes from utils.py file) model, graph = init() import base64 from PIL import Image from io import BytesIO ## Declaring output path to save our image OUTPUT = os.path.join(os.path.dirname(__file__), 'output.png')
Что такое файл utils.py?
После импорта необходимых библиотек давайте напишем несколько вспомогательных функций для обработки нашей модели MNIST в файле utils.py.
## utils.py from keras.models import model_from_json from scipy.misc.pilutil import imread, imresize, imshow import tensorflow as tf import os JSONpath = os.path.join(os.path.dirname(__file__), 'models', 'model.json') MODELpath = os.path.join(os.path.dirname(__file__), 'models', 'mnist.h5') def init(): json_file = open(JSONpath, 'r') loaded_model_json = json_file.read() json_file.close() loaded_model = model_from_json(loaded_model_json) loaded_model.load_weights(MODELpath) print("Loaded Model from disk") loaded_model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) graph = tf.get_default_graph() return loaded_model, graph
Этот файл содержит функцию init, которая в основном инициализирует наш файл модели MNIST, сохраненный с помощью Keras. Он захватывает или загружает их в файл модели и компилирует с помощью оптимизатора adam, чтобы подготовить их для прогнозирования.
здесь мы используем category_crossentropy как нашу функцию потерь, adam как наш оптимизатор и точность как нашу метрику измерения производительности.
Вы можете узнать, как сохранять модели с помощью Keras, из здесь.
Продолжаем views.py
Здесь у нас есть еще одна вспомогательная функция, которая поможет нам преобразовать наш файл изображения BASE64: который захватывается со стороны клиента в файл PNG и сохраняется как все, что находится в OUTPUT; который должен быть сохранен как файл output.png в текущем каталоге.
def getI420FromBase64(codec): base64_data = re.sub('^data:image/.+;base64,', '', codec) byte_data = base64.b64decode(base64_data) image_data = BytesIO(byte_data) img = Image.open(image_data) img.save(OUTPUT) def convertImage(imgData): getI420FromBase64(imgData)
Написание нашего API
Теперь давайте напишем наш основной API для:
- Возьмите файл изображения base64, представленный клиентом
- Преобразуйте его в файл png
- Обработайте его, чтобы он соответствовал нашему обученному файлу модели.
- Предсказать изображение с помощью нашей предыдущей вспомогательной функции и получить взамен метрику производительности.
- Вернуть его как ответ JSON
@csrf_exempt def predict(request): imgData = request.POST.get('img') convertImage(imgData) x = imread(OUTPUT, mode='L') x = np.invert(x) x = imresize(x, (28, 28)) x = x.reshape(1, 28, 28, 1) with graph.as_default(): out = model.predict(x) print(out) print(np.argmax(out, axis=1)) response = np.array_str(np.argmax(out, axis=1)) return JsonResponse({"output": response})
Он использует декоратор csrf_exempt, потому что Django очень строго относится к безопасности. Используя его, мы просто отключаем проверку CSRF.
Теперь мы закончили писать код серверной части нашего приложения для классификации метки данного изображения.
Укажите маршрут
Теперь давайте предоставим маршрут для нашей основной функции.
Перейдите в папку вашего проекта, где находятся файлы settings.py и urls.py.
в файле settings.py под массивом INSTALLED_APPS установите основное приложение, которое мы создали ранее для написания наших функций.
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ## our main application 'main' ]
После этого вернитесь в файл urls.py и напишите маршрут, который достигает нашей функции прогнозирования.
from django.contrib import admin from django.urls import path, include from main.views import predict urlpatterns = [ path('', include('main.urls')), path('api/predict/', predict) ]
Сохраните все, и теперь наш backend API готов.
Передняя часть
Пришло время написать наш интерфейсный код, который позволит нам взаимодействовать с нашим внутренним API.
Мы используем шаблон Django для написания нашего интерфейса.
давайте создадим папку шаблонов внутри нашей основной папки и внутри нее создадим файл index.html.
Внутри HTML-файла давайте напишем код для создания холста и отправим изображение, нарисованное пользователем на этом холсте.
<canvas id="canvas" width="280" height="280" style="border:2px solid; float: left; border-radius: 5px; cursor: crosshair;" ></canvas> <p id="result" class="text-center text-success"></p> <a href="#" class="btn btn-success btn-block p-2" id="predictButton"> Predict </a> <input type="button" class="btn btn-block btn-secondary p-2" id="clearButton" value="Clear" />
Вы можете создать свой Frontend так, как вам нравится, и создать холст внутри него.
После отображения холста давайте сделаем его трудноразрешимым с помощью JS (JQuery).
(function() { var canvas = document.querySelector( "#canvas" ); canvas.width = 280; canvas.height = 280; var context = canvas.getContext( "2d" ); var canvastop = canvas.offsetTop var lastx; var lasty; context.strokeStyle = "#000000"; context.lineCap = 'round'; context.lineJoin = 'round'; context.lineWidth = 5; function dot(x,y) { context.beginPath(); context.fillStyle = "#000000"; context.arc(x,y,1,0,Math.PI*2,true); context.fill(); context.stroke(); context.closePath(); } function line(fromx,fromy, tox,toy) { context.beginPath(); context.moveTo(fromx, fromy); context.lineTo(tox, toy); context.stroke(); context.closePath(); } canvas.ontouchstart = function(event){ event.preventDefault(); lastx = event.touches[0].clientX; lasty = event.touches[0].clientY - canvastop; dot(lastx,lasty); } canvas.ontouchmove = function(event){ event.preventDefault(); var newx = event.touches[0].clientX; var newy = event.touches[0].clientY - canvastop; line(lastx,lasty, newx,newy); lastx = newx; lasty = newy; } var Mouse = { x: 0, y: 0 }; var lastMouse = { x: 0, y: 0 }; context.fillStyle="white"; context.fillRect(0,0,canvas.width,canvas.height); context.color = "black"; context.lineWidth = 10; context.lineJoin = context.lineCap = 'round'; debug(); canvas.addEventListener( "mousemove", function( e ) { lastMouse.x = Mouse.x; lastMouse.y = Mouse.y; Mouse.x = e.pageX - this.offsetLeft; Mouse.y = e.pageY - this.offsetTop; }, false ); canvas.addEventListener( "mousedown", function( e ) { canvas.addEventListener( "mousemove", onPaint, false ); }, false ); canvas.addEventListener( "mouseup", function() { canvas.removeEventListener( "mousemove", onPaint, false ); }, false ); var onPaint = function() { context.lineWidth = context.lineWidth; context.lineJoin = "round"; context.lineCap = "round"; context.strokeStyle = context.color; context.beginPath(); context.moveTo( lastMouse.x, lastMouse.y ); context.lineTo( Mouse.x, Mouse.y ); context.closePath(); context.stroke(); }; function debug() { /* CLEAR BUTTON */ var clearButton = $( "#clearButton" ); clearButton.on( "click", function() { context.clearRect( 0, 0, 280, 280 ); context.fillStyle="white"; context.fillRect(0,0,canvas.width,canvas.height); }); /* COLOR SELECTOR */ $( "#colors" ).change(function() { var color = $( "#colors" ).val(); context.color = color; }); /* LINE WIDTH */ $( "#lineWidth" ).change(function() { context.lineWidth = $( this ).val(); }); } }());
По сути, это наша JS-функция, позволяющая пользователям рисовать внутри нашего холста. Он захватывает мышью + касания пользователя и рисует линии внутри холста в соответствии с его рисунками.
После этого давайте напишем код для отправки этих нарисованных линий на бэкэнд в виде файла изображения base64.
<script type="text/javascript"> $("#predictButton").click(function() { var $SCRIPT_ROOT = "/api/predict/"; var canvasObj = document.getElementById("canvas"); var context = canvas.getContext( "2d" ); var img = canvasObj.toDataURL(); $.ajax({ type: "POST", url: $SCRIPT_ROOT, data: { img: img }, success: function(data) { $("#result").text("Predicted Output is: " + data.output); context.clearRect( 0, 0, 280, 280 ); context.fillStyle="white"; context.fillRect(0,0,canvas.width,canvas.height); } }); }); </script>
Здесь мы используем jquery для:
- Слушайте наше событие нажатия кнопки
- Определение нашего пути маршрута API
- Захватываем наш элемент холста
- Получение контекста холста как изображения base64
- Отправка его в наш бэкэнд с помощью запроса ajax
- Получение ответа от нашего бэкэнда и отображение его в нашем разделе вывода.
Наконец, давайте добавим маршрут к нашему фронту и напишем функцию для обслуживания нашего HTML-файла в нашем основном приложении.
# views.py def index(request): return render(request, 'index.html', {}) # urls.py from django.urls import path from .views import index urlpatterns = [ path('', index, name="index") ]
Вот и все! мы успешно завершили нашу внутреннюю + внешнюю разработку для распознавания рукописных цифр.
Теперь давайте развернем его.
Развертывание
Мы собираемся использовать Heroku для развертывания нашего проекта Django, потому что это круто и БЕСПЛАТНО!
Вы можете узнать больше о heroku на его официальной странице документации. Это красиво, и все хорошо задокументировано.
Установите Heroku CLI на свой ноутбук и приступим.
Чтобы подготовить наш проект Django Heroku, давайте напишем Procfile внутри нашего корневого каталога.
# Procfile web: gunicorn digitrecognizer.wsgi --log-file - --log-level debug
Теперь давайте создадим новый репозиторий приложений в Heroku и получим удаленный URL-адрес этого приложения.
после этого git входит в каталог нашего проекта и добавляет удаленный URL-адрес git в URL-адрес Heroku, а затем помещает папку нашего проекта в Heroku с включенными файлами requirements.txt.
Вот и все по развертыванию 😊. Мы успешно развернули наше приложение в облаке, и теперь оно работает. Вы можете получить доступ к приложению, используя URL-адрес, предоставленный Heroku на панели инструментов вашего приложения.
Последние мысли
Очень важно развернуть ваши проекты в реальной среде, чтобы продемонстрировать свои проекты. Он отлично подойдет для вашего портфолио проектов.
Надеюсь, вы что-то узнали, попробуйте создать свой собственный рукописный классификатор цифр и развернуть его в производственной среде. Вы можете проверить мое демонстрационное приложение здесь.
использованная литература
[1] Витор Фрейтас, Как развертывать приложения Django на Heroku, 9 августа 2016 г. [ONLINE]
[2] янн лекунн, база данных MNIST, 1998 [ONLINE]