Как реализовать пиксельно-адаптивный сверточный слой - PullRequest
0 голосов
/ 23 апреля 2019

Недостаток классических CNN заключается в том, что нам нужно умножить количество фильтров, чтобы каждый фильтр был активирован отдельным объектом (например, зонтик активирует 67-й фильтр первой свертки и x-й фильтр k-я свертка, ...) Более того, CNN не учитывает сходство между пикселями. Чтобы улучшить качество сегментации / альфа-матирования с помощью нейронной сети, нам нужно придумать новые архитектуры. Недавно я наткнулся на эту статью: https://arxiv.org/pdf/1904.05373.pdf, и я нахожу эту идею очень интересной. Я хочу создать другой слой, основанный на той же идее, но для простоты скажем, я хочу реализовать PAC, как описано в упомянутой статье.

У меня есть несколько идей реализовать его с помощью TensorFlow Python API, но я думаю, что ни один из них не достаточно быстр. Идея состоит в том, что нам нужно взвесить веса каждого традиционного фильтра CNN с весами симметричной матрицы (ядра) с центром в каждой операции свертки (см. Рисунок 1 статьи), но функция tf.nn.conv2d принимает только набор фильтров размером [filter_height, filter_width, filter_input, filter_output], чтобы свертка не менялась в зависимости от положения окна, с которым мы хотим сворачиваться.

Моя идея состояла в том, чтобы определить функцию, аналогичную функции tf.nn.conv2d, используя тензор потока, а затем, если код достаточно быстр, взвешивать фильтр с весами симметричного ядра, а затем реализовать слой, расширяя tf.keras.Layer (https://www.tensorflow.org/tutorials/eager/custom_layers). Проблема в том, что код Python, который у меня есть, который имитирует функцию tf.nn.conv2d, на 100x медленнее , чем функция tf.nn.conv2d ...

Вот код, который я реализовал для имитации функции tf.nn.conv2d.

import tensorflow as tf

def inner_conv_x(w, x, y, inp, filters, strides, f_sh, indices, updates):

    # IDEA: need to pass the kernel and elementwise multiple
    # inp[..] * filters * kernel[..]
    val = tf.reduce_sum(
        inp[y * strides[1]:f_sh[1] + y * strides[1], x * strides[2] : f_sh[2] + x * strides[2]] * \
        filters, axis=(0, 1, 2))

    idx = y * w + x
    indices = indices.write(idx, [y, x])
    updates = updates.write(idx, val)

    return w, x + 1, y, inp, filters, strides, f_sh, indices, updates


def inner_conv(h, y, w, inp, filters, strides, f_sh, indices, updates):
    cond2 = lambda width, step_x, *args: width > step_x

    _, x, y, _, _, _, _, indices, updates = tf.while_loop(
        cond2, inner_conv_x,
        [w, 0, y, inp, filters, strides, f_sh, indices, updates])

    return h, y + 1, w, inp, filters, strides, f_sh, indices, updates


def my_tf_conv2(inputs, filters, padding, strides):
    f_sh = tf.shape(filters)
    in_sh = tf.shape(inputs)
    _, s_h, s_w, _ = strides

    if padding.upper() == 'SAME':
        out_h = tf.cast(tf.ceil(in_sh[1] / s_h), tf.int32)
        out_w = tf.cast(tf.ceil(in_sh[2] / s_w), tf.int32)

        padding_h = tf.maximum((out_h - 1) * s_h + f_sh[1] - in_sh[1], 0)
        pad_h1 = padding_h // 2
        pad_h2 = padding_h - pad_h1

        padding_w = tf.maximum((out_w - 1) * s_w + f_sh[2] - in_sh[2], 0)
        pad_w1 = padding_w // 2
        pad_w2 = padding_w - pad_w1

        inputs = tf.pad(inputs, [[0, 0]] + [[pad_h1, pad_h2], [pad_w1, pad_w2]] + [[0, 0]])
    elif padding.upper() == 'VALID':
        out_h = tf.cast(tf.ceil((h - f_sh[1] + 1) / s_h), tf.int32)
        out_w = tf.cast(tf.ceil((w - f_sh[2] + 1) / s_w), tf.int32)

    cond1 = lambda height, step_y, *args: height > step_y

    res = []
    inputs = tf.unstack(inputs)
    for inp in inputs:

        y, x = 0, 0
        indices = tf.TensorArray(dtype=tf.int32, size=0, dynamic_size=True)
        updates = tf.TensorArray(dtype=tf.float32, size=0, dynamic_size=True)
        inp = tf.expand_dims(inp, axis=-1)

        _, y, _, _, _, _, _, indices, updates = tf.while_loop(
            cond1,
            inner_conv, [out_h, y, out_w, inp, filters, strides, f_sh, indices, updates],
            parallel_iterations=2)

        res.append(tf.scatter_nd(
            indices.stack(),
            updates.stack(),
            shape=(out_h, out_w, f_sh[3])))

    res = tf.stack(res)

    return res

тогда вы можете проверить это следующим образом:

my_result2 = my_tf_conv2(tf_inputs, tf_filters, padding="SAME", strides=[1, 2, 2, 1])

with tf.Session() as sess:
    sess.run(tf.initializers.global_variables())
    output = sess.run(my_result2)
    print(output)

В комментарии я объяснил свою идею. Дело в том, что он настолько медленный, что я еще не начал реализовывать полный уровень PAC ...

Мне было интересно. Кто-нибудь из вас может придумать способ эффективной реализации уровня PAC (а не в 100 раз медленнее или даже больше, как это будет в случае, если я продолжу свою реализацию в этом направлении)? Я думал об использовании tf.nn.depthwise_conv2d может быть? Как вы думаете, мы можем реализовать эффективную версию только с использованием CUDA, а не высокоуровневого API-интерфейса python tenorflow?

Спасибо всем

...