Il s'agit du troisième article sur l'analyse et l'étude des ellipses, des triangles et d'autres formes géométriques.
Les articles précédents ont soulevé des questions très intéressantes parmi les lecteurs, en particulier sur la complexité ou la simplicité de certaines séquences de formation. Les questions sont en fait très intéressantes, par exemple, dans quelle mesure un triangle est-il plus difficile à apprendre qu'un quadrilatère ou un autre polygone?

Essayons de comparer, et pour comparaison, nous avons une excellente idée, testée par des générations d'étudiants, l'idée - plus la feuille de triche est courte, plus l'examen est facile.
Cet article est aussi simplement le résultat de la curiosité et d'un intérêt oiseux, rien de cela ne se rencontre dans la pratique et pour les tâches pratiques, il y a quelques bonnes idées, mais il n'y a presque rien pour le copier-coller. Ceci est une petite étude de la complexité des séquences de formation - le raisonnement et le code de l'auteur sont présentés, vous pouvez tout vérifier / compléter / changer vous-même.
Alors, essayons de découvrir quelle figure géométrique est plus compliquée ou plus simple pour la segmentation, quel cours de conférences pour l'IA est plus compréhensible et mieux absorbé.
Il existe de nombreuses formes géométriques différentes, mais nous ne comparerons que les triangles, les quadrangles et les étoiles à cinq branches. Nous utiliserons une méthode simple pour construire une séquence de trains - nous diviserons les images monochromes 128x128 en quatre parties et placerons au hasard une ellipse et, par exemple, un triangle dans ces quartiers. Nous allons détecter un triangle de la même couleur que l'ellipse. C'est-à-dire il s'agit de former le réseau à distinguer, par exemple, un polygone quadrangulaire d'une ellipse peinte de la même couleur. Voici des exemples de photos que nous étudierons



Nous ne détecterons pas un triangle et un quadrilatère dans une image, nous les détecterons séparément, dans des trains différents, sur fond d'interférence sous la forme d'une ellipse.
Prenons le U-net classique et trois types de séquences d'entraînement avec des triangles, des quadrangles et des étoiles pour la recherche.
Donc, étant donné:
- trois séquences d'apprentissage de paires image / masque;
- le réseau. U-net ordinaire, qui est largement utilisé pour la segmentation.
Idée à tester:
- déterminer laquelle des séquences d'entraînement est «la plus difficile» à apprendre;
- comment certaines techniques de prétraitement affectent l'apprentissage
Commençons par sélectionner 10 000 paires d'images de quadrangles avec des ellipses et des masques et réfléchissez-y attentivement. Nous voulons savoir combien de temps le berceau se révélera et de quoi dépend sa longueur.
Nous chargeons des bibliothèques, nous déterminons les tailles d'un tableau d'imagesimport numpy as np import matplotlib.pyplot as plt %matplotlib inline import math from tqdm import tqdm from skimage.draw import ellipse, polygon from keras import Model from keras.optimizers import Adam from keras.layers import Input,Conv2D,Conv2DTranspose,MaxPooling2D,concatenate from keras.layers import BatchNormalization,Activation,Add,Dropout from keras.losses import binary_crossentropy from keras import backend as K import tensorflow as tf import keras as keras w_size = 128 train_num = 10000 radius_min = 10 radius_max = 20
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):
Nous utiliserons la métrique du
premier article . Permettez-moi de rappeler aux lecteurs que nous allons prédire le masque du pixel - c'est le "fond" ou le "quadrilatère" et évaluer 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 un arrière-plan, correctement prévu qu'un pixel est un quadrilatère, ou fait une erreur en prédisant un «arrière-plan» ou un «quadrangle». 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.
Nous examinons le réseau comme une «boîte noire», nous ne commencerons pas à regarder ce qui arrive au réseau à l'intérieur, comment les poids changent et comment les gradients sont choisis - nous examinerons les entrailles du réseau plus tard lorsque nous comparerons les réseaux.
U-net simple def build_model(input_layer, start_neurons):
La fonction de génération de paires image / masque. Sur une image en noir et blanc 128x128 remplie de bruit aléatoire avec une sélection aléatoire de deux plages, ou 0,0 ... 0,75 ou 0,25..1,0. Sélectionnez aléatoirement un quart dans l'image et placez une ellipse orientée aléatoirement et dans l'autre quart nous plaçons un quadrilatère et de couleur égale avec du bruit aléatoire.
def next_pair(): img_l = (np.random.sample((w_size, w_size, 1))* 0.75).astype('float32') img_h = (np.random.sample((w_size, w_size, 1))* 0.75 + 0.25).astype('float32') img = np.zeros((w_size, w_size, 2), dtype='float') i0_qua = math.trunc(np.random.sample()*4.) i1_qua = math.trunc(np.random.sample()*4.) while i0_qua == i1_qua: i1_qua = math.trunc(np.random.sample()*4.) _qua = np.int(w_size/4) qua = np.array([[_qua,_qua],[_qua,_qua*3],[_qua*3,_qua*3],[_qua*3,_qua]]) p = np.random.sample() - 0.5 r = qua[i0_qua,0] c = qua[i0_qua,1] 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 = qua[i1_qua,0] - (radius_max-radius_min) p2 = qua[i1_qua,1] - (radius_max-radius_min) p3 = np.rint(np.random.sample()*radius_min) p4 = np.rint(np.random.sample()*radius_min) p5 = np.rint(np.random.sample()*radius_min) p6 = np.rint(np.random.sample()*radius_min) p7 = np.rint(np.random.sample()*radius_min) p8 = np.rint(np.random.sample()*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) if p > 0: img[:,:,:1] = img_l.copy() img[rr, cc,:1] = img_h[rr, cc] img[rr_p, cc_p,:1] = img_h[rr_p, cc_p] else: img[:,:,:1] = img_h.copy() img[rr, cc,:1] = img_l[rr, cc] img[rr_p, cc_p,:1] = img_l[rr_p, cc_p] img[:,:,1] = 0. img[rr_p, cc_p,1] = 1. return img
Créons une séquence d'entraînement de paires, voir au hasard 10. Permettez-moi de vous rappeler que les images sont monochromes, en niveaux de gris.
_txy = [next_pair() for idx in range(train_num)] f_imgs = np.array(_txy)[:,:,:,:1].reshape(-1,w_size ,w_size ,1) f_msks = np.array(_txy)[:,:,:,1:].reshape(-1,w_size ,w_size ,1) del(_txy)

Première étape. Nous nous entraînons sur le set de départ minimum
La première étape de notre expérience est simple, nous essayons de former le réseau pour ne prédire que 11 premières images.
batch_size = 10 val_len = 11 precision = 0.85 m0_select = np.zeros((f_imgs.shape[0]), dtype='int') for k in range(val_len): m0_select[k] = 1 t = tqdm() while True: fit = model.fit(f_imgs[m0_select>0], f_msks[m0_select>0], batch_size=batch_size, epochs=1, verbose=0 ) current_accu = fit.history['my_iou_metric'][0] current_loss = fit.history['loss'][0] t.set_description("accuracy {0:6.4f} loss {1:6.4f} ".\ format(current_accu, current_loss)) t.update(1) if current_accu > precision: break t.close()
accuracy 0.8545 loss 0.0674 lenght 11 : : 793it [00:58, 14.79it/s]
Nous avons sélectionné les 11 premiers dans la séquence initiale et formé le réseau sur eux. Peu importe que le réseau mémorise ces images spécifiquement ou les résume, l'essentiel est qu'il puisse reconnaître ces 11 images de la manière dont nous avons besoin. En fonction de l'ensemble de données sélectionné et de sa précision, la formation réseau peut durer très, très longtemps. Mais nous n'avons que quelques itérations. Je répète qu’il n’est plus important pour nous maintenant de savoir comment et ce que le réseau a appris ou appris, l’essentiel est qu’il ait atteint la précision de prédiction établie.
Commencez maintenant l'expérience principale
Nous allons construire la feuille de triche, nous allons construire ces feuilles de triche séparément pour les trois séquences d'entraînement et comparer leur longueur. Nous prendrons de nouvelles paires image / masque de la séquence construite et essaierons de les prédire par le réseau formé sur la séquence déjà sélectionnée. Au début, il ne s'agit que de 11 paires d'image / masque et le réseau est entraîné, peut-être pas très correctement. Si dans une nouvelle paire le masque de l'image est prédit avec une précision acceptable, alors nous rejetons cette paire, elle n'a pas de nouvelles informations pour le réseau, elle sait déjà et peut calculer le masque à partir de cette image. Si la précision de la prédiction est insuffisante, nous ajoutons cette image avec un masque à notre séquence et commençons à entraîner le réseau jusqu'à ce qu'un résultat de précision acceptable soit atteint sur la séquence sélectionnée. C'est-à-dire Cette image contient de nouvelles informations et nous les ajoutons à notre séquence de formation et extrayons les informations qu'elle contient par formation.
batch_size = 50 t_batch_size = 1024 raw_len = val_len t = tqdm(-1) id_train = 0
Accuracy 0.9338 loss 0.0266 selected img 1007 tested img 9985 : : 4291it [49:52, 1.73s/it]
Ici, la précision est utilisée dans le sens de «précision», et non pas comme la métrique de kéros standard, et le sous-programme «my_iou_metric» est utilisé pour calculer la précision.
Comparez maintenant le fonctionnement du même réseau avec les mêmes paramètres sur une séquence différente, sur des triangles

Et nous obtenons un résultat complètement différent
Accuracy 0.9823 loss 0.0108 selected img 1913 tested img 9995 : : 6343it [2:11:36, 3.03s/it]
Le réseau a sélectionné 1913 photos avec de "nouvelles" informations, c'est-à-dire le contenu des images avec des triangles est deux fois moins qu'avec des quadrangles!
Vérifions la même chose sur les étoiles et exécutons le réseau dans la troisième séquence

nous obtenons
Accuracy 0.8985 loss 0.0478 selected img 476 tested img 9985 : : 2188it [16:13, 1.16it/s]
Comme vous pouvez le voir, les étoiles se sont avérées être les plus informatives, seulement 476 images dans une feuille de triche.
Nous avons eu raison de juger de la complexité des formes géométriques pour la perception par leur réseau neuronal. La plus simple est l'étoile, avec seulement 476 images dans la feuille de triche, puis le quadrilatère avec ses 1007 et le plus complexe s'est avéré être un triangle - pour la formation, vous avez besoin de 1913 images.
Gardez à l'esprit que c'est pour nous, pour les gens, c'est une image, mais pour le réseau, c'est un cours magistral sur la reconnaissance et le cours sur les triangles s'est avéré être le plus difficile.
Maintenant sur le sérieux
À première vue, toutes ces ellipses et triangles semblent dorloter, gâteaux de sable et lego. Mais voici une question précise et sérieuse: si nous appliquons une sorte de prétraitement, filtrons la séquence initiale, comment la complexité de la séquence changera-t-elle? Par exemple, nous prenons tous les mêmes ellipses et quadrangles et leur appliquons un tel prétraitement
from scipy.ndimage import gaussian_filter _tmp = [gaussian_filter(idx, sigma = 1) for idx in f_imgs] f1_imgs = np.array(_tmp)[:,:,:,:1].reshape(-1,w_size ,w_size ,1) del(_tmp) fig, axes = plt.subplots(2, 5, figsize=(20, 7)) for k in range(5): kk = np.random.randint(train_num) axes[0,k].set_axis_off() axes[0,k].imshow(f1_imgs[kk].squeeze(), cmap="gray") axes[1,k].set_axis_off() axes[1,k].imshow(f_msks[kk].squeeze(), cmap="gray")

À première vue, tout est pareil, les mêmes ellipses, les mêmes polygones, mais le réseau a commencé à fonctionner de manière complètement différente:
Accuracy 1.0575 loss 0.0011 selected img 7963 tested img 9999 : : 17765it [29:02:00, 12.40s/it]
Ici, une petite explication est nécessaire, nous n'utilisons pas d'augmentation, car La forme du polygone et la forme de l'ellipse sont initialement sélectionnées au hasard. Par conséquent, l'augmentation ne donnera pas de nouvelles informations et n'a aucun sens dans ce cas.
Mais, comme le montre le résultat du travail, un simple gaussian_filter a créé de nombreux problèmes pour le réseau, a généré beaucoup d'informations nouvelles et probablement superflues.
Eh bien, pour les amateurs de simplicité dans sa forme la plus pure, nous prenons les mêmes ellipses avec des polygones, mais sans couleur aléatoire

le résultat suggère que la couleur aléatoire n'est pas du tout un simple ajout.
Accuracy 0.9004 loss 0.0315 selected img 251 tested img 9832 : : 1000it [06:46, 1.33it/s]
Le réseau valait complètement les informations extraites de 251 images, près de quatre fois moins que de nombreuses images peintes avec du bruit.
Le but de l'article est de montrer quelques outils et exemples de son travail sur des exemples frivoles, le lego dans le bac à sable. Nous avons un outil pour comparer deux séquences d'apprentissage, nous pouvons évaluer dans quelle mesure notre prétraitement complique ou simplifie la séquence d'apprentissage, comment telle ou telle primitive dans la séquence d'apprentissage est simple à détecter.
La possibilité d'appliquer cet exemple de Lego dans des cas réels est évidente, mais les véritables formations et les réseaux de lecteurs dépendent des lecteurs eux-mêmes.