Развертывание моделей глубокого обучения на основе Keras и Gunicorn+Flask

Flask

В этой статье в основном фиксируютсяFlaskПроцесс, используемый в процессе развертывания, возникшие проблемы и соответствующие решения.

1. Введение проекта

В этом разделе кратко описывается работа, проделанная некоторое время назад:

  • Реализуйте простую задачу классификации изображений на основе глубокого обучения.
  • Разверните его в веб-приложении с помощью фреймворка flask.
  • Высокие требования к параллелизму

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

2. Процесс проекта

Эта часть начинается с процесса реализации проекта, записи проделанной работы и использованных инструментов.

2.1 Модель классификации изображений

1. Выбор модели

Требуется классификация изображений, и первая реакция — использовать более зрелую и классическую сетевую структуру классификации, такую ​​как серия VGG (VGG16, VGG19), серия ResNet (например,ResNet50),InceptionV3Ждать.

Учитывая, что изображения неизвестных типов засекречены, а данных для обучения напрямую нет,ImagenetПредобученная модель, обученная вышеизложенному, в основном соответствует требованиям.

Если требования к производительности (затратам времени) являются строгими, рекомендуется использовать неглубокую сетевую структуру, такую ​​какVGG16, MobileNetЖдать.

в,MobileNetСеть предназначена для мобильных и встроенных приложений глубокого обучения, поэтому она также может обеспечить идеальные требования к скорости процессора. Это легкая глубокая сетевая структура.

MobileNetЗависит отGoogle 团队представлены, опубликованы вCVPR-2017, название статьи: «MobileNets: эффективные сверточные нейронные сети для приложений мобильного зрения»

2. Выбор кадра

  • обычное использованиеKerasСуществует множество фреймворков,Kerasиспользование базовой библиотекиTheanoилиTensorflow, также известный как серверная часть Keras.KerasвTensorflowВысокоуровневый API, построенный поверхTensorflowЛегче начать.

  • Классификационная сеть, упомянутая выше, вKerasВ основном это было реализовано в Keras, а структура сети, реализованная в Keras, выглядит следующим образом:

  • Он прост в использовании и может быть импортирован напрямую следующим образом:

Поэтому в качестве фреймворка для глубокого обучения выбран Keras.

3. Пример кода

отKerasРамка,VGG16Возьмите сеть в качестве примера для классификации изображений.

from keras.models import Model
from keras.applications.vgg16 import VGG16, preprocess_input
import keras.backend.tensorflow_backend as KTF
import tensorflow as tf
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1" #使用GPU
# 按需占用GPU显存
gpu_options = tf.GPUOptions(allow_growth=True)
sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options))
KTF.set_session(sess)

# 构建model
base_model = VGG16(weights=‘imagenet’, include_top=True)
model = Model(inputs=base_model.input,
outputs=base_model.get_layer(layer).output) # 获取指定层的输出值,layer为层名

# 进行预测
img = load_image(img_name, target_size=(224, 224))  # 加载图片并resize成224x224

# 图像预处理
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x) 

feature = model.predict(x) # 提取特征

2.2 Тест производительности модели

После запуска моделей классификации нам необходимо проверить их производительность, например потребление времени, использование ЦП, использование памяти и использование памяти графического процессора.

1. Отнимает много времени

Затраты времени — это время, затрачиваемое на извлечение признака классификации тестового изображения, включая сумму времени предварительной обработки изображения и времени прогнозирования модели.

# 使用python中的time模块
import time
t0 = time.time()
....
图像处理和特征提取
....

print(time.time()-t0) #耗时,以秒为单位

2. Использование памяти графического процессора

Использование командной строки Nvidianvidia-smiВы можете просмотреть использование памяти.

3. Занятие процессора, памяти

использоватьtopкоманда илиhtopКоманда для проверки использования процессора и памяти.

Использование памяти также может быть использованоfreeкоманда для просмотра:

  • free -h: добавлять-hвариант, результат вывода более дружелюбен, и будет дана соответствующая единица измерения.

  • Когда вам нужно постоянно наблюдать за состоянием памяти, вы можете использовать-sОпция указывает интервал в секундах:free -h -s 3(обновлять каждые 3 секунды, нажать при остановке обновленияCtrl+c)

Ubuntu 16.04по умолчанию в версииfreeВ версии есть баги, пользуйтесь-sПри выборе опции будет сообщено об ошибке.

В соответствии с результатами трех вышеприведенных тестов своевременно настройте структуру сети и используемые параметры занятости видеопамяти.

Чтобы узнать конкретное значение команды, обратитесь к сообщению в блоге:

Linux проверяет использование процессора и памяти

2.3 Использование Redis

Redis=Remote DIctionary Server, является высокопроизводительным, написанным Сальваторе Санфилиппо.key-valueСистема хранения. Redis — это база данных с открытым исходным кодом, написанная на языке ANSI C, совместимая с протоколом BSD, поддерживающая сеть, может храниться в памяти и быть постоянной, а также предоставляет API-интерфейсы на нескольких языках.

RedisПоддерживаемые типы хранения:string, list, set, zsetиhash, который больше используется в сценарии обработки крупномасштабных операций чтения и записи данных.

1. Основное использование

установить редис

pip install redis

# 测试
import redis

основное введение

redis.pyПредусмотрено два класса:Redis,StrictRedisиспользуется для реализацииRedisКомандаStrictRedisИспользуется для реализации большинства официальных команд и использования официального синтаксиса и команд.RedisдаStrictRedisПодкласс , длявперед совместимыйredis.pyОбычно мы используемStrictRedis.

Пример использования

# 1. 导入redis
from redis import StrictRedis

# 2. 连接数据库,指定host,端口号,数据库
r = StrictRedis(host=‘localhost’, port=6379, db=2)

# 3. 存储到redis中
r.set('test1', 'value1')  # 单个数据存储
r.set('test2', 'value2')

# 4. 从redis中获取值
r.get('test1')

# 5. 批量操作
r.mset(k1='v1', k2='v2')
r.mset({'k1':'v1', 'k2':'v2'})
r.mget('k1', 'k2')
r.mget(['k1', 'k2'])

2. Массив хранения Redis

Redis не может напрямую хранить массивы.Если вы напрямую сохраните значение типа массива, тип значения после получения изменится следующим образом: сохраните тип массива numpy, а полученный типbytesтип.

import numpy as np
from redis import StrictRedis

r = StrictRedis(host=‘localhost’, port=6379, db=2)
x1 = np.array(([0.2,0.1,0.6],[10.2,4.2,0.9]))
r.set('test1', x1)
>>> True
r.get('test1')
>>> b'[[ 0.2  0.1  0.6]\n [10.2  4.2  0.9]]'
type(r.get('test1')) #获取后的数据类型
>>> <class 'bytes'>

Чтобы поддерживать один и тот же тип до и после сохранения данных, сериализуйте массив перед его сохранением и десериализуйте его при получении массива.

С помощью питонаpickleМодуль выполняет операции сериализации.

import pickle
r.set('test2', pickle.dumps(x1))
>>> True
pickle.loads(r.get('test2'))
>>> array([[ 0.2,  0.1,  0.6],
         [10.2,  4.2,  0.9]])

Таким образом, тип данных до сохранения и после извлечения может поддерживаться согласованным.

2.4 среда веб-разработки — Flask

До изучения языка python никогда не обращал вниманияWeb开发Эта глава, потому что содержание работы не включает эту часть. Теперь надо еще раз посмотреть.

Раннее программное обеспечение в основном работало на настольных компьютерах, а программное обеспечение, такое как базы данных, работало на стороне сервера.Client/ServerАббревиатура режимаCSАрхитектура. С появлением Интернета,CSАрхитектура не подходитWeb, главная причина в том, что модификация и обновление веб-приложений происходят очень часто,CS架构Каждому клиенту необходимо обновлять настольное приложение одно за другим, поэтомуBrowser/Serverшаблон стал популярным, сокращение отBS架构.

существуетBS架构В этом случае клиенту нужен только браузер, а логика и данные приложения хранятся на стороне сервера, браузеру нужно только запросить сервер, получить веб-страницу и отобразить веб-страницу пользователю. В настоящее время веб-страницы также чрезвычайно интерактивны.

История рождения Python раньше, чем у Интернета.Поскольку Python является интерпретируемым языком сценариев с высокой эффективностью разработки, он очень подходит для веб-разработки.

Python имеет сотни веб-фреймворков с открытым исходным кодом, наиболее знакомыми из которых являютсяFlask, Django. Далее сFlaskНапример, как использовать Flask для веб-развертывания.

Для ознакомления с фреймворком веб-разработки вы можете обратиться к следующему сообщению в блоге:Три самых популярных фреймворка для веб-разработки Python, вы это заслужили!

СвязанныйFlaskКонкретное использование может относиться к другим сообщениям в блоге, информация в этой области относительно полная. Нижеследующее в основном использует конкретные примеры для иллюстрации:

1. Установите и используйте

  1. Установить колбу

    pip install flask
    
    import flask # 导入
    flask.__version__ # 版本
    >>> '1.1.1' #当前版本
    
  2. Простой пример Flask

    Flask использует декораторы Python для автоматическогоURLсвязанные с функцией.

    # hello.py
    from flask import Flask, request
    
    app = Flask(__name__) #创建Flask类的实例,第一个参数是模块或者包的名称
    app.config['JSON_AS_ASCII']=False # 支持中文显示
    
    @app.route('/', methods=['GET', 'POST']) # 使用methods参数处理不同HTTP方法
    def home():
        return 'Hello, Flask'
    
    if __name__ == '__main__':
        app.run()
    
    • использоватьroute()Decorator, чтобы сообщить Flask URL-адрес для запуска функции;
    • Имя функции используется для создания связанного URL-адреса. Наконец, функция возвращает информацию, которую необходимо отобразить в браузере пользователя.

    Запустите файл, вам будет предложено* Running on http://127.0.0.1:5000/, откройте этот URL в браузере, он автоматически вызоветhomeфункция, возвратHello, Flask, вы увидите на странице браузераHello, Flaskшрифт.

    Параметры app.run

    app.run(host="0.0.0.0", port="5000", debug=True, processes=2, threaded=False)
    
    • hostустановить как0.0.0.0, вы можете сделать сервер общедоступным
    • port: укажите номер порта, по умолчанию5000
    • debug: следует ли включить модель отладки, если вы включите режим отладки, сервер автоматически перезапустится после изменения кода приложения, а также предоставит полезный отладчик при сбое приложения.
    • processes: количество потоков, по умолчанию1
    • threaded:boolТипа, включить ли многопоточность. Примечание. При запуске нескольких процессов несколько потоков не поддерживаются одновременно.

    Уведомление: Никогда не используйте отладчик в рабочей среде.

2. Реакция колбы

Возвращаемое значение функции представления автоматически преобразуется в объект ответа. Если возвращаемое значение является строкой, оно будет преобразовано в строку, содержащую в качестве тела ответа200 OKкод ошибки иtext/htmlТип объекта ответа. Если возвращаемое значение является словарем, он вызоветjsonify()чтобы сгенерировать ответ. Ниже приведены правила конвертации:

  • Если представление возвращает объект ответа, верните его напрямую.
  • Если возвращается строка, объект ответа генерируется для возврата на основе строки и параметров по умолчанию.
  • Если возвращается словарь, вызовите jsonify, чтобы создать объект ответа.
  • Если возвращается кортеж, элементы в кортеже могут предоставить дополнительную информацию. Кортеж должен содержать хотя бы один элемент, и этот элемент должен состоять из (ответ, статус), (ответ, заголовки) или (ответ, статус, заголовки). Значение состояния переопределяет код состояния, а заголовки представляют собой список или словарь дополнительных значений заголовков.
  • Если ничего из вышеперечисленного, Flask предположит, что возвращаемое значение является действительным приложением WSGI, и преобразует его в объект ответа.

API в формате JSON

JSONФорматированные ответы распространены, и начать писать такой API на Flask несложно. при возврате из представленияdict, то он будет преобразован вJSON 响应.

@app.route("/me")
def me_api():
    user = get_current_user()
    return {
        "username": user.username,
        "theme": user.theme,
        "image": url_for("user_image", filename=user.image),
    }

еслиdictОн не соответствует требованиям, и необходимо создать другие типы ответов в формате JSON.Вы можете использоватьjsonify()функция. Эта функция сериализует все поддерживаемыеJSONтип данных.

@app.route("/users")
def users_api():
    users = get_all_users()
    return jsonify([user.to_json() for user in users])

3. Запустите сервер разработки

  1. Использование сервера разработки из командной строки

    Настоятельно рекомендуется использовать скрипт командной строки flask (интерфейс командной строки) во время разработки, потому что он имеет мощную функцию перегрузки и обеспечивает очень хороший опыт перегрузки. Основное использование заключается в следующем:

    $ export FLASK_APP=my_application
    $ export FLASK_ENV=development
    $ flask run
    

    При этом запускается среда разработки (включая интерактивный отладчик и перезагрузку) иhttp://localhost:5000/Предоставлять услуги.

    с помощью различныхrunПараметры могут управлять отдельными функциями сервера. Например, чтобы отключить перезагрузку:

    $ flask run --no-reload

  2. Использование сервера разработки из кода

    Другой способFlask.run()метод запуска приложения, который немедленно запускает локальный сервер, в отличие от использованияflaskСкрипт работает так же.

    Пример:

    if __name__ == '__main__':
        app.run()
    

    В целом это нормально, но не для развития.

2.5 Использование Гуникорна

Когда мы выполняем вышеуказанноеapp.pyкогда используешьflaskВстроенный сервер завершает запуск веб-службы. В производственной среде сервер, идущий в комплекте с flask, не может соответствовать требованиям по производительности.GunicornДелатьwsgiконтейнер, для развертыванияflaskпрограмма.

Gunicorn(зеленый единорог)Python WSGI UNIX HTTPсервер. Перенесено из проекта Ruby's Unicorn. ДолженGunicornсервер какwsgi appконтейнер, который можетСовместимость с различными веб-фреймворками, чтобы добиться очень простого и легкого потребления ресурсов. Гуникорн Директначать с команды, не нужно писать файл конфигурации, что намного проще, чем uWSGI.

В веб-разработке метод развертывания примерно такой же.

1. Установка и использование

pip install gunicorn

Если хотитеGunicornАсинхронная поддержкаworkersВам необходимо установить следующие три пакета:

pip install gevent
pip install eventlet
pip install greenlet

Запустите сервер, указав процесс и номер порта:

gunicorn -w 4 -b 127.0.0.1:5001 运行文件名称:Flask程序实例名

Возьмем приведенный выше файл hello.py в качестве примера:

gunicorn -w 4 -b 127.0.0.1:5001 hello:app

параметр:-w: представляет процесс (рабочий).-b: указывает IP-адрес привязки и номер порта (привязка).

Просмотр конкретных параметров исполняемого файла gunicorngunicorn -hПараметры конфигурации обычно записываются в файл конфигурации, напримерgunicorn_conf.py

Важные параметры:

  • bind: адрес прослушивания и порт
  • workers: количество рабочих процессов. предлагаемое значение:2~4 x (NUM_CORES), значение по умолчанию равно 1.
  • worker_class: Как работает рабочий процесс. имеют:sync(дефолт),eventlet, gevent, gthread, tornado
  • threads: количество потоков в рабочем процессе. предлагаемое значение:2~4 x (SUM_CORES), значение по умолчанию равно 1.
  • reload: Когда код изменен,Автоматический перезапуск рабочих. Для сред разработки по умолчанию используетсяFalse
  • daemon: запускается ли приложение сdaemonрежим запуска, следует ли запускать процесс демона, по умолчаниюFalse
  • accesslog: путь к файлу журнала доступа
  • errorlog: путь журнала ошибок
  • loglevel: уровень журнала.debug, info, warning, error, critical.

Пример конфигурации параметра:

# gunicorn_conf.py
bind: '0.0.0.0:5000' # 监听地址和端口号
workers = 2 # 进程数
worker_class = 'sync' #工作模式,可选sync, gevent, eventlet, gthread, tornado等
threads = 1 # 指定每个进程的线程数,默认为1
worker_connections = 2000 # 最大客户并发量
timeout = 30 # 超时时间,默认30s
reload = True # 开发模式,代码更新时自动重启
daemon = False # 守护Gunicorn进程,默认False

accesslog = './logs/access.log' # 访问日志文件
errorlog = './logs/error.log'
loglevel = 'debug' # 日志输出等级,debug, info, warning, error, critical

Вызвать команду:

gunicorn -c gunicorn_conf.py hello:app

Пример файла конфигурации параметров можно увидеть:gunicorn/example_config.py на мастере benoitc/gunicorn

3. Пример кода

#flask_feature.app
import numpy as np
from flask import Flask, jsonify
from keras.models import Model
from keras.applications.vgg16 import VGG16
from keras.backend.tensorflow_backend import set_session

app = Flask(__name__)
app.config['JSON_AS_ASCII']=False

@app.route("/", methods=["GET", "POST"])
def feature():
    img_feature = extract()
    return jsonify({'result':'true', 'msg':'成功'})

def extract(img_name):
    # 图像预处理
    img = load_image(img_name, target_size=(feature_params["size"], feature_params["size"])) 

    x = image.img_to_array(img)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    
    with graph.as_default():
        set_session(sess)
        res = model.predict(x)
    
    return res
    
    
if __name__ == '__main__':
    tf_config = some_custom_config
    sess = tf.Session(config=tf_config)
    set_session(sess)
    base_model = VGG16(weights=model_weights, include_top=True)
    model = Model(inputs=base_model.input,
                    outputs=base_model.get_layer(layer).output)
    graph = tf.get_default_graph()
    
    app.run()

использоватьgunicornЗапустите сервисную команду:

gunicorn -c gunicorn_conf.py flask_feature:app

4. Возникшие проблемы

Здесь записываются проблемы, возникающие в ходе всей работы по развертыванию, и соответствующие решения.

4.1 Многопоточность Flask и проблемы с несколькими процессами

Из-за высоких требований к производительности алгоритма попробуйте использовать параметры многопоточности и многопроцессорности, которые поставляются с Flask, чтобы проверить эффект. существуетFlaskизapp.run()функция, как описано вышеprocessesпараметр для указания количества открытых мультипроцессов,threadedПараметр используется, чтобы указать, следует ли включать многопоточность.

Flask включает режим отладки, когда служба запускается, режим отладки открывает поток тензорного потока, что приводит к смещению графика при вызове тензорного потока.

4.1 Проблема Flask и Keras

При использовании Flask для запуска сервиса здесь записываются возникшие проблемы и справочные материалы.

Q1: Тензор не является элементом этого графа

Сообщение об ошибке:

"Tensor Tensor(\"pooling/Mean:0\", shape=(?, 1280), dtype=float32) is not an element of this graph.", 

Описание: использоватьKerasКод для извлечения признаков классификации изображений в предварительно обученной модели может работать нормально.Flaskдля запуска службы при доступе к функции прогнозирования возникает вышеуказанная ошибка.

Причина: Используется динамический график, т.е. при построении прогнозов загружаетсяgraphНе тогда, когда модель инициализируется в первый разGraph, в модели нет такой информации, как параметры и узлы.

Кто-то дал следующее решение:

import tensorflow as tf
global graph, model
graph = tf.get_default_graph()

#当需要进行预测的时候
with graph.as_default():
    y = model.predict(x)

Q2: Используйте Flask, чтобы запустить службу, дважды загрузить модель и занять две копии видеопамяти.

Проблема возникает из-за использованияFlaskПри запуске службы включается режим отладки, т.е.debug=True.dubugрежим откроетtensorflowЕсли вы проверите использование видеопамяти GPU, вы обнаружите, что два процесса занимают один и тот же объем видеопамяти.

Отключить модель отладки (debug=False) может быть использован.

Использованная литература:

[1]:Keras + Flask предоставляет сервисную яму интерфейса~~~

4.2 Проблемы, связанные со службой запуска gunicorn

При использовании gunicorn для запуска сервиса возникают следующие проблемы:

Q1: Failed precondition

Конкретные проблемы:

2 root error(s) found.\n 
(0) Failed precondition: Error while reading resource variable block5_conv2/kernel from Container: localhost. This could mean that the variable was uninitialized. Not found: Container localhost does not exist. (Could not find resource: localhost/block5_conv2/kernel)\n\t [[{{node block5_conv2/convolution/ReadVariableOp}}]]\n\t [[fc2/Relu/_7]]\n 
(1) Failed precondition: Error while reading resource variable block5_conv2/kernel from Container: localhost. This could mean that the variable was uninitialized. Not found: Container localhost does not exist. (Could not find resource: localhost/block5_conv2/kernel)\n\t [[{{node block5_conv2/convolution/ReadVariableOp}}]]\n0 successful operations.\n0 derived errors ignored."

Решение:

Создавая ссылку на сеанс, используемый для загрузки модели, затем используя keras для установки сеанса для каждого запроса, который необходимо использовать. детали следующим образом:

from tensorflow.python.keras.backend import set_session
from tensorflow.python.keras.models import load_model

tf_config = some_custom_config
sess = tf.Session(config=tf_config)
graph = tf.get_default_graph()

# IMPORTANT: models have to be loaded AFTER SETTING THE SESSION for keras! 
# Otherwise, their weights will be unavailable in the threads after the session there has been set
set_session(sess)
model = load_model(...)

# 在每一个request中:
global sess
global graph
with graph.as_default():
    set_session(sess)
    model.predict(...)

Некоторые пользователи сети проанализировали причины:tensorflowизgraphиsessionНе потокобезопасный, по умолчанию каждый поток создает новыйsession(за исключением весов, моделей и т. д., которые были загружены ранее). Поэтому, сохранив глобальный сеанс, содержащий все модели, и настроив его для вызова в каждом потоке с помощьюkerasиспользовать, может решить проблему.

Некоторые пользователи сети извлекли улучшенный способ:

# on thread 1
session = tf.Session(graph=tf.Graph())
with session.graph.as_default():
    k.backend.set_session(session)
    model = k.models.load_model(filepath)

# on thread 2
with session.graph.as_default():
    k.backend.set_session(session)
    model.predict(x, **kwargs)

Новшество здесь позволяет загружать несколько моделей (одновременно) и использовать их в нескольких потоках. По умолчанию при загрузке модели используется «по умолчанию».Sessionи "по умолчанию"graph. Но здесь создаются новые. Также обратите внимание,Graphсохранить вSessionобъект, это более удобно.

Проверено, похоже, не работает

Q2: Не удается запустить службу, КРИТИЧЕСКИЙ ТАЙМ-АУТ WORKER

При использовании gunicorn для запуска службы flask при просмотре состояния сервера и файлов журнала было обнаружено, что он пытался запуститься, но безуспешно.

CRITICAL WORKER TIMEOUT

Вот параметры конфигурации gunicorntimeoutвызванный. По умолчанию30s, то есть, если оно превысит 30 с, процесс будет убит, а затем перезапущенrestart.

Когда время запуска службы для инициализации превышает значение таймаута, она всегда будет запускаться, убивать, перезапускать.

Это значение может быть соответствующим образом увеличено в зависимости от конкретной ситуации.

Использованная литература:

tensorflow - GCP ML-engine FailedPreconditionError (code: 2) - Stack Overflow

5. Ссылки

Добро пожаловать в мир Flask — Документация по Flask ( 1.1.1 )

Подробности конфигурации Gunicorn

Во время выполнения: «Ошибка при чтении переменной ресурса softmax/kernel из контейнера: localhost» · Проблема № 28287 · tensorflow/tensorflow

[Решено] Возникла ошибка при запуске Flask через gunicorn в онлайн-среде: CRITICAL WORKER TIMEOUT