我有一个包含灰度图像的数据集,我想在其上训练最新的CNN。我非常想微调经过预先训练的模型(例如here的模型)。
问题在于,几乎所有我能找到的权重模型都在包含RGB图像的ImageNet数据集中进行了训练。
我不能使用其中一个模型,因为在我的情况下,它们的输入层需要一批形状为(batch_size, height, width, 3)
或(64, 224, 224, 3)
的模型,但是我的图像批次为(64, 224, 224)
。
有什么方法可以使用其中一种模型?我已经考虑过在加载权重并添加自己的权重后删除输入层(就像我们对顶层所做的那样)。这种方法正确吗?
答案 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的数字略有不同,但是这是最小的,并且在进行微调后应该是不相关的。
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))
。