⬆️ ⬇️

Flowers, fly and well rehearsed random machine learning

In the previous series, I tried to make a reasonable creature out of a fly. In short - did not work. Mucha stubbornly did not want to learn.







The fly was a small simple neural network based on matrix multiplication, sigmoid, and back propagation of the error. Her teaching was to recognize photos with flowers.

')

Let me remind you that inside there are two networks - the first analyzes the pieces of the original image, and the second works with the matrix composed of the results of the first grid.



Training took place somehow, there was no result. Then, having abandoned attempts at training for valid reasons (such as Saturday evening, Sunday night and morning), I still wondered what to do next. Some possible solutions were outlined at the end of the first article, and continued with them.



Option 1. Another intermediate layer



It was assumed that if you add neurons to a fly, the fly will become a little smarter. Adding a layer is easy — instead of



nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape]) 


write



 nn2 = NN([middleShape, (40, middleShape[0]), (y.shape[1], 40), y.shape]) 


The layer was added, the process of learning and calculation became noticeably slower, but not better. In fact, the result has not changed at all, therefore I do not cite here.



Option 2. Use the approach to success as a blank



The idea was to memorize the weights of the synapses of the most successful learning result, and train the network with these weights, and not score with random values, as usual.



The synapses in my grid are the contents of the nn.net.syns and nn2.syns attributes for the first and second grids, respectively. The result was estimated by the number of mistakes made, i.e. the number of misclassified photographs for the entire sample, including both flowers and non-flowers.



  minFails = None lastSyns = None for epoch in range(100): 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) nextSyns=None fails = 0 failFiles = [] #    for f in all: i = readImage(f, imageSize) mid = nn.calc(i) res = nn2.calc(mid) delta = (y-res) v = round(np.std(delta),3) 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) 


When errors become less, then the current state of the grid is fixed, transferred to initialization:



  nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize) if not (lastSyns is None): nn.net.syns = lastSyns nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape]) #   #    if minFails == None or fails < minFails: minFails = fails lastSyns = nn.net.syns 


Check



Best score: 4 errors on attempt 25
Epoch = 25

flowers\178.jpg res = [[ 0.64 0.89 0.65 0.87]] v = 0.619

flowers\179.jpg res = [[ 0.91 0.2 0.96 0.15]] v = 0.12

flowers\180.jpg res = [[ 0.95 0.1 0.95 0.1 ]] v = 0.074

flowers\182.jpg res = [[ 1. 0. 1. 0.]] v = 0.0

flowers\186-2.jpg res = [[ 0.98 0.05 0.98 0.04]] v = 0.032

flowers\186.jpg res = [[ 0.99 0.01 0.99 0.01]] v = 0.01

flowers\187.jpg res = [[ 0.83 0.48 0.81 0.5 ]] v = 0.335

flowers\190 (2).jpg res = [[ 1. 0. 1. 0.]] v = 0.001

flowers\190.jpg res = [[ 0.96 0.06 0.96 0.05]] v = 0.045

flowers\191.jpg res = [[ 0.97 0.01 0.96 0.01]] v = 0.022

flowers\195.jpg res = [[ 1. 0. 1. 0.]] v = 0.004

flowers\199.jpg res = [[ 0.91 0.16 0.9 0.16]] v = 0.127

flowers\2.jpg res = [[ 0.99 0.01 0.99 0.01]] v = 0.009

flowers\200.jpg res = [[ 0.99 0.01 1. 0.01]] v = 0.009

noflowers\032.jpg res = [[ 0.71 0.73 0.79 0.73]] v = 0.49

noflowers\085.jpg res = [[ 0.87 0.29 0.85 0.32]] v = 0.222

noflowers\088.jpg res = [[ 0.92 0.22 0.94 0.24]] v = 0.15

noflowers\122.JPG res = [[ 0.72 0.68 0.73 0.68]] v = 0.479

noflowers\123.jpg res = [[ 0.74 0.54 0.69 0.6 ]] v = 0.427

noflowers\173.jpg res = [[ 0.43 0.9 0.57 0.9 ]] v = 0.702

noflowers\202.jpg res = [[ 0.99 0. 0.98 0. ]] v = 0.008

noflowers\205.jpg res = [[ 0.34 0.92 0.57 0.81]] v = 0.711

noflowers\cutxml.jpg res = [[ 0.79 0.41 0.79 0.41]] v = 0.309

noflowers\Getaway.jpg res = [[ 0.75 0.65 0.76 0.65]] v = 0.449

noflowers\IMGP1800.JPG res = [[ 0.81 0.55 0.81 0.55]] v = 0.367

noflowers\trq-4.png res = [[ 0.52 0.81 0.54 0.83]] v = 0.644

dy = 1.407 dn = 4.958

fails = 4 ['flowers\\178.jpg', 'flowers\\187.jpg', 'noflowers\\088.jpg', 'noflowers\\202.jpg']

min = 4




The network has not learned anything again. What a stupid fly!



Although in fact the network still learned something. If you look at the rejected pictures, you can see that the network has attributed to the flowers a beaten red car strewn with yellow leaves. With some imagination, it can really be mistaken for a flower.







Option 3. And what are they just learning there?



The next point of impact was the goal of learning the first network. The fact is that the accumulated error at the inputs of the second network cannot be transmitted through the outputs of the first one. Well, how not to convey ... you can transfer, but it is still necessary to program. And I was too lazy and began to train the first network on a single matrix:



  yy = np.zeros(middleShape) np.fill_diagonal(yy,1) 


\ begin {bmatrix}

1 & 0 & \ cdots & 0 \\

0 & 1 & \ cdots & 0 \\

\ cdots & \ cdots & \ cdots & \ cdots \\

0 & 0 & \ cdots & 1

\ end {bmatrix}

It looks somewhat artificial, apparently, and interferes with learning. But what should be the result matrix, I did not know. Games with synapse copying made me think that a good learning matrix would be nice yyalso copy from a successful attempt, so gradually approaching the best result.



However, there was a problem - I did not have one sample. I train the first network on a single matrix, giving various pictures to the input. For each of them gives a certain matrix of values ​​at the output. Which of the resulting matrices should I take as a sample?



Knowing that “real heroes always go around”, I simply did not do that, but took that same matrix for the sample. But only added to it a small random error. And, if the cycle was successful, i.e. we reach the next minimum of errors, the last sample will be the next target result.



  yy = np.zeros(middleShape) np.fill_diagonal(yy,1) minFails = None lastYY = yy nextYY = yy ... for epoch in range(100): ... for f in fl: i = readImage(f, imageSize) nn.learn(i, nextYY, 2) 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) ... if minFails == None or fails < minFails: minFails = fails lastYY = nextYY else: nextYY = lastYY +(np.random.random(yy.shape)-0.5)/10 




Probably, if I were a mathematician, I would hardly have used chance as a teaching tool. The mathematician would build a theory according to which learning inevitably leads to the expected result through Nmoves, you only need to calculate the integral over a curvilinear surface in a multidimensional space and take from it the derivative x. But I am not a mathematician and I can only rely on my non-mathematical expectation that there is a solution somewhere and it is near.



Best score: 4 errors on attempt 79
Epoch = 79

flowers\178.jpg res = [[ 0.5 0.13 0.52 0.12]] v = 0.309

flowers\179.jpg res = [[ 0.74 0.06 0.75 0.06]] v = 0.16

flowers\180.jpg res = [[ 0.76 0.07 0.75 0.07]] v = 0.155

flowers\182.jpg res = [[ 0.95 0.03 0.94 0.03]] v = 0.044

flowers\186-2.jpg res = [[ 0.7 0.1 0.71 0.09]] v = 0.193

flowers\186.jpg res = [[ 0.61 0.22 0.6 0.2 ]] v = 0.303

flowers\187.jpg res = [[ 0.45 0.13 0.45 0.13]] v = 0.341

flowers\190 (2).jpg res = [[ 0.84 0. 0.67 0.01]] v = 0.14

flowers\190.jpg res = [[ 0.96 0.06 0.94 0.08]] v = 0.061

flowers\191.jpg res = [[ 0.73 0.13 0.72 0.1 ]] v = 0.194

flowers\195.jpg res = [[ 0.85 0.03 0.88 0.03]] v = 0.08

flowers\199.jpg res = [[ 0.83 0.05 0.84 0.04]] v = 0.102

flowers\2.jpg res = [[ 0.81 0.06 0.81 0.06]] v = 0.125

flowers\200.jpg res = [[ 0.92 0.05 0.93 0.04]] v = 0.057

noflowers\032.jpg res = [[ 0.27 0.12 0.3 0.1 ]] v = 0.416

noflowers\085.jpg res = [[ 0.41 0.14 0.41 0.14]] v = 0.365

noflowers\088.jpg res = [[ 0.37 0.15 0.32 0.15]] v = 0.402

noflowers\122.JPG res = [[ 0.4 0.15 0.4 0.14]] v = 0.373

noflowers\123.jpg res = [[ 0.35 0.14 0.33 0.15]] v = 0.401

noflowers\173.jpg res = [[ 0.33 0.17 0.34 0.17]] v = 0.418

noflowers\202.jpg res = [[ 0.44 0.14 0.45 0.12]] v = 0.342

noflowers\205.jpg res = [[ 0.63 0.06 0.74 0.07]] v = 0.192

noflowers\cutxml.jpg res = [[ 0.52 0.13 0.45 0.13]] v = 0.323

noflowers\Getaway.jpg res = [[ 0.38 0.15 0.38 0.15]] v = 0.386

noflowers\IMGP1800.JPG res = [[ 0.4 0.15 0.4 0.14]] v = 0.371

noflowers\trq-4.png res = [[ 0.19 0.21 0.17 0.28]] v = 0.533

dy = 2.264 dn = 4.522

fails = 4 ['flowers\\178.jpg', 'flowers\\186.jpg', 'flowers\\187.jpg', 'noflowers\\205.jpg']

min = 4




Here, the network found that the white connector also looks quite like a flower.







In general, this connector is considered to be a flower in almost any variant. Well, this is understandable, they do not give her a sniff.



Option 4. And let's combine our brands



The previous versions showed approximately the same results. By this time I had time to think whether I was doing it at all, and whether I want too much from an unfortunate fly. But did not stop there.



The next option was to use the previous two at the same time - when successful attempts are made to memorize the values ​​of synapses And the goal of learning the first matrix AND to use randomness to expand the search.



  for epoch in range(100): print('Epoch =', epoch) nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize) if not (lastSyns is None): nextSyns = lastSyns for r in range(len(nextSyns)): rand = (np.random.random(nextSyns[r].shape)-0.5)/20 nextSyns[r] = nextSyns[r] + rand nn.net.syns = nextSyns nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape]) for f in fl: i = readImage(f, imageSize) nn.learn(i, nextYY, 2) mid = nn.calc(i) nn2.learn(mid, y, 1000) ... if minFails == None or fails < minFails: minFails = fails lastSyns = nn.net.syns lastYY = nextYY else: nextYY = lastYY +(np.random.random(yy.shape)-0.5)/20 


Best score: 2 errors on attempt 38
Epoch = 38

flowers\178.jpg res = [[ 0.91 0.26 0.91 0.25]] v = 0.174

flowers\179.jpg res = [[ 0.99 0. 0.99 0. ]] v = 0.005

flowers\180.jpg res = [[ 0.9 0.21 0.89 0.2 ]] v = 0.153

flowers\182.jpg res = [[ 1. 0. 1. 0.]] v = 0.0

flowers\186-2.jpg res = [[ 1. 0.01 0.99 0.01]] v = 0.008

flowers\186.jpg res = [[ 0.91 0.12 0.93 0.07]] v = 0.09

flowers\187.jpg res = [[ 0.83 0.43 0.83 0.44]] v = 0.303

flowers\190 (2).jpg res = [[ 1. 0. 1. 0.]] v = 0.0

flowers\190.jpg res = [[ 1. 0. 1. 0.]] v = 0.001

flowers\191.jpg res = [[ 1. 0. 1. 0.]] v = 0.0

flowers\195.jpg res = [[ 0.99 0. 1. 0. ]] v = 0.004

flowers\199.jpg res = [[ 0.97 0.03 0.98 0.03]] v = 0.029

flowers\2.jpg res = [[ 1. 0. 1. 0.]] v = 0.003

flowers\200.jpg res = [[ 1. 0. 1. 0.]] v = 0.0

noflowers\032.jpg res = [[ 0.88 0.55 0.8 0.67]] v = 0.389

noflowers\085.jpg res = [[ 0.25 0.96 0.27 0.96]] v = 0.848

noflowers\088.jpg res = [[ 0.84 0.42 0.79 0.37]] v = 0.29

noflowers\122.JPG res = [[ 0.68 0.66 0.69 0.66]] v = 0.49

noflowers\123.jpg res = [[ 0.74 0.63 0.71 0.6 ]] v = 0.445

noflowers\173.jpg res = [[ 0.86 0.46 0.76 0.52]] v = 0.343

noflowers\202.jpg res = [[ 0.22 0.92 0.44 0.95]] v = 0.808

noflowers\205.jpg res = [[ 0.8 0.82 0.71 0.88]] v = 0.547

noflowers\cutxml.jpg res = [[ 0.99 0.03 0.97 0.02]] v = 0.022

noflowers\Getaway.jpg res = [[ 0.7 0.65 0.7 0.65]] v = 0.474

noflowers\IMGP1800.JPG res = [[ 0.79 0.5 0.77 0.5 ]] v = 0.36

noflowers\trq-4.png res = [[ 0.77 0.21 0.69 0.07]] v = 0.215

dy = 0.77 dn = 5.231

fails = 2 ['flowers\\187.jpg', 'noflowers\\cutxml.jpg']

min = 2




2 errors on 28 files = 7% I considered to be a fairly good result, which can not be improved.



Run the final photos and conclusions



At the end of the process, I saved the trained grid, or rather the values ​​of its synapses for better results, in text files:



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


Then he drove the network from photographs:



  StartLearn = False if not StartLearn: pictDir = 'C:\\AllPictures' nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize) nn.net.syns[0] = np.loadtxt('syns_save0.txt',ndmin=nn.net.syns[0].ndim) nn.net.syns[1] = np.loadtxt('syns_save1.txt',ndmin=nn.net.syns[1].ndim) nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape]) nn2.syns[0] = np.loadtxt('syns2_save0.txt',ndmin=nn2.syns[0].ndim) nn2.syns[1] = np.loadtxt('syns2_save1.txt',ndmin=nn2.syns[1].ndim) files = [e.path for e in os.scandir(pictDir)] for f in files: i = readImage(f, imageSize) mid = nn.calc(i) res = nn2.calc(mid) delta = y-res v = round(np.std(delta),3) if v <= 0.3: print('Flower',f,v) ## else: ## print('No flower',f, v) 


In general, do not say that she found only flowers. There were cats, crabs, fireworks, just rocks and groups of people. And the colors among my photos were just not enough.



It can be concluded that, in principle, the network works, just the task for it turned out to be too complicated. If you look at ImageNet , as Dark_Daiver advised me in the comment to the previous article, then the colors are too wide to be able to distinguish them with such a simple network.



Source
 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 def readImageCVQ(file, imageSize): img = QImage(file) if img.isNull(): return 0 img = img.convertToFormat(QImage.Format_RGB888) img = img.scaled(imageSize[0],imageSize[1],Qt.IgnoreAspectRatio) srcBi = img.bits() srcBi.setsize(img.byteCount()) srcBy = bytes(srcBi) srcW, srcH = img.width(), img.height() bp = img.depth() // 8 srcArr = np.recarray((srcH, srcW, bp), dtype=np.uint8, buf=srcBy) srcArr = srcArr.view(dtype=np.uint8,type=np.ndarray) hsv = cv2.cvtColor(srcArr, cv2.COLOR_RGB2HSV) return hsv[:,:,0]/255 if __name__ == '__main__': readImage = readImageCVQ y = np.array([[1,0,1,0]]) firstShape = (40, 40) middleShape = (10, 10) imageSize = firstShape[0]*middleShape[0], firstShape[1]*middleShape[1] StartLearn = False if not StartLearn: pictDir = 'C:\\AllPictures' nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize) nn.net.syns[0] = np.loadtxt('syns_save0.txt',ndmin=nn.net.syns[0].ndim) nn.net.syns[1] = np.loadtxt('syns_save1.txt',ndmin=nn.net.syns[1].ndim) nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape]) nn2.syns[0] = np.loadtxt('syns2_save0.txt',ndmin=nn2.syns[0].ndim) nn2.syns[1] = np.loadtxt('syns2_save1.txt',ndmin=nn2.syns[1].ndim) files = [e.path for e in os.scandir(pictDir)] for f in files: i = readImage(f, imageSize) mid = nn.calc(i) res = nn2.calc(mid) delta = y-res v = round(np.std(delta),3) if v <= 0.3: print('Flower',f,v) ## else: ## print('No flower',f, v) 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 lastSyns = None nextSyns = None lastSyns2 = None lastYY = yy nextYY = yy minDy = None maxDn = None for epoch in range(100): print('Epoch =', epoch) nn = ImgNN(firstShape, resultShape=middleShape, imageSize=imageSize) if not (lastSyns is None): nextSyns = lastSyns for r in range(len(nextSyns)): rand = (np.random.random(nextSyns[r].shape)-0.5)/20 nextSyns[r] = nextSyns[r] + rand nn.net.syns = nextSyns nn2 = NN([middleShape, (y.shape[1], middleShape[0]), y.shape]) for f in fl: i = readImage(f, imageSize) nn.learn(i, nextYY, 2) ## nn.learn(i, yy, 2) mid = nn.calc(i) nn2.learn(mid, y, 1000) nextSyns=None fails = 0 failFiles = [] dy = 0.0 dn = 0.0 for f in all: i = readImage(f, imageSize) mid = nn.calc(i) res = nn2.calc(mid) delta = (y-res) v = round(np.std(delta),3) #v = round(delta.sum(),3) print(f, 'res = ', res.round(2),'v =',v) if f in fl: dy += v if f in nofl: dn += 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) print('dy =',dy,'dn =',dn) if minDy == None or dy < minDy: minDy = dy if maxDn == None or dn > maxDn: maxDn = dn if minFails == None or fails < minFails: minFails = fails lastSyns = nn.net.syns lastSyns2 = nn2.syns lastYY = nextYY else: nextYY = lastYY +(np.random.random(yy.shape)-0.5)/20 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]) 




The source code is also posted on github under the MIT license.



Bonus OpenCV and Russian letters in file paths



In the last article I said that the imread function in OpenCV had a problem with reading files with paths in which Russian letters are found. There is no such problem with QImage from PyQt, but I needed OpenCV to convert the image to the HSV color space and to highlight the chromaticity plane. Therefore I combined loading of a picture through QImage and transformation through OpenCV.



 def readImageCVQ(file, imageSize): img = QImage(file) if img.isNull(): return 0 img = img.convertToFormat(QImage.Format_RGB888) img = img.scaled(imageSize[0],imageSize[1],Qt.IgnoreAspectRatio) srcBi = img.bits() srcBi.setsize(img.byteCount()) srcBy = bytes(srcBi) srcW, srcH = img.width(), img.height() bp = img.depth() // 8 srcArr = np.recarray((srcH, srcW, bp), dtype=np.uint8, buf=srcBy) srcArr = srcArr.view(dtype=np.uint8,type=np.ndarray) hsv = cv2.cvtColor(srcArr, cv2.COLOR_RGB2HSV) return hsv[:,:,0]/255 ... readImage = readImageCVQ 

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



All Articles