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

时间:2018-08-24 00:33:05

标签: python tensorflow machine-learning keras deep-learning

我有一个包含灰度图像的数据集,我想在其上训练最新的CNN。我非常想微调经过预先训练的模型(例如here的模型)。

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

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

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

11 个答案:

答案 0 :(得分:7)

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

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)


答案 1 :(得分:6)

无法更改模型的体系结构 ,因为已经为特定的输入配置训练了权重。用您自己的第一层替换几乎会使其余的权重失效。

-编辑: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。

答案 2 :(得分:3)

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

为此,您必须在加载预训练权重的位置添加一些代码。在您选择的框架中,您需要弄清楚如何在分配给您的1通道模型之前掌握网络中第一个卷积层的权重并进行修改。所需的修改是对输入通道的尺寸上的权重张量求和。权重张量的组织方式因框架而异。 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模型一起使用,假定已添加了一个参数(增量)以为该模型指定不同数量的输入通道。

为了证明这项工作,我在ResNet50上使用预先训练的权重进行了3次ImageNet验证。运行2和3的数字略有不同,但是这是最小的,并且在进行微调后应该是不相关的。

  1. 带有RGB图像的未修改ResNet50:Prec @ 1:75.6,Prec @ 5:92.8
  2. 具有3通道灰度图像的未修改ResNet50:Prec @ 1:64.6,Prec @ 5:86.4
  3. 修改后的1-chan ResNet50 / 1-chan灰度图像: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

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

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

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

创建虚拟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”的每个通道。 (此处的尺寸为 [没有示例,高度,宽度,通道]

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,...)

答案 5 :(得分:1)

numpy的深度堆栈功能np.dstack((img,img,img))是一种自然的选择。

答案 6 :(得分:1)

删除输入层将无法解决。这将导致随后的所有层受到损害。

您可以做的是将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)

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

我相信您可以将预训练的resnet与1通道灰度图像一起使用,而不必重复图像的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模型

答案 9 :(得分:0)

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

cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)

答案 10 :(得分:-2)

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

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