Inteligencia artificial versus mentiras y engaños

En todas las tareas de enseñanza de la inteligencia artificial, hay un fenómeno desagradable: los errores en el marcado de la secuencia de entrenamiento. Estos errores son inevitables, ya que todo el marcado se realiza manualmente, porque si hay una manera de marcar datos reales mediante programación, ¿por qué necesitamos a alguien más que les enseñe a marcar y gastar tiempo y dinero en crear un diseño absolutamente innecesario!

La tarea de encontrar y eliminar máscaras falsas en una secuencia de entrenamiento grande es bastante complicada. Puede verlos todos manualmente, pero esto no lo salvará de errores repetidos. Pero si observa de cerca las herramientas para estudiar las redes neuronales propuestas en publicaciones anteriores , resulta que hay una manera simple y efectiva de detectar y extraer todos los artefactos de la secuencia de entrenamiento.

Y en esta publicación hay un ejemplo concreto, es obvio que uno simple, en elipses y polígonos, para una red en U ordinaria, es nuevamente un lego en la caja de arena, pero es inusualmente concreto, útil y efectivo. Mostraremos cómo un método simple identifica y encuentra casi todos los artefactos, todas las mentiras de la secuencia de entrenamiento.

¡Entonces comencemos!

Como antes, estudiaremos la secuencia de pares de imágenes / máscaras. En la imagen en diferentes trimestres, elegidos al azar, colocaremos una elipse de un tamaño aleatorio y un cuadrángulo de un tamaño arbitrario, y ambos colores en el mismo color, también seleccionados al azar de dos de ellos. En el segundo color restante, coloreamos el fondo. Las dimensiones de la elipse y el cuadrángulo son, por supuesto, limitadas.

Pero en este caso, haremos cambios en el programa de generación de pares y prepararemos, junto con una máscara completamente correcta, una incorrecta, envenenada por una mentira: en aproximadamente el uno por ciento de los casos, reemplaza el cuadrilátero con una elipse en la máscara, es decir. El verdadero objeto para la segmentación se denota con falsas máscaras como una elipse, no un cuadrilátero.

Ejemplos aleatorios 10



Ejemplos de 10 aleatorios, pero de marcado erróneo. La máscara superior es verdadera, la inferior es falsa y los números en la secuencia de entrenamiento se muestran en las imágenes.



para la segmentación, tomamos los mismos programas de cálculo de métrica y pérdida y la misma U-net simple, pero no usaremos Dropout.

Bibliotecas
import numpy as np import matplotlib.pyplot as plt from matplotlib.colors import NoNorm %matplotlib inline import math from tqdm import tqdm #from joblib import Parallel, delayed 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 from keras.models import load_model import tensorflow as tf import keras as keras w_size = 128 train_num = 10000 radius_min = 10 radius_max = 30 


Funciones métricas y de pérdida
 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 }) 


Red U normal
 def build_model(input_layer, start_neurons): # 128 -> 64 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 = Conv2D(start_neurons * 1, (2, 2), strides=(2, 2), activation="relu", padding="same")(conv1) # pool1 = Dropout(0.25)(pool1) # 64 -> 32 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 = Conv2D(start_neurons * 1, (2, 2), strides=(2, 2), activation="relu", padding="same")(conv2) # pool2 = Dropout(0.5)(pool2) # 32 -> 16 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 = Conv2D(start_neurons * 1, (2, 2), strides=(2, 2), activation="relu", padding="same")(conv3) # pool3 = Dropout(0.5)(pool3) # 16 -> 8 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 = Conv2D(start_neurons * 1, (2, 2), strides=(2, 2), activation="relu", padding="same")(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) # 8 -> 16 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) # 16 -> 32 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) # 32 -> 64 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) # 64 -> 128 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 input_layer = Input((w_size, w_size, 1)) output_layer = build_model(input_layer, 27) model = Model(input_layer, output_layer) model.compile(loss=bce_dice_loss, optimizer=Adam(lr=1e-4), metrics=[my_iou_metric]) model.summary() 


El programa para generar imágenes y máscaras: verdadero y falso. La primera capa de la imagen se coloca en la matriz, la segunda es la máscara verdadera y la tercera capa es la máscara falsa.

 def next_pair_f(idx): img_l = np.ones((w_size, w_size, 1), dtype='float')*0.45 img_h = np.ones((w_size, w_size, 1), dtype='float')*0.55 img = np.zeros((w_size, w_size, 3), 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[:,:,1] = 0. img[rr_p, cc_p,1] = 1. img[:,:,2] = 0. p_f = np.random.sample()*1000. if p_f > 10: img[rr_p, cc_p,2] = 1. else: img[rr, cc,2] = 1. i_false[idx] = 1 return img 

Programa de cálculo de hoja de trucos
 def make_sh(f_imgs, f_msks, val_len): precision = 0.85 batch_size = 50 t = tqdm() t_batch_size = 50 raw_len = val_len id_train = 1 #id_select = 1 v_false = np.zeros((train_num), dtype='float') while True: if id_train == 1: fit = model.fit(f_imgs[m2_select>0], f_msks[m2_select>0], batch_size=batch_size, epochs=1, verbose=0 ) current_accu = fit.history['my_iou_metric'][0] current_loss = fit.history['loss'][0] if current_accu > precision: id_train = 0 else: t_pred = model.predict( f_imgs[raw_len: min(raw_len+t_batch_size,f_imgs.shape[0])], batch_size=batch_size ) for kk in range(t_pred.shape[0]): val_iou = get_iou_vector( f_msks[raw_len+kk].reshape(1,w_size,w_size,1), t_pred[kk].reshape(1,w_size,w_size,1) > 0.5) v_false[raw_len+kk] = val_iou if val_iou < precision*0.95: new_img_test = 1 m2_select[raw_len+kk] = 1 val_len += 1 break raw_len += (kk+1) id_train = 1 t.set_description("Accuracy {0:6.4f} loss {1:6.4f} selected img {2:5d} tested img {3:5d} ". format(current_accu, current_loss, val_len, raw_len)) t.update(1) if raw_len >= train_num: break t.close() return v_false 


El principal programa de cálculos. Hicimos pequeños cambios en el mismo programa de la publicación anterior y algunas variables requieren explicación y comentarios.

 i_false = np.zeros((train_num), dtype='int') 

Hay una máscara de falso indicador. Si es 1, la máscara de F_msks no coincide con la máscara de f_msks. Este es un indicador de lo que realmente estamos buscando: máscaras falsas.

 m2_select = np.zeros((train_num), dtype='int') 

Indicador de que esta imagen está seleccionada en la hoja de trucos.

 batch_size = 50 val_len = batch_size + 1 # i_false - false mask marked as 1 i_false = np.zeros((train_num), dtype='int') # t_imgs, t_msks -test images and masks _txy = [next_pair_f(idx) for idx in range(train_num)] t_imgs = np.array(_txy)[:,:,:,:1].reshape(-1,w_size ,w_size ,1) t_msks = np.array(_txy)[:,:,:,1].reshape(-1,w_size ,w_size ,1) # m2_select - initial 51 pair m2_select = np.zeros((train_num), dtype='int') for k in range(val_len): m2_select[k] = 1 # i_false - false mask marked as 1 i_false = np.zeros((train_num), dtype='int') _txy = [next_pair_f(idx) 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) # F_msks - mask array with ~1% false mask F_msks = np.array(_txy)[:,:,:,2].reshape(-1,w_size ,w_size ,1) fig, axes = plt.subplots(2, 10, figsize=(20, 5)) for k in range(10): kk = np.random.randint(train_num) axes[0,k].set_axis_off() axes[0,k].imshow(f_imgs[kk].squeeze(), cmap="gray", norm=NoNorm()) axes[1,k].set_axis_off() axes[1,k].imshow(f_msks[kk].squeeze(), cmap="gray", norm=NoNorm()) plt.show(block=True) false_num = np.arange(train_num)[i_false>0] fig, axes = plt.subplots(3, 10, figsize=(20, 7)) for k in range(10): kk = np.random.randint(false_num.shape[0]) axes[0,k].set_axis_off() axes[0,k].set_title(false_num[kk]) axes[0,k].imshow(f_imgs[false_num[kk]].squeeze(), cmap="gray", norm=NoNorm()) axes[1,k].set_axis_off() axes[1,k].imshow(f_msks[false_num[kk]].squeeze(), cmap="gray", norm=NoNorm()) axes[2,k].set_axis_off() axes[2,k].imshow(F_msks[false_num[kk]].squeeze(), cmap="gray", norm=NoNorm()) plt.show(block=True) 

Construimos secuencias de pares de imágenes / máscaras para entrenamiento y otra secuencia para pruebas. Es decir Verificaremos una nueva secuencia independiente de 10,000 pares. Mostramos y verificamos visualmente selectivamente imágenes aleatorias con máscaras verdaderas y falsas. Se muestran las imágenes de arriba.

En este caso particular, se obtuvieron 93 máscaras falsas, en las que una elipse, en lugar de un cuadrilátero, se marcó como verdadero positivo.

Comenzamos a entrenar en el conjunto correcto, usamos f_msks como máscara

 input_layer = Input((w_size, w_size, 1)) output_layer = build_model(input_layer, 25) model = Model(input_layer, output_layer) model.compile(loss=bce_dice_loss, optimizer=Adam(lr=1e-4), metrics=[my_iou_metric]) v_false = make_sh(f_imgs, f_msks, val_len) t_pred = model.predict(t_imgs,batch_size=batch_size) print (get_iou_vector(t_msks,t_pred.reshape(-1,w_size ,w_size ,1))) 

 Accuracy 0.9807 loss 0.0092 selected img 404 tested img 10000 : : 1801it [08:13, 3.65it/s] 0.9895299999999841 

La hoja de trucos resultó en solo 404 imágenes y obtuvo una precisión aceptable en una secuencia de prueba independiente.

Ahora recompilamos la red y entrenamos en la misma secuencia de entrenamiento, pero como máscaras alimentamos F_msks con 1% de máscaras falsas a la entrada

 input_layer = Input((w_size, w_size, 1)) output_layer = build_model(input_layer, 25) model = Model(input_layer, output_layer) model.compile(loss=bce_dice_loss, optimizer=Adam(lr=1e-4), metrics=[my_iou_metric]) v_false = make_sh(f_imgs, F_msks, val_len) t_pred = model.predict(t_imgs,batch_size=batch_size) print (get_iou_vector(t_msks,t_pred.reshape(-1,w_size ,w_size ,1))) 

 Accuracy 0.9821 loss 0.0324 selected img 727 tested img 10000 : : 1679it [25:44, 1.09it/s] 0.9524099999999959 

Obtuvimos una hoja de trucos de 727 imágenes, que es significativamente mayor y la precisión de las predicciones de prueba, la misma que en la secuencia de prueba anterior, disminuyó de 0.98953 a 0.9525. Agregamos mentiras a la secuencia de entrenamiento en menos del 1%, solo 93 de cada 10,000 máscaras eran falsas, pero el resultado empeoró en un 3.7%. Y esto no es solo una mentira, ¡es una verdadera astucia! Y la hoja de trucos aumentó de solo 404 a ya 727 imágenes.

Calmante y agradable solo una cosa

 print (len(set(np.arange(train_num)[m2_select>0]).intersection(set(np.arange(train_num)[i_false>0])))) 93 

Permítanme explicar esta larga fórmula, tomamos la intersección del conjunto de imágenes seleccionadas en la hoja de trucos con el conjunto de imágenes falsas y vemos que el algoritmo seleccionó las 93 imágenes falsas en la hoja de trucos.

La tarea se simplifica significativamente, no son 10.000 imágenes para mirar manualmente, solo son 727 y todas las mentiras se concentran aquí.

Pero hay una forma aún más interesante y útil. Cuando elaboramos la hoja de trucos, incluimos solo aquellos pares de imagen / máscara cuya predicción es menor que el umbral, y en nuestro caso particular, guardamos el valor de la precisión de la predicción en la matriz v_false . Veamos pares de la secuencia de entrenamiento que tienen un valor de predicción muy pequeño, por ejemplo, menos de 0.1 y veamos cuántas mentiras hay

 print (len(set(np.arange(train_num)[v_false<0.01]).intersection(set(np.arange(train_num)[i_false>0])))) 89 


Como puede ver, la parte principal de las máscaras falsas, 89 de 93, cayó en estas máscaras.
 np.arange(train_num)[v_false<0.01].shape (382,) 

Por lo tanto, si verificamos solo 382 máscaras manualmente, y esto es de 10,000 piezas, identificaremos y destruiremos la mayoría de las máscaras falsas sin piedad.

Si es posible ver imágenes y máscaras durante la decisión de incluirlas en la hoja de trucos, a partir de un cierto paso, todas las máscaras falsas, todas las mentiras estarán determinadas por el nivel mínimo de predicción de una red ligeramente entrenada, y las máscaras correctas tendrán una predicción mayor que este nivel .

Para resumir


Si en algún mundo imaginado la verdad es siempre cuadrangular, y la mentira ovalada y alguna entidad desconocida decidieron distorsionar la verdad y llamaron a algunos puntos suspensivos la verdad, y los cuadrángulos son falsos, entonces, usando inteligencia artificial y la habilidad natural de hacer trampas, la Inquisición local encontrará rápida y fácilmente y erradica mentiras y engaños completa y completamente.

PD La capacidad de detectar óvalos, triángulos, polígonos simples es un requisito previo para crear cualquier IA que controle el automóvil. Si no sabe cómo buscar óvalos y triángulos, no encontrará todas las señales de tráfico y su IA se irá en el automóvil equivocado.

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


All Articles