Шаг 232.
Глубокое обучение на Python. ... . Интерпретация знаний, заключенных в сверточной нейронной сети. Визуализация фильтров сверточных нейронных сетей

    На этом шаге мы рассмотрим использование таких фильтров.

    Другой простой способ исследовать фильтры, полученные сетью, - отобразить визуальный шаблон, за который отвечает каждый фильтр. Это можно сделать методом градиентного восхождения в пространстве входов (gradient ascent in input space): выполняя градиентный спуск до значения входного изображения сверточной нейронной сети, максимизируя отклик конкретного фильтра, начав с пустого изображения. В результате получится версия входного изображения, для которого отклик данного фильтра был бы максимальным.

    Попробуем проделать это с фильтрами модели Xception, обученной на наборе данных ImageNet. Задача решается просто: нужно сконструировать функцию потерь, максимизирующую значение данного фильтра данного сверточного слоя, и затем использовать стохастический градиентный спуск для настройки значений входного изображения, чтобы максимизировать значение активации. Это будет наш второй пример реализации цикла низкоуровневого градиентного спуска с использованием объекта GradientTape (первый был показан на 54 шаге).

    Для начала создадим экземпляр модели Xception, загрузив веса, полученные при обучении на наборе данных ImageNet


Пример 9.12. Создание экземпляра сверточной основы модели Xception
from tensorflow import keras

model = keras.applications.xception.Xception( 
    weights="imagenet", 
    # Слои классификации в этом варианте использования модели 
    # не нужны, поэтому отключим их
    include_top=False)

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


Пример 9.13. Вывод имен всех сверточных слоев в модели Xception
for layer in model.layers:
  if isinstance(layer, (keras.layers.Conv2D, keras.layers.SeparableConv2D)):
    print(layer.name)

block1_conv1
block1_conv2
block2_sepconv1
block2_sepconv2
conv2d
block3_sepconv1
block3_sepconv2
conv2d_1
block4_sepconv1
block4_sepconv2
conv2d_2
block5_sepconv1
block5_sepconv2
block5_sepconv3
block6_sepconv1
block6_sepconv2
block6_sepconv3
block7_sepconv1
block7_sepconv2
block7_sepconv3
block8_sepconv1
block8_sepconv2
block8_sepconv3
block9_sepconv1
block9_sepconv2
block9_sepconv3
block10_sepconv1
block10_sepconv2
block10_sepconv3
block11_sepconv1
block11_sepconv2
block11_sepconv3
block12_sepconv1
block12_sepconv2
block12_sepconv3
block13_sepconv1
block13_sepconv2
conv2d_3
block14_sepconv1
block14_sepconv2

    Обратите внимание, что все слои SeparableConv2D получили имена вида block6_sepconv1, block7_sepconv2 и т. д. Модель Xception организована в блоки, каждый из которых содержит несколько сверточных слоев.

    Теперь создадим вторую модель, которая вернет выходные данные определенного слоя, - модель экстрактора признаков. Поскольку наша модель создается с применением функционального API, ее можно проверить: запросить output одного из слоев и повторно использовать его в новой модели. Нет необходимости копировать весь код Xception.


Пример 9.14. Создание модели экстрактора признаков
# Эту строку можно заменить именем любого слоя в сверточной основе Xception
layer_name = "block3_sepconv1"
# Объект слоя, который нас интересует
layer = model.get_layer(name=layer_name)
# Мы используем model.input и layer.output для создания модели, 
# которая возвращает выход целевого слоя
feature_extractor = keras.Model(inputs=model.input, outputs=layer.output)

    Чтобы использовать эту модель, просто передайте ей некоторые входные данные (обратите внимание, что модель Xception требует предварительной обработки входных данных с помощью функции keras.applications.xception.preprocess_input()).


Пример 9.15. Использование экстрактора признаков
activation = feature_extractor(
    keras.applications.xception.preprocess_input(img_tensor)
)

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


import tensorflow as tf

# Функция потерь принимает тензор с изображением и индекс фильтра (целое число)
def compute_loss(image, filter_index): 
  activation = feature_extractor(image) 
  # Обратите внимание: исключая из вычисления потерь пиксели, лежащие на 
  # границах, мы избегаем пограничных артефактов; в данном случае мы 
  # отбрасываем первые два пикселя по сторонам активации
  filter_activation = activation[:, 2:-2, 2:-2, filter_index] 
  # Вернуть среднее значений активации для фильтра
  return tf.reduce_mean(filter_activation)


Разница между model.predict(x) и model(x)

    В предыдущих шагах для извлечения признаков мы использовали predict(x). Здесь мы берем model(x). Почему?

    Оба вызова, y = model.predict(x) и y = model(x), где x - массив входных данных, подразумевают "запуск модели с исходными данными x и получение результата y". Но в обоих случаях данная формулировка обозначает не совсем одно и то же.

    Метод predict() перебирает данные (при желании можно указать размер пакета, выполнив вызов predict(x, batch_size=64)) и извлекает массив NumPy с выходными данными. Схематично его реализацию можно представить так:

def predict(x):
  y_batches = []
  for x_batch in get_batches(x): 
    y_batch = model(x).numpy() 
    y_batches.append(y_batch) 
  return np.concatenate(y_batches)

    Таким образом, вызовы predict() могут обрабатывать очень большие массивы. Между тем model(x) выполняет обработку в памяти и не масштабируется. В то же время predict() не дифференцируется: нельзя получить его градиент, вызывая в контексте GradientTape.

    Если нужно получить градиенты вызовов модели, используйте model(x); если нужен только результат применения модели - берите predict(). Иными словами, predict() будет полезен во всех случаях, кроме реализации цикла низкоуровневого градиентного спуска (как сейчас).


    На следующем шаге мы закончим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг