Кручу-верчу, обмануть хочу

от автора

Long story short

Создают ли повороты ложные зависимости в датасете?

Небольшое исследование свойств rotate.


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

После этой сети банк уже понимает, что информация скорее всего неподдельная и её можно обрабатывать и применяет другие аргументы и иные сети и соглашается или отказывает заявителю.
Если банк использует только предсказание сети, то он прогорит, но об этом другая статья.

И представим для простоты, что человеческое тело это шар соискатель предоставляет просто матрицу W_SIZE x W_SIZE, ну или 128х128, например.
Почему бы и нет!

И мы не станем пытаться объять необъятное и искать все те способы обработки, что могли быть применены.

Мы возьмем матрицу w_size х w_size, заполненную случайно, мы же не знаем, что там, в реальности, и нам годятся все матрицы такого размера, и проверим, не поворачивал ли некто эту картинку(матрицу).

Т.е. будем решать максимально упрощенную задачу — в последовательности матриц/картинок будем искать те, что искусственно были повернуты. Ну и на такой же последовательности матриц будем и учить.

Использовать будем простую сеть на keras и обычные пакеты обработки, в которых есть функция «поворот»

Обычным способом грузим

import numpy as np import cv2  from scipy import ndimage, misc, stats from skimage import exposure  from matplotlib import pyplot as plt, colors # work in interactive moode %matplotlib inline   from tensorflow import keras from tensorflow.keras import layers  from math import sqrt, sin, cos, radians  import tqdm

Теперь создадим обучающую и тестовую последовательности. Создадим одну и поделим её пополам. Картинки/матрицы у нас будут такие — круг на черном фоне заполненный случайными значениями с заранее определенным распределением. При поворотах границы вносят существенные искажения, поэтому и берем круг в квадрате.

w_size = 128 w2_size = w_size // 2 R = w2_size//2 center = (w2_size, w2_size) scale = 1  circle = np.zeros((w_size, w_size), dtype='uint8') for i in range(w_size):     for j in range(w_size):         ii = float(i - w2_size)         jj = float(j - w2_size)         r = sqrt(ii*ii + jj*jj)         if r < R:             circle[i,j] = 1  def shift(x,y,w2_size):     ii = float(x - w2_size)     jj = float(y - w2_size)     return ii,jj   def rotate(ii,jj,teta):     i1 =  ii*cos(teta) + jj*sin(teta)     j1 =  ii*sin(teta) + jj*cos(teta)     return i1,j1   circle = np.zeros((w_size, w_size), dtype='int') for i in range(w_size):     for j in range(w_size):         ii,jj = shift(i,j,w2_size)         r = sqrt(ii*ii + jj*jj)         if r < R:             circle[i,j] = 1  fig, ax = plt.subplots(1, 1,figsize=(5, 5)) ax.set_axis_off() ax.set_title("circle") ax.imshow(circle.squeeze(), cmap='gray', norm=None) 

Первый эксперимент

Первый эксперимент проведем умозрительно, на бумаге.
Заполним квадрат с помощью numpy.random.uniform, после повернем с помощью ndimage.rotate на случайно выбранный угол.
Или не повернем. И из таких квадратов составим наши последовательности.
И признак для классификации оставим такой — 0, если не повернут и 1 если квадрат повернут.
И в каждом квадрате вырезаем центральный круг. Т.е. артефакты, вызванные граничными точками, убираем.
Нам нужен только круг в центре.

Очевидно, что сеть их отличит очень уверенно, на графиках гистограмм можем увидеть подтверждение ЦПТ, исходные картинки с ровной (почти) гистограммой, повернутые картинки дают гистограмму в виде горба ( очень похож на Гауссиан )

Так что дальше будем проводить эксперименты только с numpy.random.normal распределением.

Для тех, кто не верит на слово, пожалуйста, код.

num_classes = 2 train_len = 5000 test_len = 1000 input_shape = (w_size, w_size, 1)  X = np.zeros((train_len+test_len, w_size, w_size, 1), dtype='float32') Y = np.zeros(train_len+test_len, dtype='float32')  mu = 0.25 sigma = 0.05  for iii in tqdm.tqdm(range(train_len + test_len)):          angle = np.random.randint(-5,5)+ 45      n = np.random.uniform(0.25, 0.75, size=(w_size, w_size))     t = np.zeros((w_size, w_size), dtype='float')     t[circle>0] = n[circle>0]          r = ndimage.rotate(n, angle, reshape=False, mode='nearest')     tt = np.zeros((w_size, w_size), dtype='float')     tt[circle>0] = r[circle>0]      choise = np.random.randint(0, high=65535, dtype=int) % 2     if choise == 1:         X[iii,:,:,0] = t[:,:]         Y[iii] = 0     else:         X[iii,:,:,0] = tt[:,:]         Y[iii] =1  yy_train = np.array(keras.utils.to_categorical(Y[:train_len], num_classes)) yy_test = np.array(keras.utils.to_categorical(Y[train_len:], num_classes))  xx_train = np.array(X[:train_len]) xx_test = np.array(X[train_len:])  print(xx_train.shape, yy_train.shape) print(xx_test.shape, yy_test.shape) 
 nrows=2 ncols=4 fig, ax = plt.subplots(nrows*2, ncols,figsize=(16, 20))  for ii in range(nrows):     for jj in range(ncols):         random_characters = int(np.random.uniform(0,train_len))         ax[2*ii,jj].set_axis_off()         ax[2*ii,jj].set_title(str(int(Y[random_characters])))         ax[2*ii,jj].imshow(X[random_characters].squeeze(), cmap='gray', norm=None)         tx = X[random_characters]         ax[2*ii+1,jj].hist(255.*X[random_characters].flatten(), np.arange(1,256), facecolor='blue',histtype='step')  plt.show(block=True)

Второй эксперимент

Второй эксперимент проведем, заполняя исходные квадраты случайными значениями уже с numpy.random.normal распределением.
Точно так же построим последовательность картинок, заполненных случайными значениями нормального распределения. И часть картинок повернем.
Оказывается сеть спокойно их различает.
Простой анализ гистограмм показывает, что они разные у повернутых и оригинальных картинок. Очень похожи, но совсем разные. Тест на нормальность почти все повернутые не проходят.

X = np.zeros((train_len+test_len, w_size, w_size, 1), dtype='float32') Y = np.zeros(train_len+test_len, dtype='float32')  mu = 0.45 sigma = 0.1  for iii in tqdm.tqdm(range(train_len + test_len)):          angle = np.random.randint(-5,5)+ 45      n = np.random.normal(mu, sigma, size=(w_size, w_size))     t = np.zeros((w_size, w_size), dtype='float')     t[circle>0] = n[circle>0]          r = ndimage.rotate(n, angle, reshape=False, mode='nearest')     tt = np.zeros((w_size, w_size), dtype='float')     tt[circle>0] = r[circle>0]      choise = np.random.randint(0, high=65535, dtype=int) % 2     if choise == 1:         X[iii,:,:,0] = t[:,:]         Y[iii] = 0     else:         X[iii,:,:,0] = tt[:,:]         Y[iii] =1  yy_train = np.array(keras.utils.to_categorical(Y[:train_len], num_classes)) yy_test = np.array(keras.utils.to_categorical(Y[train_len:], num_classes))  xx_train = np.array(X[:train_len]) xx_test = np.array(X[train_len:])  print(xx_train.shape, yy_train.shape) print(xx_test.shape, yy_test.shape) 
nrows=2 ncols=4 fig, ax = plt.subplots(nrows*2, ncols,figsize=(16, 20))  for ii in range(nrows):     for jj in range(ncols):         random_characters = int(np.random.uniform(0,train_len))         ax[2*ii,jj].set_axis_off()         ax[2*ii,jj].set_title(str(int(Y[random_characters])))         ax[2*ii,jj].imshow(X[random_characters].squeeze(), cmap='gray', norm=None)         ax[2*ii+1,jj].hist(255.*X[random_characters].flatten(), np.arange(1,256),\                            facecolor='blue',histtype='step')  plt.show(block=True) 
model = keras.Sequential(     [         keras.Input(shape=input_shape),         layers.Conv2D(32, kernel_size=(3, 3), activation="relu"),         layers.Conv2D(32, kernel_size=(3, 3), activation="relu"),         layers.MaxPooling2D(pool_size=(2, 2)),         layers.Conv2D(64, kernel_size=(3, 3), activation="relu"),         layers.Conv2D(64, kernel_size=(3, 3), activation="relu"), #        layers.MaxPooling2D(pool_size=(2, 2)),         layers.Flatten(),         layers.Dropout(0.5),         layers.Dense(128, activation="relu"),         layers.Dense(num_classes, activation="softmax"),     ] )  batch_size = 25 epochs = 5 num_classes = 2 n_bins = 256  model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) #model.summary()  model.fit(xx_train, yy_train, batch_size=batch_size, epochs=epochs,            validation_data=(xx_test, yy_test), verbose = 2) 

Epoch 1/5

200/200 — 6s — loss: 0.2437 — accuracy: 0.8638 — val_loss: 4.7537e-05 — val_accuracy: 1.0000

Epoch 2/5

200/200 — 4s — loss: 3.7514e-05 — accuracy: 1.0000 — val_loss: 2.2031e-05 — val_accuracy: 1.0000

Epoch 3/5

200/200 — 4s — loss: 1.8480e-05 — accuracy: 1.0000 — val_loss: 1.1520e-05 — val_accuracy: 1.0000

Epoch 4/5

200/200 — 4s — loss: 1.0300e-05 — accuracy: 1.0000 — val_loss: 6.6436e-06 — val_accuracy: 1.0000

Epoch 5/5

200/200 — 4s — loss: 5.9791e-06 — accuracy: 1.0000 — val_loss: 4.1065e-06 — val_accuracy: 1.0000

Третий эксперимент

Третий эксперимент проведем, заполняя исходные квадраты случайными значениями с numpy.random.normal распределением.
Точно так же построим последовательность картинок, заполненных случайными значениями нормального распределения.
И часть картинок повернем.

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

Не так уж он оказался умен, этот ваш искусственный интеллект

X = np.zeros((train_len+test_len, w_size, w_size, 1), dtype='float32') Y = np.zeros(train_len+test_len, dtype='float32')  mu = 0.45 sigma = 0.1  view_test = np.random.randint(0, train_len+test_len, (3))  for iii in tqdm.tqdm(range(train_len + test_len)):          angle = np.random.randint(-5,5)+ 45      n = np.random.normal(mu, sigma, size=(w_size, w_size))     t = np.zeros((w_size, w_size), dtype='float')     t[circle>0] = n[circle>0]     tf = t.flatten()          r = ndimage.rotate(n, angle, reshape=False, mode='nearest')     tt = np.zeros((w_size, w_size), dtype='float')     tt[circle>0] = r[circle>0]     tts = tt.copy()     ttf = tt.flatten()      step = np.arange(0., 1., 1./255.)          for ii in range(1):         for i in range(1,253):             T = np.sum(np.bitwise_and(tf>step[i], tf<step[i+1]))             TT = np.sum(np.bitwise_and(ttf>step[i], ttf<step[i+1]))             T_TT = abs(T-TT)             if T_TT == 0:                 continue             if T>TT:                 jj = 0                 while True:                     tt_array = np.nonzero(np.bitwise_and(ttf>step[i+1],ttf<step[i+2]))[0]                     if len(tt_array)>=T_TT or i+jj > w_size-2:                         break                     ttf[ttf>step[i+2]] -=step[1]                     jj += 1                 indices = np.arange(len(tt_array))                 np.random.shuffle(indices)                 for j in range(min(len(tt_array), T_TT)):                     ttf[tt_array[indices[j]]] -= step[1]             else:                 tt_array = np.nonzero(np.bitwise_and(ttf>step[i], ttf<step[i+1]))[0]                 if len(tt_array) != 0:                     indices = np.arange(len(tt_array))                     np.random.shuffle(indices)                     for j in range(min(len(tt_array), T_TT)):                         ttf[tt_array[indices[j]]] +=step[1]                                  tt = ttf.reshape(w_size,w_size)      choise = np.random.randint(0, high=65535, dtype=int) % 2     if choise == 1:         X[iii,:,:,0] = t[:,:]         Y[iii] = 0     else:         tt = ttf.reshape(w_size,w_size)         X[iii,:,:,0] = tt         Y[iii] =1          if iii in view_test:         random_idx = int(np.random.uniform(0,train_len))         alpha = 0.05         fig, axs = plt.subplots(1, 3, figsize=(30, 10))              stat, p = stats.normaltest(t[circle>0].flatten())         if p > alpha:             axs[0].set_title("Оригинал Принять "+ str(p))         else:             axs[0].set_title("Оригинал Отклонить ?"+ str(p))         axs[0].set_axis_off()         axs[0].imshow(t, cmap="gray")              stat, pp = stats.normaltest(tt[circle>0].flatten())         if pp > alpha:             axs[1].set_title("выровненный поворот Принять "+ str(pp))         else:             axs[1].set_title("выровненный поворот Отклонить "+ str(pp))         axs[1].set_axis_off()         axs[1].imshow(tt, cmap="gray")              stat, p = stats.normaltest(tts[circle>0].flatten())         if p > alpha:             axs[2].set_title("просто поворот Принять "+ str(p))         else:             axs[2].set_title("просто поворот Отклонить "+ str(p))         axs[2].set_axis_off()         axs[2].imshow(tts, cmap="gray")                       tt_hist,b = np.histogram(256*tt.flatten(), np.arange(1,257))         tts_hist,b = np.histogram(256*tts.flatten(), np.arange(1,257))         t_hist,_ = np.histogram(256*t.flatten(), np.arange(1,257))         fig, axs = plt.subplots(1, 1, figsize=(30, 30))         bins = np.arange(1,256)         axs.plot(bins, tts_hist, 'y')         axs.plot(bins, tt_hist, 'b')         axs.plot(bins, t_hist, 'g')         plt.show(block=True)  yy_train = np.array(keras.utils.to_categorical(Y[:train_len], num_classes)) yy_test = np.array(keras.utils.to_categorical(Y[train_len:], num_classes))  xx_train = np.array(X[:train_len]) xx_test = np.array(X[train_len:])  print(xx_train.shape, yy_train.shape) print(xx_test.shape, yy_test.shape) 

Сеть та же самая.

batch_size = 25 epochs = 5 num_classes = 2 n_bins = 256  model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) #model.summary()  model.fit(xx_train, yy_train, batch_size=batch_size, epochs=epochs,            validation_data=(xx_test, yy_test), verbose = 2) 
  • Epoch 1/5

  • 200/200 — 4s — loss: 0.6990 — accuracy: 0.4966 — val_loss: 0.6932 — val_accuracy: 0.4970

  • Epoch 2/5

  • 200/200 — 4s — loss: 0.6932 — accuracy: 0.4992 — val_loss: 0.6932 — val_accuracy: 0.4970

  • Epoch 3/5

  • 200/200 — 4s — loss: 0.6932 — accuracy: 0.4988 — val_loss: 0.6931 — val_accuracy: 0.5030

  • Epoch 4/5

  • 200/200 — 4s — loss: 0.6932 — accuracy: 0.4930 — val_loss: 0.6931 — val_accuracy: 0.4970

  • Epoch 5/5

  • 200/200 — 4s — loss: 0.6932 — accuracy: 0.4990 — val_loss: 0.6932 — val_accuracy: 0.4970

Главный эксперимент

Ну и главный эксперимент проведем на эллипсах. Очень хорошая и удобная геометрическая фигура. Картинки для испытаний создадим так — тот же квадрат, заполненный случайными данными нормального распределения и внутри квадрата эллипс, заполненный случайными значениями тоже нормального распределения, но с другими ожиданием и дисперсией. Так же после поворота выровняем гистограмму, так же как и в предыдущем эксперименте. И вот такие повороты нейронная простая сеть отлично находит.

Т.е. если картинка структурирована и неоднородна, то даже если сегменты по распределению не искажаются — все равно сеть определяет поворот исходной картинки.

w_size = 128 w2_size = w_size // 2 R = w2_size//2 center = (w2_size, w2_size) scale = 1  RR = R //2 A = RR + 4 B = RR - 4 c = sqrt(float(A*A - B*B))  num_classes = 2 train_len = 5000 test_len = 1000 input_shape = (w_size, w_size, 1)  circle = np.zeros((w_size, w_size), dtype='uint8') for i in range(w_size):     for j in range(w_size):         ii = float(i - w2_size)         jj = float(j - w2_size)         r = sqrt(ii*ii + jj*jj)         if r < R:             circle[i,j] = 1  def shift(x,y,w2_size):     ii = float(x - w2_size)     jj = float(y - w2_size)     return ii,jj   def rotate(ii,jj,teta):     i1 =  ii*cos(teta) + jj*sin(teta)     j1 =  ii*sin(teta) + jj*cos(teta)     return i1,j1   circle = np.zeros((w_size, w_size), dtype='int') for i in range(w_size):     for j in range(w_size):         ii,jj = shift(i,j,w2_size)         r = sqrt(ii*ii + jj*jj)         if r < R:             circle[i,j] = 1  def ellipse_p(teta_grad):     ellipse = np.zeros((w_size, w_size), dtype='float32')     teta = radians(teta_grad)     ci1, cj1 = rotate( c,0,teta)     ci2, cj2 = rotate(-c,0,teta)     for i in range(w_size):         for j in range(w_size):             ii,jj = shift(i,j,w2_size)             r1 = sqrt((ii-ci1)*(ii-ci1) + (jj-cj1)*(jj-cj1))             r2 = sqrt((ii-ci2)*(ii-ci2) + (jj-cj2)*(jj-cj2))                          if r1+r2 < 2*A:                 ellipse[i,j] = 255     return (ellipse)  teta_grad = 45.0 ellipse = ellipse_p(teta_grad)  fig, ax = plt.subplots(1, 2,figsize=(10, 20)) ax[0].set_axis_off() ax[0].set_title("circle") ax[0].imshow(circle.squeeze(), cmap='gray', norm=None) ax[1].set_axis_off() ax[1].set_title("ellipse") ax[1].imshow(ellipse.squeeze(), cmap='gray', norm=None) 
X = np.zeros((train_len+test_len, w_size, w_size, 1), dtype='float32') Y = np.zeros(train_len+test_len, dtype='float32')  mu = 0.35 sigma = 0.1 el_mu = 0.65 el_sigma = 0.1 view_test = np.random.randint(0, train_len+test_len, (3))  for iii in tqdm.tqdm(range(train_len + test_len)):          angle = np.random.randint(-5,5)+ 45      n = np.random.normal(mu, sigma, size=(w_size, w_size))     el = np.random.normal(el_mu, el_sigma, size=(w_size, w_size))     t = np.zeros((w_size, w_size), dtype='float')     t[circle>0] = n[circle>0]     ellipse = ellipse_p(angle)     t[ellipse>0] = el[ellipse>0]     tf = t.flatten()          ellipse = ellipse_p(0)     n[ellipse>0] = el[ellipse>0]     r = ndimage.rotate(n, angle, reshape=False, mode='nearest')     tts = np.zeros((w_size, w_size), dtype='float')     tts[circle>0] = r[circle>0]     ttf = tts.flatten()           step = np.arange(0., 1., 1./255.)          for ii in range(1):         for i in range(1,253):             T = np.sum(np.bitwise_and(tf>step[i], tf<step[i+1]))             TT = np.sum(np.bitwise_and(ttf>step[i], ttf<step[i+1]))             T_TT = T-TT             if abs(T_TT) == 0:                 continue             if T>TT:                 jj = 0                 while True:                     tt_array = np.nonzero(np.bitwise_and(ttf>step[i+1],ttf<step[i+2]))[0]                     if len(tt_array)>=T_TT or i+jj > w_size-2:                         break                     ttf[ttf>step[i+2]] -=step[1]                     jj += 1                 indices = np.arange(len(tt_array))                 np.random.shuffle(indices)                 for j in range(min(len(tt_array), T_TT)):                     ttf[tt_array[indices[j]]] -= step[1]             else:                 tt_array = np.nonzero(np.bitwise_and(ttf>step[i], ttf<step[i+1]))[0]                 if len(tt_array) != 0:                     indices = np.arange(len(tt_array))                     np.random.shuffle(indices)                     for j in range(min(len(tt_array), abs(T_TT))):                         ttf[tt_array[indices[j]]] +=step[1]                                  tt = ttf.reshape(w_size,w_size)      choise = np.random.randint(0, high=65535, dtype=int) % 2     if choise == 1:         X[iii,:,:,0] = t[:,:]         Y[iii] = 0     else:         tt = ttf.reshape(w_size,w_size)         X[iii,:,:,0] = tt         Y[iii] =1              if iii in view_test:         random_idx = int(np.random.uniform(0,train_len))         alpha = 0.05         fig, axs = plt.subplots(1, 3, figsize=(30, 10))         axs[0].set_axis_off()         axs[1].set_axis_off()         axs[2].set_axis_off()         axs[0].imshow(t, cmap="gray")         axs[1].imshow(tt, cmap="gray")         axs[2].imshow(tts, cmap="gray")                      tt_hist,b = np.histogram(256*tt.flatten(), np.arange(1,257))         tts_hist,b = np.histogram(256*tts.flatten(), np.arange(1,257))         t_hist,_ = np.histogram(256*t.flatten(), np.arange(1,257))         fig, axs = plt.subplots(1, 1, figsize=(30, 30))         bins = np.arange(1,256)         axs.plot(bins, tts_hist, 'y')         axs.plot(bins, tt_hist, 'b')         axs.plot(bins, t_hist, 'g')         plt.show(block=True)  yy_train = np.array(keras.utils.to_categorical(Y[:train_len], num_classes)) yy_test = np.array(keras.utils.to_categorical(Y[train_len:], num_classes))  xx_train = np.array(X[:train_len]) xx_test = np.array(X[train_len:])  print(xx_train.shape, yy_train.shape) print(xx_test.shape, yy_test.shape) print(np.min(X), np.max(X))
batch_size = 25 epochs = 5 num_classes = 2 n_bins = 256  model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"]) #model.summary()  model.fit(xx_train, yy_train, batch_size=batch_size, epochs=epochs,            validation_data=(xx_test, yy_test), verbose = 2)  Epoch 1/5 200/200 - 4s - loss: 0.3143 - accuracy: 0.8330 - val_loss: 0.0030 - val_accuracy: 0.9990 Epoch 2/5 200/200 - 4s - loss: 0.0077 - accuracy: 0.9978 - val_loss: 2.2245e-04 - val_accuracy: 1.0000 Epoch 3/5 200/200 - 4s - loss: 0.0019 - accuracy: 0.9994 - val_loss: 4.2272e-04 - val_accuracy: 1.0000 Epoch 4/5 200/200 - 4s - loss: 0.0091 - accuracy: 0.9966 - val_loss: 9.0115e-04 - val_accuracy: 0.9990 Epoch 5/5 200/200 - 4s - loss: 0.0105 - accuracy: 0.9964 - val_loss: 0.0067 - val_accuracy: 0.9980

Краткое резюме.

Если в вашем датасете с картинками не выровненные классы, и вы решили добавить картинок для выравнивания с помощью аугментации, то результат будет удивительным и коварным.


ссылка на оригинал статьи https://habr.com/ru/post/677394/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *