Обновление. Эта статья была обновлена, чтобы исправить возможные проблемы с расчетом точности, на которые указала Тереза ​​Бартон. Также был обновлен репозиторий git, в котором вы можете найти все изменения.

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

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

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

Требования

Предварительная обработка данных

Подготовьте обучающие изображения

  1. Поместите все обучающие изображения в одну папку в каталоге images.
    Попробуйте удалить все повторяющиеся изображения, они могут искусственно завышать тест и точность проверки.
    Имя папки не имеет значения. Я использую multi-label.

Подготовьте ярлыки для каждого тренировочного образа.

Нам нужно подготовить файлы с правильными метками для каждого изображения. Назовите файлы ‹image_file_name.jpg› .txt = если у вас есть изображение car.jpg, сопутствующий файл должен называться car.jpg.txt .

Поместите каждую метку в новую строку внутри файла, ничего больше.

Теперь скопируйте все созданные файлы в каталог image_labels_dir, расположенный в корне проекта. Вы можете изменить путь к этой папке, отредактировав глобальную переменную IMAGE_LABELS_DIR в retrain.py.

Создайте файл, содержащий все ярлыки

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

Создайте файл labels.txt в корне проекта и заполните его всеми возможными ярлыками. Каждый ярлык на новой строке, больше ничего. Точно так же, как файл image_label для изображения, которое находится во всех возможных классах.

Изменение основного

Метод main () изначально загружал структуру каталогов, содержащую изображения для каждой метки в отдельных папках, и создавал наборы проверки, тестирования и обучения для каждого класс по:

image_lists = create_image_lists(FLAGS.image_dir, FLAGS.testing_percentage,cFLAGS.validation_percentage)

Теперь у нас есть все изображения в одном каталоге, и поэтому image_lists.keys () содержит только один элемент, а именно папку со всеми нашими изображениями (например, с несколькими метками). Все обучающие изображения разделены на наборы проверки, тестирования и обучения, доступные с помощью этого ключа.

Теперь, когда наши данные правильно разделены, нам просто нужно загрузить список меток и вычислить количество классов:

with open(ALL_LABELS_FILE) as f:
    labels = f.read().splitlines()
class_count = len(labels)

Создание векторов Ground_truth

  1. Добавьте метод get_image_labels_path (), который является слегка отредактированным методом get_image_path (), возвращающим путь к файлу, содержащему правильные метки изображений = например, image_labels_dir / car.jpg.txt для car.jpg.
  2. Отредактируйте метод get_random_cached_bottlenecks ():

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

ground_truth = np.zeros(class_count, dtype=np.float32)

а затем поместите 1.0 в позицию правильной метки, которую мы знали, потому что это имя папки, из которой мы взяли изображение:

ground_truth[label_index] = 1.0

С многокомпонентной классификацией все не так просто. Нам нужно будет загрузить все правильные метки для данного изображения из файла image_label_file.

Получите путь к файлу с правильными метками:

labels_file = get_image_labels_path(image_lists,label_name,image_index, IMAGE_LABELS_DIR, category)

Прочитать все строки = метки из файла и сохранить их в массив true_labels:

with open(labels_file) as f:
   true_labels = f.read().splitlines()

Инициализируйте вектор ground_truth нулями:

ground_truth = np.zeros(class_count, dtype=np.float32)

Укажите правильные метки в векторе Ground_truth с помощью 1.0:

idx = 0
for label in labels:
   if label in true_labels:
      ground_truth[idx] = 1.0
   idx += 1

Список меток является добавленным параметром к методу get_random_cached_bottlenecks () и содержит имена всех возможных классов.

Вот и все! Мы можем улучшить это решение, кэшируя созданные ground_truths. Это предотвращает создание вектора ground_truth каждый раз, когда мы запрашиваем его для одного и того же изображения, что неизбежно произойдет, если мы тренируемся для нескольких эпох. Для этого и нужен глобальный словарь CACHED_GROUND_TRUTH_VECTORS.

Изменение обучения

Метод add_final_training_ops () изначально добавлял новый softmax и полностью связанный слой для обучения. Нам просто нужно заменить функцию softmax на другую.

Почему?

Функция softmax сжимает все значения вектора в диапазон [0,1], суммируя их до 1. Это именно то, что мы хотим в классификации с одной меткой. Но для нашего случая с несколькими метками мы хотели бы, чтобы наши результирующие вероятности классов могли выразить, что изображение автомобиля принадлежит классу car с вероятностью 90% и классу авария с вероятностью 30% и т. д.

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

В частности, мы заменим:

final_tensor = tf.nn.softmax(logits, name=final_tensor_name)

с участием:

final_tensor = tf.nn.sigmoid(logits, name=final_tensor_name)

Мы также должны обновить способ вычисления кросс-энтропии, чтобы правильно обучить нашу сеть:

Опять же, просто замените softmax на сигмоид:

cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(logits,ground_truth_input)

Изменение оценки

Метод add_evaluation_step () вставляет операции, необходимые для оценки точности предсказанных меток. Первоначально это выглядело так:

correct_prediction = tf.equal(tf.argmax(result_tensor, 1), tf.argmax(ground_truth_tensor, 1))

Хорошо, что здесь происходит?

И result_tensor, и Ground_truth_tensor можно представить как 2D-массивы:

|        | label1  | label2 | label3  |
| image1 |    0    |    1   |    0    |
| image2 |    1    |    0   |    0    |

Поэтому эта строка:

tf.argmax(result_tensor, 1)

возвращает индекс максимального значения в каждой строке. Каждая строка из-за параметра (axis = 1).

Мы получим индексы с наивысшими значениями и сравним их, зная, что, поскольку только одна метка может быть правильной, Ground_truth_tensor содержит только один 1 в каждой строке.

Чтобы адаптировать этот подход к нашему случаю с несколькими метками, мы просто заменяем argmax () на round (), который превращает вероятности в 0 и 1. Затем мы сравниваем result_tensor с ground_truth_tensor, уже содержащим только 0 и 1:

correct_prediction = tf.equal(tf.round(result_tensor), ground_truth_tensor)

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

Проведение переподготовки

Просто запустите эту команду из корня проекта:

python retrain.py \
--bottleneck_dir=bottlenecks \
--how_many_training_steps 500 \
--model_dir=model_dir \
--output_graph=retrained_graph.pb \
--output_labels=retrained_labels.txt \
--summaries_dir=retrain_logs \
--image_dir=images

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

Тестирование переобученной модели

Запустить:

python label_image.py <image_name>

Я немного изменил label_image.py, чтобы записать результирующие проценты классов в results.txt.

Визуализация тренировки

После переобучения вы можете просмотреть логи, запустив:

tensorboard --logdir retrain_logs

и перейдя на http://127.0.0.1:6006/ в вашем браузере.

Конец

Надеюсь, что я максимально прояснил все изменения и их причины, и что сегодня вы узнали что-то новое :)

Если у вас есть дополнительные вопросы, вы можете найти меня на linkedin или напишите мне напрямую.