週末のプログラムとして、私は一種の「ニューラル」ネットワーク(ネタバレ-その中にニューロンはありません)で遊びたいと思いました。 そして、後でそれが目的のない生活を送って何時間も耐え難いほど苦痛にならないように、私たちは無駄に餌を与えて、それが利益をもたらすと考えました-同時にこのグリッドに家の写真アーカイブを分解させ、少なくとも花の写真を別のフォルダーに入れてください。
最も単純なネットワーク

最も単純なネットワークは、「
Pythonの11行のニューラルネットワーク 」という記事で見つかりました(これは、「
Pythonの11行 のニューラルネットワーク(パート1) 」の
SLY_Gからの翻訳
です。一般的に、著者は、
(パート2-勾配降下) 」ですが、ここでは最初の記事で十分です)。
グリッドの簡単な説明-このネットワークには、
NumPyという 1つの依存関係があります。
多くの入力は行列と見なされます。
X 、多くの出力-ベクトルとして
y 。 元の記事では、ネットワークは入力行列の次元(4 x 3)に入力重みの行列を掛けます
syn0 (3 x 4)、伝達関数を製品に適用し、層の行列を取得します
l1 (4 x 4)。
X= beginbmatrix1&2&3&45&6&7&89&10&11&12 endbmatrixy= beginbmatrix123 endbmatrix
f(X timessyn0)\はl1を意味します
次の層
l1 出力重み行列で乗算
syn1 (4 x 1)、これも関数を介して渡され、結果はレイヤーです
l2 (4 x 1)、これはネットワークの結果です。
f(l1 timessyn1)\はl2を意味します\はyを意味します
合計、スカラー伝達関数を省略して、ネットワークは2つの行列乗算を実装します。
X timessyn0 timessyn1\はyを意味します
この結果、行列乗算の規則に従って、ネットワークの操作中に次元の1つが変化しないことが判明しました。
(4∗3)\回(3∗1)=(4∗1) 単一の番号を取得することは不可能です。
そのため、記事のコードをわずかに変更し、乗算後に転置を追加し、グリッド内の任意の数のレイヤーを操作しました。 これにより、入力と出力のディメンションの任意の組み合わせを取得する機会が与えられました。
たとえば、入力行列を(3 x 4)に、出力を特異にしたい場合、2つのシナプス行列(4 x 1)と(3 x 1)を追加します。
(( beginbmatrix1&0&0&00&1&0&00&0&1&0 endbmatrix times beginbmatrix00 11 endbmatrix)T times beginbmatrix011 endbmatrix)T=[1](((3∗4) times(4∗1))T\回(3∗1))T=(1∗1)
または、たとえば、入力行列(10 x 8)を出力(4 x 5)に変換できます。
((((10∗8) times(8∗5))T times(10∗4))T=(4∗5)
結果のコードは次のとおりです。
nnmat.pyimport 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] += XTdot(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の範囲で、撮影したものと処理方法に応じてサイズが異なります。
最初はQImageクラスであるQtを介して写真をアップロードしようとしました。さまざまな形式で動作し、変換を提供し、画像データに直接アクセスします。 確かに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関数を見つけました。また、データをコピーせずにnumpy.ndarray行列を作成するviewメソッドがあります。
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)
画像のネットワーク
ネットワーク入力に画像を直接送信するのは高価ですが、小さいものです-ネットワークは行列乗算を行うと既に述べたので、1トレーニングサイクルでも400x400x400 = 64百万回の乗算になります。 専門家は
畳み込みの使用を推奨しています。 ウィキペディアには彼女の作品の素晴らしいイラストがあります:
このアニメーションは、結果の次元が元の行列の次元に等しいことを示しています。 しかし、私は生活を少し簡略化し、ピクセル単位ではなく移動しますが、入力マトリックスのサイズに画像を分割し、グリッドを1つずつ適用します。 マトリックスでは、ピースのカットは非常に簡単に行われます。
srcArr[x:x+dw, y:y+dw]
ネットワークによってピースを処理した結果は、より小さなマトリックスに追加され、このマトリックスは共通ネットワークの入力に送信されます。 つまり、2つのネットワークが存在します。最初のネットワークはイメージの断片で動作し、2番目のネットワークは断片で動作する最初のネットワークの結果です。
プライマリネットワークを作成します。
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は内部に作成されます-形状の入力のマトリックスの所定のサイズと、基本マトリックス1x1の形式の出力を持つネットワーク自体。 はい、NNネットワークのクラスから継承することはできましたが、休みがあり、結果をより早く取得したかったため、アーキテクチャはまだ落ち着いていません。 私たちの心の中でビートを市場に出す時間!
最初のネットワークで画像を数える:
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マトリックスがあります。 この行列を2番目のネットワークの入力に渡すと、最終結果が得られます。
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]])
これは、肯定的な答えの場合のネットワークの期待される結果です。 ネットワークが花の入力画像を信じている場合。 「より少なくもよりも大きくない」という原則からディメンションを選択しました。ディメンション1x1を取得した場合、結果の数値の1つから、ネットワークが結果をどれだけ「疑う」かを判断することは困難です。 大きなディメンションを要求しても意味がありません。詳細な情報は提供されません。 ゼロと1の数が等しいと、明確な参照が得られます。それに近いほど、一致が大きくなります。 すべてのユニットまたはすべてゼロを使用すると、ネットワークには再トレーニングのインセンティブがあります-すべての要因を増やすか、入力データに関係なくそれらをリセットして目的の結果を取得します。
たたみ込みネットワークをトレーニングする方法は?
私は自分の写真からトレーニングサンプルを作成し、2つのディレクトリに単純に入れました。
花
と
noflowers
2つの配列で画像へのパスを収集します
import os fl = [e.path for e in os.scandir('flowers')] nofl = [e.path for e in os.scandir('noflowers')] all = fl+nofl
通常、元の記事を含む単純なネットワークを、
エラーの逆伝播という従来の方法でトレーニングすることが提案されています。 ただし、この方法を2つの基本ネットワークで構成される畳み込みネットワークに適用するには、2番目のネットワークから最初のネットワークへの累積誤差のエンドツーエンド送信を保証する必要があります。 一般に、畳み込みネットワークには
他の方法があります。 少なくとも今のところ作業ネットワークをやり直すのが面倒だったので、2番目のネットワークを訓練し、最初の1つはまったく教えず、ランダムな値で詰まらせたままにしました。画像を「見て」。
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)
それぞれの時代に、トレーニングの直後に、サンプル全体をネットワークを介して実行し、何が起こったかを確認します。
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])
そうでない場合は、バラと呼びます
希望を持って、私は立ち上げて...待って...、それから別の...、そしてもっと...私は完全なナンセンスを得る-グリッドは学習しません:
何も起こらなかったflowers\178.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers\179.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers\180.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers\182.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers\186-2.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers\186.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers\187.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers\190 (2).jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers\190.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers\191.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers\195.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers\199.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
flowers\2.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
flowers\200.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers\032.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers\085.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
noflowers\088.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers\122.JPG res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers\123.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers\173.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
noflowers\202.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers\205.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers\cutxml.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.241
noflowers\Getaway.jpg res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
noflowers\IMGP1800.JPG res = [[ 0.98 0.5 0.98 0.5 ]] v = 0.24
noflowers\trq-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)
私はそれを再び始めました-それははるかに興味深いものになり、数字は変わり始めましたが、理想には達しませんでした。
最高の結果Epoch = 34
flowers\178.jpg res = [[ 0.86 0.47 0.88 0.47]] v = 0.171
flowers\179.jpg res = [[ 0.87 0.51 0.89 0.5 ]] v = 0.194
flowers\180.jpg res = [[ 0.79 0.69 0.79 0.67]] v = 0.233
flowers\182.jpg res = [[ 0.87 0.53 0.88 0.48]] v = 0.189
flowers\186-2.jpg res = [[ 0.89 0.41 0.89 0.39]] v = 0.144
flowers\186.jpg res = [[ 0.85 0.54 0.83 0.55]] v = 0.194
flowers\187.jpg res = [[ 0.86 0.54 0.86 0.54]] v = 0.199
flowers\190 (2).jpg res = [[ 0.96 0.25 0.97 0.15]] v = 0.089
flowers\190.jpg res = [[ 0.95 0.13 0.97 0.14]] v = 0.048
flowers\191.jpg res = [[ 0.81 0.57 0.82 0.57]] v = 0.195
flowers\195.jpg res = [[ 0.81 0.55 0.79 0.56]] v = 0.177
flowers\199.jpg res = [[ 0.89 0.45 0.89 0.45]] v = 0.171
flowers\2.jpg res = [[ 0.83 0.56 0.83 0.55]] v = 0.195
flowers\200.jpg res = [[ 0.91 0.42 0.89 0.43]] v = 0.163
noflowers\032.jpg res = [[ 0.7 0.79 0.69 0.8 ]] v = 0.246
noflowers\085.jpg res = [[ 0.86 0.53 0.86 0.53]] v = 0.192
noflowers\088.jpg res = [[ 0.86 0.56 0.87 0.53]] v = 0.207
noflowers\122.JPG res = [[ 0.81 0.63 0.81 0.62]] v = 0.218
noflowers\123.jpg res = [[ 0.83 0.59 0.84 0.55]] v = 0.204
noflowers\173.jpg res = [[ 0.83 0.6 0.83 0.58]] v = 0.209
noflowers\202.jpg res = [[ 0.78 0.7 0.8 0.65]] v = 0.234
noflowers\205.jpg res = [[ 0.84 0.77 0.79 0.75]] v = 0.287
noflowers\cutxml.jpg res = [[ 0.81 0.61 0.81 0.63]] v = 0.213
noflowers\Getaway.jpg res = [[ 0.85 0.56 0.85 0.55]] v = 0.202
noflowers\IMGP1800.JPG res = [[ 0.85 0.55 0.86 0.54]] v = 0.199
noflowers\trq-4.png res = [[ 0.7 0.72 0.7 0.71]] v = 0.208
fails = 3 ['flowers\\180.jpg', 'noflowers\\085.jpg', 'noflowers\\IMGP1800.JPG']
min = 3
さらに...そして、週末が終わり、私は家事をする時間になりました。
次に何をする?
もちろん、このネットワーク、私が教えた方法、およびテストデータセットは、実際のネットワークやデータサイエンティストが行うこととはほとんど関係がありません。 これは心の体操のための単なるおもちゃであり、それに対する高い希望を持っていません。
必要な結果を達成する方法について、さらに手順を説明できます(必要な場合)。
- 1つまたは複数の中間層を2つ目のネットワークに追加します。これにより、学習の自由度が高まります。 それでも、マトリックス乗算のネットワークは、層間のシナプスリンクが少なく、シナプス自体が一意ではないため、完全に古典的ではありません。
- 成功した結果の近似値を、後続のトレーニングのブランクとして使用します。 ランダムな値で上書きするのではなく、最も成功した結果のシナプスの重みを覚えておいてください。
- 遺伝的アルゴリズムを試してください-ミックスして共有し、成功したものを増やし、失敗したものを拒否します
- 他の学習方法を試してください。既にワゴンと小さなカートがあります。
- 元の画像からより多くの情報を使用します。たとえば、さまざまなネットワークにカラーとモノクロを同時に適用し、結果を共通のネットワークで処理します。
ソースコード 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)
継続