Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шолле Ф. - Глубокое обучение на Python (Библиотека программиста) - 2023.pdf
Скачиваний:
6
Добавлен:
07.04.2024
Размер:
11.34 Mб
Скачать

9.4. Интерпретация знаний, заключенных в сверточной нейронной сети    337

9.4.2. Визуализация фильтров сверточных нейронных сетей

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

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

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

Листинг 9.12. Создание экземпляра сверточной основы модели Xception

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)

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

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

Функция потерь принимает тензор с изображением и индекс фильтра (целое число)

338    Глава 9. Введение в глубокое обучение в технологиях зрения

Листинг 9.14. Создание модели экстрактора признаков

Эту строку можно заменить именем любого

 

 

 

 

 

 

слоя в сверточной основе Xception

 

 

 

 

layer_name = "block3_sepconv1"

 

 

 

 

Объект слоя, который

 

 

 

 

 

 

 

 

layer = model.get_layer(name=layer_name)

 

нас интересует

 

feature_extractor = keras.Model(inputs=model.input, outputs=layer.output)

Мы используем model.input и layer.output для создания модели, которая возвращает выход целевого слоя

Чтобы .использовать .эту .модель, .просто .передайте .ей .некоторые .входные. данные.(обратите.внимание,.что.модель.Xception.требует.предварительной.обработки.входных.данных.с.помощью.функции.keras.applications.xception.pre­ process_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». Но в обоих случаях данная формулировка обозначает

не совсем одно и то же.

Применить «трюк нормализации градиента»
Вычислить градиенты потерь по отношению к изображению

9.4. Интерпретация знаний, заключенных в сверточной нейронной сети    339

Метод 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() будет полезен во всех случаях, кроме реализации цикла низкоуровневого градиентного спуска (как сейчас).

Давайте.реализуем.функцию.градиентного.восхождения.с.помощью.GradientTape.. Обратите .внимание, .что .для .ускорения .мы .будем .использовать .декоратор.@tf.function.

Иногда.для.ускорения.процесса.градиентного.спуска.используется.неочевидный. трюк.—.нормализация.градиентного.тензора.делением.на.его.L2-норму.(квадрат- ный.корень.из.усредненных.квадратов.значений.в.тензоре)..Это.гарантирует,. что.величина.обновлений.во.входном.изображении.всегда.будет.находиться. в.одном.диапазоне.

Листинг 9.16. Максимизация потерь методом стохастического градиентного восхождения

Явно передать для наблюдения тензор с изображением,

Вычислить скаляр потерь,

потому что это не объект Variable (автоматически

показывающий, насколько текущее

под наблюдение попадают только объекты Variable)

изображение активирует фильтр

@tf.function

def gradient_ascent_step(image, filter_index, learning_rate): with tf.GradientTape() as tape:

tape.watch(image)

loss = compute_loss(image, filter_index) grads = tape.gradient(loss, image)

grads = tf.math.l2_normalize(grads) image += learning_rate * grads return image

Вернуть обновленное изображение,

Немного сдвинуть изображение

чтобы дать возможность вызывать

в направлении наибольшей

эту функцию в цикле

активации целевого фильтра

Центрировать результат, чтобы избежать артефактов на границах

340    Глава 9. Введение в глубокое обучение в технологиях зрения

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

Листинг 9.17. Функция, которая генерирует изображение, представляющее фильтр

img_width = 200

 

 

 

 

 

 

 

 

img_height = 200

 

 

 

Количество шагов

def generate_filter_pattern(filter_index):

градиентного восхождения

iterations = 30

 

 

 

 

 

 

 

 

Инициализировать тензор изображения

learning_rate = 10.

 

Амплитуда

 

случайными значениями (модель

image = tf.random.uniform(

 

 

одного шага

Xception принимает входные значения

minval=0.4,

 

 

 

 

 

 

в диапазоне [0, 1], поэтому здесь мы

maxval=0.6,

 

 

 

 

 

 

 

 

 

 

выбираем диапазон с центром в точке

shape=(1, img_width, img_height, 3))

 

 

 

 

со значением 0,5)

 

 

for i in range(iterations):

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

image = gradient_ascent_step(image, filter_index, learning_rate)

 

 

 

return image[0].numpy()

В цикле обновлять значения тензора с изображением,

 

 

 

 

 

 

 

чтобы максимизировать функцию потерь

 

Полученный.тензор.с.изображением.—.это.массив.с.формой.(200,.200,.3).и.ве- щественными.значениями,.которые.могут.быть.нецелочисленными,.в.диапазоне. [0, 255]..Поэтому.нужно.дополнительно.его.обработать,.чтобы.превратить. в.изображение,.пригодное.для.показа..Сделаем.это.с.помощью.простой.вспомогательной.функции.

Листинг 9.18. Вспомогательная функция для преобразования тензора в изображение

def deprocess_image(image): image -= image.mean() image /= image.std() image *= 64

image += 128

image = np.clip(image, 0, 255).astype("uint8") image = image[25:-25, 25:-25, :]

return image

Нормализовать значения в тензоре приведением их в диапазон [0, 255]

Взглянем.на.получившееся.изображение.(рис..9.16):

>>>plt.axis("off")

>>>plt.imshow(deprocess_image(generate_filter_pattern(filter_index=2)))

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

А.теперь.самое.интересное:.мы.можем.визуализировать.все.фильтры.в.слое.или. даже.все.фильтры.во.всех.слоях.модели.

Подготовить чистый холст для добавления изображений фильтров
Сгенерировать и сохранить изображения для первых 64 фильтров в слое

9.4. Интерпретация знаний, заключенных в сверточной нейронной сети    341

Рис. 9.16. Шаблон, на который второй канал в слое block3_sepconv1 дает максимальный отклик

Листинг 9.19. Создание сетки со всеми шаблонами откликов фильтров в слое

all_images = [] for filter_index in range(64):

print(f"Processing filter {filter_index}") image = deprocess_image(

generate_filter_pattern(filter_index)

)

all_images.append(image)

margin = 5 n = 8

cropped_width = img_width - 25 * 2 cropped_height = img_height - 25 * 2

width = n * cropped_width + (n - 1) * margin height = n * cropped_height + (n - 1) * margin stitched_filters = np.zeros((width, height, 3))

for i in range(n):

 

 

 

Заполнить изображение

 

 

 

for j in range(n):

 

 

сохраненными фильтрами

image = all_images[i * n + j]

 

 

 

 

 

stitched_filters[

 

 

 

 

row_start = (cropped_width + margin) * i

 

 

row_end = (cropped_width + margin) * i + cropped_width

column_start = (cropped_height + margin) * j

 

 

column_end = (cropped_height + margin) * j + cropped_height

stitched_filters[

 

 

 

 

row_start: row_end,

 

 

 

 

column_start: column_end, :] = image

 

Сохранить холст

 

 

 

 

 

 

 

keras.utils.save_img(

 

 

 

 

на диск

 

 

 

 

 

f"filters_for_layer_{layer_name}.png", stitched_filters)

342    Глава 9. Введение в глубокое обучение в технологиях зрения

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

.фильтры.из.первого.слоя.в.модели.кодируют.простые.направленные.контуры. и.цвета.(или.в.некоторых.случаях.цветные.контуры);

.фильтры.из.слоев.чуть.выше.(таких.как.block4_sepconv1).кодируют.простые. текстуры,.состоящие.из.комбинаций.контуров.и.цветов;

.фильтры.в.более.высоких.слоях.начинают.напоминать.текстуры,.встреча­ ющиеся.в.естественных.изображениях,.—.перья,.глаза,.листья.и.т..д.

Рис. 9.17. Некоторые шаблоны фильтров из слоев block2_sepconv1, block4_sepconv1 и block8_sepconv1