tensorflow == 2.2.0 keras == 2.3.1 (Version par défaut de Google Colab à partir du 202.6.10)
Vous pouvez trouver tout le code sur github. https://github.com/milky1210/Segnet Le code de l'article est un extrait, donc si vous voulez vraiment le travailler, veuillez télécharger le code.
Dans le problème de la déduction de l'étiquette de ce qui est reflété pour chaque pixel de l'image appelée segmentation SEMANTIQUE par apprentissage en profondeur, il est précis de restaurer la carte de caractéristiques qui a été abaissée en résolution par Pooling, etc. à la dimension d'origine. Nous proposons un modèle qui correspond à la ligne de démarcation.
SegNet effectue un UpSampling après avoir réduit la résolution dans la couche de convolution et la couche de pooling comme un FCN normal, mais lors de l'augmentation de la résolution, il utilise une technique appelée indice de pooling pour éviter que la limite ne devienne floue. Il y a. Ici, Encode et Decode héritent de la forme du modèle VGG16 (un modèle célèbre pour la classification d'images). Pooling indices Comme le montre cette figure, rappelez-vous où se trouvait Max lorsque Max Pooling a été effectué et transférez chaque carte d'entités à cette position pendant l'échantillonnage ascendant.
Il s'agit d'un ensemble de données qui prend en charge des problèmes tels que la reconnaissance d'image, la détection d'image et la segmentation, qui sont également utilisés dans les articles de SegNet pour la vérification des performances. Vous pouvez le télécharger depuis ici.
Une fois téléchargés, JPEGImages / et SegmentationObject / sont inclus dans VOCdevkit / VOC2012 /, et la formation et la vérification sont effectuées avec JPEGImage comme image d'entrée et SegmentationObject comme image de sortie.
JPEGImages / ~ .jpg et Segmentation Object / ~ .png sont pris en charge dans chaque répertoire. 22 classes sont classées, y compris le contexte et les limites.
Seules la définition du modèle, la définition de la fonction de perte et la formation seront couvertes dans l'article. De plus, la formation et la vérification seront menées à une résolution de 64x64.
Premièrement, comme cible de comparaison, SegNet (encodeur-décodeur) sans indice de regroupement est modélisé comme VGG16 comme suit.
def build_FCN():
ffc = 32
inputs = layers.Input(shape=(64,64,3))
for i in range(2):
x = layers.Conv2D(ffc,kernel_size=3,padding="same")(inputs)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.MaxPooling2D((2,2))(x)
for i in range(2):
x = layers.Conv2D(ffc*2,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.MaxPooling2D((2,2))(x)
for i in range(3):
x = layers.Conv2D(ffc*4,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.MaxPooling2D((2,2))(x)
for i in range(3):
x = layers.Conv2D(ffc*8,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.MaxPooling2D((2,2))(x)
for i in range(3):
x = layers.Conv2D(ffc*8,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.UpSampling2D((2,2))(x)
for i in range(3):
x = layers.Conv2D(ffc*4,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.UpSampling2D((2,2))(x)
for i in range(3):
x = layers.Conv2D(ffc*2,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.UpSampling2D((2,2))(x)
for i in range(2):
x = layers.Conv2D(ffc*2,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.UpSampling2D((2,2))(x)
for i in range(2):
x = layers.Conv2D(ffc,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.Conv2D(22,kernel_size=3,padding="same",activation="softmax")(x)
return models.Model(inputs,x)
S'il est modélisé d'après vgg16, il aura une telle structure, et ce sera un réseau avec 24 couches de convolution. Notez que MaxPooling2D est utilisé pour rendre l'image plus petite et UpSampling2D est utilisé pour agrandir l'image. Ensuite, regardons la différence entre Segnet et ce modèle. Premièrement, Segnet contient les informations correspondant à ArgMaxPooling2D dans cette couche comme suit avant d'exécuter MaxPooling2D. Cette fonction n'est pas dans Keras et utilise celle de tensorflow. Par conséquent, il est nécessaire de créer la couche Keras d'origine. Si vous définissez la fonction comme suit, ce sera une couche qui s'exécute sur Keras.
class MaxPoolingWithArgmax2D(Layer):
def __init__(self):
super(MaxPoolingWithArgmax2D,self).__init__()
def call(self,inputs):
output,argmax = tf.nn.max_pool_with_argmax(inputs,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
argmax = K.cast(argmax,K.floatx())
return [output,argmax]
def compute_output_shape(self,input_shape):
ratio = (1,2,2,1)
output_shape = [dim//ratio[idx] if dim is not None else None for idx, dim in enumerate(input_shape)]
output_shape = tuple(output_shape)
return [output_shape,output_shape]
Définissez un calque pour revenir à l'emplacement qui était argmax la prochaine fois que vous effectuez un échantillonnage ascendant (c'est assez long)
class MaxUnpooling2D(Layer):
def __init__(self):
super(MaxUnpooling2D,self).__init__()
def call(self,inputs,output_shape = None):
updates, mask = inputs[0],inputs[1]
with tf.variable_scope(self.name):
mask = K.cast(mask, 'int32')
input_shape = tf.shape(updates, out_type='int32')
# calculation new shape
if output_shape is None:
output_shape = (input_shape[0],input_shape[1]*2,input_shape[2]*2,input_shape[3])
self.output_shape1 = output_shape
# calculation indices for batch, height, width and feature maps
one_like_mask = K.ones_like(mask, dtype='int32')
batch_shape = K.concatenate([[input_shape[0]], [1 ], [1], [1]],axis=0)
batch_range = K.reshape(tf.range(output_shape[0], dtype='int32'),shape=batch_shape)
b = one_like_mask * batch_range
y = mask // (output_shape[2] * output_shape[3])
x = (mask // output_shape[3]) % output_shape[2]
feature_range = tf.range(output_shape[3], dtype='int32')
f = one_like_mask * feature_range
# transpose indices & reshape update values to one dimension
updates_size = tf.size(updates)
indices = K.transpose(K.reshape(
K.stack([b, y, x, f]),
[4, updates_size]))
values = K.reshape(updates, [updates_size])
ret = tf.scatter_nd(indices, values, output_shape)
return ret
def compute_output_shape(self,input_shape):
shape = input_shape[1]
return (shape[0],shape[1]*2,shape[2]*2,shape[3])
Si Segnet est défini en utilisant la couche définie par ceux-ci, ce sera comme suit.
def build_Segnet():
ffc = 32
inputs = layers.Input(shape=(64,64,3))
for i in range(2):
x = layers.Conv2D(ffc,kernel_size=3,padding="same")(inputs)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x,x1 = MaxPoolingWithArgmax2D()(x)
for i in range(2):
x = layers.Conv2D(ffc*2,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x,x2 = MaxPoolingWithArgmax2D()(x)
for i in range(3):
x = layers.Conv2D(ffc*4,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x,x3 = MaxPoolingWithArgmax2D()(x)
for i in range(3):
x = layers.Conv2D(ffc*8,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x,x4 = MaxPoolingWithArgmax2D()(x)
for i in range(3):
x = layers.Conv2D(ffc*8,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.Dropout(rate = 0.5)(x)
x = MaxUnpooling2D()([x,x4])
for i in range(3):
x = layers.Conv2D(ffc*4,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = MaxUnpooling2D()([x,x3])
for i in range(3):
x = layers.Conv2D(ffc*2,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = MaxUnpooling2D()([x,x2])
for i in range(2):
x = layers.Conv2D(ffc,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = MaxUnpooling2D()([x,x1])
for i in range(2):
x = layers.Conv2D(ffc,kernel_size=3,padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.ReLU()(x)
x = layers.Conv2D(22,kernel_size=3,padding="same",activation="softmax")(x)
return models.Model(inputs,x)
Cette fois, la fonction de perte a utilisé l'entropie croisée de chaque pixel. Adam (lr = 0,001, beta_1 = 0,9, beta_2 = 0,999) a été utilisé pour l'optimisation.
Nous avons confirmé combien le résultat change en fonction de la présence ou de l'absence de mise en commun dans les dés. La perte de la formation et la moyenne du taux de réponse correct à chaque pixel ont été représentées graphiquement. Premièrement, le résultat du modèle sans indice de pooling
Les données de vérification avaient un taux de réponse correcte d'environ 78%. Ensuite, je publierai les résultats de SegNet.
Il était stable avec un taux de réponse correcte d'environ 82%, et nous avons pu voir le comportement selon l'article.
Entrée de gauche, pas d'indice de regroupement, toutes les données de test avec SegNet, GT
Il a été constaté que la détention de l'indice de mise en commun devrait améliorer considérablement la précision.
Recommended Posts