11.6 C
New York

Weblog Posit AI : Segmentation d’photographs avec U-Web


Bien sûr, c’est bien quand j’ai une picture d’un objet, et un réseau de neurones peut me dire de quel kind d’objet il s’agit. De manière plus réaliste, il pourrait y avoir plusieurs objets saillants dans cette picture, et cela me dit ce qu’ils sont et où ils se trouvent. Cette dernière tâche (appelée détection d’objet) semble particulièrement prototypique des purposes contemporaines de l’IA qui sont à la fois intellectuellement fascinantes et éthiquement discutables. C’est différent avec le sujet de ce put up : Réussi segmentation d’photographs a beaucoup d’purposes indéniablement utiles. Par exemple, c’est une situation sine qua non en médecine, en neurosciences, en biologie et dans d’autres sciences de la vie.

Alors, qu’est-ce que la segmentation d’picture, techniquement, et remark pouvons-nous former un réseau de neurones pour le faire ?

La segmentation d’photographs en bref

Supposons que nous ayons une picture avec un groupe de chats. Dans classification, la query est « qu’est-ce que c’est? » et la réponse que nous voulons entendre est : « chat ». Dans détection d’objetnous demandons à nouveau « qu’est-ce que c’est », mais maintenant que « quoi » est implicitement au pluriel, et nous attendons une réponse comme « il y a un chat, un chat et un chat, et ils sont ici, ici et ici » (imaginez le réseau pointant, au moyen du dessin boîtes englobantes, c’est-à-dire des rectangles autour des objets détectés). Dans segmentationnous en voulons plus : nous voulons que toute l’picture soit recouverte de « boîtes » – qui ne sont plus des boîtes, mais des unions de « boîtes » de la taille d’un pixel – ou autrement : Nous voulons que le réseau étiquette chaque pixel de l’picture.

Voici un exemple tiré du doc dont nous allons parler dans un instantaneous. Sur la gauche se trouve l’picture d’entrée (cellules HeLa), ensuite la vérité terrain, et la troisième est le masque de segmentation appris.


Exemple de segmentation de Ronneberger et al.  2015.

Determine 1 : Exemple de segmentation de Ronneberger et al. 2015.

Techniquement, une distinction est faite entre segmentation de classe et segmentation des cases. Dans la segmentation de classe, en se référant à l’exemple du « groupe de chats », il existe deux étiquettes possibles : chaque pixel est soit « chat », soit « pas de chat ». La segmentation des cases est plus difficile : Ici, chaque chat reçoit sa propre étiquette. (Soit dit en passant, pourquoi cela devrait-il être plus difficile ? En supposant une cognition de kind humain, ce ne serait pas le cas – si j’ai le idea d’un chat, au lieu de simplement « chatouilleux », je « vois » qu’il y a deux chats, pas 1. Mais selon ce sur quoi repose le plus un réseau de neurones spécifique – texture, couleur, events isolées – ces tâches peuvent différer considérablement en difficulté.)

L’structure réseau utilisée dans cet article est adéquate pour segmentation de classe tâches et devrait s’appliquer à un grand nombre d’purposes pratiques, scientifiques et non scientifiques. En parlant d’structure réseau, à quoi devrait-elle ressembler ?

Présentation de U-Web

Compte tenu de leur succès dans la classification d’photographs, ne pouvons-nous pas simplement utiliser une structure classique comme Création V(n), ResNet, ResSuivant … , peu importe? Le problème est que notre tâche à accomplir – étiqueter chaque pixel – ne correspond pas si bien à l’idée classique d’un CNN. Avec les convnets, l’idée est d’appliquer des couches successives de convolution et de pooling pour construire des function maps de granularité décroissante, pour finalement arriver à un niveau abstrait où l’on se contente de dire : « ouais, un chat ». La contrepartie étant, on perd des informations détaillées : Pour le classement remaining, peu importe que les cinq pixels de la zone en haut à gauche soient noirs ou blancs.

En pratique, les architectures classiques utilisent le (max) pooling ou les convolutions avec stride > 1 pour réaliser ces abstractions successives – entraînant nécessairement une diminution de la résolution spatiale. Alors, remark pouvons-nous utiliser un convnet tout en préservant les informations détaillées ? Dans leur article de 2015 U-Web : Réseaux convolutifs pour la segmentation d’photographs biomédicales (Ronneberger, Fischer et Brox 2015), Olaf Ronneberger et al. est venu avec ce qui, quatre ans plus tard, en 2019, est toujours l’approche la plus populaire. (Ce qui veut dire quelque selected, quatre ans, c’est lengthy, dans l’apprentissage en profondeur.)

L’idée est incroyablement easy. Alors que les étapes successives d’encodage (convolution / max pooling), comme d’habitude, réduisent la résolution, le décodage ultérieur – nous devons arriver à une sortie de taille identique à l’entrée, automobile nous voulons étiqueter chaque pixel ! – ne suréchantillonne pas simplement à partir de la couche la plus compressée. Au lieu de cela, lors du suréchantillonnage, à chaque étape, nous alimentons les informations de la couche correspondante, en résolution, dans la chaîne de réduction des effectifs.

Pour U-Web, une picture en dit vraiment plus que beaucoup de mots :


Architecture U-Net de Ronneberger et al.  2015.

Determine 2 : Structure U-Web de Ronneberger et al. 2015.

A chaque étape de suréchantillonnage, nous enchaîner la sortie de la couche précédente avec celle de son homologue dans l’étage de compression. La sortie finale est un masque de taille l’picture originale, obtenue par convolution 1×1 ; aucune couche dense finale n’est requise, à la place la couche de sortie est juste une couche convolutive avec un seul filtre.

Entraînons maintenant un U-Web. Nous allons utiliser le unet emballer qui vous permet de créer un modèle performant en une seule ligne :

remotes::install_github("r-tensorflow/unet")
library(unet)

# takes extra parameters, together with variety of downsizing blocks, 
# variety of filters to begin with, and variety of courses to establish
# see ?unet for more information
mannequin <- unet(input_shape = c(128, 128, 3))

Nous avons donc un modèle, et il semble que nous voudrons lui fournir des photographs RVB 128×128. Maintenant, remark obtient-on ces photographs ?

Les données

Pour illustrer remark les purposes se présentent même en dehors du domaine de la recherche médicale, nous utiliserons comme exemple le Kaggle Défi de masquage d’photographs Carvana. La tâche consiste à créer un masque de segmentation séparant les voitures de l’arrière-plan. Pour notre objectif actuel, nous avons seulement besoin prepare.zip et train_mask.zip du archive fournie en téléchargement. Dans ce qui go well with, nous supposons que ceux-ci ont été extraits dans un sous-répertoire appelé data-raw.

Voyons d’abord quelques photographs et leurs masques de segmentation associés.

Les images sont des JPEG à espace RVB, tandis que les masques sont des GIF en noir et blanc.

Nous divisons les données en un ensemble d’entraînement et un ensemble de validation. Nous utiliserons ce dernier pour surveiller les performances de généralisation pendant la formation.

knowledge <- tibble(
  img = checklist.information(right here::right here("data-raw/prepare"), full.names = TRUE),
  masks = checklist.information(right here::right here("data-raw/train_masks"), full.names = TRUE)
)

knowledge <- initial_split(knowledge, prop = 0.8)

Pour transmettre les données au réseau, nous utiliserons tfdatasets. Tout le prétraitement se terminera dans un pipeline easy, mais nous allons d’abord passer en revue les actions requises étape par étape.

Pipeline de prétraitement

La première étape consiste à lire dans les photographs, en utilisant les fonctions appropriées dans tf$picture.

training_dataset <- coaching(knowledge) %>%  
  tensor_slices_dataset() %>% 
  dataset_map(~.x %>% list_modify(
    # decode_jpeg yields a 3d tensor of form (1280, 1918, 3)
    img = tf$picture$decode_jpeg(tf$io$read_file(.x$img)),
    # decode_gif yields a 4d tensor of form (1, 1280, 1918, 3),
    # so we take away the unneeded batch dimension and all however one 
    # of the three (similar) channels
    masks = tf$picture$decode_gif(tf$io$read_file(.x$masks))(1,,,)(,,1,drop=FALSE)
  ))

Lors de la development d’un pipeline de prétraitement, il est très utile de vérifier les résultats intermédiaires. C’est facile à faire en utilisant reticulate::as_iterator sur le jeu de données :

$img
tf.Tensor(
(((243 244 239)
  (243 244 239)
  (243 244 239)
  ...
 ...
  ...
  (175 179 178)
  (175 179 178)
  (175 179 178))), form=(1280, 1918, 3), dtype=uint8)

$masks
tf.Tensor(
(((0)
  (0)
  (0)
  ...
 ...
  ...
  (0)
  (0)
  (0))), form=(1280, 1918, 1), dtype=uint8)

Tandis que le uint8 Le kind de données rend les valeurs RVB faciles à lire pour les humains, le réseau va s’attendre à des nombres à virgule flottante. Le code suivant convertit son entrée et, en outre, met les valeurs à l’échelle de l’intervalle (0,1) :

training_dataset <- training_dataset %>% 
  dataset_map(~.x %>% list_modify(
    img = tf$picture$convert_image_dtype(.x$img, dtype = tf$float32),
    masks = tf$picture$convert_image_dtype(.x$masks, dtype = tf$float32)
  ))

Pour réduire les coûts de calcul, nous redimensionnons les photographs à la taille 128x128. Cela modifiera le rapport d’side et donc déformera les photographs, mais ce n’est pas un problème avec l’ensemble de données donné.

training_dataset <- training_dataset %>% 
  dataset_map(~.x %>% list_modify(
    img = tf$picture$resize(.x$img, dimension = form(128, 128)),
    masks = tf$picture$resize(.x$masks, dimension = form(128, 128))
  ))

Maintenant, il est bien connu que dans l’apprentissage en profondeur, l’augmentation des données est primordiale. Pour la segmentation, il y a une selected à considérer, qui est de savoir si une transformation doit également être appliquée au masque – ce serait le cas, par exemple, pour les rotations ou le retournement. Ici, les résultats seront assez bons en appliquant uniquement les transformations qui préservent les positions :

random_bsh <- perform(img) {
  img %>% 
    tf$picture$random_brightness(max_delta = 0.3) %>% 
    tf$picture$random_contrast(decrease = 0.5, higher = 0.7) %>% 
    tf$picture$random_saturation(decrease = 0.5, higher = 0.7) %>% 
    # ensure that we nonetheless are between 0 and 1
    tf$clip_by_value(0, 1) 
}

training_dataset <- training_dataset %>% 
  dataset_map(~.x %>% list_modify(
    img = random_bsh(.x$img)
  ))

Encore une fois, nous pouvons utiliser as_iterator pour voir ce que ces transformations font à nos photographs :

Voici le pipeline de prétraitement complet.

create_dataset <- perform(knowledge, prepare, batch_size = 32L) {
  
  dataset <- knowledge %>% 
    tensor_slices_dataset() %>% 
    dataset_map(~.x %>% list_modify(
      img = tf$picture$decode_jpeg(tf$io$read_file(.x$img)),
      masks = tf$picture$decode_gif(tf$io$read_file(.x$masks))(1,,,)(,,1,drop=FALSE)
    )) %>% 
    dataset_map(~.x %>% list_modify(
      img = tf$picture$convert_image_dtype(.x$img, dtype = tf$float32),
      masks = tf$picture$convert_image_dtype(.x$masks, dtype = tf$float32)
    )) %>% 
    dataset_map(~.x %>% list_modify(
      img = tf$picture$resize(.x$img, dimension = form(128, 128)),
      masks = tf$picture$resize(.x$masks, dimension = form(128, 128))
    ))
  
  # knowledge augmentation carried out on coaching set solely
  if (prepare) {
    dataset <- dataset %>% 
      dataset_map(~.x %>% list_modify(
        img = random_bsh(.x$img)
      )) 
  }
  
  # shuffling on coaching set solely
  if (prepare) {
    dataset <- dataset %>% 
      dataset_shuffle(buffer_size = batch_size*128)
  }
  
  # prepare in batches; batch dimension would possibly should be tailored relying on
  # out there reminiscence
  dataset <- dataset %>% 
    dataset_batch(batch_size)
  
  dataset %>% 
    # output must be unnamed
    dataset_map(unname) 
}

La formation et la création d’ensembles de check ne sont plus qu’une query de deux appels de fonction.

training_dataset <- create_dataset(coaching(knowledge), prepare = TRUE)
validation_dataset <- create_dataset(testing(knowledge), prepare = FALSE)

Et nous sommes prêts à former le modèle.

Former le modèle

Nous avons déjà montré remark créer le modèle, mais répétons-le ici et vérifions l’structure du modèle :

mannequin <- unet(input_shape = c(128, 128, 3))
abstract(mannequin)
Mannequin: "mannequin"
______________________________________________________________________________________________
Layer (kind)                   Output Form        Param #    Linked to                    
==============================================================================================
input_1 (InputLayer)           ((None, 128, 128, 3 0                                          
______________________________________________________________________________________________
conv2d (Conv2D)                (None, 128, 128, 64 1792       input_1(0)(0)                   
______________________________________________________________________________________________
conv2d_1 (Conv2D)              (None, 128, 128, 64 36928      conv2d(0)(0)                    
______________________________________________________________________________________________
max_pooling2d (MaxPooling2D)   (None, 64, 64, 64)  0          conv2d_1(0)(0)                  
______________________________________________________________________________________________
conv2d_2 (Conv2D)              (None, 64, 64, 128) 73856      max_pooling2d(0)(0)             
______________________________________________________________________________________________
conv2d_3 (Conv2D)              (None, 64, 64, 128) 147584     conv2d_2(0)(0)                  
______________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D) (None, 32, 32, 128) 0          conv2d_3(0)(0)                  
______________________________________________________________________________________________
conv2d_4 (Conv2D)              (None, 32, 32, 256) 295168     max_pooling2d_1(0)(0)           
______________________________________________________________________________________________
conv2d_5 (Conv2D)              (None, 32, 32, 256) 590080     conv2d_4(0)(0)                  
______________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D) (None, 16, 16, 256) 0          conv2d_5(0)(0)                  
______________________________________________________________________________________________
conv2d_6 (Conv2D)              (None, 16, 16, 512) 1180160    max_pooling2d_2(0)(0)           
______________________________________________________________________________________________
conv2d_7 (Conv2D)              (None, 16, 16, 512) 2359808    conv2d_6(0)(0)                  
______________________________________________________________________________________________
max_pooling2d_3 (MaxPooling2D) (None, 8, 8, 512)   0          conv2d_7(0)(0)                  
______________________________________________________________________________________________
dropout (Dropout)              (None, 8, 8, 512)   0          max_pooling2d_3(0)(0)           
______________________________________________________________________________________________
conv2d_8 (Conv2D)              (None, 8, 8, 1024)  4719616    dropout(0)(0)                   
______________________________________________________________________________________________
conv2d_9 (Conv2D)              (None, 8, 8, 1024)  9438208    conv2d_8(0)(0)                  
______________________________________________________________________________________________
conv2d_transpose (Conv2DTransp (None, 16, 16, 512) 2097664    conv2d_9(0)(0)                  
______________________________________________________________________________________________
concatenate (Concatenate)      (None, 16, 16, 1024 0          conv2d_7(0)(0)                  
                                                              conv2d_transpose(0)(0)          
______________________________________________________________________________________________
conv2d_10 (Conv2D)             (None, 16, 16, 512) 4719104    concatenate(0)(0)               
______________________________________________________________________________________________
conv2d_11 (Conv2D)             (None, 16, 16, 512) 2359808    conv2d_10(0)(0)                 
______________________________________________________________________________________________
conv2d_transpose_1 (Conv2DTran (None, 32, 32, 256) 524544     conv2d_11(0)(0)                 
______________________________________________________________________________________________
concatenate_1 (Concatenate)    (None, 32, 32, 512) 0          conv2d_5(0)(0)                  
                                                              conv2d_transpose_1(0)(0)        
______________________________________________________________________________________________
conv2d_12 (Conv2D)             (None, 32, 32, 256) 1179904    concatenate_1(0)(0)             
______________________________________________________________________________________________
conv2d_13 (Conv2D)             (None, 32, 32, 256) 590080     conv2d_12(0)(0)                 
______________________________________________________________________________________________
conv2d_transpose_2 (Conv2DTran (None, 64, 64, 128) 131200     conv2d_13(0)(0)                 
______________________________________________________________________________________________
concatenate_2 (Concatenate)    (None, 64, 64, 256) 0          conv2d_3(0)(0)                  
                                                              conv2d_transpose_2(0)(0)        
______________________________________________________________________________________________
conv2d_14 (Conv2D)             (None, 64, 64, 128) 295040     concatenate_2(0)(0)             
______________________________________________________________________________________________
conv2d_15 (Conv2D)             (None, 64, 64, 128) 147584     conv2d_14(0)(0)                 
______________________________________________________________________________________________
conv2d_transpose_3 (Conv2DTran (None, 128, 128, 64 32832      conv2d_15(0)(0)                 
______________________________________________________________________________________________
concatenate_3 (Concatenate)    (None, 128, 128, 12 0          conv2d_1(0)(0)                  
                                                              conv2d_transpose_3(0)(0)        
______________________________________________________________________________________________
conv2d_16 (Conv2D)             (None, 128, 128, 64 73792      concatenate_3(0)(0)             
______________________________________________________________________________________________
conv2d_17 (Conv2D)             (None, 128, 128, 64 36928      conv2d_16(0)(0)                 
______________________________________________________________________________________________
conv2d_18 (Conv2D)             (None, 128, 128, 1) 65         conv2d_17(0)(0)                 
==============================================================================================
Whole params: 31,031,745
Trainable params: 31,031,745
Non-trainable params: 0
______________________________________________________________________________________________

La colonne « forme de sortie » montre numériquement la forme en U attendue : la largeur et la hauteur diminuent d’abord, jusqu’à ce que nous atteignions une résolution minimale de 8x8; ils remontent ensuite, jusqu’à ce que nous ayons atteint la résolution d’origine. Dans le même temps, le nombre de filtres augmente d’abord, puis redescend, jusqu’à ce que dans la couche de sortie, nous ayons un seul filtre. Vous pouvez également voir le concatenate couches ajoutant des informations qui viennent « du bas » aux informations qui viennent « latéralement ».

Quelle devrait être la fonction de perte ici? Nous étiquetons chaque pixel, donc chaque pixel contribue à la perte. Nous avons un problème binaire – chaque pixel peut être « voiture » ou « arrière-plan » – nous voulons donc que chaque sortie soit proche de 0 ou 1. Cela rend binar_crossentropy la fonction de perte adéquate.

Pendant la formation, nous gardons une hint de l’exactitude de la classification ainsi que de la coefficient de dés, la métrique d’évaluation utilisée dans le concours. Le coefficient de dé est un moyen de mesurer la proportion de classements corrects :

cube <- custom_metric("cube", perform(y_true, y_pred, clean = 1.0) {
  y_true_f <- k_flatten(y_true)
  y_pred_f <- k_flatten(y_pred)
  intersection <- k_sum(y_true_f * y_pred_f)
  (2 * intersection + clean) / (k_sum(y_true_f) + k_sum(y_pred_f) + clean)
})

mannequin %>% compile(
  optimizer = optimizer_rmsprop(lr = 1e-5),
  loss = "binary_crossentropy",
  metrics = checklist(cube, metric_binary_accuracy)
)

L’ajustement du modèle prend un sure temps – combien, bien sûr, dépendra de votre matériel. Mais l’attente porte ses fruits : après cinq époques, nous avons constaté un coefficient de dé d’environ 0,87 sur l’ensemble de validation et une précision d’environ 0,95.

Prédictions

Bien sûr, ce qui nous intéresse en fin de compte, ce sont les prédictions. Voyons quelques masques générés pour les éléments du jeu de validation :

batch <- validation_dataset %>% as_iterator() %>% iter_next()
predictions <- predict(mannequin, batch)

photographs <- tibble(
  picture = batch((1)) %>% array_branch(1),
  predicted_mask = predictions(,,,1) %>% array_branch(1),
  masks = batch((2))(,,,1)  %>% array_branch(1)
) %>% 
  sample_n(2) %>% 
  map_depth(2, perform(x) {
    as.raster(x) %>% magick::image_read()
  }) %>% 
  map(~do.name(c, .x))


out <- magick::image_append(c(
  magick::image_append(photographs$masks, stack = TRUE),
  magick::image_append(photographs$picture, stack = TRUE), 
  magick::image_append(photographs$predicted_mask, stack = TRUE)
  )
)

plot(out)

De gauche à droite : vérité terrain, image d'entrée et masque prédit de U-Net.

Determine 3 : De gauche à droite : vérité terrain, picture d’entrée et masque prédit de U-Web.

Conclusion

S’il y avait un concours pour la somme la plus élevée d’utilité et de transparence architecturale, U-Web serait certainement un concurrent. Sans beaucoup de réglage, il est attainable d’obtenir des résultats décents. Si vous êtes en mesure d’utiliser ce modèle dans votre travail, ou si vous rencontrez des problèmes pour l’utiliser, faites-le nous savoir ! Merci d’avoir lu!

Ronneberger, Olaf, Philipp Fischer et Thomas Brox. 2015. « U-Web : Réseaux convolutifs pour la segmentation d’photographs biomédicales. » CoRR abs/1505.04597. http://arxiv.org/abs/1505.04597.

Related Articles

LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici

Latest Articles