Неэквивалентный вывод из tf.nn.conv2d и keras.layers.Conv2D - PullRequest
1 голос
/ 07 апреля 2020

Я читал учебник «Практическое машинное обучение» (2-е издание) Аурелиана Жерона ( веб-страница издателя учебника здесь ). Я попал в контент, который применяет CNN к изображениям. В разделе под названием Реализация Tensorflow главы 14 они вручную создают фильтры, которые передаются в tf.nn.conv2d и применяются к изображению для создания набора карт объектов. После этих примеров ручного фильтра в книге говорится:

в реальном CNN вы обычно определяете фильтры как обучаемые переменные ... Вместо того, чтобы создавать переменные вручную, используйте слой keras.layers.Conv2D.

Приведенная выше цитата подразумевает для меня, что при одинаковых входных данных (и эквивалентных инициализациях) мы должны иметь возможность получать идентичные выходные данные из tf.nn.conv2d и keras.layers.Conv2D. Чтобы проверить эту идею, я посмотрел, были ли две функции эквивалентны. В соответствии с , на который ранее отвечал SO сообщение , для свертки, две функции одинаковы .

Я намеревался выполнить простой тест на их эквивалентность. Я создал сверточный слой, состоящий из одной карты объектов, используя фильтр 7x7 (он же: сверточное ядро ​​) всех нулей , который был реализован отдельно для tf.nn.conv2d и keras.layers.Conv2D. Как и ожидалось, после суммирования всех значений пикселей в разности обоих изображений этот фильтр заставил выходные изображения иметь нулевое значение для каждого значения пикселя. Эта нулевая разница означает, что выходные изображения идентичны.

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

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

Я приложил свой код и информацию о версиях ниже для справки. В коде в качестве входных данных используется образец изображения china.jpg scikit-learn и matplotlib.pyplot.imshow для визуализации выходных изображений и их различий.

TF Версия: 2.2.0-dev20200229

Версия Keras: 2.3.1

Версия Scikit-Learn: 0.22.1

Matplotlib Версия: 3.1.3

Numpy Версия: 1.18.1

from sklearn.datasets import load_sample_image
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import numpy as np

# Get the feature map as a result of tf.nn.conv2d
def featureMap1(batch):

    # Extract the channels
    batch_size, height, width, channels = batch.shape

    # Make a (7,7,3,1) filter set (one set of a 7x7 filter per channel)
    # of just ones. 
    filters = np.ones(shape=(7, 7, channels, 1), dtype=np.float32)

    # Run the conv2d with stride of 1 (i.e: in.shape = out.shape)
    # Generate one feature map for this conv layer
    fmaps = tf.nn.conv2d(batch, filters,
                         strides=1, padding='SAME',
                         data_format='NHWC')

    # Return the feature map
    return fmaps

# Get the feature map as a result of keras.layers.Conv2D
def featureMap2(batch):

    # Create the input layer with the shape of the images
    inputLayer = keras.layers.Input(shape=batch.shape[1:])

    # Create the convLayer which should apply the filter of all ones
    convLayer = keras.layers.Conv2D(filters=1, kernel_size=7,
                                    strides=1, padding='SAME',
                                    kernel_initializer='ones',
                                    data_format='channels_last',
                                    activation='linear')

    # Create the ouput layer
    outputLayer = convLayer(inputLayer)

    # Set up the model
    model = keras.Model(inputs=inputLayer,
                        outputs=outputLayer)

    # Perform a prediction, no model fitting or compiling
    fmaps = model.predict(batch)

    return fmaps 

def main():

    # Get the image and scale the RGB values to [0, 1]
    china = load_sample_image('china.jpg') / 255

    # Build a batch of just one image
    batch = np.array([china])

    # Get the feature maps and extract
    # the images within them
    img1 = featureMap1(batch)[0, :, :, 0]
    img2 = featureMap2(batch)[0, :, :, 0]

    # Calculate the difference in the images
    # Ideally, this should be all zeros...
    diffImage = np.abs(img1 - img2)

    # Add up all the pixels in the diffImage,
    # we expect a value of 0 if the images are
    # identical
    print('Differences value: ', diffImage.sum())

    # Plot the images as a set of 4
    figsize = 10
    f, axarr = plt.subplots(2, 2, figsize=(figsize,figsize))

    axarr[0,0].set_title('Original Image')
    axarr[0,0].imshow(batch[0], cmap='gray')

    axarr[1,0].set_title('Conv2D through tf.nn.conv2d')
    axarr[1,0].imshow(img1, cmap='gray')

    axarr[1,1].set_title('Conv2D through keras.layers.Conv2D')
    axarr[1,1].imshow(img2, cmap='gray')

    axarr[0,1].set_title('Diff')
    axarr[0,1].imshow(diffImage, cmap='gray')

    plt.show()

    return


main()

1 Ответ

1 голос
/ 09 апреля 2020

Выход двух сверточных слоев должен быть одинаковым.

Вы сравниваете Модель с Операцией , тогда как вы должны сравнивать Операция * От 1008 * (tf.keras.Conv2D) до Операция (tf.nn.conv2d).

Изменена функция featureMap2 .

def featureMap2(batch):
    # Create the convLayer which should apply the filter of all ones
    convLayer = keras.layers.Conv2D(filters=1, kernel_size = 7,
                                    strides=1, padding='SAME',
                                    kernel_initializer='ones',
                                    data_format='channels_last',
                                    activation='linear')
    fmaps = convLayer(batch)
    return fmaps

Вот сгенерированные графики.

conv2d_plots

Вот полный фрагмент кода, выполненный в среде Google Colab с добавлением Семя просто для обеспечения воспроизводимости и закомментированного предыдущего кода.

%tensorflow_version 2.x

from sklearn.datasets import load_sample_image
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import numpy as np

tf.random.set_seed(26)
np.random.seed(26)
tf.keras.backend.set_floatx('float64')


# Get the feature map as a result of tf.nn.conv2d
def featureMap1(batch):

    # Extract the channels
    batch_size, height, width, channels = batch.shape

    # Make a (7,7,3,1) filter set (one set of a 7x7 filter per channel)
    # of just ones. 
    filters = np.ones(shape=(7, 7, channels, 1), dtype=np.float32)

    # Run the conv2d with stride of 1 (i.e: in.shape = out.shape)
    # Generate one feature map for this conv layer
    fmaps = tf.nn.conv2d(batch, filters,
                         strides=1, padding='SAME',
                         data_format='NHWC')

    # Return the feature map
    return fmaps

# Get the feature map as a result of keras.layers.Conv2D
def featureMap2(batch):

    # Create the convLayer which should apply the filter of all ones
    convLayer = keras.layers.Conv2D(filters=1, kernel_size = 7,
                                    strides=1, padding='SAME',
                                    kernel_initializer='ones',
                                    data_format='channels_last',
                                    activation='linear')

    fmaps = convLayer(batch)

    # Create the ouput layer
    # outputLayer = convLayer(inputLayer)

    # # Set up the model
    # model = keras.Model(inputs=inputLayer,
    #                     outputs=outputLayer)

    # Perform a prediction, no model fitting or compiling
    # fmaps = model.predict(batch)

    return fmaps 

def main():

    # Get the image and scale the RGB values to [0, 1]
    china = load_sample_image('china.jpg') / 255

    # Build a batch of just one image
    batch = np.array([china])

    # Get the feature maps and extract
    # the images within them
    img1 = featureMap1(batch)[0, :, :, 0]
    img2 = featureMap2(batch)[0, :, :, 0]
    # Calculate the difference in the images
    # Ideally, this should be all zeros...
    diffImage = np.abs(img1 - img2)

    # Add up all the pixels in the diffImage,
    # we expect a value of 0 if the images are
    # identical
    print('Differences value: ', diffImage.sum())

    # Plot the images as a set of 4
    figsize = 10
    f, axarr = plt.subplots(2, 2, figsize=(figsize,figsize))

    axarr[0,0].set_title('Original Image')
    axarr[0,0].imshow(batch[0], cmap='gray')

    axarr[1,0].set_title('Conv2D through tf.nn.conv2d')
    axarr[1,0].imshow(img1, cmap='gray')

    axarr[1,1].set_title('Conv2D through keras.layers.Conv2D')
    axarr[1,1].imshow(img2, cmap='gray')

    axarr[0,1].set_title('Diff')
    axarr[0,1].imshow(diffImage, cmap='gray')

    plt.show()

    return


main()

РЕДАКТИРОВАТЬ:

Основным виновником было Кастинг по умолчанию поведение TensorFlow 2.x .

WARNING:tensorflow:Layer conv2d is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

Это снижает точность вычислений из-за потери точности с float64 до float32.
Вы можете избежать этой потери точности, установив настройку Tensorflow Keras Backend по умолчанию floatx в float64 .

tf.keras.backend.set_floatx('float64')
...