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

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

TL; DR. Прочтите мою записную книжку :)

Передача обучения с помощью Keras

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

Inception-v3 в Keras
Использовать предварительно обученную модель в Keras довольно просто, всего две строчки, как показано ниже.

from keras.applications.inception_v3 import InceptionV3
model = InceptionV3(weights=’imagenet’)

Эта модель была предварительно обучена с помощью наборов данных ImageNet, которые содержат один миллион изображений и 1000 классов.

Давайте классифицируем следующие два изображения с помощью этой модели. Поскольку модель Inception-v3 принимает изображение RGB 299x299 в качестве входных данных, вы должны преобразовать изображение перед его классификацией. У Кераса также есть полезные модули для этого.

from keras.preprocessing import image
from keras.applications.inception_v3 import preprocess_input, decode_predictions
import numpy as np

# Make input data from Jpeg file
img_path = 'seagull.jpg'
img = image.load_img(img_path, target_size=(299, 299))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)

# Classify image
preds = model.predict(x)

# Print predicted classes
print('Predicted:')
for p in decode_predictions(preds, top=5)[0]:
    print("Score {}, Label {}".format(p[2], p[1]))

Изображения для классификации и результаты;

Predicted:
Score 0.965535342693, Label Indian_elephant
Score 0.0246694963425, Label tusker
Score 0.000626200810075, Label African_elephant
Score 0.000182053816388, Label Mexican_hairless
Score 0.000138055766001, Label hippopotamus

Predicted:
Score 0.278156191111, Label albatross
Score 0.0422638729215, Label drake
Score 0.0255650430918, Label goose
Score 0.0211290325969, Label red-breasted_merganser
Score 0.019902639091, Label lakeside

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

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

import pandas as pd
pd.DataFrame(model.layers).tail()

Результат выглядит следующим образом.

308  <keras.layers.merge.Concatenate object at 0x7f...
309  <keras.layers.core.Activation object at 0x7fb7...
310  <keras.layers.merge.Concatenate object at 0x7f...
311  <keras.layers.pooling.GlobalAveragePooling2D o...
312  <keras.layers.core.Dense object at 0x7fb7a1a5b...

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

from keras.models import Model

# The model which outputs intermediate layer features
intermediate_layer_model = Model(inputs=model.input, 
                                 outputs=model.layers[311].output)

Чтобы извлечь функции и визуализировать, запустите следующий код.

features = intermediate_layer_model.predict(x)
pd.DataFrame(features.reshape(-1,1)).plot(figsize=(12, 3))

Результатом GlobalAveragePooling2D являются 2048 размерных элементов. Модель Inception-v3 классифицирует 1000 классов, используя плотный слой в конце сети, который использует эти функции в качестве входных данных. Но теперь мы хотели бы классифицировать «другие» классы. Итак, давайте удалим этот слой и добавим еще один.

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

from keras.layers import Dense

# Connect Dense layers at the end
x = intermediate_layer_model.output
x = Dense(1024, activation='relu')(x)
predictions = Dense(2, activation='softmax')(x)

# Transfer Learning model
transfer_model = Model(inputs=intermediate_layer_model.input, outputs=predictions)

В этот момент модель обучает все свои переменные. Но мы хотим обучать только добавленные нами плотные слои, поэтому давайте остановим необученные слои.

# Freeze all layers
for layer in transfer_model.layers:
    layer.trainable = False

# Unfreeze last dense layers
transfer_model.layers[312].trainable = True
transfer_model.layers[313].trainable = True

transfer_model.compile(loss='categorical_crossentropy',
                       optimizer='adam',
                       metrics=['accuracy'])

Выполнено! Теперь мы можем настроить эту модель для выделенной классификации двух классов.

Тонкая настройка для классификации по двум классам
Давайте классифицируем изображения ниже. Наборы данных называются наборами данных Opera-Capitol, которые я создал, включая 100 изображений Оперы и Капитолия для каждого. Вы можете загрузить код для создания этих наборов данных.
https://github.com/hayatoy/deep-learning-datasets

Загрузить набор данных
Набор данных сжимается в формате NumPy и сохраняется в GitHub. Вы можете использовать его следующим образом.

import requests

url = 'https://github.com/hayatoy/deep-learning-datasets/releases/download/v0.1/tl_opera_capitol.npz'
response = requests.get(url)
dataset = np.load(BytesIO(response.content))

X_dataset = dataset['features']
y_dataset = dataset['labels']

Давайте разделим набор данных на поезд и тест, здесь я разделил его на 80% на поезд и 20% на тест.

from keras.utils import np_utils
from sklearn.model_selection import train_test_split

X_dataset = preprocess_input(X_dataset)
y_dataset = np_utils.to_categorical(y_dataset)
X_train, X_test, y_train, y_test = train_test_split(
    X_dataset, y_dataset, test_size=0.2, random_state=42)

Кстати, каков будет результат, если обычная модель Inception-v3 классифицирует набор данных? Посмотрим, как пойдет.

x = X_dataset[0]
x = np.expand_dims(x, axis=0)

preds = model.predict(x)
print('Predicted:')
for p in decode_predictions(preds, top=5)[0]:
    print("Score {}, Label {}".format(p[2], p[1]))

Первое изображение набора данных - Оперный театр. и вот результат .. крушение. Опять же, потому что исходный набор данных ImageNet не включает Opera House.

Predicted:
Score 0.110657587647, Label wreck
Score 0.0671983659267, Label lakeside
Score 0.0309968702495, Label seashore
Score 0.0249739717692, Label breakwater
Score 0.0229569561779, Label fountain

Точная настройка модели для Opera-Capitol
Чтобы обучить модель обучения передачи, просто вызовите fit функцию. После этого давайте оценим модель, насколько она правильно предсказывает.

transfer_model.fit(X_train, y_train, epochs=20,
                   validation_data=(X_test, y_test))
loss, acc = transfer_model.evaluate(X_test, y_test)
print('Loss {}, Accuracy {}'.format(loss, acc))

Результат оценки тестовых данных:

Loss 0.112133163214, Accuracy 0.975

Точность достигла 97,5%!

Обучение модели на Cloud ML Engine

Модель трансферного обучения занимает ок. от десяти до двадцати минут для обучения на локальной машине без графического процессора, если вы работаете на Cloud ML Engine ... всего минуту. (плюс несколько минут на постановку)

Создание пакета
Чтобы обучить модель в Cloud ML Engine, вы должны сначала создать пакет своего кода. В этом примере мы используем keras, h5py и Pillow в качестве внешних библиотек, поэтому вы должны включить эти библиотеки в свой setup.py.

from setuptools import setup
if __name__ == '__main__':
    setup(name='trainer',
          packages=['trainer'],
          install_requires=['keras','h5py','Pillow'])

Cloud ML Engine от Jupyter Notebook
Упаковка, загрузка в Google Cloud Storage, выполнение задания ML Engine действительно утомительны. Разве вы не хотите просто запустить код Jupyter Notebook на ML Engine? Я сделал для этого расширение!
https://github.com/hayatoy/cloudml-magic

Онлайн-прогнозирование на Cloud ML Engine

Теперь вы хотите обслуживать свою обученную модель, как это сделать? Внедрите HTTP-сервер, настройте TensorFlow или Keras на сервере, а также балансировку нагрузки и т. Д. Это сенсорная работа. В Cloud ML Engine вам нужно только загрузить свою модель в GCS (Google Cloud Storage). Он обслуживает вашу модель, принимает запросы прогнозов через REST API и, конечно же, автоматическое масштабирование.

Постройте график, который преобразует изображение
Поскольку модель Keras принимает в качестве входных данных только необработанный массив изображений, мы должны преобразовать формат Jpeg или Png в необработанный массив изображений, в противном случае полезная нагрузка запросов REST API будет слишком большой.

with tf.Graph().as_default() as g_input:
    input_b64 = tf.placeholder(shape=(1,),
                               dtype=tf.string,
                               name='input')
    input_bytes = tf.decode_base64(input_b64[0])
    image = tf.image.decode_image(input_bytes)
    image_f = tf.image.convert_image_dtype(image, dtype=tf.float32)
    input_image = tf.expand_dims(image_f, 0)
    output = tf.identity(input_image, name='input_image')

# Convert to GraphDef
g_input_def = g_input.as_graph_def()

Затем мы конвертируем модель Keras в tf.GraphDef, чтобы мы могли соединить приведенный выше график.

sess = K.get_session()

from tensorflow.python.framework import graph_util

# Make GraphDef of Transfer Model
g_trans = sess.graph
g_trans_def = graph_util.convert_variables_to_constants(sess, 
                      g_trans.as_graph_def(),
                      [transfer_model.output.name.replace(':0','')])

Вот комбинированный график, он принимает файл Jpeg или Png в кодировке base64, а затем выводит классифицированный результат из модели обучения передачи.

with tf.Graph().as_default() as g_combined:
    x = tf.placeholder(tf.string, name="input_b64")

    im, = tf.import_graph_def(g_input_def,
                              input_map={'input:0': x},
                              return_elements=["input_image:0"])

    pred, = tf.import_graph_def(g_trans_def,
             input_map={transfer_model.input.name: im,
             'batch_normalization_1/keras_learning_phase:0': False},
             return_elements=[transfer_model.output.name])

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

with tf.Session() as sess2:
  inputs = {"inputs": tf.saved_model.utils.build_tensor_info(x)}
  outputs = {"outputs":tf.saved_model.utils.build_tensor_info(pred)}
  signature =tf.saved_model.signature_def_utils.build_signature_def(
            inputs=inputs,
            outputs=outputs,
  method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME
        )

  # save as SavedModel
  b = tf.saved_model.builder.SavedModelBuilder('gs://{BUCKET}/mdl')
  b.add_meta_graph_and_variables(sess2,
                  [tf.saved_model.tag_constants.SERVING],
                  signature_def_map={'serving_default': signature})
  b.save()

Зарегистрируем модель в ML Engine. Просто введите следующие две команды. Это может занять несколько минут ... так что выпейте чашку чая, и все будет готово.

gcloud ml-engine models create OperaCapitol
gcloud ml-engine versions create v1 \
          --model OperaCapitol \
          --runtime-version 1.2 \
          --origin gs://{BUCKET}/mdl

Классифицируйте изображение по онлайн-прогнозу

Чтобы классифицировать изображение по онлайн-прогнозу, просто вызовите его REST API. Если вы запрашиваете в среде Python, использовать библиотеку Discovery api намного проще. Вот как инициализировать API обнаружения для онлайн-прогнозирования.

from oauth2client.client import GoogleCredentials
from googleapiclient import discovery
from googleapiclient import errors

PROJECTID = 'PROJECTID'
projectID = 'projects/{}'.format(PROJECTID)
modelName = 'OperaCapitol'
modelID = '{}/models/{}'.format(projectID, modelName)

credentials = GoogleCredentials.get_application_default()
ml = discovery.build('ml', 'v1', credentials=credentials)

Давайте классифицируем изображение. График конвертера не имеет функции изменения размера, вы должны сами изменить размер изображения до 299x299. Также не забудьте кодировать изображение в base64.

with open('opera.jpg', 'rb') as f:
    b64_x = f.read()
import base64
import json

b64_x = base64.urlsafe_b64encode(b64_x)
input_instance = dict(inputs=b64_x)
input_instance = json.loads(json.dumps(input_instance))
request_body = {"instances": [input_instance]}

request = ml.projects().predict(name=modelID, body=request_body)
try:
    response = request.execute()
except errors.HttpError as err:
    print(err._get_reason())
response

Вот ответ от Online Prediction. Список «выходов» представляет доверие Оперного театра и Капитолия соответственно.
99,7% для Оперного театра, это верно!

{u'predictions': [
    {u'outputs': [0.9974665641784668, 0.00253341649658978]}
]}

Вы можете найти код этой статьи здесь;
https://github.com/hayatoy/cloudml-magic

Наслаждайтесь Cloud ML Engine!