с возвращением
Я делал некоторые TensorFlow примеры для моего сайта, fomoro.com , и одним из тех, которые я создал, был легкий обратный поиск изображений . Пока это свежо в моей голове, я хотел написать подробное описание того, как создать приложение для машинного обучения, и, более конкретно, как сделать свой собственный обратный поиск изображений. Для этой демонстрации работа заключается в “сборке/настройке данных”, “разработке моделей” и “разработке приложений”.
На высоком уровне я использую TensorFlow для создания автоэнкодера , тренирую его на куче изображений , использую обученную модель для поиска связанных изображений и отображения их с помощью приложения Flask .
Прочитайте часть 1 этой серии
В последнем посте я говорил о настройке проекта и интеллектуальном анализе данных. Теперь, когда с этим покончено, я собираюсь погрузиться в это . Для этого проекта я использую модель autoencoder с 6 слоями кодирования и 6 слоями декодирования.
Готовы? Давайте начнем…
Создание модели
Для тех из вас, кто не знаком с ними, автоэнкодеры-это сети, которые изучают функцию идентификации. Другими словами, то, что входит, должно быть близко к тому, что выходит. Поначалу это кажется не очень полезным, потому что у вас уже есть оригинал, зачем делать его нечеткую копию?
Ну, вы можете представить себе сеть в виде песочных часов. Первые слои постепенно сжимают выходные данные, а последние слои расширяют их обратно. Волшебство происходит в середине, где у нас есть сжатое представление, которое все еще кодирует достаточно информации, чтобы воссоздать оригинал.
В этом случае я заставляю слои моей модели изучать все более абстрактные (сжатые) представления исходного изображения (кодирование). Я использую эту кодировку для расширения обратно до нового полноразмерного изображения (декодирование). Сжатие является распространенным прокси-сервером для абстракции. Если я даю своим слоям все меньше и меньше места для информации, это заставляет их изучать функции более высокого уровня.
Это тот высокоабстрагированный средний слой, который мы в конечном итоге будем использовать для оценки всех изображений и поиска связанных изображений. Это более надежно, чем использование оригиналов, потому что мы будем сравнивать объекты с объектами, а не пиксели с пикселями.
def model_fn(features, labels, mode): "Return ModelFnOps for use with Estimator." encoded_image = encoder(features["image"]) decoded_image = decoder(encoded_image) loss = get_loss(decoded_image, labels["image"], mode) train_op = get_train_op(loss, mode) predictions = { 'encoded_image': encoded_image, 'decoded_image': decoded_image }
model.py строка:131
Компромиссы
Автокодеры учатся без присмотра. Большинство моделей машинного обучения контролируются (т. Е. Они используют метки, чтобы сообщить им, каков правильный ответ). Даже для этого проекта вполне вероятно, что контролируемая модель классификатора даст лучшие результаты, но я все еще предпочитаю использовать неконтролируемую модель. Почему?
Ну, все дело в компромиссах. Если бы я действительно пытался повысить надежность, я бы использовал помеченные данные, чтобы модель имела семантическую информацию об изображениях. Тем не менее, мне понадобится большое разнообразие классов (меток) в достаточном количестве. Поэтому, если бы я хотел использовать только набор данных мостов, который я использую в настоящее время, мне пришлось бы добавить гранулированные метки или, возможно, разделить его на различные типы мостов (например, подвесные или ферменные). Такие вещи требуют либо кучу денег, либо времени, часто и то, и другое. Кроме того, это, вероятно, означало бы более длительное время обучения. Я также мог бы, вероятно, получить готовый заранее подготовленный классификатор для работы, но в чем в этом удовольствие? Мне нравится делать свои собственные модели.
Кроме того, еще раз взгляните на приведенный выше код. Есть кое-что, что можно сказать о его простоте. Модели машинного обучения не должны быть более сложными, чем они должны быть. Распространенной ошибкой является слияние слоев с надежностью. В то время как модели, которые работают с огромными наборами данных, абсолютно должны быть большими и глубокими, многие из них этого не делают и фактически теряют производительность, чем они сложнее.
Слои кодирования
Слои кодирования-это просто куча 2D сверточных слоев, сложенных друг на друга. Трудно научиться большим сдвигам в абстракции, поэтому я не мог просто перейти от исходного изображения прямо к окончательному встраиванию. Вместо этого я каждый раз получаю выходной размер, шагая по входам. В то время как я уменьшаю входные данные, я также увеличиваю функции, которые я заставляю кодировщик изучать на каждом уровне. В конце концов, кодировщик учится полагаться на эти функции для создания точного встраивания, а не полагаться на необработанные пиксельные данные. То, что я возвращаю из кодера (layer_6), – это то, что я в конечном итоге буду использовать для сравнения изображений друг с другом, как только у меня будет обученная модель.
Сравнение абстрактных представлений более полезно, чем сравнение пикселей между изображениями. Когда люди решают, похожи ли два изображения, нам все равно, является ли пиксель A более синим, чем пиксель B, или изображение X темнее, чем изображение Y. Мы ищем понятия и общее сходство, а не точные совпадения. То же самое относится и к нейронным сетям.
def encoder(inputs): layer_1 = tf.layers.conv2d( inputs=inputs, kernel_size=3, strides=2, filters=16, padding='SAME', activation=tf.nn.relu) # 128 layer_2 = tf.layers.conv2d( inputs=layer_1, kernel_size=3, strides=2, filters=32, padding='SAME', activation=tf.nn.relu) # 64 ... layer_6 = tf.layers.conv2d( inputs=layer_5, kernel_size=3, strides=2, filters=512, padding='SAME', activation=tf.nn.relu) # 4 return layer_6
model.py линия:10
Слои декодирования
Декодер очень похож на кодер. Главное отличие заключается в том, что я транспонирую сверточный слой и каждый раз расширяю его на два. Окончательный размер вывода должен соответствовать размеру исходного изображения, но так как я начал с изображений размером 256×256 пикселей, математика хороша и чиста. Другая деталь, которую следует отметить, заключается в том, что самый последний слой имеет активацию tanh вместо стандартной активации ReLU. Relax имеют диапазон от 0 до inf., в то время как активации tanh масштабируются от -1 до 1. Поскольку я масштабировал свой исходный входной тензор, а выходные данные должны соответствовать входным, конечный слой должен быть от -1 до 1.
def decoder(inputs): layer_1 = tf.layers.conv2d_transpose( inputs=inputs, kernel_size=3, strides=2, filters=512, padding='SAME', activation=tf.nn.relu) # 8 layer_2 = tf.layers.conv2d_transpose( inputs=layer_1, kernel_size=3, strides=2, filters=256, padding='SAME', activation=tf.nn.relu) # 16 ... layer_6 = tf.layers.conv2d_transpose( inputs=layer_5, kernel_size=3, strides=2, filters=3, padding='SAME', activation=tf.tanh) # 256 return layer_6
model.py линия:57
Функции потери и обучения
Последние две функции в model.py это мои тренировочные и убыточные операции. Они довольно просты. Для них обоих я ничего не возвращаю, если только я активно не тренирую модель. Я использую простую статическую скорость обучения 0,01 и оптимизатор Adam для обучения. Оптимизаторы Adam полезны тем, что они могут быть менее чувствительны к настройке гиперпараметров, чем оптимизатор градиентного спуска. Они используют адаптивную скорость обучения и динамически устанавливают скорость обучения по каждому параметру. Используя оптимизатор Adam, я получаю скорость обучения, которая немного автоматически корректируется, пока я начинаю в правильном диапазоне.
Для многих разработок моделей этот начальный диапазон действительно имеет значение, поэтому я обычно использую аргумент командной строки вместо жесткого кодирования, как я делаю ниже. Как только я познакомлюсь с моделью (как эта) Я могу сойти с рук, просто установив и забыв, но лучше всего было бы передать это.
def get_train_op(loss, mode): … global_step = tf.contrib.framework.get_or_create_global_step() train_op = tf.contrib.layers.optimize_loss( loss=loss, global_step=global_step, learning_rate=0.01, optimizer='Adam') return train_op
model.py строка:114
Обучение
А теперь самое интересное. После всей этой подготовки я, наконец, готов приступить к тренировкам. Поскольку мои данные находятся в моем расположении по умолчанию, единственный аргумент командной строки, который мне нужно передать, – это место, где я буду хранить результаты тренировочного запуска. Я использую новый каталог под названием “журналы”. Контрольные точки также сохраняются там, что удобно, потому что я могу остановить и перезапустить обучение, если мне нужно.
# command to kick off a training session $ python -m imagesearch.main --job-dir logs
Мне нравится видеть много сообщений, когда я тренируюсь, поэтому я регистрирую потерю каждые 100 шагов. Таким образом, я могу определить, делает ли моя модель более или менее правильные вещи на ранней стадии, и внести необходимые коррективы. Я также добавил сводку изображений в свой model.py файл ( строка:147 ), который позволяет мне отслеживать результаты в TensorBoard . Приятно иметь возможность визуально проверить результаты. Чтобы увидеть их, я открываю новую вкладку терминала и запускаю сервер.
# kick off TensorBoard in a new tab $ python -m tensorflow.tensorboard --logdir=logs
Вот сравнение скриншотов тензорной доски с начала и до конца обучения. Вы можете видеть, как они превращаются из случайного шума в довольно точные реконструкции. Декодированные изображения всегда будут немного размытыми, чем оригиналы, так как мы выбрасываем информацию, когда перешагиваем через наши слои. Для моего варианта использования мне нужны только изображения, которые близки к восстановлению оригиналов, так как на самом деле меня волнуют вложения.
Начало и конец:
Начать
Заканчивать
Закругляйся, Еще Раз, Пока…
Спасибо, что до сих пор оставалась со мной. Это было много вещей, которые нужно было упаковать, решить, какую модель использовать, пересмотреть компромиссы, разработать автоэнкодер и, наконец, обучить его и наблюдать за прогрессом с помощью TensorBoard.
Так что же дальше? У меня есть обученная модель, но как я могу сделать с ней что-нибудь полезное? Прочитайте часть 3, где я рассказываю о том, как взять нашу модель и использовать ее в приложении.
Вопросы? Комментарии? Дайте мне знать в комментариях или свяжитесь со мной в Twitter: @jimfleming