如何使用带有灰度图像的预训练神经网络?

Posted

技术标签:

【中文标题】如何使用带有灰度图像的预训练神经网络?【英文标题】:How can I use a pre-trained neural network with grayscale images? 【发布时间】:2019-01-30 10:24:51 【问题描述】:

我有一个包含灰度图像的数据集,我想在它们上训练一个最先进的 CNN。我非常想微调一个预训练模型(比如here)。

问题是我能找到权重的几乎所有模型都在包含 RGB 图像的 ImageNet 数据集上进行了训练。

我不能使用其中一个模型,因为在我的情况下,它们的输入层需要一批形状 (batch_size, height, width, 3)(64, 224, 224, 3),但我的图像批是 (64, 224, 224)

有什么方法可以让我使用其中一种模型吗?在加载权重并添加自己的权重后,我曾考虑删除输入层(就像我们对顶层所做的那样)。这种方法正确吗?

【问题讨论】:

您可以尝试删除输入层并添加自己的。然后您可以尝试仅训练该层。如果在锁定所有其他层的情况下您没有看到损失减少,那么这种方式对您来说是行不通的。 不要问我们这种做法是否正确:问电脑!试试吧!另一种方法是将输入向量增加三倍:将灰度值提供给所有三个颜色层。 我个人的感觉是,这对你来说是行不通的。这些分类网络肯定是使用颜色之间的相互关系来对物体进行分类,而这些信息在中间层的权重中根深蒂固 @Prune 训练这些模型可能需要几天时间,如果有人之前遇到过这个问题,我会很感激...... 正如其他人所提到的,堆叠 3 个相同的灰度数组作为输入是可行的——但我会将此作为实现更多数据增强的机会——将图像过滤器应用于原始灰度图像并随机将它们分配给 3 个通道。 【参考方案1】:

我在使用 VGG16 和灰度图像时遇到了同样的问题。我解决了这个问题如下:

假设我们的训练图像在train_gray_images 中,每一行都包含展开的灰度图像强度。因此,如果我们直接将其传递给 fit 函数,则会产生错误,因为 fit 函数需要 3 通道 (RGB) 图像数据集而不是灰度数据集。因此,在传递给 fit 函数之前,请执行以下操作:

创建一个虚拟的RGB图像数据集,就像具有相同形状的灰度数据集(此处为dummy_RGB_image)。唯一的区别是这里我们使用的通道数是 3。

dummy_RGB_images = np.ndarray(shape=(train_gray_images.shape[0], train_gray_images.shape[1], train_gray_images.shape[2], 3), dtype= np.uint8) 

因此只需将整个数据集复制 3 次到“dummy_RGB_images”的每个通道。 (这里的维度是[no_of_examples, height, width, channel]

dummy_RGB_images[:, :, :, 0] = train_gray_images[:, :, :, 0]
dummy_RGB_images[:, :, :, 1] = train_gray_images[:, :, :, 0]
dummy_RGB_images[:, :, :, 2] = train_gray_images[:, :, :, 0]

最后通过dummy_RGB_images而不是灰度数据集,比如:

model.fit(dummy_RGB_images,...)

【讨论】:

【参考方案2】:

您可以使用 OpenCV 将灰度转换为 RGB。

cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)

【讨论】:

COLOR_GRAY2BGR 色彩模式基本上是把所有的B、G、R通道都替换为灰度值Y,所以B=Y、G=Y、R=Y。【参考方案3】:

一个简单的方法是在基础模型之前添加一个卷积层,然后将输出馈送到基础模型。像这样:

from keras.models import Model
from keras.layers import Input 

resnet = Resnet50(weights='imagenet',include_top= 'TRUE') 

input_tensor = Input(shape=(IMG_SIZE,IMG_SIZE,1) )
x = Conv2D(3,(3,3),padding='same')(input_tensor)    # x has a dimension of (IMG_SIZE,IMG_SIZE,3)
out = resnet (x) 

model = Model(inputs=input_tensor,outputs=out)


【讨论】:

ValueError: You are trying to load a weight file containing 13 layers into a model with 14 layers. 知道如何避免这种情况吗? @Madara 你能分享更多细节吗?另外请检查您是否包括最后一层。 # input_shape = (64,64,1) input_tensor = Input(shape=image_shape) model_input = Conv2D(filters = 3, kernel_size=3, padding='same', name="input_conv")(input_tensor) 现在我导入 VGG16 模型 vgg16 = VGG16(include_top=False, weights='imagenet',input_tensor=model_input) 然后我尝试添加一个 Conv 层 X = Conv2D(channels, kernel_size=3, padding='same')(vgg16.output) X = Activation('tanh')(X) 和最终模型:model = Model(inputs = input_tensor, outputs = X)跨度> 只需将vgg16 = VGG16(include_top=False, weights='imagenet',input_tensor=model_input) 拆分为vgg16 = VGG16(include_top=False, weights='imagenet') vgg16 = vgg16(model_input) 即可。【参考方案4】:

我相信您可以使用带有 1 通道灰度图像的预训练 resnet,而无需重复 3 次图像。

我所做的是替换第一层(这是pythorch而不是keras,但想法可能相似):

(conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)

以下层:

(conv1): Conv2d(1, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)

然后将权重的总和(在通道轴上)复制到新层,例如,原始权重的形状为:

torch.Size([64, 3, 7, 7])

所以我做到了:

resnet18.conv1.weight.data = resnet18.conv1.weight.data.sum(axis=1).reshape(64, 1, 7, 7)

然后检查新模型的输出与灰度图的输出是否一致:

y_1 = model_resnet_1(input_image_1)
y_3 = model_resnet_3(input_image_3)
print(torch.abs(y_1).sum(), torch.abs(y_3).sum())
(tensor(710.8860, grad_fn=<SumBackward0>),
 tensor(710.8861, grad_fn=<SumBackward0>))

input_image_1:单通道图像

input_image_3:3通道图像(灰度-所有通道相等)

model_resnet_1:修改后的模型

model_resnet_3:原始 resnet 模型

【讨论】:

【参考方案5】:

删除输入层是行不通的。这会导致后面的所有层都受到影响。

您可以做的是将 3 张黑白图像连接在一起以扩展您的颜色维度。

img_input = tf.keras.layers.Input(shape=(img_size_target, img_size_target,1))
img_conc = tf.keras.layers.Concatenate()([img_input, img_input, img_input])    

model = ResNet50(include_top=True, weights='imagenet', input_tensor=img_conc)

【讨论】:

虽然这段代码可能会解决问题,包括解释如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请编辑您的答案以添加解释并说明适用的限制和假设。在此链接查看有关如何回答的更多详细信息:***.com/help/how-to-answer【参考方案6】:

numpy 的深度堆栈函数,np.dstack((img, img, img)) 是一种很自然的方式。

【讨论】:

这与Djib2011's 2018 suggestion of np.repeat(img, 3, -1)相比如何? 它们基本上产生相同的结果,np.dstack 似乎更简单一些【参考方案7】:

如果您已经在使用scikit-image,则可以使用 gray2RGB 获得所需的结果。

from skimage.color import gray2rgb
rgb_img = gray2rgb(gray_img)

【讨论】:

【参考方案8】:

根据当前接受的答案将灰度图像转换为 RGB 是解决此问题的一种方法,但不是最有效的方法。您当然可以修改模型的第一个卷积层的权重并实现既定目标。修改后的模型既可以开箱即用(精度降低),也可以微调。修改第一层的权重不会像其他人建议的那样使其余的权重无用。

为此,您必须在加载预训练权重的位置添加一些代码。在您选择的框架中,您需要弄清楚如何获取网络中第一个卷积层的权重并在分配给您的单通道模型之前对其进行修改。所需的修改是在输入通道的维度上对权重张量求和。权重张量的组织方式因框架而异。 PyTorch 默认为 [out_channels, in_channels, kernel_height, kernel_width]。在 Tensorflow 中,我相信它是 [kernel_height, kernel_width, in_channels, out_channels]。

以 PyTorch 为例,在 Torchvision (https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) 的 ResNet50 模型中,conv1 的权重形状为 [64, 3, 7, 7]。对维度 1 求和会产生一个形状为 [64, 1, 7, 7] 的张量。在底部,我包含了一段可以与 Torchvision 中的 ResNet 模型一起使用的代码,假设添加了一个参数 (inchans) 来为模型指定不同数量的输入通道。

为了证明这一点,我在 ResNet50 上使用预训练的权重进行了 3 次 ImageNet 验证。第 2 次和第 3 次运行的数字略有不同,但差异很小,微调后应该无关紧要。

    带 RGB 图像的未修改 ResNet50:Prec @1:75.6,Prec @5:92.8 未修改的 ResNet50,带 3 通道灰度图像:Prec @1:64.6,Prec @5:86.4 修改后的 1 通道 ResNet50 带 1 通道灰度图像:Prec @1:63.8,Prec @5:86.1
def _load_pretrained(model, url, inchans=3):
    state_dict = model_zoo.load_url(url)
    if inchans == 1:
        conv1_weight = state_dict['conv1.weight']
        state_dict['conv1.weight'] = conv1_weight.sum(dim=1, keepdim=True)
    elif inchans != 3:
        assert False, "Invalid number of inchans for pretrained weights"
    model.load_state_dict(state_dict)

def resnet50(pretrained=False, inchans=3):
    """Constructs a ResNet-50 model.
    Args:
        pretrained (bool): If True, returns a model pre-trained on ImageNet
    """
    model = ResNet(Bottleneck, [3, 4, 6, 3], inchans=inchans)
    if pretrained:
        _load_pretrained(model, model_urls['resnet50'], inchans=inchans)
    return model

【讨论】:

听起来超级酷!所以这是有效的,因为r*w0+g*w1+b*w2r = g = b(灰度)时等于r*(w0+w1+w2) 所以,这看起来正是我感兴趣的,但是,尚不清楚我们是否会获得 3 倍的处理速度?我有一个 RGB 图像,其中绿色 chan 比其他图像好得多,并且希望只使用绿色会快得多。您能否澄清一下这是什么时候,以及这种方法的目的是否确实是为了加快训练和预测?非常感谢! @Cat 你可能会观察到小地图的加速,但远没有接近 3 倍,因为这只是一层,网络的其余部分保持不变。【参考方案9】:

将Resnet添加到模型时,应在Resnet定义中输入input_shape,如

model = ResNet50(include_top=True,input_shape=(256,256,1))

.

【讨论】:

这不会运行:ValueError: The input must have 3 channels【参考方案10】:

为什么不尝试将灰度图像转换为 RGB 图像?

tf.image.grayscale_to_rgb(
    images,
    name=None
)

【讨论】:

【参考方案11】:

模型的架构无法更改,因为权重已针对特定输入配置进行了训练。用你自己的替换第一层几乎会使其余的权重变得无用。

-- 编辑:Prune 建议的详细说明-- CNN 的构建是为了随着它们的深入,它们可以从前一层提取的低级特征中提取高级特征。通过删除 CNN 的初始层,您正在破坏该特征层次结构,因为后续层不会接收它们应该作为输入的特征。在您的情况下,第二层已被训练为预期第一层的特征。通过用随机权重替换你的第一层,你基本上放弃了在后续层上完成的任何训练,因为它们需要重新训练。我怀疑他们能否保留在初始培训中学到的任何知识。 --- 结束编辑 ---

不过,有一种简单的方法可以让您的模型处理灰度图像。您只需要将图像 显示 为 RGB。最简单的方法是在一个新维度上重复图像数组 3 次。因为您将在所有 3 个通道上获得相同的图像,所以模型的性能应该与它在 RGB 图像上的性能相同。

numpy 中,可以这样轻松完成:

print(grayscale_batch.shape)  # (64, 224, 224)
rgb_batch = np.repeat(grayscale_batch[..., np.newaxis], 3, -1)
print(rgb_batch.shape)  # (64, 224, 224, 3)

它的工作方式是它首先创建一个新维度(放置通道),然后在这个新维度上重复现有数组 3 次。

我也很确定 keras 的 ImageDataGenerator 可以将灰度图像加载为 RGB。

【讨论】:

堆叠1通道图像很容易做到,但问题不是如何使图像成为3通道,而是当他的原始图像为1通道时,他是否可以使用预训练模型进行分类,以及我认为答案可能是否定的 这几乎是处理灰度图像时的默认方法。我已经完成了几次,它工作正常,它甚至是 keras 的 ImageDataGenerator 中的默认设置,用于加载重复 3 次的灰度图像。将其视为反向 RGB -> 灰度变换(其中 gray=(R+B+G)/3)。 这显示了如何进行我建议的第二次尝试;它没有回答原始问题。这会导致对灰度输入的有效微调吗? 你回答的第一段是直接部分:你能详细说明一下以说服 OP 吗? “用你自己的替换第一层几乎会使其余的权重变得无用。” - 您确定吗?检查这一点的实验是训练神经网络,例如在 ImageNet 上,看看它“通常”需要多长时间才能达到一定的准确性。然后重新初始化输入层,看看需要多长时间才能再次达到该精度。我确信初始化网络将花费更少的时间。

以上是关于如何使用带有灰度图像的预训练神经网络?的主要内容,如果未能解决你的问题,请参考以下文章

源码二维LeNet-5网络的预训练神经网络工具箱模型

在灰度图像网络上训练的 VGG16

训练首个神经网络:基本分类

Caffe训练好的网络对图像分类

从头开始训练 Resnet 深度神经网络

手把手写深度学习(14):如何利用官方预训练模型做微调/迁移学习?(以Resnet50提取图像特征为例)