向 keras 顺序模型添加调整大小层

Posted

技术标签:

【中文标题】向 keras 顺序模型添加调整大小层【英文标题】:Add a resizing layer to a keras sequential model 【发布时间】:2017-06-13 17:20:41 【问题描述】:

如何添加调整大小的图层

model = Sequential()

使用

model.add(...)

要将图像的大小从 (160, 320, 3) 调整为 (224,224,3)?

【问题讨论】:

【参考方案1】:

我认为你应该考虑使用 tensorflow 的 resize_images 层。

https://www.tensorflow.org/api_docs/python/tf/image/resize_images

似乎 keras 不包含此功能,可能是因为该功能在 theano 中不存在。我写了一个自定义的 keras 层,它做同样的事情。这是一个快速破解,所以它可能不适用于您的情况。

import keras
import keras.backend as K
from keras.utils import conv_utils
from keras.engine import InputSpec
from keras.engine import Layer
from tensorflow import image as tfi

class ResizeImages(Layer):
    """Resize Images to a specified size

    # Arguments
        output_size: Size of output layer width and height
        data_format: A string,
            one of `channels_last` (default) or `channels_first`.
            The ordering of the dimensions in the inputs.
            `channels_last` corresponds to inputs with shape
            `(batch, height, width, channels)` while `channels_first`
            corresponds to inputs with shape
            `(batch, channels, height, width)`.
            It defaults to the `image_data_format` value found in your
            Keras config file at `~/.keras/keras.json`.
            If you never set it, then it will be "channels_last".

    # Input shape
        - If `data_format='channels_last'`:
            4D tensor with shape:
            `(batch_size, rows, cols, channels)`
        - If `data_format='channels_first'`:
            4D tensor with shape:
            `(batch_size, channels, rows, cols)`

    # Output shape
        - If `data_format='channels_last'`:
            4D tensor with shape:
            `(batch_size, pooled_rows, pooled_cols, channels)`
        - If `data_format='channels_first'`:
            4D tensor with shape:
            `(batch_size, channels, pooled_rows, pooled_cols)`
    """
    def __init__(self, output_dim=(1, 1), data_format=None, **kwargs):
        super(ResizeImages, self).__init__(**kwargs)
        data_format = conv_utils.normalize_data_format(data_format)
        self.output_dim = conv_utils.normalize_tuple(output_dim, 2, 'output_dim')
        self.data_format = conv_utils.normalize_data_format(data_format)
        self.input_spec = InputSpec(ndim=4)

    def build(self, input_shape):
        self.input_spec = [InputSpec(shape=input_shape)]

    def compute_output_shape(self, input_shape):
        if self.data_format == 'channels_first':
            return (input_shape[0], input_shape[1], self.output_dim[0], self.output_dim[1])
        elif self.data_format == 'channels_last':
            return (input_shape[0], self.output_dim[0], self.output_dim[1], input_shape[3])

    def _resize_fun(self, inputs, data_format):
        try:
            assert keras.backend.backend() == 'tensorflow'
            assert self.data_format == 'channels_last'
        except AssertionError:
            print "Only tensorflow backend is supported for the resize layer and accordingly 'channels_last' ordering"
        output = tfi.resize_images(inputs, self.output_dim)
        return output

    def call(self, inputs):
        output = self._resize_fun(inputs=inputs, data_format=self.data_format)
        return output

    def get_config(self):
        config = 'output_dim': self.output_dim,
                  'padding': self.padding,
                  'data_format': self.data_format
        base_config = super(ResizeImages, self).get_config()
        return dict(list(base_config.items()) + list(config.items()))

【讨论】:

就我而言,我仍然遇到错误:TypeError: Output tensors to a Model must be Keras tensors. Found: Tensor("ResizeBilinear_1:0", shape=(?, 224, 224, 3), dtype=float32) @VadymB。您使用的是哪个后端,以及 Keras 的哪个版本?你确定你给层提供了正确的输入吗? keras 2.0.6。我已经发现了我正在使用tf.image.resize_images 的一个问题,我不得不将它放入keras.layers.Lambda @VadymB。不是要死灵这个线程,而是当你把它放在 lambda 层时,你能成功地在它上面调用 'fit' 或 'fit_generator' 吗?你能发布你是怎么做到的吗?我已经断断续续地处理类似的问题好几天了,并且在装配过程中继续出现错误......谢谢! @Decker,它曾经可以工作,但不幸的是我没有使用此功能的工作代码。对于我的用例,我最终切换到tf.depth_to_space,同时仍然使用 Lambda 层。这是伪代码:``` def custom_fun(input, k=2, **kwargs): import tensorflow as tf return tf.depth_to_space(input, block_size=k) input = Input(...) ... out = Lambda (custom_fun)(输入) ```【参考方案2】:

接受的答案使用Reshape 层,其工作方式类似于NumPy's reshape,可用于将 4x4 矩阵重塑为 2x8 矩阵,但这会导致图像丢失位置信息:

0 0 0 0
1 1 1 1    ->    0 0 0 0 1 1 1 1
2 2 2 2          2 2 2 2 3 3 3 3
3 3 3 3

相反,应使用 Tensorflows image_resize 等重新缩放/“调整大小”图像数据。 但请注意正确的usage 和错误! 如related question 所示,这可以与 lambda 层一起使用:

model.add( keras.layers.Lambda( 
    lambda image: tf.image.resize_images( 
        image, 
        (224, 224), 
        method = tf.image.ResizeMethod.BICUBIC,
        align_corners = True, # possibly important
        preserve_aspect_ratio = True
    )
))

在您的情况下,由于您拥有 160x320 的图像,您还必须决定是否保持宽高比。如果你想使用预训练的网络,那么你应该使用与网络训练相同的大小调整。

【讨论】:

这是一个好方法,但接受答案的问题不在于它“丢失了位置信息”——而是它完全做错了事情,而“丢失了位置信息”只是一种症状。【参考方案3】:

我想我应该发布一个更新的答案,因为接受的答案是错误的,并且最近的 Keras 版本中有一些重大更新。

添加一个调整大小的图层,根据documentation:

tf.keras.layers.experimental.preprocessing.Resizing(height, width, interpolation="bilinear", crop_to_aspect_ratio=False, **kwargs)

对你来说,应该是:

from tensorflow.keras.layers.experimental.preprocessing import Resizing

model = Sequential()
model.add(Resizing(224,224))

【讨论】:

【参考方案4】:

@KeithWM 答案的修改,添加 output_scale,例如output_scale=2 表示输出是输入形状的 2 倍 :)

class ResizeImages(Layer):
    """Resize Images to a specified size
    https://***.com/questions/41903928/add-a-resizing-layer-to-a-keras-sequential-model

    # Arguments
        output_dim: Size of output layer width and height
        output_scale: scale compared with input
        data_format: A string,
            one of `channels_last` (default) or `channels_first`.
            The ordering of the dimensions in the inputs.
            `channels_last` corresponds to inputs with shape
            `(batch, height, width, channels)` while `channels_first`
            corresponds to inputs with shape
            `(batch, channels, height, width)`.
            It defaults to the `image_data_format` value found in your
            Keras config file at `~/.keras/keras.json`.
            If you never set it, then it will be "channels_last".

    # Input shape
        - If `data_format='channels_last'`:
            4D tensor with shape:
            `(batch_size, rows, cols, channels)`
        - If `data_format='channels_first'`:
            4D tensor with shape:
            `(batch_size, channels, rows, cols)`

    # Output shape
        - If `data_format='channels_last'`:
            4D tensor with shape:
            `(batch_size, pooled_rows, pooled_cols, channels)`
        - If `data_format='channels_first'`:
            4D tensor with shape:
            `(batch_size, channels, pooled_rows, pooled_cols)`
    """

    def __init__(self, output_dim=(1, 1), output_scale=None, data_format=None, **kwargs):
        super(ResizeImages, self).__init__(**kwargs)
        data_format = normalize_data_format(data_format)  # does not have
        self.naive_output_dim = conv_utils.normalize_tuple(output_dim,
                                                           2, 'output_dim')
        self.naive_output_scale = output_scale
        self.data_format = normalize_data_format(data_format)
        self.input_spec = InputSpec(ndim=4)

    def build(self, input_shape):
        self.input_spec = [InputSpec(shape=input_shape)]
        if self.naive_output_scale is not None:
            if self.data_format == 'channels_first':
                self.output_dim = (self.naive_output_scale * input_shape[2],
                                   self.naive_output_scale * input_shape[3])
            elif self.data_format == 'channels_last':
                self.output_dim = (self.naive_output_scale * input_shape[1],
                                   self.naive_output_scale * input_shape[2])
        else:
            self.output_dim = self.naive_output_dim

    def compute_output_shape(self, input_shape):
        if self.data_format == 'channels_first':
            return (input_shape[0], input_shape[1], self.output_dim[0], self.output_dim[1])
        elif self.data_format == 'channels_last':
            return (input_shape[0], self.output_dim[0], self.output_dim[1], input_shape[3])

    def _resize_fun(self, inputs, data_format):
        try:
            assert keras.backend.backend() == 'tensorflow'
            assert self.data_format == 'channels_last'
        except AssertionError:
            print("Only tensorflow backend is supported for the resize layer and accordingly 'channels_last' ordering")
        output = tf.image.resize_images(inputs, self.output_dim)
        return output

    def call(self, inputs):
        output = self._resize_fun(inputs=inputs, data_format=self.data_format)
        return output

    def get_config(self):
        config = 'output_dim': self.output_dim,
                  'padding': self.padding,
                  'data_format': self.data_format
        base_config = super(ResizeImages, self).get_config()
        return dict(list(base_config.items()) + list(config.items()))

【讨论】:

【参考方案5】:

将给定的输入图像调整为目标尺寸(在本例中为 224x224x3):

在常规 Keras 中使用 Lambda 层:

from keras.backend import tf as ktf

inp = Input(shape=(None, None, 3))

[参考:https://www.tensorflow.org/api_docs/python/tf/keras/backend/resize_images]:

【讨论】:

【参考方案6】:

通常您会为此使用Reshape 层:

model.add(Reshape((224,224,3), input_shape=(160,320,3))

但由于您的目标维度不允许保存来自输入维度 (224*224 != 160*320) 的所有数据,因此这不起作用。如果元素数量不变,则只能使用Reshape

如果您可以在图像中丢失一些数据,您可以指定自己的有损整形:

model.add(Reshape(-1,3), input_shape=(160,320,3))
model.add(Lambda(lambda x: x[:50176])) # throw away some, so that #data = 224^2
model.add(Reshape(224,224,3))

也就是说,这些转换通常是在将数据应用到模型之前完成的,因为如果在每个训练步骤中都完成,这实际上是在浪费计算时间。

【讨论】:

我不明白你关于计算时间的说法。每个图像必须进行一次整形。如果它之前完成而不是作为模型管道的一部分,它有什么区别? 如果您只训练一次模型并没有什么不同(如果我们不考虑这些操作在 GPU 上是否更快)。但是,一旦您训练模型两次并多次计算重塑,您就会受益于一次,将结果缓存到文件中(numpy 保存或 pickle)并简单地加载它。 问题是关于调整大小,而不是重塑。在 reshape 中,总数组大小必须保持不变,这对于 resize 是不正确的。 Frome Keras 函数 cmets:“如果 output_shape 的总数组大小与 input_shape 不同,或者指定了多个未知维度,则引发 ValueError。” 我很确定这是个坏主意。所提议的重塑将丢失输入数据中的所有空间结构。此外,如果输入层和输出层的 size(元素总数)差异很大,则会产生很大的损失。 我对此投了反对票,因为问题几乎肯定是在寻找图像下采样/上采样/插值,而不是您建议的张量重塑。

以上是关于向 keras 顺序模型添加调整大小层的主要内容,如果未能解决你的问题,请参考以下文章

在 Keras Lambda 层中调整输入图像的大小

UIPopOver、UITextView 和自动调整大小

在 Keras 中,如何任意调整一维张量的大小?

UIView 层不随视图调整大小?

如何在 Swift 中调整视图中的子层大小?

Keras:如何在顺序模型中获取图层形状