Рубрики
Без рубрики

Как я построил обратный поиск изображений с помощью машинного обучения и TensorFlow: Часть 3

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

Автор оригинала: Jim Fleming.

С Возвращением…

Я сделал несколько TensorFlow примеров для своего сайта, fomoro.com , и одним из тех, что я создал, был легкий обратный поиск изображений . Пока это свежо в моей голове, я хотел бы написать полное описание того, каково это-создавать приложение для машинного обучения, и, более конкретно, как сделать свой собственный обратный поиск изображений. Для этой демо-версии работа заключается в “сборке/настройке данных”, “разработке моделей” и “разработке приложений”.

На высоком уровне я использую TensorFlow для создания автоэнкодера , тренирую его на куче изображений , использую обученную модель для поиска связанных изображений и отображения их с помощью приложения Flask .

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

Читать Часть 1: Настройка проекта Читать Часть 2: Разработка модели

– Готовы? Давайте начнем…

Использование Модели с Контрольными точками

Использование модели для вывода очень похоже на обучение модели — вы просто не говорите ей, правильно это или нет. Есть все виды способов, которыми вы можете использовать его в производстве. Для высокопроизводительного вывода TensorFlow Serving -это путь, но это не самая простая вещь для настройки.

Поскольку это демо-версия, я иду по легкому маршруту и просто использую конечную контрольную точку модели. Контрольные точки хороши тем, что вы можете останавливать и перезапускать тренировки, поэтому я часто их использую. К счастью, обработка контрольных точек встроена прямо в train_and_evaluate , часть класса Experiment, что является еще одним аргументом в пользу их использования вместо более распространенного session.run() . Вы можете настроить частоту сохранения контрольных точек с помощью RunConfig:

run_config = tf.contrib.learn.RunConfig(
    save_summary_steps=1000,
    save_checkpoints_steps=1000,
    save_checkpoints_secs=None,
    gpu_memory_fraction=0.8)

experiment.py строка:33

experiment.py строка:33

Чтобы использовать мои существующие контрольные точки, я создал новый файл, predict.py . Это специально созданный файл, и он действительно имеет отношение только к данному конкретному случаю использования, в отличие от файлов моделей и экспериментов, которые я включаю почти в каждый проект.

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

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

estimator = tf.contrib.learn.Estimator(
    model_dir=args.model_dir,
    model_fn=model_fn,
    config=None)

predictions_iter = estimator.predict(
    input_fn=input_fn,
    as_iterable=True)

predict.py строка:53

От TensorFlow к Python

Теперь, когда у меня есть все мои предсказания, пришло время покинуть землю тензорного потока и вернуться в обычный Python. Я превращаю результаты моей модели в список, и теперь я могу делать обычные вещи Python с результатами, например, перебирать их или получать общее количество объектов в моем закодированном изображении.

# drop out of tensorflow into regular python/numpy
predictions_list = list(predictions_iter)
features_length = len(predictions_list[0]['encoded_image'].flatten())

predict.py линия:62

Сравнение ближайших Соседей

Я использую очень удобную библиотеку Annoy для сравнения моих ближайших соседей. Это приблизительный поиск, поэтому он торгует точностью для скорости, но это компромисс, который мне нравится в этой ситуации. Недостатком этой скорости является то, что она индексируется целыми числами, поэтому мне также нужно было построить ассоциации имен файлов. Мне также нужно было знать точную длину объектов, которые я сохраняю заранее (и все объекты должны быть одинаковой длины). Но в большой схеме кода это мелкая картошка, и я рад, что мне не придется писать поиск с нуля.

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

# build search and filename indexes
filenames = []
nn_search = AnnoyIndex(features_length)
for i in tqdm(range(len(predictions_list))):
    nn_search.add_item(i, embeddings_norm[i])
    filenames.append(predictions_list[i]['filename'].split("/")[-1])

predict.py линия:77

Последний шаг-построить наше дерево поиска и сохранить все результаты в несколько файлов для использования в нашем приложении. Мы могли бы сохранить индекс в базе данных. Однако для чего-то такого размера добавление базы данных кажется более излишним. Я также сохраняю длину объектов как часть своих метаданных, потому что она мне понадобится, когда я использую Раздражающее мое приложение. Мы также могли бы предварительно обработать всех соседей, но нам пришлось бы либо выбрать меньшую гибкость в нашем окончательном приложении, либо плоский файл, который слишком велик.

# build and save filename metadata
with open('{}/metadata.json'.format(args.out_dir), 'w') as outfile:
    json.dump({
        'timestamp': time.time(),
        'features_length': features_length,
        'filenames': filenames
    }, outfile)

# build and save search trees
nn_search.build(args.tree_count)
nn_search.save('{}/index.ann'.format(args.out_dir))

predict.py линия:81

Настройка приложения

Теперь перейдем к завершающим битам и настройке базового приложения. Я создал новый каталог приложений с собственным каталогом данных, и именно там я сохранил свои файлы раздражения и метаданных. Мой проект и расширенный каталог приложений теперь выглядят так:

/imagesearch
----/app
--------/data
------------index.ann
------------metadata.json
--------/static
--------/templates
--------app.py
--------other files...
----/data
----/imagesearch
----other files…

Теперь перейдем к завершающим битам и настройке базового приложения. Я создал новый каталог приложений с собственным каталогом данных, и именно там я сохранил свои файлы раздражения и метаданных. Мой проект и расширенный каталог приложений теперь выглядят так:

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

class AnnoyLookup(object):
    def __init__(self, 
                 metadata_path='./data/metadata.json',
                 annoy_path='./data/index.ann'):

        with open(metadata_path) as f:
            self._data = json.load(f)

        self._limit = len(self._data['filenames'])
        self._index = AnnoyIndex(self._data['features_length'])
        self._index.load(annoy_path)

    def get_neighbors(self, image_id, max_neighbors=13):
        results = []

        if image_id < 0 or image_id >= self._limit:
            image_id = random.randrange(self._limit)

        for item_id in self._index.get_nns_by_item(image_id,
                                                   max_neighbors):
            results.append({
                'id': item_id,
                'image': self._data['filenames'][item_id]
            })

        return results

annoy_lookup.линия py:15

Установка колбы

Мы наконец-то готовы создать наше приложение. Я люблю Фляжку за ее простоту. Это была буквально самая легкая часть проекта для написания. Что было приятно, потому что я ненавижу оставлять трудные вещи напоследок — это почти всегда означает, что доставка задерживается. Поскольку я не хочу перемещать свои изображения в каталог приложений из каталога данных, я использую встроенную функцию Flask send_from_directory для возврата их с сервера. Это также позволяет мне использовать любой путь, с которого я хочу служить им.

@app.route('/')
def index_route():
    results = lookup.get_multiple_neighbors(-1) # random starting image.
    return render_template('index.html', results=results)

@app.route('/nearest/', methods=['GET'])
def get_nearest_html_route(image_id):
    results = lookup.get_multiple_neighbors(image_id)
    return render_template('index.html', results=results)

@app.route('/images/')
def get_data_route(path):
    return send_from_directory('../data/results/', path)

app.py строка:14

Шаблон-это просто базовый html с добавлением bootstrap, поэтому он в основном выглядит как искусство программиста. Вот ссылка на источник .

Дополнительный Кредит

Дополнительный Кредит

Поскольку приложение создавалось так быстро, у меня появилось дополнительное время. Я использовал его, чтобы запустить приложение с JavaScript-интерфейсом, чтобы разместить его на страницах GitHub. Для этого требовалось добавить API-маршрут на сервер и немного разгрузить логику отображения в some js .

@app.route('/api/nearest/', methods=['GET'])
def get_nearest_api_route(image_id):
    results = lookup.get_multiple_neighbors(image_id)
    return jsonify(results=results)

app.py линия:24

Вы можете увидеть готовое приложение по адресу http://fomorians.github.io/imagesearch/ .

Резюме

Так вот оно что. От настройки до создания модели и разработки приложений. Именно так я использовал машинное обучение и TensorFlow для создания обратного поиска изображений. Было весело писать все это. Надеюсь, вы тоже что-нибудь из этого извлекли.

Вопросы? Комментарии? Дайте мне знать в комментариях или напишите мне в Твиттере: @jimfleming