Tensorflow нейронной сети свертки с разными по размеру изображений

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

Репозиторий Github

enter image description here

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

 x = tf.placeholder(tf.float32, shape=[None, 32*32*7])
 y_ = tf.placeholder(tf.float32, shape=[None, 32*32*7, 3])
 ...
 DeConnv1 = tf.nn.conv3d_transpose(layer1, filter = w, output_shape = [1,32,32,7,1], strides = [1,2,2,2,1], padding = 'SAME')
 ...
 final = tf.reshape(final, [1, 32*32*7])
 W_final = weight_variable([32*32*7,32*32*7,3])
 b_final = bias_variable([32*32*7,3])
 final_conv = tf.tensordot(final, W_final, axes=[[1], [1]]) + b_final

2 ответов


динамический заполнители

Tensorflow позволяет иметь несколько dynamic (a.к. a. None) размеры в заполнители. Движок не сможет обеспечить корректность при построении графика, поэтому клиент отвечает за подачу правильного ввода, но он обеспечивает большую гибкость.

Итак, я ухожу...

x = tf.placeholder(tf.float32, shape=[None, N*M*P])
y_ = tf.placeholder(tf.float32, shape=[None, N*M*P, 3])
...
x_image = tf.reshape(x, [-1, N, M, P, 1])

to...

# Nearly all dimensions are dynamic
x_image = tf.placeholder(tf.float32, shape=[None, None, None, None, 1])
label = tf.placeholder(tf.float32, shape=[None, None, 3])

так как вы намерены изменить вход в 5D в любом случае, так почему не используйте 5D в x_image С самого начала. В этот момент второе измерение label произвольная, но мы обещание tensorflow, что он будет соответствовать x_image.

динамические формы в деконволюции

далее хорошая вещь о tf.nn.conv3d_transpose является то, что его выходная форма может быть динамической. Поэтому вместо этого:

# Hard-coded output shape
DeConnv1 = tf.nn.conv3d_transpose(layer1, w, output_shape=[1,32,32,7,1], ...)

... вы можете сделать это:

# Dynamic output shape
DeConnv1 = tf.nn.conv3d_transpose(layer1, w, output_shape=tf.shape(x_image), ...)

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

обратите внимание, что статическая форма x_image is (?, ?, ?, ?, 1).

все свертки сети

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

к счастью для нас, Springenberg at al описывает способ замены слоя FC слоем CONV в "стремление к простоте: вся сверточная сеть" бумаги. Я собираюсь использовать свертку с 3 1x1x1 фильтры (см. Также этот вопрос):

final_conv = conv3d_s1(final, weight_variable([1, 1, 1, 1, 3]))
y = tf.reshape(final_conv, [-1, 3])

если мы гарантируем, что final имеет те же размеры, что и DeConnv1 (и другие), это сделает y правильная форма, которую мы хотим: [-1, N * M * P, 3].

объединение всех вместе

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

полный код:

sess = tf.InteractiveSession()

def conv3d_dilation(tempX, tempFilter):
  return tf.layers.conv3d(tempX, filters=tempFilter, kernel_size=[3, 3, 1], strides=1, padding='SAME', dilation_rate=2)

def conv3d(tempX, tempW):
  return tf.nn.conv3d(tempX, tempW, strides=[1, 2, 2, 2, 1], padding='SAME')

def conv3d_s1(tempX, tempW):
  return tf.nn.conv3d(tempX, tempW, strides=[1, 1, 1, 1, 1], padding='SAME')

def weight_variable(shape):
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)

def bias_variable(shape):
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)

def max_pool_3x3(x):
  return tf.nn.max_pool3d(x, ksize=[1, 3, 3, 3, 1], strides=[1, 2, 2, 2, 1], padding='SAME')

x_image = tf.placeholder(tf.float32, shape=[None, None, None, None, 1])
label = tf.placeholder(tf.float32, shape=[None, None, 3])

W_conv1 = weight_variable([3, 3, 1, 1, 32])
h_conv1 = conv3d(x_image, W_conv1)
# second convolution
W_conv2 = weight_variable([3, 3, 4, 32, 64])
h_conv2 = conv3d_s1(h_conv1, W_conv2)
# third convolution path 1
W_conv3_A = weight_variable([1, 1, 1, 64, 64])
h_conv3_A = conv3d_s1(h_conv2, W_conv3_A)
# third convolution path 2
W_conv3_B = weight_variable([1, 1, 1, 64, 64])
h_conv3_B = conv3d_s1(h_conv2, W_conv3_B)
# fourth convolution path 1
W_conv4_A = weight_variable([3, 3, 1, 64, 96])
h_conv4_A = conv3d_s1(h_conv3_A, W_conv4_A)
# fourth convolution path 2
W_conv4_B = weight_variable([1, 7, 1, 64, 64])
h_conv4_B = conv3d_s1(h_conv3_B, W_conv4_B)
# fifth convolution path 2
W_conv5_B = weight_variable([1, 7, 1, 64, 64])
h_conv5_B = conv3d_s1(h_conv4_B, W_conv5_B)
# sixth convolution path 2
W_conv6_B = weight_variable([3, 3, 1, 64, 96])
h_conv6_B = conv3d_s1(h_conv5_B, W_conv6_B)
# concatenation
layer1 = tf.concat([h_conv4_A, h_conv6_B], 4)
w = tf.Variable(tf.constant(1., shape=[2, 2, 4, 1, 192]))
DeConnv1 = tf.nn.conv3d_transpose(layer1, filter=w, output_shape=tf.shape(x_image), strides=[1, 2, 2, 2, 1], padding='SAME')

final = DeConnv1
final_conv = conv3d_s1(final, weight_variable([1, 1, 1, 1, 3]))
y = tf.reshape(final_conv, [-1, 3])
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=label, logits=y))

print('x_image:', x_image)
print('DeConnv1:', DeConnv1)
print('final_conv:', final_conv)

def try_image(N, M, P, B=1):
  batch_x = np.random.normal(size=[B, N, M, P, 1])
  batch_y = np.ones([B, N * M * P, 3]) / 3.0

  deconv_val, final_conv_val, loss = sess.run([DeConnv1, final_conv, cross_entropy],
                                              feed_dict={x_image: batch_x, label: batch_y})
  print(deconv_val.shape)
  print(final_conv.shape)
  print(loss)
  print()

tf.global_variables_initializer().run()
try_image(32, 32, 7)
try_image(16, 16, 3)
try_image(16, 16, 3, 2)

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

однако, должны быть осторожны, когда вы определяете график. Нужно использовать tf.shape вместо tf.get_shape(). первые динамически выводят форму только тогда, когда вы session.run, последний может получить форму при определении графика. Но когда размер входного сигнала установлен в none, последнее не получает true изменить (возможно, просто нет).

и, чтобы сделать вещи сложными, если вы используете tf.layers.conv2d или upconv2d, иногда эти функции высокого уровня не нравится tf.shape, потому что, похоже, они предполагают, что информация о форме доступна во время построения графика.

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