Построение веб-сервиса с машинным обучением

Построение веб-сервиса с машинным обучением. Понятия

Здравствуйте и вновь добро пожаловать на занятия по теме «Машинное обучение на языке Python».

В этой статье мы обсудим реальное приложение машинного обучения – создадим веб-сервис с машинным обучением. Это для тех из вас, кто уже знаком с API веб-сервисов, вроде API Twitter, Instagram, Facebook, YouTube и так далее. API очень полезны, поскольку позволяют поручать сложную работу другому приложению. В данном же случае сложную работу будем выполнять мы, но с помощью машинного обучения. Все это даст схему основных компонент нашего веб-приложения, а в конце мы пройдёмся по коду.

Обратитевнимание, что целью данного курса вовсе не является научить вас API веб-сервисов – эта тема сама по себе может занятьцелый курс. Поэтому предполагается, что вы уже знакомы с фреймворками вроде Ruby onRails или Django, знаете, чтотакое http getи http postи так далее.

Основнаясхема нашей задачи заключается в следующем. Мы собираемся создать веб-сервис,который бы распознавал рукописные цифры. Очень удачно, что у нас уже есть наборданных по написанным от руки цифрам для обучения. Но, как вы можетепредставить, всё это можно распространить на изображения чего угодно. Кпримеру, вы можете написать веб-сервис для выявления мошенничества с чеками припомощи распознавания лиц и так далее. Изображения могут быть любыми, какимипожелаете, и пометить их вы тоже можете, как вам угодно.

Нашеприложение будет состоять из трёх основных файлов. Первый файл app_trainer.py – учебный. Он обучает модель неизвестным данным исохраняет её, как это обсуждалось в лекции, посвящённой Sci-Kit Learn. Мывоспользуемся тем обстоятельством, что APISci-Kit Learn одинаков для всех моделей, а потому применим более сложнуюмодель, называющуюся RandomForrestClassifier. Это как еслибы в реальном мире запускалось периодическое обновление, как только новыеданные будут собраны некоторым другим связанным приложением. Например, у насможет быть команда сотрудников, которые маркируют мошеннические чеки, и мыхотели бы, чтобы наша модель всегда включала новые метки чеков по мере ихпоявления.

Второйфайл – это собственно веб-сервис, называется он app.py. Мы будем использовать фреймворк Tornado, являющийся библиотекой Python. В этом случае детали кода не важны, поскольку ониразличны для различных фреймворков. Мы будем обслуживать одну конечную точку predict, принимающую один параметр input. Поскольку HTML-формымогут отправлять несколько значений на один ключ, мы будем хранить весь векторданных на этом одном и том же входе. Когда приложение получает данные, оноделает прогноз, используя предварительно обученную модель, и возвращает этотпрогноз в формате JSON. Мы такжевоспользуемся запросом POST, хотятехнически он используется для запросов, изменяющих данные на сервере. Но еслисделать запрос GET и попытатьсязапросить браузер, то получим целый массив в URL,что вряд ли нужно.

Третийкомпонент – код, вызывающий API; называется он app_caller.py. Он имитирует вызов APIвнешним приложением. Что касается нашего случая, то он просто выбираетслучайным образом цифру, вызывает веб-сервис, делает прогноз, отображает его наэкране вместе с истинной меткой и показывает само изображение.

Коротенькоезамечание относительно библиотек, если у вас их ещё нет и вы не знаете, как ихустановить. В Python всё устанавливается с помощью команд pip или easyinstall. Так что вамнужны лишь две команды:

sudo pipinstall tornado

sudo pip install requests

Построение веб-сервиса с машинным обучением. Код

Вначале рассмотрим файл app_trainer.py. Обратите внимание на дополнительные импорты – Pickle для сохранения модели и RandomForestClassifier для её применения. Обучение будет происходить лишь на 25% данных, поэтому мы делим N на 4. Обратите также внимание на создание модели и её согласование – в точности так же, как это делалось нами ранее. Тут действительно не требуется никаких дополнительных усилий с вашей стороны, а вы можете пользоваться новейшими достижениями машинного обучения. Далее идёт вывод на экран точности обучения (на случай, если вам это будет интересно), а последний этап – сохранения модели в файле mymodel.pkl с помощью библиотеки Pickle:

import pickle

import numpy as np

from util import get_data

from sklearn.ensemble import RandomForestClassifier

if __name__ == ‘__main__’:

    X, Y =get_data()

    Ntrain =len(Y) / 4

    Xtrain,Ytrain = X[:Ntrain], Y[:Ntrain]

    model =RandomForestClassifier()

   model.fit(Xtrain, Ytrain)

    # just incase you’re curious

    Xtest, Ytest= X[Ntrain:], Y[Ntrain:]

   print(“test accuracy:”, model.score(Xtest, Ytest))

    with open(‘mymodel.pkl’, ‘wb’) as f:

        pickle.dump(model, f)

Далеерассмотрим app_caller.py. Дополнительно тут импортируется библиотека Requests – очень полезна для создания http-запросов. Вначале мы загружаем данные, а затемзапускаем бесконечный цикл. Далее выбираем случайным образом одну из точекданных и называем её i. Обратитевнимание, что URL API– локальный хост, поскольку мы запускаем сервер локально. Путь являетсяпрогнозом, а данные передаются благодаря ключу входа. Мы знаем, что наш API возвращает ответ в формате JSON,потому просто вызываем функцию JSON для анализарезультата в словаре, выводим результат и истинную метку, если прогнозправилен, после чего выводим изображение с использованием библиотеки Matplotlib. При этом цветовую карту нужноустановить серой, иначе изображение получится в странных радужных цветах. Инаконец, мы делаем запрос, следует ли продолжать. При нажатии “n” идёт прерывание цикла.

import requests

import numpy as np

import matplotlib.pyplot as plt

from util import get_data

# make a prediction from our own server!

# in reality this could be coming from any client

X, Y = get_data()

N = len(Y)

while True:

    i =np.random.choice(N)

    r =requests.post(“http://localhost:8888/predict”, data={‘input’: X[i]})

   print(“RESPONSE:”)

   print(r.content)

    j = r.json()

    print(j)

   print(“target:”, Y[i])

   plt.imshow(X[i].reshape(28, 28), cmap=’gray’)

   plt.title(“Target: %d, Prediction: %d” % (Y[i],j[‘prediction’]))

    plt.show()

    response =input(“Continue? (Y/n)\n”)

    if responsein (‘n’, ‘N’):

        break

Инаконец перейдём к app.py. У нас есть импорт библиотеки Tornado, но вам не следует особо беспокоитьсяпо этому поводу, если не собираетесь ставать экспертом по данной библиотеке.Прежде всего проверяем, существует ли модель, и если нет – просто выходим,поскольку сделать прогноз без модели мы не в состоянии. В качестве альтернативыможно было бы обучить новую модель и сохранить её, но это уже будет чересчур,если обучение занимает несколько дней или недель.

Далееу нас идут классы-обработчики, отвечающие, разумеется, за обработку запросов. Уних есть функции get и post, которые вызываются при получении или послеотправки запроса.

Запускописывается в разделе main, который мырассмотрим позже. Класс MainHandler выводит «Hello, Tornado» – вроде «Hello, world», когда мы идёммаршруту, а также показывает, как реализуется запрос get.

Далееидёт класс PredictionHandler, представляющий, конечно же, наибольшийинтерес. Предупреждаю, в документации по Tornadoя не нашёл ничего особо полезного, но если вы ранее когда-либо имели дело сфреймворками, то узнаете основные компоненты. Вначале мы должны получитьпараметры, включённые в запрос. Тут есть две опции: request.body и request.arguments. Первая выдаётнечто вроде того, что отображается в URL, что,разумеется, нам не нужно, поскольку мы не собираемся его анализировать. Втораядаёт словарь – то, что надо. Обратите внимание, что все дробные числапреобразованы в строки, поскольку данные в http-запросахпередаются в виде строк. Поэтому нам необходимо преобразовать их обратно вчисла, что и делается с помощью функции map.

Посколькумодель ожидает данных в виде двухмерного массива размерности NxD, нам необходимо также конвертировать имеющийсяпример, который является вектором размерности D,в матрицу размерности 1xD. Достигаетсяэто путём помещения X в список содним элементом. К тому же благодаря этому прогноз возвращается в видеодномерного массива размерности N (для нас N = 1). Tornado возвращаетданные благодаря функциям write и finish. Поэтому сначала мы преобразуем наш исходящийсловарь в строку с помощью json.dumps и передаём функции write.

import pickle

import numpy as np

import os

import json

import tornado.ioloop

import tornado.web

if not os.path.exists(‘mymodel.pkl’):

   exit(“Can’t run without the model!”)

with open(‘mymodel.pkl’,‘rb’) as f:

    model =pickle.load(f)

class MainHandler(tornado.web.RequestHandler):

    def get(self):

       self.write(“Hello, Tornado!”)

class PredictionHandler(tornado.web.RequestHandler):

    # predictone sample at a time

    def post(self):

        # print“body:”, self.request.body

        # print“arguments:”, self.request.arguments

        # willlook like this:

        # body:three=four&one=two

        #arguments: {‘three’: [‘four’], ‘one’: [‘two’]}

        params =self.request.arguments

        x =np.array(list(map(float,params[‘input’])))

        y =model.predict([x])[0]

       self.write(json.dumps({‘prediction’: y.item()}))

        self.finish()

Инаконец, раздел main. Мы создаёмэкземпляр веб-приложения Tornado и передаём помаршруту. MainHandler отвечает, когда переходим по путимаршрута, а PredictionHandler – когда переходим к прогнозированию.Был выбран порт 8888 для старта приложения и его работы, пока мы не закончим.

if __name__ == “__main__”:

    application= tornado.web.Application([

       (r”/”, MainHandler),

       (r”/predict”, PredictionHandler),

    ])

   application.listen(8888)

tornado.ioloop.IOLoop.current().start()

Итак,запустим и посмотрим, что получится.

Унас два окна. В первом запускается app.py, и вначале там ничего не выводится. В другом окнезапускается app_caller.py.

Итак,видим изображение восьмёрки. Прогноз был 8, метка – тоже 8. Продолжим.Изображение двойки. Прогноз 2, метка – тоже 2. Опять правильно. Далее идётнечто, похожее на 5. Прогноз – 5, метка – 5. Нажимаем “n”и выходим.

Андрей Никитенко
Андрей Никитенко
Задать вопрос эксперту
Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: