我有一个包含灰度图像的数据集,我想在它们上训练一个最先进的 CNN。我非常想微调一个预训练模型(例如 here)。
问题是我能找到权重的几乎所有模型都在包含 RGB 图像的 ImageNet 数据集上进行了训练。
我不能使用其中一个模型,因为在我的情况下,它们的输入层需要一批形状 (batch_size, height, width, 3)
或 (64, 224, 224, 3)
,但我的图像批次是 (64, 224, 224)
。
有什么方法可以使用其中一种模型吗?在加载权重并添加自己的权重后,我曾考虑删除输入层(就像我们对顶层所做的那样)。这种方法正确吗?
模型的架构无法更改,因为权重已针对特定输入配置进行了训练。用你自己的替换第一层几乎会使其余的权重无用。
-- 编辑:由 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。
根据当前接受的答案将灰度图像转换为 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 上使用预训练的权重进行了三轮 ImageNet 验证。运行 2 和 3 的数字略有不同,但它是最小的,一旦微调应该是无关紧要的。
带 RGB 图像的未修改 ResNet50:Prec @1:75.6,Prec @5:92.8 带 3 通道灰度的未修改 ResNet50 图像:Prec @1:64.6,Prec @5:86.4 带 1 通道灰度的修改 1 通道 ResNet50图片: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 = g = b
(灰度)时 r*w0+g*w1+b*w2
等效于 r*(w0+w1+w2)
?
一个简单的方法是在基础模型之前添加一个卷积层,然后将输出提供给基础模型。像这样:
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.
知道如何避免这种情况吗?
# 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)
应该可以工作。
为什么不尝试将灰度图像转换为 RGB 图像?
tf.image.grayscale_to_rgb(
images,
name=None
)
删除输入层是行不通的。这将导致所有后续层都会受到影响。
您可以做的是将 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)
我在使用 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,...)
如果您已经在使用 scikit-image
,则可以使用 gray2RGB 获得所需的结果。
from skimage.color import gray2rgb
rgb_img = gray2rgb(gray_img)
我相信您可以使用带有 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 模型
这真的很容易! 'resnet50' 的例子:在做之前你应该有:
resnet_50= torchvision.models.resnet50()
print(resnet_50.conv1)
Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
就这样做吧!
resnet_50.conv1 = nn.Conv2d(1, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
最后一步是更新 state_dict。
resnet_50.state_dict()['conv1.weight'] = resnet_50.state_dict()['conv1.weight'].sum(dim=1, keepdim=True)
所以如果运行如下:
print(resnet_50.conv1)
结果将是:
Conv2d(1, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
如您所见,输入通道用于灰度图像。
我所做的只是通过使用以下变换阶段将灰度扩展为 RGB 图像:
import torchvision as tv
tv.transforms.Compose([
tv.transforms.ToTensor(),
tv.transforms.Lambda(lambda x: x.broadcast_to(3, x.shape[1], x.shape[2])),
])
您可以使用 OpenCV 将灰度转换为 RGB。
cv2.cvtColor(image, cv2.COLOR_GRAY2RGB)
将 Resnet 添加到模型时,应在 Resnet 定义中输入 input_shape,例如
模型 = ResNet50(include_top=True,input_shape=(256,256,1))
.
ValueError: The input must have 3 channels