Пусть ваша модель глубокого обучения летает

Развертывание вашей первой модели глубокого обучения: MNIST в производственной среде

Как вы можете развернуть свою модель MNIST в производственной среде

Набор данных MNIST - это набор данных hello world для большинства любителей машинного обучения, которые любят нас. В какой-то момент каждый, кто начал свой путь в этой области или желает начать, наткнется на этот набор данных и наверняка получит в свои руки.

Это хороший набор данных для людей, которые хотят опробовать методы обучения и методы распознавания образов на реальных данных, затрачивая минимальные усилия на предварительную обработку и форматирование. - Янн ЛеКун

Что мы строим?

В этом посте я собираюсь написать о том, как каждый, кто прошел MNIST, может развернуть там обученную модель как красивое веб-приложение в производственной среде с использованием Django и Heroku.

Предпосылки

Вы должны иметь базовые знания о:

  1. Язык программирования Python
  2. Django - фреймворк веб-приложений
  3. 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 для:

  1. Возьмите файл изображения base64, представленный клиентом
  2. Преобразуйте его в файл png
  3. Обработайте его, чтобы он соответствовал нашему обученному файлу модели.
  4. Предсказать изображение с помощью нашей предыдущей вспомогательной функции и получить взамен метрику производительности.
  5. Вернуть его как ответ 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 для:

  1. Слушайте наше событие нажатия кнопки
  2. Определение нашего пути маршрута API
  3. Захватываем наш элемент холста
  4. Получение контекста холста как изображения base64
  5. Отправка его в наш бэкэнд с помощью запроса ajax
  6. Получение ответа от нашего бэкэнда и отображение его в нашем разделе вывода.

Наконец, давайте добавим маршрут к нашему фронту и напишем функцию для обслуживания нашего 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]