Une petite étude des propriétés d'un simple U-net, un réseau convolutionnel classique pour la segmentation

L'article est écrit sur l'analyse et l'étude des matériaux du concours pour la recherche de navires en mer.

image

Essayons de comprendre comment et ce que le réseau recherche et ce qu'il trouve. Cet article est simplement le résultat de la curiosité et d'un intérêt oiseux, rien ne se trouve dans la pratique et pour les tâches pratiques, il n'y a rien pour copier-coller. Mais le résultat n'est pas entièrement attendu. Internet regorge de descriptions du fonctionnement des réseaux dans lesquelles les auteurs décrivent magnifiquement et avec des images comment les réseaux déterminent les primitives - angles, cercles, moustaches, queues, etc., puis ils sont recherchés pour la segmentation / classification. De nombreuses compétitions sont gagnées en utilisant des poids provenant d'autres grands et larges réseaux. Il est intéressant de comprendre et de voir comment et quelles primitives un réseau construit.

Nous effectuerons une petite étude et examinerons les options - le raisonnement et le code de l'auteur sont présentés, vous pouvez tout vérifier / compléter / changer vous-même.

Le concours de recherche marine de kaggle a récemment pris fin. Airbus a proposé d'analyser les images satellites de la mer avec et sans navires. Au total, 192555 images 768x768x3 - c'est 340 720 680 960 octets si uint8 et quatre fois plus si float32 (d'ailleurs float32 est plus rapide que float64, moins d'accès mémoire) et sur 15606 images vous devez trouver des navires. Comme d'habitude, toutes les places importantes ont été prises par des personnes impliquées dans les SAO (ods.ai), ce qui est naturel et attendu, et j'espère que nous pourrons bientôt étudier le fil de la pensée et le code des gagnants et des lauréats.

Nous considérerons un problème similaire, mais le simplifierons considérablement - prenez la mer np.random.sample () * 0,5, nous n'avons pas besoin de vagues, de vent, de plages et d'autres motifs et visages cachés. Rendons l'image de la mer vraiment aléatoire dans la plage RVB de 0,0 à 0,5. Nous colorerons les vaisseaux de la même couleur et pour les distinguer de la mer, nous les mettrons dans la plage de 0,5 à 1,0, et ils auront tous la même forme - des ellipses de tailles et d'orientations différentes.

image

Prenez une version très courante du réseau (vous pouvez prendre votre réseau préféré) et nous ferons toutes les expériences avec.

Ensuite, nous allons modifier les paramètres de l'image, créer des interférences et construire des hypothèses - nous mettons donc en évidence les principales caractéristiques par lesquelles le réseau trouve des ellipses. Peut-être que le lecteur tirera ses conclusions et réfutera l'auteur.

Nous chargeons des bibliothèques, nous déterminons les tailles d'un tableau d'images
import numpy as np import pandas as pd import matplotlib.pyplot as plt %matplotlib inline import math from tqdm import tqdm_notebook, tqdm from skimage.draw import ellipse, polygon from keras import Model from keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau from keras.models import load_model from keras.optimizers import Adam from keras.layers import Input, Conv2D, Conv2DTranspose, MaxPooling2D, concatenate, Dropout from keras.losses import binary_crossentropy import tensorflow as tf import keras as keras from keras import backend as K from tqdm import tqdm_notebook w_size = 256 train_num = 8192 train_x = np.zeros((train_num, w_size, w_size,3), dtype='float32') train_y = np.zeros((train_num, w_size, w_size,1), dtype='float32') img_l = np.random.sample((w_size, w_size, 3))*0.5 img_h = np.random.sample((w_size, w_size, 3))*0.5 + 0.5 radius_min = 10 radius_max = 30 


déterminer les fonctions de perte et de précision
 def dice_coef(y_true, y_pred): y_true_f = K.flatten(y_true) y_pred = K.cast(y_pred, 'float32') y_pred_f = K.cast(K.greater(K.flatten(y_pred), 0.5), 'float32') intersection = y_true_f * y_pred_f score = 2. * K.sum(intersection) / (K.sum(y_true_f) + K.sum(y_pred_f)) return score def dice_loss(y_true, y_pred): smooth = 1. y_true_f = K.flatten(y_true) y_pred_f = K.flatten(y_pred) intersection = y_true_f * y_pred_f score = (2. * K.sum(intersection) + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth) return 1. - score def bce_dice_loss(y_true, y_pred): return binary_crossentropy(y_true, y_pred) + dice_loss(y_true, y_pred) def get_iou_vector(A, B): # Numpy version batch_size = A.shape[0] metric = 0.0 for batch in range(batch_size): t, p = A[batch], B[batch] true = np.sum(t) pred = np.sum(p) # deal with empty mask first if true == 0: metric += (pred == 0) continue # non empty mask case. Union is never empty # hence it is safe to divide by its number of pixels intersection = np.sum(t * p) union = true + pred - intersection iou = intersection / union # iou metrric is a stepwise approximation of the real iou over 0.5 iou = np.floor(max(0, (iou - 0.45)*20)) / 10 metric += iou # teake the average over all images in batch metric /= batch_size return metric def my_iou_metric(label, pred): # Tensorflow version return tf.py_func(get_iou_vector, [label, pred > 0.5], tf.float64) from keras.utils.generic_utils import get_custom_objects get_custom_objects().update({'bce_dice_loss': bce_dice_loss }) get_custom_objects().update({'dice_loss': dice_loss }) get_custom_objects().update({'dice_coef': dice_coef }) get_custom_objects().update({'my_iou_metric': my_iou_metric }) 


Nous utilisons la métrique classique dans la segmentation d'image, il y a beaucoup d'articles, du code avec des commentaires et du texte sur la métrique sélectionnée, sur le même kaggle il y a beaucoup d'options avec des commentaires et des explications. Nous prédirons le masque du pixel - c'est la «mer» ou le «bateau» et évaluerons la vérité ou la fausseté de la prédiction. C'est-à-dire Les quatre options suivantes sont possibles - nous avons correctement prédit qu'un pixel est une «mer», correctement prédit qu'un pixel est un «navire» ou fait une erreur en prédisant une «mer» ou un «navire». Et donc, pour toutes les images et tous les pixels, nous estimons le nombre des quatre options et calculons le résultat - ce sera le résultat du réseau. Et moins les prévisions sont erronées et vraies, plus le résultat est précis et meilleur est le réseau.

Et pour la recherche, prenons le u-net bien étudié, qui est un excellent réseau pour la segmentation d'images. Le réseau est très courant dans ces compétitions et il existe de nombreuses descriptions, subtilités d'application, etc. Une variante du U-net classique a été choisie et, bien sûr, il a été possible de le mettre à niveau, d'ajouter des blocs résiduels, etc. Mais «vous ne pouvez pas embrasser l'immensité» et effectuer toutes les expériences et les tests à la fois. U-net effectue une opération très simple avec les images - il réduit la taille de l'image avec quelques transformations étape par étape, puis essaie de récupérer le masque de l'image compressée. C'est-à-dire la dimension de l'image dans notre cas est portée à 32x32 puis nous essayons de restaurer le masque en utilisant les données de toutes les compressions précédentes.

Dans l'image, le schéma U-net est de l'article original, mais nous l'avons refait un peu, mais l'essence reste la même - nous compressons l'image → développons dans un masque.

image

Juste U-net
 def build_model(input_layer, start_neurons): conv1 = Conv2D(start_neurons*1,(3,3),activation="relu", padding="same")(input_layer) conv1 = Conv2D(start_neurons*1,(3,3),activation="relu", padding="same")(conv1) pool1 = MaxPooling2D((2, 2))(conv1) pool1 = Dropout(0.25)(pool1) conv2 = Conv2D(start_neurons*2,(3,3),activation="relu", padding="same")(pool1) conv2 = Conv2D(start_neurons*2,(3,3),activation="relu", padding="same")(conv2) pool2 = MaxPooling2D((2, 2))(conv2) pool2 = Dropout(0.5)(pool2) conv3 = Conv2D(start_neurons*4,(3,3),activation="relu", padding="same")(pool2) conv3 = Conv2D(start_neurons*4,(3,3),activation="relu", padding="same")(conv3) pool3 = MaxPooling2D((2, 2))(conv3) pool3 = Dropout(0.5)(pool3) conv4 = Conv2D(start_neurons*8,(3,3),activation="relu", padding="same")(pool3) conv4 = Conv2D(start_neurons*8,(3,3),activation="relu", padding="same")(conv4) pool4 = MaxPooling2D((2, 2))(conv4) pool4 = Dropout(0.5)(pool4) # Middle convm = Conv2D(start_neurons*16,(3,3),activation="relu", padding="same")(pool4) convm = Conv2D(start_neurons*16,(3,3),activation="relu", padding="same")(convm) deconv4 = Conv2DTranspose(start_neurons * 8, (3, 3), strides=(2, 2), padding="same")(convm) uconv4 = concatenate([deconv4, conv4]) uconv4 = Dropout(0.5)(uconv4) uconv4 = Conv2D(start_neurons*8,(3,3),activation="relu", padding="same")(uconv4) uconv4 = Conv2D(start_neurons*8,(3,3),activation="relu", padding="same")(uconv4) deconv3 = Conv2DTranspose(start_neurons*4,(3,3),strides=(2, 2), padding="same")(uconv4) uconv3 = concatenate([deconv3, conv3]) uconv3 = Dropout(0.5)(uconv3) uconv3 = Conv2D(start_neurons*4,(3,3),activation="relu", padding="same")(uconv3) uconv3 = Conv2D(start_neurons*4,(3,3),activation="relu", padding="same")(uconv3) deconv2 = Conv2DTranspose(start_neurons*2,(3,3),strides=(2, 2), padding="same")(uconv3) uconv2 = concatenate([deconv2, conv2]) uconv2 = Dropout(0.5)(uconv2) uconv2 = Conv2D(start_neurons*2,(3,3),activation="relu", padding="same")(uconv2) uconv2 = Conv2D(start_neurons*2,(3,3),activation="relu", padding="same")(uconv2) deconv1 = Conv2DTranspose(start_neurons*1,(3,3),strides=(2, 2), padding="same")(uconv2) uconv1 = concatenate([deconv1, conv1]) uconv1 = Dropout(0.5)(uconv1) uconv1 = Conv2D(start_neurons*1,(3,3),activation="relu", padding="same")(uconv1) uconv1 = Conv2D(start_neurons*1,(3,3),activation="relu", padding="same")(uconv1) uncov1 = Dropout(0.5)(uconv1) output_layer = Conv2D(1,(1,1), padding="same", activation="sigmoid")(uconv1) return output_layer 


Première expérience. Le plus simple


La première version de notre expérience a été choisie pour que la simplicité soit très simple - la mer est plus claire, les navires sont plus sombres. Tout est très simple et évident, nous émettons l'hypothèse que le réseau trouvera des vaisseaux / ellipses sans problème et avec n'importe quelle précision. La fonction next_pair génère une paire d'image / masque, dans laquelle le lieu, la taille, l'angle de rotation sont sélectionnés au hasard. De plus, tous les changements seront apportés à cette fonction - un changement de couleur, de forme, d'interférence, etc. Mais maintenant l'option la plus simple, nous testons l'hypothèse de bateaux sombres sur fond clair.

 def next_pair(): p = np.random.sample() - 0.5 #    # r,c -    r = np.random.sample()*(w_size-2*radius_max) + radius_max c = np.random.sample()*(w_size-2*radius_max) + radius_max #      r_radius = np.random.sample()*(radius_max-radius_min) + radius_min c_radius = np.random.sample()*(radius_max-radius_min) + radius_min rot = np.random.sample()*360 #   rr, cc = ellipse( r, c, r_radius, c_radius, rotation=np.deg2rad(rot), shape=img_l.shape ) #     #   /    0.5  1.0 img = img_h.copy() #       0.0  0.5 img[rr, cc] = img_l[rr, cc] msk = np.zeros((w_size, w_size, 1), dtype='float32') msk[rr, cc] = 1. #     return img, msk 

Nous générons l'ensemble du train et voyons ce qui s'est passé. On dirait des bateaux en mer et rien de plus. Tout est clairement visible, clair et compréhensible. L'emplacement est aléatoire et il n'y a qu'une seule ellipse dans chaque image.

 for k in range(train_num): #   img train img, msk = next_pair() train_x[k] = img train_y[k] = msk fig, axes = plt.subplots(2, 10, figsize=(20, 5)) #    10   for k in range(10): axes[0,k].set_axis_off() axes[0,k].imshow(train_x[k]) axes[1,k].set_axis_off() axes[1,k].imshow(train_y[k].squeeze()) 

image

Il ne fait aucun doute que le réseau apprendra avec succès et trouvera des ellipses. Mais testons notre hypothèse selon laquelle le réseau est formé pour trouver des ellipses / navires et en même temps avec une grande précision.

 input_layer = Input((w_size, w_size, 3)) output_layer = build_model(input_layer, 16) model = Model(input_layer, output_layer) model.compile(loss=bce_dice_loss, optimizer=Adam(lr=1e-3), metrics=[my_iou_metric]) model.save_weights('./keras.weights') while True: history = model.fit(train_x, train_y, batch_size=32, epochs=1, verbose=1, validation_split=0.1 ) if history.history['my_iou_metric'][0] > 0.75: break 

Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 55s 7ms/step - loss: 0.2272 - my_iou_metric: 0.7325 - val_loss: 0.0063 - val_my_iou_metric: 1.0000
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 53s 7ms/step - loss: 0.0090 - my_iou_metric: 1.0000 - val_loss: 0.0045 - val_my_iou_metric: 1.0000


Le réseau a réussi à trouver des ellipses. Mais il n'est pas du tout prouvé qu'elle cherche des ellipses dans la compréhension de l'homme, en tant que région délimitée par l'équation d'ellipse et remplie de contenu différent de l'arrière-plan, il n'y a aucune certitude qu'il existe des poids de réseau similaires aux coefficients de l'équation d'ellipse quadratique. Et il est évident que la luminosité de l'ellipse est inférieure à la luminosité de l'arrière-plan et sans secret ni énigme - nous supposons que nous venons de vérifier le code. Corrigeons le visage évident, rendons également l'arrière-plan et la couleur de l'ellipse aléatoires.

Deuxième option


Maintenant, les mêmes ellipses sont sur la même mer, mais la couleur de la mer et, par conséquent, le bateau est choisi au hasard. Si la mer est plus sombre, le navire sera plus léger et vice versa. C'est-à-dire par la luminosité du groupe de points, il est impossible de déterminer s'ils sont en dehors de l'ellipse, c'est-à-dire la mer ou ce sont des points à l'intérieur de l'ellipse. Encore une fois, nous testons notre hypothèse selon laquelle le réseau trouvera des ellipses quelle que soit la couleur.

 def next_pair(): p = np.random.sample() - 0.5 #    / r = np.random.sample()*(w_size-2*radius_max) + radius_max c = np.random.sample()*(w_size-2*radius_max) + radius_max r_radius = np.random.sample()*(radius_max-radius_min) + radius_min c_radius = np.random.sample()*(radius_max-radius_min) + radius_min rot = np.random.sample()*360 rr, cc = ellipse( r, c, r_radius, c_radius, rotation=np.deg2rad(rot), shape=img_l.shape ) if p > 0: #     img = img_l.copy() img[rr, cc] = img_h[rr, cc] else: #     img = img_h.copy() img[rr, cc] = img_l[rr, cc] msk = np.zeros((w_size, w_size, 1), dtype='float32') msk[rr, cc] = 1. return img, msk 

Maintenant, par le pixel et ses environs, il est impossible de déterminer l'arrière-plan ou l'ellipse. Nous générons également des images et des masques et regardons les 10 premiers à l'écran.

masques de construction
 for k in range(train_num): img, msk = next_pair() train_x[k] = img train_y[k] = msk fig, axes = plt.subplots(2, 10, figsize=(20, 5)) for k in range(10): axes[0,k].set_axis_off() axes[0,k].imshow(train_x[k]) axes[1,k].set_axis_off() axes[1,k].imshow(train_y[k].squeeze()) 



image
 input_layer = Input((w_size, w_size, 3)) output_layer = build_model(input_layer, 16) model = Model(input_layer, output_layer) model.compile(loss=bce_dice_loss, optimizer=Adam(lr=1e-3), metrics=[my_iou_metric]) model.load_weights('./keras.weights', by_name=False) while True: history = model.fit(train_x, train_y, batch_size=32, epochs=1, verbose=1, validation_split=0.1 ) if history.history['my_iou_metric'][0] > 0.75: break 

Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 56s 8ms/step - loss: 0.4652 - my_iou_metric: 0.5071 - val_loss: 0.0439 - val_my_iou_metric: 0.9005
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 55s 7ms/step - loss: 0.1418 - my_iou_metric: 0.8378 - val_loss: 0.0377 - val_my_iou_metric: 0.9206


Le réseau s'adapte facilement et trouve toutes les ellipses. Mais ici, il y a une faille dans l'implémentation, et tout est évident - la plus petite des deux zones de l'image est une ellipse, un autre arrière-plan. C'est peut-être une fausse hypothèse, mais corrigez-la, ajoutez un autre polygone à l'image de la même couleur que l'ellipse.

Troisième option


Dans chaque image, nous sélectionnons au hasard la couleur de la mer parmi les deux options et ajoutons une ellipse et un rectangle, tous deux différents de la couleur de la mer. Il se révèle la même «mer», également un «bateau» peint, mais dans la même image, nous ajoutons un rectangle de la même couleur que le «bateau» et également avec une taille choisie au hasard. Maintenant, notre hypothèse est plus compliquée, dans l'image, il y a deux objets de couleur identique, mais nous émettons l'hypothèse que le réseau apprendra toujours à choisir le bon objet.

programme pour dessiner des ellipses et des rectangles
 def next_pair(): #        p = np.random.sample() - 0.5 r = np.random.sample()*(w_size-2*radius_max) + radius_max c = np.random.sample()*(w_size-2*radius_max) + radius_max r_radius = np.random.sample()*(radius_max-radius_min) + radius_min c_radius = np.random.sample()*(radius_max-radius_min) + radius_min rot = np.random.sample()*360 rr, cc = ellipse( r, c, r_radius, c_radius, rotation=np.deg2rad(rot), shape=img_l.shape ) p1 = np.rint(np.random.sample()*(w_size-2*radius_max) + radius_max) p2 = np.rint(np.random.sample()*(w_size-2*radius_max) + radius_max) p3 = np.rint(np.random.sample()*(2*radius_max - radius_min) + radius_min) p4 = np.rint(np.random.sample()*(2*radius_max - radius_min) + radius_min) #   /,    poly = np.array(( (p1, p2), (p1, p2+p4), (p1+p3, p2+p4), (p1+p3, p2), (p1, p2), )) rr_p, cc_p = polygon(poly[:, 0], poly[:, 1], img_l.shape) in_sc = list(set(rr) & set(rr_p)) #   ,    #     #        if len(in_sc) > 0: if np.mean(rr_p) > np.mean(in_sc): poly += np.max(in_sc) - np.min(in_sc) else: poly -= np.max(in_sc) - np.min(in_sc) rr_p, cc_p = polygon(poly[:, 0], poly[:, 1], img_l.shape) if p > 0: img = img_l.copy() img[rr, cc] = img_h[rr, cc] img[rr_p, cc_p] = img_h[rr_p, cc_p] else: img = img_h.copy() img[rr, cc] = img_l[rr, cc] img[rr_p, cc_p] = img_l[rr_p, cc_p] msk = np.zeros((w_size, w_size, 1), dtype='float32') msk[rr, cc] = 1. return img, msk 


Comme précédemment, nous calculons des images et des masques et examinons les 10 premières paires.

masque de construction photos ellipses et rectangles
 for k in range(train_num): img, msk = next_pair() train_x[k] = img train_y[k] = msk fig, axes = plt.subplots(2, 10, figsize=(20, 5)) for k in range(10): axes[0,k].set_axis_off() axes[0,k].imshow(train_x[k]) axes[1,k].set_axis_off() axes[1,k].imshow(train_y[k].squeeze()) 



image
 input_layer = Input((w_size, w_size, 3)) output_layer = build_model(input_layer, 16) model = Model(input_layer, output_layer) model.compile(loss=bce_dice_loss, optimizer=Adam(lr=1e-3), metrics=[my_iou_metric]) model.load_weights('./keras.weights', by_name=False) while True: history = model.fit(train_x, train_y, batch_size=32, epochs=1, verbose=1, validation_split=0.1 ) if history.history['my_iou_metric'][0] > 0.75: break 

Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 57s 8ms/step - loss: 0.7557 - my_iou_metric: 0.0937 - val_loss: 0.2510 - val_my_iou_metric: 0.4580
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 55s 7ms/step - loss: 0.0719 - my_iou_metric: 0.8507 - val_loss: 0.0183 - val_my_iou_metric: 0.9812


Il n'a pas été possible de confondre les rectangles du réseau et notre hypothèse est confirmée. À en juger par les exemples et les discussions, tout le monde au concours Airbus avait des navires uniques, et plusieurs navires étaient à proximité assez précisément. L'ellipse du rectangle - c.-à-d. le navire est de la maison sur le rivage, le réseau est distingué, bien que les polygones soient de la même couleur que les ellipses. Ce n'est pas une question de couleur, car l'ellipse et le rectangle sont également peints au hasard.

Quatrième option


Le réseau se distingue peut-être par des rectangles - corrigez-les, déformez-les. C'est-à-dire le réseau trouve facilement les deux zones fermées quelle que soit leur forme et supprime celle qui est un rectangle. C'est l'hypothèse de l'auteur - nous allons la vérifier, pour laquelle nous ajouterons non pas des rectangles, mais des polygones quadrangulaires de forme arbitraire. Et encore une fois, notre hypothèse est que le réseau distingue une ellipse d'un polygone quadrangulaire arbitraire de la même coloration.

Vous pouvez bien sûr pénétrer à l'intérieur du réseau et y regarder les couches et analyser la signification des poids et des décalages. L'auteur s'intéresse au comportement résultant du réseau, le jugement sera basé sur le résultat du travail, bien qu'il soit toujours intéressant de regarder à l'intérieur.

apporter des modifications à la génération d'images
 def next_pair(): p = np.random.sample() - 0.5 r = np.random.sample()*(w_size-2*radius_max) + radius_max c = np.random.sample()*(w_size-2*radius_max) + radius_max r_radius = np.random.sample()*(radius_max-radius_min) + radius_min c_radius = np.random.sample()*(radius_max-radius_min) + radius_min rot = np.random.sample()*360 rr, cc = ellipse( r, c, r_radius, c_radius, rotation=np.deg2rad(rot), shape=img_l.shape ) p0 = np.rint(np.random.sample()*(radius_max-radius_min) + radius_min) p1 = np.rint(np.random.sample()*(w_size-radius_max)) p2 = np.rint(np.random.sample()*(w_size-radius_max)) p3 = np.rint(np.random.sample()*2.*radius_min - radius_min) p4 = np.rint(np.random.sample()*2.*radius_min - radius_min) p5 = np.rint(np.random.sample()*2.*radius_min - radius_min) p6 = np.rint(np.random.sample()*2.*radius_min - radius_min) p7 = np.rint(np.random.sample()*2.*radius_min - radius_min) p8 = np.rint(np.random.sample()*2.*radius_min - radius_min) poly = np.array(( (p1, p2), (p1+p3, p2+p4+p0), (p1+p5+p0, p2+p6+p0), (p1+p7+p0, p2+p8), (p1, p2), )) rr_p, cc_p = polygon(poly[:, 0], poly[:, 1], img_l.shape) in_sc = list(set(rr) & set(rr_p)) if len(in_sc) > 0: if np.mean(rr_p) > np.mean(in_sc): poly += np.max(in_sc) - np.min(in_sc) else: poly -= np.max(in_sc) - np.min(in_sc) rr_p, cc_p = polygon(poly[:, 0], poly[:, 1], img_l.shape) if p > 0: img = img_l.copy() img[rr, cc] = img_h[rr, cc] img[rr_p, cc_p] = img_h[rr_p, cc_p] else: img = img_h.copy() img[rr, cc] = img_l[rr, cc] img[rr_p, cc_p] = img_l[rr_p, cc_p] msk = np.zeros((w_size, w_size, 1), dtype='float32') msk[rr, cc] = 1. return img, msk 


Nous calculons des images et des masques et examinons les 10 premières paires.

nous construisons des masques d'images ellipses et polygones
 for k in range(train_num): img, msk = next_pair() train_x[k] = img train_y[k] = msk fig, axes = plt.subplots(2, 10, figsize=(20, 5)) for k in range(10): axes[0,k].set_axis_off() axes[0,k].imshow(train_x[k]) axes[1,k].set_axis_off() axes[1,k].imshow(train_y[k].squeeze()) 



image
Nous lançons notre réseau. Permettez-moi de vous rappeler que c'est la même chose pour toutes les options.

 input_layer = Input((w_size, w_size, 3)) output_layer = build_model(input_layer, 16) model = Model(input_layer, output_layer) model.compile(loss=bce_dice_loss, optimizer=Adam(lr=1e-3), metrics=[my_iou_metric]) model.load_weights('./keras.weights', by_name=False) while True: history = model.fit(train_x, train_y, batch_size=32, epochs=1, verbose=1, validation_split=0.1 ) if history.history['my_iou_metric'][0] > 0.75: break 

Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 56s 8ms/step - loss: 0.6815 - my_iou_metric: 0.2168 - val_loss: 0.2078 - val_my_iou_metric: 0.4983
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 53s 7ms/step - loss: 0.1470 - my_iou_metric: 0.6396 - val_loss: 0.1046 - val_my_iou_metric: 0.7784
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 53s 7ms/step - loss: 0.0642 - my_iou_metric: 0.8586 - val_loss: 0.0403 - val_my_iou_metric: 0.9354

L'hypothèse est confirmée, les polygones et les ellipses se distinguent facilement. Un lecteur attentif notera ici - bien sûr, ils sont différents, une question absurde, toute IA normale peut distinguer une courbe du deuxième ordre de la ligne du premier. C'est-à-dire le réseau détermine facilement la présence d'une frontière sous la forme d'une courbe de second ordre. Nous ne discuterons pas, remplacer l'ovale par un heptagone et vérifier.

Cinquième expérience, la plus difficile


Il n'y a pas de courbes, seulement des faces lisses d'heptagones réguliers inclinés et tournés et des polygones quadrangulaires arbitraires. Nous introduisons dans la fonction le générateur d'image / masque change - uniquement des projections d'heptagones réguliers et de polygones quadrangulaires arbitraires de la même couleur.

révision finale de la fonction de génération d'images
 def next_pair(_n = 7): p = np.random.sample() - 0.5 c_x = np.random.sample()*(w_size-2*radius_max) + radius_max c_y = np.random.sample()*(w_size-2*radius_max) + radius_max radius = np.random.sample()*(radius_max-radius_min) + radius_min d = np.random.sample()*0.5 + 1 a_deg = np.random.sample()*360 a_rad = np.deg2rad(a_deg) poly = [] #    for k in range(_n): #     # _ _ -  poly.append(c_x+radius*math.sin(2.*k*math.pi/_n)) poly.append(c_y+radius*math.cos(2.*k*math.pi/_n)) # \  #    0.5  1.5  poly[-2] = (poly[-2]-c_x)/d +c_x poly[-1] = (poly[-1]-c_y) +c_y #     poly[-2] = ((poly[-2]-c_x)*math.cos(a_rad)\ - (poly[-1]-c_y)*math.sin(a_rad)) + c_x poly[-1] = ((poly[-2]-c_x)*math.sin(a_rad)\ + (poly[-1]-c_y)*math.cos(a_rad)) + c_y poly = np.rint(poly).reshape(-1,2) rr, cc = polygon(poly[:, 0], poly[:, 1], img_l.shape) p0 = np.rint(np.random.sample()*(radius_max-radius_min) + radius_min) p1 = np.rint(np.random.sample()*(w_size-radius_max)) p2 = np.rint(np.random.sample()*(w_size-radius_max)) p3 = np.rint(np.random.sample()*2.*radius_min - radius_min) p4 = np.rint(np.random.sample()*2.*radius_min - radius_min) p5 = np.rint(np.random.sample()*2.*radius_min - radius_min) p6 = np.rint(np.random.sample()*2.*radius_min - radius_min) p7 = np.rint(np.random.sample()*2.*radius_min - radius_min) p8 = np.rint(np.random.sample()*2.*radius_min - radius_min) poly = np.array(( (p1, p2), (p1+p3, p2+p4+p0), (p1+p5+p0, p2+p6+p0), (p1+p7+p0, p2+p8), (p1, p2), )) rr_p, cc_p = polygon(poly[:, 0], poly[:, 1], img_l.shape) in_sc = list(set(rr) & set(rr_p)) if len(in_sc) > 0: if np.mean(rr_p) > np.mean(in_sc): poly += np.max(in_sc) - np.min(in_sc) else: poly -= np.max(in_sc) - np.min(in_sc) rr_p, cc_p = polygon(poly[:, 0], poly[:, 1], img_l.shape) if p > 0: img = img_l.copy() img[rr, cc] = img_h[rr, cc] img[rr_p, cc_p] = img_h[rr_p, cc_p] else: img = img_h.copy() img[rr, cc] = img_l[rr, cc] img[rr_p, cc_p] = img_l[rr_p, cc_p] msk = np.zeros((w_size, w_size, 1), dtype='float32') msk[rr, cc] = 1. return img, msk 


Comme précédemment, nous construisons des tableaux et examinons les 10 premiers.

masques de construction
 for k in range(train_num): img, msk = next_pair() train_x[k] = img train_y[k] = msk fig, axes = plt.subplots(2, 10, figsize=(20, 5)) for k in range(10): axes[0,k].set_axis_off() axes[0,k].imshow(train_x[k]) axes[1,k].set_axis_off() axes[1,k].imshow(train_y[k].squeeze()) 


image
 input_layer = Input((w_size, w_size, 3)) output_layer = build_model(input_layer, 16) model = Model(input_layer, output_layer) model.compile(loss=dice_loss, optimizer=Adam(lr=1e-3), metrics=[my_iou_metric]) model.load_weights('./keras.weights', by_name=False) while True: history = model.fit(train_x, train_y, batch_size=32, epochs=1, verbose=1, validation_split=0.1 ) if history.history['my_iou_metric'][0] > 0.75: break 

Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 54s 7ms/step - loss: 0.5005 - my_iou_metric: 0.1296 - val_loss: 0.1692 - val_my_iou_metric: 0.3722
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 52s 7ms/step - loss: 0.1287 - my_iou_metric: 0.4522 - val_loss: 0.0449 - val_my_iou_metric: 0.6833
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 52s 7ms/step - loss: 0.0759 - my_iou_metric: 0.5985 - val_loss: 0.0397 - val_my_iou_metric: 0.7215
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 52s 7ms/step - loss: 0.0455 - my_iou_metric: 0.6936 - val_loss: 0.0297 - val_my_iou_metric: 0.7304
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 52s 7ms/step - loss: 0.0432 - my_iou_metric: 0.7053 - val_loss: 0.0215 - val_my_iou_metric: 0.7846
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 53s 7ms/step - loss: 0.0327 - my_iou_metric: 0.7417 - val_loss: 0.0171 - val_my_iou_metric: 0.7970
Train on 7372 samples, validate on 820 samples
Epoch 1/1
7372/7372 [==============================] - 52s 7ms/step - loss: 0.0265 - my_iou_metric: 0.7679 - val_loss: 0.0138 - val_my_iou_metric: 0.8280


Résumé


Comme vous pouvez le voir, le réseau fait la distinction entre les projections d'heptagones réguliers et les polygones quadrangulaires arbitraires avec une précision de 0,828 sur l'ensemble de test. La formation réseau est interrompue par une valeur arbitraire de 0,75 et la précision devrait probablement être bien meilleure. Si nous partons de la thèse que le réseau trouve des primitives et que leurs combinaisons déterminent l'objet, alors dans notre cas il y a deux zones avec une moyenne différente de l'arrière-plan, il n'y a pas de primitives dans la compréhension de l'homme. Il n'y a pas de lignes monochromes évidentes, et il n'y a pas de coins, respectivement, seulement des zones avec des bordures très similaires. Même si vous créez des lignes, les deux objets de l'image sont construits à partir des mêmes primitives.

Une question pour les connaisseurs - qu'est-ce que le réseau considère comme un signe par lequel il distingue les «bateaux» des «interférences»? Évidemment, ce n'est pas la couleur ou la forme des bordures des bateaux. Bien sûr, nous pouvons continuer à approfondir cette construction abstraite de la "mer" / "navires", nous ne sommes pas l'Académie des Sciences et pouvons mener des recherches exclusivement par curiosité. Nous pouvons changer les heptagones en octogones ou remplir l'image avec des angles réguliers de cinq et six et voir si leur réseau se distingue ou non. Je laisse cela aux lecteurs - même si je me suis également demandé si le réseau pouvait compter le nombre de coins du polygone et, pour le test, organiser non pas des polygones réguliers dans l'image, mais leurs projections aléatoires.

Il existe d'autres propriétés non moins intéressantes de ces bateaux, et de telles expériences sont utiles dans la mesure où nous définissons nous-mêmes toutes les caractéristiques probabilistes de l'ensemble étudié et le comportement inattendu de réseaux bien étudiés ajoutera des connaissances et apportera des avantages.

Arrière-plan sélectionné au hasard, couleur sélectionnée au hasard, emplacement du bateau / ellipse sélectionné au hasard. Il n'y a pas de lignes sur les photos, il y a des zones avec des caractéristiques différentes, mais il n'y a pas de lignes monochromes! Dans ce cas, bien sûr, il y a des simplifications et la tâche peut être encore plus compliquée - par exemple, choisir des couleurs comme 0,0 ... 0,9 et 0,1 ... 1,0 - mais pour le réseau, il n'y a pas de différence. Le réseau peut trouver et trouver des modèles différents de ceux qu'une personne voit et trouve clairement.

Si l'un des lecteurs est intéressé, vous pouvez continuer à rechercher et à sélectionner sur les réseaux, si ce qui ne fonctionne pas ou n'est pas clair, ou si une nouvelle et bonne pensée apparaît et impressionne par sa beauté, alors vous pouvez toujours partager avec nous ou demander aux maîtres (et grands-maîtres aussi) et demander de l'aide qualifiée dans la communauté ODS.

Source: https://habr.com/ru/post/fr431512/


All Articles