3
votes

Utilisation de Keras Sparse Catégorical Crossentropy pour la classification multi-classes par pixel

Je vais commencer par révéler que je suis un novice en apprentissage automatique et Keras et que je ne connais pas grand-chose au-delà des classificateurs binaires CNN généraux. J'essaie d'effectuer une classification multi-classes par pixel en utilisant une architecture U-Net (backend TF) sur de nombreuses images 256x256. En d'autres termes, j'entre une image 256x256, et je veux qu'elle produise un "masque" 256x256 (ou image d'étiquette) où les valeurs sont des entiers de 0-30 (chaque entier représente une classe unique). Je m'entraîne sur 2 GPU NVIDIA 1080Ti.

Lorsque j'essaye d'effectuer un encodage à chaud, j'obtiens une erreur OOM, c'est pourquoi j'utilise une entropie croisée catégorielle clairsemée comme fonction de perte au lieu de l'entropie croisée catégorielle régulière . Cependant, lors de la formation de mon U-Net, ma valeur de perte est "nan" du début à la fin (elle s'initialise en tant que nan et ne change jamais). Lorsque je normalise mes "masques" en divisant toutes les valeurs par 30 (elles vont donc de 0 à 1), j'obtiens une précision d'environ 0,97, ce qui, je suppose, est dû au fait que la plupart des étiquettes de mon image sont à 0 (et qu'elles ne font que produire un tas de 0).

Voici le U-Net que j'utilise:

model = unet()
model.fit(x=x_train, y=y_train, batch_size=1, epochs=1, verbose=1, validation_split=0.2, shuffle=True)

Notez que j'avais besoin d'aplatir la sortie juste pour être éparse entropie croisée catégorielle pour fonctionner correctement (il n'aimait pas ma matrice 2D pour une raison quelconque).

Et voici un exemple de course d'entraînement (juste 1 époque car c'est la même chose quel que soit le nombre que je lance)

def unet(pretrained_weights = None,input_size = (256,256,1)):
inputs = keras.engine.input_layer.Input(input_size)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(inputs)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv3)
pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv4)
#drop4 = Dropout(0.5)(conv4)
drop4 = SpatialDropout2D(0.5)(conv4)
pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)

conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool4)
conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv5)
#drop5 = Dropout(0.5)(conv5)
drop5 = SpatialDropout2D(0.5)(conv5)

up6 = Conv2D(512, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(drop5))
merge6 = concatenate([drop4,up6], axis = 3)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge6)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv6)

up7 = Conv2D(256, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv6))
merge7 = concatenate([conv3,up7], axis = 3)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge7)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv7)

up8 = Conv2D(128, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv7))
merge8 = concatenate([conv2,up8], axis = 3)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge8)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv8)

up9 = Conv2D(64, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv8))
merge9 = concatenate([conv1,up9], axis = 3)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge9)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv9 = Conv2D(32, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv10 = Conv2D(1, 1, activation = 'softmax')(conv9)
#conv10 = Flatten()(conv10)
#conv10 = Dense(65536, activation = 'softmax')(conv10)
flat10 = Reshape((65536,1))(conv10)
#conv10 = Conv1D(1, 1, activation='linear')(conv10)

model = Model(inputs = inputs, outputs = flat10)

opt = Adam(lr=1e-6,clipvalue=0.01)
model.compile(optimizer = opt, loss = 'sparse_categorical_crossentropy', metrics = ['sparse_categorical_accuracy'])
#model.compile(optimizer = Adam(lr = 1e-6), loss = 'sparse_categorical_crossentropy', metrics = ['accuracy'])
#model.compile(optimizer = Adam(lr = 1e-4),

#model.summary()

if(pretrained_weights):

    model.load_weights(pretrained_weights)

return model

Former sur 2308 échantillons, valider sur 577 échantillons Époque 1/1 2308/2308 [===============================] - 191s 83ms / pas - perte: nan - sparse_categorical_accuracy: 0.9672 - val_loss : nan - précision_catégorique_val_sparse: 0.9667 Sortie [18]:

Faites-moi savoir si plus d'informations sont nécessaires pour diagnostiquer le problème. Merci d'avance!


0 commentaires

4 Réponses :


2
votes

Le problème est que pour la classification multiclasse, vous devez générer un vecteur avec une dimension par catégorie, qui représente la confiance dans cette catégorie. Si vous voulez identifier 30 classes différentes, alors votre couche finale doit être un tenseur 3D, (256, 256, 30).

conv10 = Conv2D(30, 1, activation = 'softmax')(conv9)
flat10 = Reshape((256*256*30,1))(conv10)

opt = Adam(lr=1e-6,clipvalue=0.01)
model.compile(optimizer = opt, loss = 'sparse_categorical_crossentropy', metrics = 
['sparse_categorical_accuracy'])

Je suppose que votre entrée est un (256, 256, 1) tenseur flottant avec des valeurs comprises entre 0 et 1, et votre cible est un tenseur Int (256 * 256).

Est-ce que ça aide?


3 commentaires

J'avais l'impression que ce n'est le cas que pour l'entropie croisée catégorielle régulière où vous avez besoin d'un encodage à chaud (et donc d'une sortie 256x256x30). D'après les informations limitées disponibles sur l'entropie croisée catégorielle clairsemée, il semble que vous n'ayez pas besoin d'un encodage à chaud, ce qui résoudrait mon erreur de mémoire. N'est-ce pas vraiment le cas?


La crossentropie catégorielle clairsemée prend une cible clairsemée, mais elle ne nécessite pas une prédiction clairsemée, mais prend un tenseur dense pour la prédiction. Essayez-le.


Si vous avez besoin de beaucoup plus de mémoire, vous pouvez redimensionner vos images à 224x224 (ce qui est une taille plus courante pour les CNN que 256x256), ou vous pouvez supprimer les couches les plus centrales du U-net, qui prennent le plus de taille.



1
votes
conv10 = Conv2D(nclasses, kernel_size=(1, 1))(up9)
out = BatchNormalization()(conv10)
out = Reshape((img_height*img_width, nclasses), input_shape=(img_height, img_width, nclasses))(out)
out = Activation('softmax')(out)


model = Model(inputs=[inputs], outputs=[out])
model.compile(optimizer = Adam(lr = 1e-4), loss = 'sparse_categorical_crossentropy', metrics = ['sparse_categorical_accuracy'])
x_train :(batch_size, 224, 224, 3) float32 (Input images)
y_train: (batch_size, 50176, 1) uint8 (Target labels)The above code seems to work for multi-class segmentation (nclasses), where target labels are not one hot encoded. One hot encoding creates memory issues if your data size and/or model is very large.The last layer has shape (None, 50176, 16) (since nclasses=16, None corr to batch). The elements in labels have value 0 - (nclasses-1).Using argmax on class index(-1) and reshaping the output outside seems to be the trick, in case you want a corr. image output ... NB: Sparse Categorical Entropy seems to have issues in keras 2.2.2 and above !!!

0 commentaires

0
votes

MOO:

Créez une fonction personnalisée pour dériver un encodage one-hot au lieu d'utiliser une fonction prédéfinie comme "to_categorical".

Cela prend 1/4 de la quantité de mémoire (dans mon cas).

entrez la description de l'image ici


0 commentaires

0
votes

Il semble maintenant que vous pouvez simplement faire une activation softmax sur le dernier calque Conv2D puis spécifier la perte categorical_crossentropy et vous entraîner sur l'image sans des astuces de remodelage. J'ai testé avec un jeu de données factice et cela fonctionne bien. Essayez-le ~!

inp = keras.Input(...)
# define your model here
out = keras.layers.Conv2D(classes, (1, 1), activation='softmax') (...)
model = keras.Model(inputs=[inp], outputs=[out], name='unet')
model.compile(loss='categorical_crossentropy',
                      optimizer='adam',
                      metrics=['accuracy'])
model.fit(tensor4d, tensor4d)

Vous pouvez également compiler en utilisant sparse_categorical_crossentropy puis vous entraîner avec une sortie de forme (échantillons, hauteur, largeur) où chaque pixel de la sortie correspond à une étiquette de classe: model.fit (tensor4d, tensor3d)

PS. J'utilise keras de tensorflow.keras (tensorflow 2)


0 commentaires