Я пытаюсь преобразовать код Keras в тензор потока 1.14, и у меня возникают некоторые вопросы, когда я проверял распределение весов после 1 эпохи, используя набор данных игрушек, состоящий из пустых изображений.
Модель в основном U - Net но он использует частичные свертки для закрашивания неправильных отверстий. Когда я запускаю обе реализации с простыми данными, я получаю значительную разницу в выходных данных, поэтому я решил проверить каждый из весов. Хотя изначально они одинаковы, после одной эпохи основное различие проявляется в смещениях и гиперпараметрах периодической нормализации (бета и гамма). Как показано на следующих изображениях:
Частичный сверточный слой - смещение
Уровень нормализации партии - гамма
Уровень пакетной нормализации - бета
Вот краткое изложение соответствующих разделов обеих реализаций:
(1) Keras (реализация взята из здесь )
Слой частичной свертки
from keras.utils import conv_utils
from keras import backend as K
from keras.engine import InputSpec
from keras.layers import Conv2D
from keras.initializers import glorot_uniform
class PConv2D(Conv2D):
def __init__(self, *args, n_channels=3, mono=False, **kwargs):
super().__init__(*args, **kwargs)
self.input_spec = [InputSpec(ndim=4), InputSpec(ndim=4)]
def build(self, input_shape):
if self.data_format == 'channels_first': channel_axis = 1
else: channel_axis = -1
if input_shape[0][channel_axis] is None: raise ValueError('The channel dimension of the inputs should be defined. Found `None`.')
self.input_dim = input_shape[0][channel_axis]
# Image kernel
kernel_shape = self.kernel_size + (self.input_dim, self.filters)
self.kernel = self.add_weight(shape=kernel_shape,
initializer= glorot_uniform(seed=0),
name='img_kernel',
regularizer=self.kernel_regularizer,
constraint=self.kernel_constraint)
# Mask kernel
self.kernel_mask = K.ones(shape=self.kernel_size + (self.input_dim, self.filters))
# Calculate padding size to achieve zero-padding
self.pconv_padding = (
(int((self.kernel_size[0]-1)/2), int((self.kernel_size[0]-1)/2)),
(int((self.kernel_size[0]-1)/2), int((self.kernel_size[0]-1)/2)),
)
# Window size - used for normalization
self.window_size = self.kernel_size[0] * self.kernel_size[1]
if self.use_bias:
self.bias = self.add_weight(shape=(self.filters,),
initializer=self.bias_initializer,
name='bias',
regularizer=self.bias_regularizer,
constraint=self.bias_constraint)
else:
self.bias = None
self.built = True
def call(self, inputs, mask=None):
# Both image and mask must be supplied
if type(inputs) is not list or len(inputs) != 2:
raise Exception('PartialConvolution2D must be called on a list of two tensors [img, mask]. Instead got: ' + str(inputs))
# Padding done explicitly so that padding becomes part of the masked partial convolution
images = K.spatial_2d_padding(inputs[0], self.pconv_padding, self.data_format)
masks = K.spatial_2d_padding(inputs[1], self.pconv_padding, self.data_format)
# Apply convolutions to mask
mask_output = K.conv2d(masks,
self.kernel_mask,
strides=self.strides,
padding='valid',
data_format=self.data_format,
dilation_rate=self.dilation_rate
)
# Apply convolutions to image
img_output = K.conv2d((images*masks), self.kernel,
strides=self.strides,
padding='valid',
data_format=self.data_format,
dilation_rate=self.dilation_rate
)
# Calculate the mask ratio on each pixel in the output mask
mask_ratio = self.window_size / (mask_output + 1e-8)
# Clip output to be between 0 and 1
mask_output = K.clip(mask_output, 0, 1)
# Remove ratio values where there are holes
mask_ratio = mask_ratio * mask_output
# Normalize iamge output
img_output = img_output * mask_ratio
# Apply bias only to the image (if chosen to do so)
if self.use_bias:
img_output = K.bias_add(img_output, self.bias,
data_format=self.data_format)
# Apply activations on the image
if self.activation is not None:
img_output = self.activation(img_output)
return [img_output, mask_output]
Интересующий сетевой блок
def encoder_layer(img_in, mask_in, filters, kernel_size, bn=True):
conv, mask = PConv2D(filters, kernel_size, strides=2, padding='same')([img_in, mask_in])
if bn:
conv = BatchNormalization(name='EncBN')(conv, training=train_bn)
conv = Activation('relu')(conv)
return conv, mask
Оптимизатор:
optimizer = Adam(lr=0.0002)
(2) Tensorflow
Слой частичной свертки
import tensorflow as tf
import numpy as np
def PConv2D(input, output_dim, kernel_size, stride, padding, use_bias=True):
with tf.variable_scope('partial_conv'):
_, h, w, input_dim = input[0].get_shape().as_list()
# Window size - used for normalization
window_size = kernel_size * kernel_size
images = input[0]
masks = input[1]
# Update mask
update_mask = tf.layers.conv2d(inputs=masks,
filters=output_dim,
kernel_size=kernel_size,
strides=(stride, stride),
padding='same', #'valid'
trainable=False,
use_bias=False,
kernel_initializer=tf.constant_initializer(1.0),
name='up_mask')
# Calculate the mask ratio on each pixel in the output mask
mask_ratio = window_size / (update_mask + 1e-8)
# Clip output to be between 0 and 1
update_mask = tf.clip_by_value(update_mask, 0, 1)
# Remove ratio values where there are holes
mask_ratio = mask_ratio * update_mask
# Inpaint image
img_output = tf.layers.conv2d(inputs=images*masks,
filters=output_dim,
kernel_size=kernel_size,
strides=(stride, stride),
padding='same',
use_bias=False,
kernel_initializer=tf.contrib.layers.xavier_initializer(seed=0),
name='img_out'
)
# Normalize image output
img_output = img_output * mask_ratio
# Apply bias only to the image (if chosen to do so)
if use_bias:
bias = tf.get_variable("bias", [output_dim,],
initializer=tf.constant_initializer(0.0))
img_output = tf.nn.bias_add(img_output, bias)
return [img_output, update_mask]
Сеть блок интересов:
def encoder_layer(img_in, mask_in, filters, kernel_size, name, training_bn, bn=True):
with tf.variable_scope(name):
conv, mask = PConv.PConv2D((img_in, mask_in), filters, kernel_size, 2, "same")
if bn:
conv = tf.layers.batch_normalization(inputs=conv,
training=training_bn,
trainable=True,
fused=True,
name='bn')
conv = tf.nn.relu(conv, name='relu')
return conv, mask
Оптимизатор:
def optimize(loss, learning_rate=0.0002):
global_step = tf.Variable(0, dtype=tf.int32, trainable=False, name='global_step')
U_vars = [var for var in tf.trainable_variables() if 'UNET' in var.name]
opt = tf.train.AdamOptimizer(learning_rate=learning_rate, epsilon=1e-07)
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
train_opt = opt.minimize(loss, var_list=U_vars, global_step=global_step)
train_opt = tf.group([train_opt, update_ops])
return train_opt
Я проверил каждую часть своей реализации тензорного потока по сравнению с Keras Кроме того, функция потерь не должна быть проблемой. Я уже проверил ее поведение.
Что может испортить результаты? Я думаю, что это связано с моей реализацией, но я не могу понять, что это может быть.
Я буду очень благодарен, если кто-то может привести меня.