Kesederhanaan dan kompleksitas primitif atau cara menentukan preprocessing yang tidak perlu untuk jaringan saraf

Ini adalah artikel ketiga tentang analisis dan studi elips, segitiga, dan bentuk geometris lainnya.
Artikel-artikel sebelumnya menimbulkan beberapa pertanyaan yang sangat menarik di antara pembaca, khususnya, tentang kompleksitas atau kesederhanaan urutan pelatihan tertentu. Pertanyaan-pertanyaan itu sebenarnya sangat menarik, misalnya, seberapa sulitkah segitiga untuk dipelajari daripada segi empat atau poligon lain?



Mari kita coba untuk membandingkan, dan untuk perbandingan kita memiliki ide bagus, diuji oleh generasi siswa, ide - semakin pendek lembar cheat, semakin mudah ujian.

Artikel ini juga semata-mata hasil dari rasa ingin tahu dan ketertarikan kosong, tidak ada yang ditemui dalam praktik dan untuk tugas-tugas praktis ada beberapa ide besar, tetapi hampir tidak ada apa-apa untuk copy-paste. Ini adalah studi kecil tentang kompleksitas urutan pelatihan - alasan dan kode penulis disajikan, Anda dapat memeriksa / menambah / mengubah semuanya sendiri.

Jadi, mari kita coba mencari tahu sosok geometrik mana yang lebih rumit atau lebih sederhana untuk segmentasi, kursus kuliah untuk AI mana yang lebih mudah dipahami dan diserap dengan lebih baik.

Ada banyak bentuk geometris yang berbeda, tetapi kita hanya akan membandingkan segitiga, segi empat dan bintang berujung lima. Kami akan menggunakan metode sederhana untuk membangun urutan kereta - kami akan membagi 128x128 gambar monokrom menjadi empat bagian dan secara acak menempatkan elips dan, misalnya, segitiga di tempat ini. Kami akan mendeteksi segitiga dengan warna yang sama dengan elips. Yaitu tugasnya adalah melatih jaringan untuk membedakan, misalnya, poligon segi empat dari elips yang dicat dengan warna yang sama. Berikut adalah contoh gambar yang akan kita pelajari







Kami tidak akan mendeteksi segitiga dan segi empat dalam satu gambar, kami akan mendeteksi mereka secara terpisah, di kereta yang berbeda, dengan latar belakang gangguan dalam bentuk elips.

Mari kita ambil U-net klasik dan tiga jenis rangkaian pelatihan dengan segitiga, segi empat, dan bintang untuk penelitian.

Jadi, diberikan:

  • tiga urutan pelatihan pasangan gambar / topeng;
  • jaringan. U-net biasa, yang banyak digunakan untuk segmentasi.

Gagasan untuk menguji:

  • tentukan urutan pelatihan mana yang โ€œlebih sulitโ€ untuk dipelajari;
  • bagaimana beberapa teknik preprocessing memengaruhi pembelajaran

Mari kita mulai, pilih 10.000 pasang gambar segi empat dengan elips dan topeng dan pertimbangkan dengan cermat. Kami tertarik pada seberapa pendek boks bayi akan berubah dan tergantung pada panjangnya.

Kami memuat perpustakaan, kami menentukan ukuran array gambar
import 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 


menentukan fungsi kerugian dan akurasi
 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 }) 


Kami akan menggunakan metrik dari artikel pertama . Biarkan saya mengingatkan pembaca bahwa kami akan memprediksi topeng piksel - ini adalah "latar belakang" atau "segi empat" dan mengevaluasi kebenaran atau kepalsuan prediksi. Yaitu Keempat opsi berikut dimungkinkan - kami memperkirakan dengan benar bahwa suatu piksel adalah latar belakang, dengan tepat memperkirakan bahwa suatu piksel adalah segi empat, atau membuat kesalahan dalam memprediksi "latar belakang" atau "segi empat". Jadi, untuk semua gambar dan semua piksel, kami memperkirakan jumlah keempat opsi dan menghitung hasilnya - ini akan menjadi hasil dari jaringan. Dan semakin sedikit prediksi yang salah dan semakin benar, semakin akurat hasilnya dan semakin baik jaringannya.

Kami memeriksa jaringan sebagai "kotak hitam", kami tidak akan mulai melihat apa yang terjadi pada jaringan di dalamnya, bagaimana bobot berubah dan bagaimana gradien dipilih - kami akan melihat ke dalam usus jaringan nanti ketika kami membandingkan jaringan.

U-net sederhana
 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 = MaxPooling2D((2, 2))(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 = MaxPooling2D((2, 2))(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 = MaxPooling2D((2, 2))(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 = 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) # 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 # model input_layer = Input((w_size, w_size, 1)) output_layer = build_model(input_layer, 26) model = Model(input_layer, output_layer) model.compile(loss=bce_dice_loss, optimizer=Adam(lr=1e-4), metrics=[my_iou_metric]) model.summary() 


Fungsi menghasilkan pasangan gambar / topeng. Pada gambar hitam dan putih 128x128 diisi dengan noise acak dengan yang dipilih secara acak dari dua rentang, atau 0,0 ... 0,75 atau 0,25..1.0. Pilih secara acak seperempat pada gambar dan letakkan elips yang berorientasi acak dan pada kuartal lainnya kita menempatkan segi empat dan sama-sama warnanya dengan noise acak.

 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 

Mari kita buat urutan latihan pasangan, lihat acak 10. Biarkan saya mengingatkan Anda bahwa gambarnya monokrom, skala abu-abu.

 _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) #    10   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]) axes[1,k].set_axis_off() axes[1,k].imshow(f_msks[kk].squeeze()) 



Langkah pertama. Kami melatih pada set awal minimum


Langkah pertama percobaan kami sederhana, kami mencoba melatih jaringan untuk memprediksi hanya 11 gambar pertama.

 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]

Kami memilih 11 pertama dari urutan awal dan melatih jaringan pada mereka. Sekarang tidak masalah apakah jaringan menghafal gambar-gambar ini secara khusus atau meringkas, yang utama adalah bahwa ia dapat mengenali 11 gambar ini seperti yang kita butuhkan. Bergantung pada dataset dan akurasi yang dipilih, pelatihan jaringan dapat bertahan lama, sangat lama. Tetapi kami hanya memiliki beberapa iterasi. Saya ulangi bahwa itu tidak penting bagi kami sekarang bagaimana dan apa yang dipelajari atau dipelajari jaringan, yang utama adalah bahwa ia telah mencapai akurasi prediksi yang ditetapkan.

Sekarang mulailah eksperimen utama


Kami akan membuat lembar contekan, kami akan membuat contekan secara terpisah untuk ketiga urutan pelatihan dan membandingkan panjangnya. Kami akan mengambil pasangan gambar / topeng baru dari urutan yang dibuat dan akan mencoba untuk memprediksi mereka dengan jaringan terlatih pada urutan yang sudah dipilih. Pada awalnya, itu hanya 11 pasang gambar / topeng dan jaringannya dilatih, mungkin tidak terlalu benar. Jika dalam pasangan baru topeng dari gambar diprediksi dengan akurasi yang dapat diterima, maka kami membuang pasangan ini, itu tidak memiliki informasi baru untuk jaringan, ia sudah tahu dan dapat menghitung topeng dari gambar ini. Jika akurasi prediksi tidak mencukupi, maka kami menambahkan gambar ini dengan mask ke urutan kami dan mulai melatih jaringan sampai hasil akurasi yang dapat diterima tercapai pada urutan yang dipilih. Yaitu Gambar ini berisi informasi baru dan kami menambahkannya ke urutan pelatihan kami dan mengekstrak informasi yang terkandung di dalamnya dengan pelatihan.

 batch_size = 50 t_batch_size = 1024 raw_len = val_len t = tqdm(-1) id_train = 0 #id_select = 1 while True: 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 id_train == 1: 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] 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) if val_iou < precision*0.95: new_img_test = 1 m0_select[raw_len+kk] = 1 val_len += 1 break raw_len += (kk+1) id_train = 1 if raw_len >= train_num: break t.close() 

 Accuracy 0.9338 loss 0.0266 selected img 1007 tested img 9985 : : 4291it [49:52, 1.73s/it] 

Di sini akurasi digunakan dalam arti "akurasi", dan bukan sebagai metrik keras standar, dan subrutin "my_iou_metric" digunakan untuk menghitung akurasi.

Sekarang bandingkan operasi jaringan yang sama dengan parameter yang sama pada urutan yang berbeda, pada segitiga



Dan kami mendapatkan hasil yang sangat berbeda

 Accuracy 0.9823 loss 0.0108 selected img 1913 tested img 9995 : : 6343it [2:11:36, 3.03s/it] 

Jaringan memilih 1913 gambar dengan informasi "baru", mis. isi gambar dengan segitiga setengah sama dengan quadrangles!

Mari kita periksa hal yang sama pada bintang-bintang dan menjalankan jaringan di urutan ketiga



kita dapatkan

 Accuracy 0.8985 loss 0.0478 selected img 476 tested img 9985 : : 2188it [16:13, 1.16it/s] 

Seperti yang Anda lihat, bintang-bintang itu ternyata yang paling informatif, hanya 476 gambar dalam lembar contekan.

Kami punya alasan untuk menilai kompleksitas bentuk geometris untuk persepsi oleh jaringan saraf mereka. Yang paling sederhana adalah bintang, dengan hanya 476 gambar di lembar contekan, kemudian segi empat dengan 1007 dan yang paling kompleks ternyata menjadi segitiga - untuk pelatihan Anda membutuhkan 1913 gambar.

Ingatlah, ini untuk kita, untuk orang-orang itu adalah gambaran, tetapi untuk jaringan itu adalah kuliah tentang pengakuan dan kursus tentang segitiga ternyata yang paling sulit.

Sekarang tentang yang serius


Pada pandangan pertama, semua elips dan segitiga ini tampak memanjakan, kue pasir dan lego. Tapi di sini ada pertanyaan spesifik dan serius: jika kita menerapkan semacam preprocessing, filter ke urutan awal, bagaimana kompleksitas urutan berubah? Sebagai contoh, kami mengambil semua elips dan segi empat yang sama dan menerapkan preprocessing tersebut untuk mereka

 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") 



Pada pandangan pertama, semuanya sama, elips yang sama, poligon yang sama, tetapi jaringan mulai bekerja dengan cara yang sangat berbeda:

 Accuracy 1.0575 loss 0.0011 selected img 7963 tested img 9999 : : 17765it [29:02:00, 12.40s/it] 

Di sini sedikit penjelasan diperlukan, kami tidak menggunakan augmentasi, karena Bentuk poligon dan bentuk elips awalnya dipilih secara acak. Karena itu, augmentasi tidak akan memberikan informasi baru dan tidak masuk akal dalam kasus ini.

Tetapi, seperti dapat dilihat dari hasil pekerjaan, gaussian_filter sederhana menciptakan banyak masalah untuk jaringan, menghasilkan banyak informasi baru, dan mungkin berlebihan.

Nah, bagi pecinta kesederhanaan dalam bentuknya yang paling murni, kami mengambil elips yang sama dengan poligon, tetapi tanpa warna acak.



hasilnya menunjukkan bahwa warna acak sama sekali bukan tambahan sederhana.

 Accuracy 0.9004 loss 0.0315 selected img 251 tested img 9832 : : 1000it [06:46, 1.33it/s] 

Jaringan itu sepadan dengan informasi yang diekstrak dari 251 gambar, hampir empat kali lebih sedikit dari banyak gambar yang dilukis dengan noise.

Tujuan artikel ini adalah untuk menunjukkan beberapa alat dan contoh pekerjaannya pada contoh sembrono, lego di kotak pasir. Kami mendapat alat untuk membandingkan dua urutan pelatihan, kami dapat mengevaluasi seberapa banyak preprocessing kami mempersulit atau menyederhanakan urutan pelatihan, bagaimana ini atau itu primitif dalam urutan pelatihan mudah dideteksi.

Kemungkinan menerapkan contoh Lego ini dalam kasus nyata jelas, tetapi pelatihan nyata dan jaringan pembaca tergantung pada pembaca itu sendiri.

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


All Articles