Posterior a investigar en varios repositorios y foros de internet al terminar descubrimos la solución que te mostramos más adelante.
Solución:
La arquitectura del modelo. no poder cambiarse porque los pesos se han entrenado para una configuración de entrada específica. Reemplazar la primera capa con la tuya prácticamente inutilizaría el resto de los pesos.
— Editar: elaboración sugerida por Prune–
Las CNN están diseñadas para que, a medida que profundizan, puedan extraer características de alto nivel derivadas de las características de nivel inferior que extrajeron las capas anteriores. Al eliminar las capas iniciales de una CNN, está destruyendo esa jerarquía de funciones porque las capas posteriores no recibirán las funciones que se supone que deben recibir como entrada. En su caso, la segunda capa ha sido entrenada para suponer las características de la primera capa. Al reemplazar su primera capa con pesos aleatorios, básicamente está descartando cualquier entrenamiento que se haya realizado en las capas posteriores, ya que tendrían que volver a entrenarse. Dudo que pudieran retener alguno de los conocimientos aprendidos durante la formación inicial.
— fin de edición —
Sin embargo, hay una manera fácil de hacer que su modelo funcione con imágenes en escala de grises. Solo necesitas hacer la imagen para aparecer ser RGB. La forma más fácil de hacerlo es repetir la imagen array 3 veces en una nueva dimensión. porque tendrás la misma imagen en los 3 canales, el rendimiento del modelo debería ser el mismo que en las imágenes RGB.
En entumecido esto se puede hacer fácilmente así:
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)
La forma en que esto funciona es que primero crea una nueva dimensión (para colocar los canales) y luego repite la existente array 3 veces en esta nueva dimensión.
También estoy bastante seguro de que ImageDataGenerator de Keras puede cargar imágenes en escala de grises como RGB.
Convertir imágenes en escala de grises a RGB según la respuesta actualmente aceptada es un enfoque para este problema, pero no el más eficiente. Sin duda, puede modificar los pesos de la primera capa convolucional del modelo y lograr el objetivo establecido. El modelo modificado funcionará de inmediato (con precisión reducida) y se podrá ajustar con precisión. Modificar los pesos de la primera capa no inutiliza el resto de los pesos como sugieren otros.
Para hacer esto, deberá agregar un código donde se cargan los pesos preentrenados. En el marco de su elección, debe descubrir cómo tomar los pesos de la primera capa convolucional en su red y modificarlos antes de asignarlos a su modelo de 1 canal. La modificación requerida es sumar el tensor de peso sobre la dimensión de los canales de entrada. La forma en que se organiza el tensor de pesos varía de un marco a otro. El valor predeterminado de PyTorch es [out_channels, in_channels, kernel_height, kernel_width]. En Tensorflow creo que es [kernel_height, kernel_width, in_channels, out_channels].
Usando PyTorch como ejemplo, en un modelo ResNet50 de Torchvision (https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py), la forma de los pesos para conv1 es [64, 3, 7, 7]. Sumar sobre la dimensión 1 da como resultado un tensor de forma [64, 1, 7, 7]. En la parte inferior, he incluido un fragmento de código que funcionaría con los modelos ResNet en Torchvision, suponiendo que se agregó un argumento (pulgadas) para especificar un número diferente de canales de entrada para el modelo.
Para probar que esto funciona, realicé tres ejecuciones de validación de ImageNet en ResNet50 con pesos previamente entrenados. Hay una ligera diferencia en los números para las ejecuciones 2 y 3, pero es mínima y debería ser irrelevante una vez ajustada.
- ResNet50 sin modificar con imágenes RGB: Prec @ 1: 75.6, Prec @ 5: 92.8
- ResNet50 sin modificar con imágenes en escala de grises de 3 canales: Prec @ 1: 64.6, Prec @ 5: 86.4
- ResNet50 de 1 canal modificado con imágenes en escala de grises de 1 canal: 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
Una forma sencilla de hacer esto es agregar una capa de convolución antes del modelo base y luego enviar la salida al modelo base. Me gusta esto:
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)
Comentarios y valoraciones
Recuerda algo, que puedes optar por la opción de añadir un enjuiciamiento acertado si diste con el hallazgo.