Как отличать птиц от цветов. Или цветы от птиц

МЕНЮ


Искусственный интеллект. Новости
Поиск

ТЕМЫ


Внедрение ИИНовости ИИРобототехника, БПЛАТрансгуманизмЛингвистика, обработка текстаБиология, теория эволюцииВиртулаьная и дополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информации

RSS


RSS новости

Авторизация



Новостная лента форума ailab.ru

В качестве программы выходного дня мне захотелось поиграться с как бы «нейронной» сетью (спойлер — в ней нет нейронов). А чтобы потом не было мучительно больно за бесцельно прожитые годы часы, я подумал, что зря мы его кормим, пусть пользу приносит — пусть заодно эта сетка разберет домашний фотоархив и хотя бы разложит фотографии цветов в отдельную папку.

Самая простая сеть


Самая простая сеть нашлась в статье "Нейросеть в 11 строчек на Python" (это перевод от SLY_G статьи "A Neural Network in 11 lines of Python (Part 1)", вообще у автора есть еще продолжение "A Neural Network in 13 lines of Python (Part 2 — Gradient Descent)", но здесь достаточно первой статьи).

Краткое описание сетки — в этой сети есть ровно одна зависимость — NumPy.

Множество входов рассматривается как матрица $X$, множество выходов — как вектор $y$. В оригинальной статье сеть умножает входную матрицу, размерностью (4 x 3), на матрицу весов входов $syn0$ (3 x 4), к произведению применяет передаточную функцию, и получает матрицу слоя $l1$ (4 x 4).

$ X = egin{bmatrix} 1 & 2 & 3 & 4  5 & 6 & 7 & 8  9 & 10 & 11 & 12 end{bmatrix}  y = egin{bmatrix} 1  2  3 end{bmatrix} $

$ f(X 	imes syn0) implies l1 $

Далее слой $l1$ умножается на матрицу весов выходов $syn1$ (4 x 1), также пропускается через функцию, и получается слой $l2$ (4 x 1), который и есть результат работы сети.

$ f(l1 	imes syn1) implies l2 implies y $

Итого, опуская скалярную передаточную функцию, сеть реализует два матричных умножения:

$ X 	imes syn0 	imes syn1 implies y $

Следствием этого, согласно правилам матричного умножения, получилось, что одна из размерностей в ходе работы сети не изменяется $(4 * 3) 	imes (3 * 1) = (4 * 1)$ и получить на выходе единственное число невозможно.

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

Например, если нужно, чтобы было на входе матрица (3 x 4), а выход — единственное число, то добавляем две матрицы синапсов (4 x 1) и (3 x 1):

$((egin{bmatrix} 1 & 0 & 0 & 0  0 & 1 & 0 & 0  0 & 0 & 1 & 0 end{bmatrix} 	imes egin{bmatrix} 0  0 1  1 end{bmatrix})^T 	imes egin{bmatrix} 0  1  1 end{bmatrix})^T = [1]  (((3 * 4) 	imes (4 * 1))^T 	imes (3*1))^T = (1 * 1)  $

Или, скажем, можно преобразовать входную матрицу (10 x 8) на выход (4 x 5):

$(((10 * 8) 	imes (8 * 5))^T 	imes (10*4))^T = (4 * 5)$

Получившийся код:

nnmat.py

import numpy as np  def nonlin(x,deriv=False):     if(deriv==True):         return (x)*(1-(x))     return 1/(1+np.exp(-x))  def fmax(x,deriv=False):     if(deriv==True):         return 0.33     return np.maximum(x,0)/3  class NN:     def __init__(self, shapes, func=nonlin):         self.func = func         self.shapes = shapes         self.syns = [ 2*np.random.random((shapes[i-1][1],shapes[i][0])) - 1                       for i in range(1, len(shapes)) ]         self.layers = [ np.zeros(shapes[i])                         for i in range(1, len(shapes)) ]          def learn(self, X, y, cycles):         for j in range(cycles):             res = self.calc(X)             prev = y - res             for i in range(len(self.layers)-1,-1,-1):                 l_delta = (prev*self.func(self.layers[i], True)).T                 if i == 0:                     self.syns[i] += X.T.dot(l_delta)                 else:                     prev = l_delta.dot(self.syns[i].T)                     self.syns[i] += self.layers[i-1].T.dot(l_delta)         return self.layers[-1]      def calc(self,X):         for i in range(len(self.syns)):             if i == 0:                 self.layers[i] = self.func(np.dot(X,self.syns[i])).T             else:                 self.layers[i] = self.func(np.dot(self.layers[i-1],self.syns[i])).T         return self.layers[-1]  if __name__ == '__main__':     X = np.array([ [0,0,1],[0,1,1],[1,0,1],[1,1,1] ])     y = np.array([[0,1,1,0]])     print('X =',X)     print('y =',y)     nn = NN((X.shape, (y.shape[1], X.shape[0]), y.shape))     nn.learn(X,y,1000)     print('Result =',nn.calc(X).round(2))   

Результат работы:

X = [[0 0 1]  [0 1 1]  [1 0 1]  [1 1 1]] y = [[0 1 1 0]] Result = [[ 0.02  0.99  0.98  0.02]]

Загрузка фотографий

Так, сетка есть, теперь надо разобраться с загрузкой фоток. Фотографии лежат на диске, в основном в JPG, но встречаются и другие форматы. Размеры у них тоже разные, смотря чем снимали и как обрабатывали, от 3 Mpx до 16 Mpx. Сначала я попробовал загружать фотографии через Qt, класс QImage, он умеет работать с разными форматами, обеспечивает конверсию и дает прямой доступ к данным картинки. Наверняка в Python существует способ проще, но зато с QImage мне не надо было разбираться. Чтобы сеть могла работать с картинкой, следует перевести в монохромное изображение и уменьшить до стандартного размера.

def readImage(file, imageSize):         img = QImage(file)         if img.isNull():             return 0         img = img.convertToFormat(QImage.Format_Grayscale8)         img = img.scaled(imageSize[0],imageSize[1],Qt.IgnoreAspectRatio)         return img 

Для передачи в сетку нужно преобразовать изображение в матрицу numpy.ndarray. QImage.bits() дает указатель на данные изображения, где каждый байт соответствует пикселу. В NumPy нашлась функция recarray, способная сделать массив записей из буфера, а у него есть метод view, который нам сделает матрицу numpy.ndarray без копирования данных.

        srcBi = img.bits()         srcBi.setsize(img.width() * img.height())         srcBy = bytes(srcBi)         srcW, srcH = img.width(), img.height()         srcArr = np.recarray((srcH, srcW), dtype=np.int8, buf=srcBy).view(dtype=np.byte,type=np.ndarray) 

Сеть для изображений

Картинку, хоть и уменьшенную, непосредственно подавать на вход сети будет слишком накладно — я уже говорил, что сеть делает матричное умножение, поэтому даже один цикл обучения будет приводить к 400x400x400 = 64 млн. умножений. Знатоки рекомендуют использовать свертку. В Википедии есть замечательная иллюстрация ее работы:

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

srcArr[x:x+dw, y:y+dw]

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

Создание первичной сети:

class ImgNN:     def __init__(self, shape, resultShape = (16, 16), imageSize = (400,400)):         self.resultShape = resultShape         self.w = imageSize[0] // shape[0]         self.h = imageSize[1] // shape[1]         self.net = NN([shape, (1,shape[0]), (1,1)])         self.shape = shape         self.imageSize = imageSize 

Внутри создается self.net — собственно сеть, с заданным размером матрицы входов shape и c выходом в виде элементарной матрицы 1х1. Да, можно было наследоваться от класса сети NN, но был выходной, хотелось побыстрее получить результат, а архитектура еще не устоялась. Time to market бьется в наших сердцах!

Обсчет изображения первой сетью:

    def calc(self, srcArr):         w = srcArr.shape[0] // self.shape[0]         h = srcArr.shape[1] // self.shape[1]         resArr = np.zeros(self.resultShape)         for x in range(w):             for y in range(h):                 a = srcArr[x:x+self.shape[0], y:y+self.shape[1]]                 if a.shape != (self.shape[0], self.shape[1]):                     continue                 if x >= self.resultShape[0] or y >= self.resultShape[1]:                     continue                 res = self.nn.calc(a)                 resArr[x,y] = res[0,0]         return resArr 

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

    y = np.array([[1,0,1,0]])     firstShape = (40, 40)     middleShape = (5, 5)     imageSize = firstShape[0]*middleShape[0], firstShape[1]*middleShape[1] ...             nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize)             nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape]) ...                 i = readImage(f, imageSize)                 mid = nn.calc(i)                 res = nn2.calc(mid) 

Тут вы должны меня спросить, откуда я взял первую строчку, и что она значит:

y = np.array([[1,0,1,0]])

Это — ожидаемый результат сети в случае положительного ответа, т.е. если сеть считает, что на входе изображение цветка. Размерность выбрал из принципа «ни мало, ни много» — если брать размерность 1х1, то из одного получившегося числа трудно судить, насколько сеть «сомневается» в результате. Большую размерность задавать тоже смысла нет — она не даст больше информации. Равное количество нулей и единиц дает четкий ориентир — чем ближе к нему, тем больше совпадение. Если же взять все единицы или все нули, то у сети появится стимул к переобучению — увеличить все сомножители или, соответственно, обнулить их, чтобы получать нужный результат независимо от входных данных.

Как обучать сверточную сеть?

Обучающую выборку я сделал из своих же фотографий, попросту разложив их в два каталога: flowers


и noflowers

Пути к картинкам соберу в два массива

            import os             fl = [e.path for e in os.scandir('flowers')]             nofl = [e.path for e in os.scandir('noflowers')]             all = fl+nofl 

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

         for epoch in range(100):             print('Epoch =', epoch)             nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize)             nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape])             for f in fl:                 i = readImage(f, imageSize)                 # nn.learn(i, yy, 1)                 mid = nn.calc(i)                 nn2.learn(mid, y, 1000) 

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

            for f in all:                 i = readImage(f, imageSize)                 mid = nn.calc(i)                 res = nn2.calc(mid)                 delta = abs(y-res)                 v = round(np.std(delta),3) 

Если сеть обучилась правильно, то на ее выходе должно быть значение, близкое к заданному [[1,0,1,0]], если на входе цветок, и как можно более отличающееся от заданного, например [[0,1,0,1]], если на входе не цветок. Результат оценивается, эмпирически я принял отклонение от успешного результат не более 0,2 — это тоже успешный результат, и считается число ошибок. Из всех прогонов выбираем такую, где делается меньше всего ошибок, и сохраняем веса синапсов обоих сеток в файлы. Дальше эти файлы можно использовать для загрузки сеток.

                if v > 0.2 and f in fl:                     fails += 1                     failFiles.append(f)                 elif v<0.2 and f in nofl:                     fails +=1                     failFiles.append(f)             if minFails == None or fails < minFails:                 minFails = fails                 lastSyns = nn.net.syns                 lastSyns2 = nn2.syns             print('fails =',fails, failFiles)             print('min =',minFails)             if minFails <= 1:                 print('found!')                 break         for i in range(len(lastSyns)):             np.savetxt('syns_save%s.txt'%i, lastSyns[i])         for i in range(len(lastSyns2)):             np.savetxt('syns2_save%s.txt'%i, lastSyns2[i]) 

Хоть розой назови её, хоть нет

С надеждой запускаю и… подождав..., потом еще подождав..., и еще… получаю полный бред — сетка не обучается:
Ничего не вышло

flowers178.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers179.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers180.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers182.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers186-2.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers186.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers187.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers190 (2).jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers190.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers191.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers195.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers199.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers2.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers200.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers32.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers85.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
noflowers88.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers122.JPG res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers123.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers173.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
noflowers202.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers205.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowerscutxml.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowersGetaway.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
noflowersIMGP1800.JPG res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
noflowers rq-4.png res = [[ 0.97 0.51 0.97 0.51]] v = 0.239
fails = 14


Будучи носителем настоящих живых, а не искусственных нейронов, до меня дошло, что главным отличием цветов является цвет (да, кэп, спасибо, что ты всегда рядом, хотя зачастую опаздываешь со своими советами). Поэтому надо бы перевести его в какую-то цветовую модель, где цветовая составляющая будет выделена (HSV или HSL), и обучать сеть на цвете.

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

import cv2  def readImageCV(file, imageSize):     img = cv2.imread(file)     small = cv2.resize(img, imageSize)     hsv = cv2.cvtColor(small, cv2.COLOR_BGR2HSV)     return hsv[:,:,0]/255 

Правда, OpenCV наотрез отказался работать с русскими буквами в именах файлов, пришлось их переименовать.

Запустил — результат не порадовал, практически тот же.

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

        yy = np.zeros(middleShape)         np.fill_diagonal(yy,1) ...             for f in fl:                 i = readImage(f, imageSize)                 nn.learn(i, yy, 2) # чуть-чуть обучаем первую сетку                 mid = nn.calc(i)                 nn2.learn(mid, y, 1000) 

Снова запустил — стало куда интереснее, цифры стали меняться, хотя идеала не достиг.

Лучший результат

Epoch = 34
flowers178.jpg res = [[ 0.86 0.47 0.88 0.47]] v = 0.171
flowers179.jpg res = [[ 0.87 0.51 0.89 0.5 ]] v = 0.194
flowers180.jpg res = [[ 0.79 0.69 0.79 0.67]] v = 0.233
flowers182.jpg res = [[ 0.87 0.53 0.88 0.48]] v = 0.189
flowers186-2.jpg res = [[ 0.89 0.41 0.89 0.39]] v = 0.144
flowers186.jpg res = [[ 0.85 0.54 0.83 0.55]] v = 0.194
flowers187.jpg res = [[ 0.86 0.54 0.86 0.54]] v = 0.199
flowers190 (2).jpg res = [[ 0.96 0.25 0.97 0.15]] v = 0.089
flowers190.jpg res = [[ 0.95 0.13 0.97 0.14]] v = 0.048
flowers191.jpg res = [[ 0.81 0.57 0.82 0.57]] v = 0.195
flowers195.jpg res = [[ 0.81 0.55 0.79 0.56]] v = 0.177
flowers199.jpg res = [[ 0.89 0.45 0.89 0.45]] v = 0.171
flowers2.jpg res = [[ 0.83 0.56 0.83 0.55]] v = 0.195
flowers200.jpg res = [[ 0.91 0.42 0.89 0.43]] v = 0.163
noflowers32.jpg res = [[ 0.7 0.79 0.69 0.8 ]] v = 0.246
noflowers85.jpg res = [[ 0.86 0.53 0.86 0.53]] v = 0.192
noflowers88.jpg res = [[ 0.86 0.56 0.87 0.53]] v = 0.207
noflowers122.JPG res = [[ 0.81 0.63 0.81 0.62]] v = 0.218
noflowers123.jpg res = [[ 0.83 0.59 0.84 0.55]] v = 0.204
noflowers173.jpg res = [[ 0.83 0.6 0.83 0.58]] v = 0.209
noflowers202.jpg res = [[ 0.78 0.7 0.8 0.65]] v = 0.234
noflowers205.jpg res = [[ 0.84 0.77 0.79 0.75]] v = 0.287
noflowerscutxml.jpg res = [[ 0.81 0.61 0.81 0.63]] v = 0.213
noflowersGetaway.jpg res = [[ 0.85 0.56 0.85 0.55]] v = 0.202
noflowersIMGP1800.JPG res = [[ 0.85 0.55 0.86 0.54]] v = 0.199
noflowers rq-4.png res = [[ 0.7 0.72 0.7 0.71]] v = 0.208
fails = 3 ['flowers180.jpg', 'noflowers85.jpg', 'noflowersIMGP1800.JPG']
min = 3


Дальше… А дальше выходной закончился, и мне пора было заниматься хозработами.

Что делать дальше?

Конечно, и эта сеть, и то, как я ее учил, и тестовый dataset очень мало соотносятся с реальными сетями и тем, чем занимаются data scientists. Это лишь игрушка для гимнастики ума, не возлагайте на нее больших надежд. Можно наметить дальнейшие шаги, как добиться нужного результата (если он вам нужен):

  1. Добавить еще один промежуточный слой или несколько во вторую сеть — так у нее появится больше свободы в обучении. Все-таки сеть на матричном умножении не совсем классическая, так как в ней меньше связей-синапсов между слоями, да и сами синапсы не уникальны.
  2. Использовать приближения к успешным результатам как заготовки для последующих обучений — т.е. запоминать веса синапсов самого успешного результата, а не затиратьвсе случайными значениями.
  3. Попробовать генетические алгоритмы — смешивать и делить, размножать успешное и отбраковывать неудачное.
  4. Пробовать другие способы обучения, коих уже вагон и маленькая тележка.
  5. Использовать больше информации из исходного изображения, например, одновременно цвет и монохром подавать на различные сети, результаты обрабатывать в общей сети.

Исходный код

import numpy as np from nnmat import * import os  import sys from PyQt5.QtGui import * from PyQt5.QtCore import * import meshandler   import random import cv2  class ImgNN:     def __init__(self, shape, resultShape = (16, 16), imageSize = (400,400)):         self.resultShape = resultShape         self.w = imageSize[0] // shape[0]         self.h = imageSize[1] // shape[1]         self.net = NN([shape, (1,shape[0]), (1,1)])         self.shape = shape         self.imageSize = imageSize      def learn(self, srcArr, result, cycles):         for c in range(cycles):             for x in range(self.w):                 for y in range(self.h):                     a = srcArr[x:x+self.shape[0], y:y+self.shape[1]]                     if a.shape != (self.shape[0], self.shape[1]):                         print(a.shape)                         continue                     self.net.learn(a, result[x,y], 1)      def calc(self, srcArr):         resArr = np.zeros(self.resultShape)         for x in range(self.w):             for y in range(self.h):                 a = srcArr[x:x+self.shape[0], y:y+self.shape[1]]                 if a.shape != (self.shape[0], self.shape[1]):                     continue                 if x >= self.resultShape[0] or y >= self.resultShape[1]:                     continue                 res = self.net.calc(a)                 resArr[x,y] = res[0,0]         return resArr              def learnFile(self, file, result, cycles):         return self.learn(readImage(file, self.imageSize), result, cycles)      def calcFile(self, file):         return self.calc(readImage(file, self.imageSize))  def readImageCV(file, imageSize):     img = cv2.imread(file)     small = cv2.resize(img, imageSize)     hsv = cv2.cvtColor(small, cv2.COLOR_BGR2HSV)     return hsv[:,:,0]/255  def readImageQ(file, imageSize):     img = QImage(file)     if img.isNull():         return 0     img = img.convertToFormat(QImage.Format_Grayscale8)     img = img.scaled(imageSize[0],imageSize[1],Qt.IgnoreAspectRatio)     srcBi = img.bits()     srcBi.setsize(img.width() * img.height())     srcBy = bytes(srcBi)      srcW, srcH = img.width(), img.height()     srcArr = np.recarray((srcH, srcW), dtype=np.uint8, buf=srcBy).view(dtype=np.uint8,type=np.ndarray)     return srcArr/255  if __name__ == '__main__':     readImage = readImageCV          y = np.array([[1,0,1,0]])     firstShape = (40, 40)     middleShape = (10, 10)     imageSize = firstShape[0]*middleShape[0], firstShape[1]*middleShape[1]          StartLearn = True      if not StartLearn:         pictDir = '2014-05'         nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize)         nn.net.syns[0] = np.loadtxt('syns_save0.txt')         nn.net.syns[1] = np.loadtxt('syns_save1.txt')         nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape])         nn2.syns[0] = np.loadtxt('syns2_save0.txt')         nn2.syns[1] = np.loadtxt('syns2_save1.txt')         files = [e.path for e in os.scandir(pictDir)]         for f in files:             i = readImage(f, imageSize)             res = nn2.calc(i)             delta = y-res             v = round(np.std(delta),3)             if v < 0.2:                 print('Flower',f)             else:                 print('No flower',f)     else:             fl = [e.path for e in os.scandir('flowers')]         nofl = [e.path for e in os.scandir('noflowers')]         all = fl+nofl         yy = np.zeros(middleShape)         np.fill_diagonal(yy,1)         minFails = None         for epoch in range(100):             print('Epoch =', epoch)             nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize)             nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape])             for f in fl:                 i = readImage(f, imageSize)                 nn.learn(i, yy, 2)                 mid = nn.calc(i)                 nn2.learn(mid, y, 1000)             fails = 0             failFiles = []             for f in all:                 i = readImage(f, imageSize)                 mid = nn.calc(i)                 res = nn2.calc(mid)                 delta = abs(y-res)                 v = round(np.std(delta),3)                 #v = round(delta.sum(),3)                 print(f, 'res = ', res.round(2),'v =',v)                 if v > 0.2 and f in fl:                     fails += 1                     failFiles.append(f)                 elif v<0.2 and f in nofl:                     fails +=1                     failFiles.append(f)             if minFails == None or fails < minFails:                 minFails = fails                 lastSyns = nn.net.syns                 lastSyns2 = nn2.syns             print('fails =',fails, failFiles)             print('min =',minFails)             if minFails <= 1:                 print('found!')                 break         for i in range(len(lastSyns)):             np.savetxt('syns_save%s.txt'%i, lastSyns[i])         for i in range(len(lastSyns2)):             np.savetxt('syns2_save%s.txt'%i, lastSyns2[i]) 

Источник: habrahabr.ru