Вопрос или проблема
Я работаю над реализацией пользовательского слоя макс-пулинга в TensorFlow и нуждаюсь в помощи проекции булевой маски на входной тензор, чтобы указать позиции, которые способствовали максимальным значениям в каждом окне пулинга.
Я хочу избежать использования циклов и ищу эффективное векторизованное решение, которое полностью использует параллельные вычислительные возможности GPU.
Входные данные
Вот упрощенная версия моей текущей настройки:
- Форма входного тензора: (размер_пакета, высота_входа, ширина_входа, n_каналов)
- Размер окна пулинга: (высота_пулинга, ширина_пулинга)
- Шаги: (высота_шага, ширина_шага)
Я использую tf.image.extract_patches
, чтобы получить все области пулинга из входных данных за один шаг, затем изменяю форму, чтобы выделить каждое окно пулинга. Затем я вычисляю максимум для каждого окна, чтобы получить выходной тензор и создать булевую маску, указывающую позиции максимальных значений.
Вот мой код:
import tensorflow as tf
# Входные размеры
batch_size = 3
input_height = 4
input_width = 4
n_channels = 3
pool_height = 2
pool_width = 2
stride_height = 2
stride_width = 2
# Входной тензор
x = tf.random.normal((batch_size, input_height, input_width, n_channels)) # Форма: (размер_пакета, высота_входа, ширина_входа, n_каналов)
print("Входные данные:")
print(x[0, :, :, 0])
# Извлечение патчей для всех областей пулинга за один шаг
patches = tf.image.extract_patches(
images = x,
sizes = [1, pool_height, pool_width, 1],
strides = [1, stride_height, stride_width, 1],
rates = [1, 1, 1, 1],
padding = "VALID"
) # Форма: (размер_пакета, высота_выхода, ширина_выхода, высота_пулинга * ширина_пулинга * n_каналов)
# Изменение формы патчей для выделения каждого канала и окна пулинга
_, output_height, output_width, _ = patches.shape
patches = tf.reshape(patches, [batch_size, output_height, output_width, pool_height, pool_width, n_channels]) # Форма: (размер_пакета, высота_выхода, ширина_выхода, высота_пулинга, ширина_пулинга, n_каналов)
# Вычисление максимума по окну пулинга
output = tf.reduce_max(patches, axis=(3, 4)) # Форма: (размер_пакета, высота_выхода, ширина_выхода, n_каналов)
print("\nВыходные данные:")
print(output[0, :, :, 0])
# Создание маски для позиций максимумов
max_values = tf.expand_dims(tf.expand_dims(output, axis=3), axis=4) # Форма: (размер_пакета, высота_выхода, ширина_выхода, 1, 1, n_каналов)
mask = tf.equal(patches, max_values) # Форма: (размер_пакета, высота_выхода, ширина_выхода, высота_пулинга, ширина_пулинга, n_каналов)
print("\nМаска:")
print(mask[0, :, : ,:, :, 0])
# Тензор, который я хочу получить
cache = tf.reshape(mask, tf.shape(x)) # Форма: (размер_пакета, высота_входа, ширина_входа, n_каналов)
print("\nКэш:")
print(cache[0, :, :, 0])
Выходные данные
Выходные данные, которые я получаю, таковы (для простоты я вывел только первый канал первого элемента пакета):
Входные данные:
tf.Tensor(
[[ 1.8739315 -1.1739935 0.21598558 -1.0357039 ]
[ 0.5625862 1.6689821 0.6279541 -0.6442553 ]
[-1.4488437 0.4760101 -0.34631395 0.25045732]
[ 0.8405037 -1.8099107 1.0800457 0.06632996]], shape=(4, 4), dtype=float32)
Выходные данные:
tf.Tensor(
[[1.8739315 0.6279541]
[0.8405037 1.0800457]], shape=(2, 2), dtype=float32)
Маска:
tf.Tensor(
[[[[ True False]
[False False]]
[[False False]
[ True False]]]
[[[False False]
[ True False]]
[[False False]
[ True False]]]], shape=(2, 2, 2, 2), dtype=bool)
Кэш:
tf.Tensor(
[[ True False False False]
[False False True False]
[False False True False]
[False False True False]], shape=(4, 4), dtype=bool)
Как видите, даже если маска кажется корректной, тензор кэша имеет некоторые неверные значения.
Ответ или решение
Проецирование булевой маски из областей макс-пулинга обратно на входной тензор
Ваша проблема заключается в корректной проекции булевой маски, созданной на этапе макс-пулинга, обратно на исходный входной тензор. Недостаток кодирования, который вы описали, заключается в неправильном формировании выходного тензора (cache), который не отражает корреляцию с входным тензором. Давайте рассмотрим, как это можно исправить и оптимизировать.
Пошаговое решение
-
Извлечение патчей: Вы правильно используете
tf.image.extract_patches()
, чтобы извлечь области, которые будут подвергаться макс-пулингу. Это позволяет обрабатывать входной тензор векториально. -
Расчет максимумов: Создание выходного тензора с помощью
tf.reduce_max()
также выполнено корректно. Этот шаг необходим для получения значений, которые будут представлены в результирующем тензоре. -
Создание маски: Маска, которая вы создаете с помощью
tf.equal()
, позволяет отследить позицию максимумов в пулл-функции, однако последующий reshape может быть неправильно обработан.
Обработка маски
После создания маски вам нужно правильно её встроить обратно в размер входного тензора. Основная задача состоит в правильной интерпретации размера и формата маски.
Определение корректного reshaping
Для того чтобы маска правильно отображалась на исходный тензор, необходимо следующее:
- После создания маски с помощью
tf.equal()
, ее нужно ребразить так, чтобы соответствовать размерности входного тензора. Маска будет содержатьTrue
на позициях максимальных значений иFalse
в остальных, и ее размер должен быть(batch_size, input_height, input_width, n_channels)
.
Единственное, что нужно сделать, это ввести дополнительное представление VR с использованием tf.reshape()
.
Вот улучшенная версия вашего кода с корректной проекцией маски:
import tensorflow as tf
# Входные размеры
batch_size = 3
input_height = 4
input_width = 4
n_channels = 3
pool_height = 2
pool_width = 2
stride_height = 2
stride_width = 2
# Входной тензор
x = tf.random.normal((batch_size, input_height, input_width, n_channels))
# Извлечение патчей для всех областей пулинга
patches = tf.image.extract_patches(
images=x,
sizes=[1, pool_height, pool_width, 1],
strides=[1, stride_height, stride_width, 1],
rates=[1, 1, 1, 1],
padding="VALID"
)
# Изменяем форму патчей для выделения каждого канала и окон
_, output_height, output_width, _ = patches.shape
patches = tf.reshape(patches, [batch_size, output_height, output_width, pool_height, pool_width, n_channels])
# Вычисляем максимум для окна пулинга
output = tf.reduce_max(patches, axis=(3, 4))
# Создание замены для максимумов
max_values = tf.expand_dims(tf.expand_dims(output, axis=3), axis=4)
# Маска для позиций максимумов
mask = tf.equal(patches, max_values)
# Правильная проекция маски на входной тензор
mask_output_shape = tf.shape(x)
projected_mask = tf.reshape(tf.cast(mask, tf.float32), [batch_size, output_height, output_width, pool_height, pool_width, n_channels])
cache = tf.reduce_sum(projected_mask, axis=[3, 4]) > 0 # Получение окончательной булевой маски
# Печать информации
print("Вход:")
print(x[0, :, :, 0])
print("\nВыход:")
print(output[0, :, :, 0])
print("\nМаска:")
print(mask[0, :, :, :, :, 0])
print("\nКэш:")
print(cache[0, :, :, 0])
Заключение
Данное решение позволяет вам избежать использования циклов и эффективно обрабатывает данные с использованием возможностей параллельной обработки на GPU. Проверка и корректировка reshaping обеспечит точность в отображении результатов маски на исходные данные. Проводя подобные оптимизации, вы можете значительно улучшить производительность вашего кода и точность ваших расчётов, что особенно важно в задачах глубокого обучения и компьютационной визуализации.