制作自定义 Keras 层时不能使用未知的输入尺寸(批量大小)

Posted

技术标签:

【中文标题】制作自定义 Keras 层时不能使用未知的输入尺寸(批量大小)【英文标题】:Can't Use Unknown Input Dims (Batch Size) When Making Custom Keras Layer 【发布时间】:2019-12-27 02:31:37 【问题描述】:

我正在尝试在 Keras(后端 TensorFlow)中构建一个自定义层,该层跨卷积层的过滤器执行 KMeans 聚类。在构建这一层的逻辑时,我遍历了批量大小,但 Keras/TensorFlow 似乎不允许这种情况发生,因为批量大小在运行时是未知的维度。

我试图追踪错误消息,它引导我找到两个文件:keras/engine/training.py 和 keras/engine/training_utils.py,据我所知,错误源于问题使用定义为 NoneTypendim 变量,因为在编译模型时没有已知的批量大小。

在查看各种 *** 和 GitHub 资源时,我还没有看到任何关于如何处理 Keras/TensorFlow 拒绝未知批量大小参数的解决方案。

作为参考,这里是示例代码:

import numpy as np
import tensorflow as tf
import keras
from sklearn.cluster import KMeans

class KMeansLayer(keras.layers.Layer):
    def __init__(self, clusters=8, n_init=5, trainable=False, **kwargs):
        super(KMeansLayer, self).__init__(**kwargs)
        self.clusters = clusters
        self.n_init = n_init

    def build(self, input_shape):
        self.output_s = (input_shape[0],input_shape[1], input_shape[2],1)
        self.depth = input_shape[3]
        self.built=True

    def call(self, inputs):

        def KMeansFunc(input_tens,clusters=self.clusters,n_init=self.n_init):
            base_mat = np.zeros((input_tens.shape[0],input_tens.shape[1],input_tens.shape[2]))

            for frame in range(input_tens.shape[0]):
                init_mat = np.zeros((input_tens.shape[1]*input_tens.shape[2]))
                # print(init_mat.shape)
                reshape_mat = np.reshape(input_tens[frame],(input_tens.shape[1]*input_tens.shape[2],input_tens.shape[3]))
                # print(reshape_mat.shape)
                kmeans_init = KMeans(n_clusters=clusters, n_init=n_init)
                class_pred = kmeans_init.fit_predict(reshape_mat)

                for clust in range(self.clusters):
                    init_mat[class_pred==clust] = np.mean(reshape_mat[class_pred==clust],axis=1)
                    init_mat[class_pred==clust] = np.mean(reshape_mat[class_pred==clust],None)
                # print(base_mat.shape)

                base_mat[frame]=np.reshape(init_mat,(input_tens.shape[1],input_tens.shape[2]))

            return np.expand_dims(base_mat,axis=-1).astype('float32')


        output = tf.py_func(KMeansFunc,[inputs],tf.float32) 
        return output

    def compute_output_shape(self, input_shape):
        return self.output_s


input_1 = keras.Input(shape=(28,28,1), name='input_1', dtype='float32')

conv_1 = keras.layers.Conv2D(filters=20, kernel_size=3, strides=1, padding='same', data_format='channels_last', activation='elu', kernel_initializer='glorot_uniform')(input_1)
pool_1 = keras.layers.MaxPooling2D(pool_size=2, padding='same', data_format='channels_last')(conv_1)

up_conv_1 = keras.layers.SeparableConv2D(filters=20, kernel_size=2, strides=1, padding='same', data_format='channels_last', activation='elu', kernel_initializer='glorot_uniform')(pool_1)
up_1 = keras.layers.UpSampling2D(size=(2, 2), interpolation='bilinear')(up_conv_1)
conv_2 = keras.layers.Conv2D(filters=20, kernel_size=3, strides=1, padding='same', data_format='channels_last', activation='elu', kernel_initializer='glorot_uniform')(up_1)

conv_3 = keras.layers.Conv2D(filters=3, kernel_size=3, strides=1, padding='same', data_format='channels_last', activation='elu', kernel_initializer='glorot_uniform')(conv_2)

kmeans_out = KMeansLayer(clusters=8,n_init=5)(conv_3)


model = keras.Model(inputs=[input_1], outputs=kmeans_out)
keras.utils.plot_model(model, show_shapes=True)
model.compile(optimizer='adam', loss='mse', metrics=['mse'])

从上面的代码中可以看出,如果我有一个大小为(batch_size,28,28,3) 的自定义层的输入,我希望创建大小为(batch_size,28,28,1) 的输出。

运行上述代码导致的错误是:

Traceback (most recent call last):
  File "example_error_file.py", line 64, in <module>
    model.compile(optimizer='adam', loss='mse', metrics=['mse'])
  File "~/fluoro/fenv/lib/python3.6/site-packages/keras/engine/training.py", line 347, in compile
    sample_weight, mask)
  File "~/fluoro/fenv/lib/python3.6/site-packages/keras/engine/training_utils.py", line 426, in weighted
    axis=list(range(weight_ndim, ndim)))
TypeError: 'NoneType' object cannot be interpreted as an integer

我有两个主要问题:

在定义我的自定义 Keras 层时,我是否做错了什么? 在这种情况下,有什么方法可以强制 Keras 在不知道批量大小的情况下运行(这似乎是合理的)?

TensorFlow 版本:1.7.0 Keras 版本:2.2.4 Python:3.6.6

【问题讨论】:

【参考方案1】:

如果我们完全在 tensorflow 中查看这个事件,主要问题似乎是那个事件,因为直到运行时才知道形状,当 tensorflow 尝试编译这个图时,它会在整个循环过程中出现一些错误没有。

考虑使用 tf.function 或 autograph(如果您使用的是 tensorflow 1.14.0 或更高版本),而不是使用 tf.pyfunc。

或者,您可以使用 tf.scan 将函数应用于张量中的每个元素,其中每个元素由从维度 0 的原始张量解包的每个张量组成。

两者都应该工作!

【讨论】:

以上是关于制作自定义 Keras 层时不能使用未知的输入尺寸(批量大小)的主要内容,如果未能解决你的问题,请参考以下文章

使用自定义维度输入 tensorflow 或 keras 神经网络

无法向自定义损失输入数据:渴望执行函数的输入不能是 Keras 符号张量

tf.keras.Concatenate Graph 连接两个输入层时断开连接

当我将numpy数组作为输入传递给keras层时,它具有不同的形状

在 keras 中制作自定义损失函数

导入 Keras 层时出错