Простота и cложность примитивов или как определить ненужный препроцессинг для нейронной сети
- Получить ссылку
- X
- Электронная почта
- Другие приложения
что сложнее для искусственного интеллекта, треугольник или четырехугольник?
https://habr.com/ru/post/439122/
Это третья статья по анализу и изучению эллипсов, треугольников и других геометрических фигур.
Предыдущие статьи вызвали у читателей несколько очень интересных вопросов, в частности о сложности или простоте тех или иных обучающих последовательностей. Вопросы на самом деле очень интересные, например насколько треугольник сложнее для обучения, чем четырехугольник или другой многоугольник?

Попробуем сравнить, и для сравнения у нас есть отличная, проверенная поколениями студентов, идея — чем короче шпаргалка, тем легче экзамен.
Статья эта тоже есть просто результат любопытства и праздного интереса, ничего из нее в практике не встречается и для практических задач тут есть пара отличных идей, но нет почти ничего для копипастинга. Это небольшое исследование сложности обучающих последовательностей — рассуждения автора и код изложены, можно все проверить/дополнить/изменить самим.
Итак, попробуем выяснить, какая геометрическая фигура сложнее или проще для сегментации, какой курс лекций для ИИ понятней и лучше усваивается.
Геометрических фигур много разных, но мы будем сравнивать только треугольники, четырехугольники и пятиконечные звезды. Мы применим простой метод построения трейн последовательности — мы разделим 128х128 одноцветной картинки на четыре части и случайным образом будем помещать в эти четверти эллипс и, например, треугольник. Будем детектить треугольник того же цвета, что и эллипс. Т.е. задача состоит в том, что бы обучить сеть отличать, например четырехугольный полигон от эллипса, окрашенного в тот же цвет. Вот примеры картинок, которые будем изучать



Мы не будем детектить на одной картинке треугольник и четырехугольник, мы будем детектить их отдельно, в разных трейн, на фоне помехи в виде эллипса.
Возьмем для исследования классическую U-net и три вида обучающих последовательностей с треугольниками, четырехугольниками и звездами.
Итак, дано:
- три обучающие последовательности пар картинка/маска;
- сеть. Обыкновенная U-net, которая широко используются для сегментации.
Идея для проверки:
- определим, какая из обучающих последовательностей «сложнее» для обучения;
- как влияют на обучение некоторые приемы предобработки.
Начнем, выберем 10 000 пар картинок четырехугольников с эллипсами и масок и рассмотрим их внимательно. Нам интересно, насколько короткой получится шпаргалка и от чего её длина зависит.
Загружаем библиотеки, определяем размеры массива картинок
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
определяем функции потерь и точности
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 })
Мы будем использовать метрику из первой статьи. Напомню читателям, что будем предсказывать маску пикселя — это «фон» или «четырехугольник» и оценивать истинность или ложность предсказания. Т.е. возможны следующие четыре варианта — мы правильно предсказали, что пиксель это фон, правильно предсказали, что пиксель это четырехугольник или ошиблись в предсказании «фон» или «четырехугольник». И так по всем картинкам и всем пикселям оцениваем количество всех четырех вариантов и подсчитываем результат — это и будет результат работы сети. И чем меньше ошибочных предсказаний и больше истинных, то тем точнее полученный результат и лучше работа сети.
Мы исследуем сеть как «черный ящик», мы не станем смотреть, что происходит с сетью внутри, как меняются веса и как выбираются градиенты — заглянем в недра сети попозже, когда будем сравнивать сети.
простая U-net
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()
Функция генерации пар картинка/маска. На черно-белой картинке 128х128 заполненной случайным шумом со случайно выбранным из двух диапазонов, или 0.0...0.75 или 0.25..1.0. Случайным образом выбираем четверть на картинке и размещаем случайно ориентированный эллипс и в другой четверти размещаем четырехугольник и одинаково раскрашиваем случайным шумом.
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
Создадим обучающую последовательность пар, посмотрим случайные 10. Напомню, что картинки монохромные, градации серого.
_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())
Это третья статья по анализу и изучению эллипсов, треугольников и других геометрических фигур.
Предыдущие статьи вызвали у читателей несколько очень интересных вопросов, в частности о сложности или простоте тех или иных обучающих последовательностей. Вопросы на самом деле очень интересные, например насколько треугольник сложнее для обучения, чем четырехугольник или другой многоугольник?

Попробуем сравнить, и для сравнения у нас есть отличная, проверенная поколениями студентов, идея — чем короче шпаргалка, тем легче экзамен.
Статья эта тоже есть просто результат любопытства и праздного интереса, ничего из нее в практике не встречается и для практических задач тут есть пара отличных идей, но нет почти ничего для копипастинга. Это небольшое исследование сложности обучающих последовательностей — рассуждения автора и код изложены, можно все проверить/дополнить/изменить самим.
Итак, попробуем выяснить, какая геометрическая фигура сложнее или проще для сегментации, какой курс лекций для ИИ понятней и лучше усваивается.
Геометрических фигур много разных, но мы будем сравнивать только треугольники, четырехугольники и пятиконечные звезды. Мы применим простой метод построения трейн последовательности — мы разделим 128х128 одноцветной картинки на четыре части и случайным образом будем помещать в эти четверти эллипс и, например, треугольник. Будем детектить треугольник того же цвета, что и эллипс. Т.е. задача состоит в том, что бы обучить сеть отличать, например четырехугольный полигон от эллипса, окрашенного в тот же цвет. Вот примеры картинок, которые будем изучать



Мы не будем детектить на одной картинке треугольник и четырехугольник, мы будем детектить их отдельно, в разных трейн, на фоне помехи в виде эллипса.
Возьмем для исследования классическую U-net и три вида обучающих последовательностей с треугольниками, четырехугольниками и звездами.
Итак, дано:
Идея для проверки:
Начнем, выберем 10 000 пар картинок четырехугольников с эллипсами и масок и рассмотрим их внимательно. Нам интересно, насколько короткой получится шпаргалка и от чего её длина зависит.
Мы будем использовать метрику из первой статьи. Напомню читателям, что будем предсказывать маску пикселя — это «фон» или «четырехугольник» и оценивать истинность или ложность предсказания. Т.е. возможны следующие четыре варианта — мы правильно предсказали, что пиксель это фон, правильно предсказали, что пиксель это четырехугольник или ошиблись в предсказании «фон» или «четырехугольник». И так по всем картинкам и всем пикселям оцениваем количество всех четырех вариантов и подсчитываем результат — это и будет результат работы сети. И чем меньше ошибочных предсказаний и больше истинных, то тем точнее полученный результат и лучше работа сети.
Мы исследуем сеть как «черный ящик», мы не станем смотреть, что происходит с сетью внутри, как меняются веса и как выбираются градиенты — заглянем в недра сети попозже, когда будем сравнивать сети.
Функция генерации пар картинка/маска. На черно-белой картинке 128х128 заполненной случайным шумом со случайно выбранным из двух диапазонов, или 0.0...0.75 или 0.25..1.0. Случайным образом выбираем четверть на картинке и размещаем случайно ориентированный эллипс и в другой четверти размещаем четырехугольник и одинаково раскрашиваем случайным шумом.
Создадим обучающую последовательность пар, посмотрим случайные 10. Напомню, что картинки монохромные, градации серого.

Предыдущие статьи вызвали у читателей несколько очень интересных вопросов, в частности о сложности или простоте тех или иных обучающих последовательностей. Вопросы на самом деле очень интересные, например насколько треугольник сложнее для обучения, чем четырехугольник или другой многоугольник?

Попробуем сравнить, и для сравнения у нас есть отличная, проверенная поколениями студентов, идея — чем короче шпаргалка, тем легче экзамен.
Статья эта тоже есть просто результат любопытства и праздного интереса, ничего из нее в практике не встречается и для практических задач тут есть пара отличных идей, но нет почти ничего для копипастинга. Это небольшое исследование сложности обучающих последовательностей — рассуждения автора и код изложены, можно все проверить/дополнить/изменить самим.
Итак, попробуем выяснить, какая геометрическая фигура сложнее или проще для сегментации, какой курс лекций для ИИ понятней и лучше усваивается.
Геометрических фигур много разных, но мы будем сравнивать только треугольники, четырехугольники и пятиконечные звезды. Мы применим простой метод построения трейн последовательности — мы разделим 128х128 одноцветной картинки на четыре части и случайным образом будем помещать в эти четверти эллипс и, например, треугольник. Будем детектить треугольник того же цвета, что и эллипс. Т.е. задача состоит в том, что бы обучить сеть отличать, например четырехугольный полигон от эллипса, окрашенного в тот же цвет. Вот примеры картинок, которые будем изучать



Мы не будем детектить на одной картинке треугольник и четырехугольник, мы будем детектить их отдельно, в разных трейн, на фоне помехи в виде эллипса.
Возьмем для исследования классическую U-net и три вида обучающих последовательностей с треугольниками, четырехугольниками и звездами.
Итак, дано:
- три обучающие последовательности пар картинка/маска;
- сеть. Обыкновенная U-net, которая широко используются для сегментации.
Идея для проверки:
- определим, какая из обучающих последовательностей «сложнее» для обучения;
- как влияют на обучение некоторые приемы предобработки.
Начнем, выберем 10 000 пар картинок четырехугольников с эллипсами и масок и рассмотрим их внимательно. Нам интересно, насколько короткой получится шпаргалка и от чего её длина зависит.
Загружаем библиотеки, определяем размеры массива картинок
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
определяем функции потерь и точности
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 })
Мы будем использовать метрику из первой статьи. Напомню читателям, что будем предсказывать маску пикселя — это «фон» или «четырехугольник» и оценивать истинность или ложность предсказания. Т.е. возможны следующие четыре варианта — мы правильно предсказали, что пиксель это фон, правильно предсказали, что пиксель это четырехугольник или ошиблись в предсказании «фон» или «четырехугольник». И так по всем картинкам и всем пикселям оцениваем количество всех четырех вариантов и подсчитываем результат — это и будет результат работы сети. И чем меньше ошибочных предсказаний и больше истинных, то тем точнее полученный результат и лучше работа сети.
Мы исследуем сеть как «черный ящик», мы не станем смотреть, что происходит с сетью внутри, как меняются веса и как выбираются градиенты — заглянем в недра сети попозже, когда будем сравнивать сети.
простая U-net
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()
Функция генерации пар картинка/маска. На черно-белой картинке 128х128 заполненной случайным шумом со случайно выбранным из двух диапазонов, или 0.0...0.75 или 0.25..1.0. Случайным образом выбираем четверть на картинке и размещаем случайно ориентированный эллипс и в другой четверти размещаем четырехугольник и одинаково раскрашиваем случайным шумом.
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
Создадим обучающую последовательность пар, посмотрим случайные 10. Напомню, что картинки монохромные, градации серого.
_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())

Первый шаг. Обучаем на минимальном стартовом множестве
-
Первый шаг нашего эксперимента простой, мы пробуем обучить сеть предсказывать всего 11 первых картинок.
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]
Мы выбрали из исходной последовательности первые 11 и обучили сеть на них. Сейчас не важно, заучивает сеть конкретно эти картинки или обобщает, главное, что эти 11 картинок она может распознать так, как нам нужно. В зависимости от выбранного датасета и точности, обучение сети может продолжаться долго, очень долго. Но у нас всего несколько итераций. Повторю, что нам сейчас не важно как и что заучила или выучила сеть, главное, что она достигла установленной точности предсказания.
Первый шаг нашего эксперимента простой, мы пробуем обучить сеть предсказывать всего 11 первых картинок.
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]
Мы выбрали из исходной последовательности первые 11 и обучили сеть на них. Сейчас не важно, заучивает сеть конкретно эти картинки или обобщает, главное, что эти 11 картинок она может распознать так, как нам нужно. В зависимости от выбранного датасета и точности, обучение сети может продолжаться долго, очень долго. Но у нас всего несколько итераций. Повторю, что нам сейчас не важно как и что заучила или выучила сеть, главное, что она достигла установленной точности предсказания.
Теперь начнем главный эксперимент
-
Построим шпаргалку, будем строить такие шпаргалки раздельно для всех трех обучающих последовательностей и сравнивать их длину. Мы будем брать новые пары картинка/маска из построенной последовательности и будем пытаться предсказать их сетью обученной на уже отобранной последовательности. В начале это всего 11 пар картинка/маска и сеть обучена, возможно и не очень корректно. Если в новой паре маска по картинке предсказывается с приемлемой точностью, то эту пару выбрасываем, в ней нет новой информации для сети, она уже знает и может вычислить по этой картинке маску. Если же точность предсказания недостаточна, то эту картинку с маской добавляем в нашу последовательность и начинаем тренировать сеть до достижения приемлемого результата точности на отобранной последовательности. Т.е. эта картинка содержит новую информацию и мы её добавляем в наш обучающую последовательность и извлекаем тренировкой содержащуюся в ней информацию.
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]
Здесь accuracy используется в смысле «точность», а не как стандартная метрика keras и для вычисления точности используется подпрограмма «my_iou_metric».
А теперь сравним работу той же самой сети с теми же самыми параметрами на другой последовательности, на треугольниках

И получим совсем другой результат
Accuracy 0.9823 loss 0.0108 selected img 1913 tested img 9995 : : 6343it [2:11:36, 3.03s/it]
Сеть выбрала 1913 картинок с «новой» информацией, т.е. содержательность картинок с треугольниками получается в два раза ниже, чем с четырехугольниками!
Проверим то же самое на звездах и запустим сеть на третьей последовательности

получим
Accuracy 0.8985 loss 0.0478 selected img 476 tested img 9985 : : 2188it [16:13, 1.16it/s]
Как видим, звезды оказались наиболее информативными, всего 476 картинок в шпаргалке.
У нас появились основания судить о сложности геометрических фигур для восприятия их нейронной сетью. Самая простая это звезда, всего 476 картинок в шпаргалке, далее четырехугольник с его 1007 и самым сложным оказался треугольник — для обучения нужно 1913 картинок.
Учтите, это для нас, для людей это картинки, а для сети это курс лекций по распознаванию и курс про треугольники оказался самым сложным.
Построим шпаргалку, будем строить такие шпаргалки раздельно для всех трех обучающих последовательностей и сравнивать их длину. Мы будем брать новые пары картинка/маска из построенной последовательности и будем пытаться предсказать их сетью обученной на уже отобранной последовательности. В начале это всего 11 пар картинка/маска и сеть обучена, возможно и не очень корректно. Если в новой паре маска по картинке предсказывается с приемлемой точностью, то эту пару выбрасываем, в ней нет новой информации для сети, она уже знает и может вычислить по этой картинке маску. Если же точность предсказания недостаточна, то эту картинку с маской добавляем в нашу последовательность и начинаем тренировать сеть до достижения приемлемого результата точности на отобранной последовательности. Т.е. эта картинка содержит новую информацию и мы её добавляем в наш обучающую последовательность и извлекаем тренировкой содержащуюся в ней информацию.
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]
Здесь accuracy используется в смысле «точность», а не как стандартная метрика keras и для вычисления точности используется подпрограмма «my_iou_metric».
А теперь сравним работу той же самой сети с теми же самыми параметрами на другой последовательности, на треугольниках

И получим совсем другой результат
Accuracy 0.9823 loss 0.0108 selected img 1913 tested img 9995 : : 6343it [2:11:36, 3.03s/it]
Сеть выбрала 1913 картинок с «новой» информацией, т.е. содержательность картинок с треугольниками получается в два раза ниже, чем с четырехугольниками!
Проверим то же самое на звездах и запустим сеть на третьей последовательности

получим
Accuracy 0.8985 loss 0.0478 selected img 476 tested img 9985 : : 2188it [16:13, 1.16it/s]
Как видим, звезды оказались наиболее информативными, всего 476 картинок в шпаргалке.
У нас появились основания судить о сложности геометрических фигур для восприятия их нейронной сетью. Самая простая это звезда, всего 476 картинок в шпаргалке, далее четырехугольник с его 1007 и самым сложным оказался треугольник — для обучения нужно 1913 картинок.
Учтите, это для нас, для людей это картинки, а для сети это курс лекций по распознаванию и курс про треугольники оказался самым сложным.
Теперь о серьезном
-
На первый взгляд все эти эллипсы и треугольники кажутся баловством, куличи из песка и лего. Но вот конкретный и серьезный вопрос: если к исходной последовательности применить какую нибудь предобработку, фильтр, то как изменится сложность последовательности? Например, возьмем всё те же эллипсы и четырехугольники и применим к ним вот такую предобработку
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")

На первый взгляд всё то же самое, такие же эллипсы, такие же полигоны, но сеть стала работать совсем по-другому:
Accuracy 1.0575 loss 0.0011 selected img 7963 tested img 9999 : : 17765it [29:02:00, 12.40s/it]
Тут необходимо небольшое пояснение, мы не используем аугментацию, т.к. форма полигона и форма эллипса изначально выбираются случайно. Поэтому аугментаци не даст новой информации и не имеет смысла с данном случае.
Но, как видно из результата работы, простой gaussian_filter создал сети много проблем, породил много новой, и наверно лишней, информации.
Ну и для любителей простоты в чистом виде, возьмем те же самые эллипсы с полигонами, но без какой бы то ни было случайности в цвете

результат говорит о том, что случайный цвет совсем не простая добавка.
Accuracy 0.9004 loss 0.0315 selected img 251 tested img 9832 : : 1000it [06:46, 1.33it/s]
Сеть вполне обошлась информацией, извлеченной из 251 картинки, почти в четыре раза меньше, чем из множества картинок раскрашенных шумом.
Цель статьи показать некоторый инструмент и примеры его работы на несерьезных примерах, лего в песочнице. Мы получили инструмент сравнения двух обучающих последовательностей, мы можем оценить насколько наша предобработка усложняет или упрощает обучающую последовательность, насколько тот или иной примитив в обучайющей последовательности прост для детекции.
Возможность применения этого лего примера в реальных делах очевидна, но реальные трейны и сети читателей дело самих читателей.
-
На первый взгляд все эти эллипсы и треугольники кажутся баловством, куличи из песка и лего. Но вот конкретный и серьезный вопрос: если к исходной последовательности применить какую нибудь предобработку, фильтр, то как изменится сложность последовательности? Например, возьмем всё те же эллипсы и четырехугольники и применим к ним вот такую предобработку
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")

На первый взгляд всё то же самое, такие же эллипсы, такие же полигоны, но сеть стала работать совсем по-другому:
Accuracy 1.0575 loss 0.0011 selected img 7963 tested img 9999 : : 17765it [29:02:00, 12.40s/it]
Тут необходимо небольшое пояснение, мы не используем аугментацию, т.к. форма полигона и форма эллипса изначально выбираются случайно. Поэтому аугментаци не даст новой информации и не имеет смысла с данном случае.
Но, как видно из результата работы, простой gaussian_filter создал сети много проблем, породил много новой, и наверно лишней, информации.
Ну и для любителей простоты в чистом виде, возьмем те же самые эллипсы с полигонами, но без какой бы то ни было случайности в цвете

результат говорит о том, что случайный цвет совсем не простая добавка.
Accuracy 0.9004 loss 0.0315 selected img 251 tested img 9832 : : 1000it [06:46, 1.33it/s]
Сеть вполне обошлась информацией, извлеченной из 251 картинки, почти в четыре раза меньше, чем из множества картинок раскрашенных шумом.
Цель статьи показать некоторый инструмент и примеры его работы на несерьезных примерах, лего в песочнице. Мы получили инструмент сравнения двух обучающих последовательностей, мы можем оценить насколько наша предобработка усложняет или упрощает обучающую последовательность, насколько тот или иной примитив в обучайющей последовательности прост для детекции.
Возможность применения этого лего примера в реальных делах очевидна, но реальные трейны и сети читателей дело самих читателей.
P.S. Как пример применения: на заводе производящем болты нужно наладить контроль и учет. Для чего нужно понять по видео или фото, какого размера болт, точно ли он изготовлен, есть ли каверны и прочий брак. Вы делаете конечный автомат (ансамбль) в узлах которого нейронные сети и первый слой распознает примитивы - эллипсы, ромбы, трапеции и т.д. и далее, другие узлы конечного автомата определяют нужные параметры.
Если автомат пропустил брак, то вы это узнаете рано или поздно и тогда можно спокойно и конкретно определить, пропущен ли примитив или пропущен более сложный объект и, добавив новую инфо в шпаргалку, переобучть только один узел конечного автомата.
- Теги:
-
P.S. Как пример применения: на заводе производящем болты нужно наладить контроль и учет. Для чего нужно понять по видео или фото, какого размера болт, точно ли он изготовлен, есть ли каверны и прочий брак. Вы делаете конечный автомат (ансамбль) в узлах которого нейронные сети и первый слой распознает примитивы - эллипсы, ромбы, трапеции и т.д. и далее, другие узлы конечного автомата определяют нужные параметры.
Если автомат пропустил брак, то вы это узнаете рано или поздно и тогда можно спокойно и конкретно определить, пропущен ли примитив или пропущен более сложный объект и, добавив новую инфо в шпаргалку, переобучть только один узел конечного автомата.
Если автомат пропустил брак, то вы это узнаете рано или поздно и тогда можно спокойно и конкретно определить, пропущен ли примитив или пропущен более сложный объект и, добавив новую инфо в шпаргалку, переобучть только один узел конечного автомата.
- Получить ссылку
- X
- Электронная почта
- Другие приложения
Комментарии
Отправить комментарий