En lisant "Deep Learning from scratch" (écrit par Yasuki Saito, publié par O'Reilly Japan), je prendrai note des sites auxquels j'ai fait référence. Partie 10 ←
En ce qui concerne les réseaux de neurones convolutifs du chapitre 7, cela est assez différent de ce que nous avons fait jusqu'au chapitre 6. On dirait que vous faites beaucoup de choses différentes, mais à la fin, vous trouverez et stockerez les gradients de poids et de biais. En d'autres termes, le principe de base n'a pas du tout changé, ce sont les données d'entrée qui ont changé
P207 Si les données d'entrée sont une image, l'image est généralement une forme tridimensionnelle dans les directions verticale, horizontale et de canal. Cependant, lors de l'entrée dans la couche entièrement connectée, les données tridimensionnelles doivent être des données plates unidimensionnelles. En fait, dans les exemples précédents utilisant le jeu de données MNIST, l'image d'entrée était de la forme (1, 28, 28) - 1 canal, 28 pixels de haut, 28 pixels de large - mais disposée en ligne. Vous avez entré 784 données dans la première couche Affine. ・ ・ ・ Le calque Convolution, quant à lui, conserve sa forme. Dans le cas d'une image, les données d'entrée sont reçues sous forme de données 3D et les données sont également transmises à la couche suivante sous forme de données 3D. Par conséquent, CNN peut (potentiellement) comprendre correctement les données avec des formes telles que des images.
En fait, j'ai moi-même utilisé ce Mémo d'auto-apprentissage n ° 6-2 pour traiter les ensembles de données sur les chats et les chiens de Kaggle par 1 Je le convertis en une dimension et je l'utilise. Si cela peut être traité en trois dimensions, le taux de reconnaissance peut s'améliorer.
Ces explications ne sont pas du tout difficiles, et je peux les comprendre comme telles, mais puisque cette formule apparaît soudainement sur P212, qu'est-ce que c'est? Est-ce vraiment le cas? Alors j'y ai pensé.
Pour le moment, réfléchissons au fait qu'il n'y a pas de S (foulée). Vérifions une taille d'entrée et une taille de filtre
Lorsque la taille d'entrée (n, n) et la taille de filtre (m, m) La taille de sortie semble être (n-m + 1, n-m + 1). Si vous appliquez le filtre dans le coin supérieur gauche, vous pouvez le faire pivoter vers la droite (nm). Il peut tourner vers le bas (nm). Donc, en ajoutant 1 minute dans le coin supérieur gauche, est-ce nm + 1?
Alors que se passe-t-il avec les foulées? Lorsque la foulée est de 2, le nombre de tours vers la droite (nm) est divisé par deux. (Nm) / 2 Quand il est 3, il devient 1/3.
En d'autres termes, le nombre de fois que vous pouvez vous déplacer est de (nm) / s, donc La taille de sortie est (nm) / s + 1.
En supposant que la taille des données d'entrée est (H, W), le remplissage est P et la taille du filtre est (FH, FW) n = H + 2 × P De même, n = W + 2 × P m=FH n=FW Alors La taille de sortie est OH=(H+2×P-FH)/s + 1 OW=(W+2×P-FW)/s + 1
À partir de P230, il y a une description de la classe SimpleConvNet comme exemple pour l'apprentissage des données MNIST. Laissez-moi apprendre en utilisant ce cours
import sys, os
sys.path.append(os.pardir) #Paramètres d'importation des fichiers dans le répertoire parent
import numpy as np
from dataset.mnist import load_mnist
from common.simple_convnet import SimpleConvNet
from common.trainer import Trainer
#Lire les données
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)
max_epochs = 20
network = SimpleConvNet(input_dim=(1,28,28),
conv_param = {'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},
hidden_size=100, output_size=10, weight_init_std=0.01)
trainer = Trainer(network, x_train, t_train, x_test, t_test,
epochs=max_epochs, mini_batch_size=100,
optimizer='Adam', optimizer_param={'lr': 0.001},
evaluate_sample_num_per_epoch=1000, verbose=False)
trainer.train()
J'ai essayé de vérifier le contenu du jugement des données de test.
import numpy as np
from common.simple_convnet import SimpleConvNet
from dataset.mnist import load_mnist
import pickle
import matplotlib.pyplot as plt
def showImg(x):
example = x.reshape((28, 28))
plt.figure()
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(example, cmap=plt.cm.binary)
plt.show()
return
#Évaluer avec des données de test
x = x_test
t = t_test
network = SimpleConvNet(input_dim=(1,28,28),
conv_param = {'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},
hidden_size=100, output_size=10, weight_init_std=0.01)
network.load_params("params.pkl")
y = network.predict(x)
accuracy_cnt = 0
for i in range(len(x)):
p= np.argmax(y[i])
#print(str(x[i]) + " : " + str(p))
if p == t[i]:
accuracy_cnt += 1
else:
print("Bonne réponse:"+str(t[i])+"Résultat d'inférence:"+str(p))
showImg(x[i])
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
En conséquence, le taux de réponse correct est
Accuracy:0.988
Ce qui n'allait pas, c'est comme ça
Cependant, il a fallu des heures pour traiter 60 000 données. De plus, après avoir appris, lorsque j'ai essayé de traiter les données de test, j'ai été frappé par le problème de mémoire insuffisante et je n'ai pas pu procéder facilement. Le Deep Learning est-il déraisonnable en mémoire 4G?
Pour le moment, j'ai pu confirmer que j'étais capable d'apprendre avec une grande précision avec CNN.
alors
Comme d'habitude, je souhaite suivre le contenu du programme.
# coding: utf-8
import sys, os
sys.path.append(os.pardir) #Paramètres d'importation des fichiers dans le répertoire parent
import pickle
import numpy as np
from collections import OrderedDict
from common.layers import *
from common.gradient import numerical_gradient
class SimpleConvNet:
def __init__(self, input_dim=(1, 28, 28),
conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},
hidden_size=100, output_size=10, weight_init_std=0.01):
filter_num = conv_param['filter_num']
filter_size = conv_param['filter_size']
filter_pad = conv_param['pad']
filter_stride = conv_param['stride']
input_size = input_dim[1]
conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1
pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))
#Initialisation du poids
self.params = {}
self.params['W1'] = weight_init_std * \
np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
self.params['b1'] = np.zeros(filter_num)
self.params['W2'] = weight_init_std * \
np.random.randn(pool_output_size, hidden_size)
self.params['b2'] = np.zeros(hidden_size)
self.params['W3'] = weight_init_std * \
np.random.randn(hidden_size, output_size)
self.params['b3'] = np.zeros(output_size)
#Génération de couches
self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'],
conv_param['stride'], conv_param['pad'])
self.layers['Relu1'] = Relu()
self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)
self.layers['Affine1'] = Affine(self.params['W2'], self.params['b2'])
self.layers['Relu2'] = Relu()
self.layers['Affine2'] = Affine(self.params['W3'], self.params['b3'])
self.last_layer = SoftmaxWithLoss()
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss(self, x, t):
y = self.predict(x)
return self.last_layer.forward(y, t)
def accuracy(self, x, t, batch_size=100):
if t.ndim != 1 : t = np.argmax(t, axis=1)
acc = 0.0
for i in range(int(x.shape[0] / batch_size)):
tx = x[i*batch_size:(i+1)*batch_size]
tt = t[i*batch_size:(i+1)*batch_size]
y = self.predict(tx)
y = np.argmax(y, axis=1)
acc += np.sum(y == tt)
return acc / x.shape[0]
def gradient(self, x, t):
# forward
self.loss(x, t)
# backward
dout = 1
dout = self.last_layer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
#Réglage
grads = {}
grads['W1'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db
grads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
grads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].db
return grads
def save_params(self, file_name="params.pkl"):
params = {}
for key, val in self.params.items():
params[key] = val
with open(file_name, 'wb') as f:
pickle.dump(params, f)
def load_params(self, file_name="params.pkl"):
with open(file_name, 'rb') as f:
params = pickle.load(f)
for key, val in params.items():
self.params[key] = val
for i, key in enumerate(['Conv1', 'Affine1', 'Affine2']):
self.layers[key].W = self.params['W' + str(i+1)]
self.layers[key].b = self.params['b' + str(i+1)]
La seule différence est que les couches sont empilées et que les autres ne sont pas très différentes de la classe MultiLayerNet.
self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'],
conv_param['stride'], conv_param['pad'])
La classe Convolution est également définie dans layer.py
class Convolution:
def __init__(self, W, b, stride=1, pad=0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
#Données intermédiaires (utilisées en arrière)
self.x = None
self.col = None
self.col_W = None
#Gradient du paramètre poids / biais
self.dW = None
self.db = None
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
out_w = 1 + int((W + 2*self.pad - FW) / self.stride)
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
self.x = x
self.col = col
self.col_W = col_W
return out
def backward(self, dout):
FN, C, FH, FW = self.W.shape
dout = dout.transpose(0,2,3,1).reshape(-1, FN)
self.db = np.sum(dout, axis=0)
self.dW = np.dot(self.col.T, dout)
self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)
dcol = np.dot(dout, self.col_W.T)
dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)
return dx
im2col Le cœur de ceci est la fonction im2col. Défini dans util.py
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
N, C, H, W = input_data.shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
return col
Donc, cela semble être la cause du manque de mémoire. Si le nombre de lignes de données à traiter augmente, une erreur de mémoire se produira ici.
Dans les trois premières lignes, nous vérifions la taille des données d'entrée et calculons la taille de sortie à partir de la taille d'entrée et de la taille du filtre. La raison pour laquelle // est utilisé pour la division des foulées semble être de tronquer après la virgule décimale si elle n'est pas divisible.
len(x_test) #Le nombre de données
10000
len(x_test[0]) #canal
1
len(x_test[0][0]) #la taille
28
len(x_test[0][0][0]) #Largeur
28
len(network.params['W1']) #Nombre de filtres
30
len(network.params['W1'][0]) #Nombre de canaux
1
len(network.params['W1'][0][0]) #Hauteur du filtre
5
len(network.params['W1'][0][0][0]) #Largeur de filtre
5
conv_param = {'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},
Le remplissage 0 et la foulée 1 sont spécifiés lors de la création de l'objet réseau.
len(network.layers['Conv1'].forward(x_test)) #Le nombre de données
10000
len(network.layers['Conv1'].forward(x_test)[0]) #Nombre de filtres
30
len(network.layers['Conv1'].forward(x_test)[0][0]) #Hauteur de sortie
24
len(network.layers['Conv1'].forward(x_test)[0][0][0]) #Largeur de sortie
24
self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'],
conv_param['stride'], conv_param['pad'])
class Convolution:
(Omis)
def forward(self, x):
FN, C, FH, FW = self.W.shape # 30, 1, 5, 5
N, C, H, W = x.shape # 10000, 1, 28, 28
out_h = 1 + int((H + 2*self.pad - FH) / self.stride) # 24
out_w = 1 + int((W + 2*self.pad - FW) / self.stride) # 24
col = im2col(x, FH, FW, self.stride, self.pad)
(Omis)
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
N, C, H, W = input_data.shape # 10000, 1, 28, 28
out_h = (H + 2*pad - filter_h)//stride + 1 # 24
out_w = (W + 2*pad - filter_w)//stride + 1 # 24
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
input_data est de 4 dimensions (10000 lignes de données, 1 canal, 28 hauteur, 28 largeur) Lorsque pad = 0, [(0,0), (0,0), (0, 0), (0, 0)] ne remplit pas. Lorsque pad = 1, [(0,0), (0,0), (1, 1), (1, 1)] pad un par un en haut, en bas, à gauche et à droite de la hauteur et de la largeur. Lorsque pad = 2, [(0,0), (0,0), (2, 2), (2, 2)] Remplit deux chacun en haut, en bas, à gauche et à droite de la hauteur et de la largeur. Dans cet exemple de programme, pad = 0. La même chose que input_data est définie dans img.
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w)) #10000, 1, 5, 5, 24, 24
Les données d'entrée (image image) sont développées dans le tableau col, mais en tant que conteneur pour développer les données, un tableau de la taille (nombre de données, canal, hauteur de filtre, largeur de filtre, hauteur de sortie, largeur de sortie) est créé. ..
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
return col
Je ne peux pas du tout obtenir l'image ici, alors je l'ai testée avec la séquence simplifiée suivante.
import numpy as np
N=1
C=1
H=8
W=8
filter_h=4
filter_w=4
stride=2
out_h=3
out_w=3
img= np.arange(64).reshape(N, C, 8, 8)
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
col
array([[ 0., 1., 2., 3., 8., 9., 10., 11., 16., 17., 18., 19., 24., 25., 26., 27.], [ 2., 3., 4., 5., 10., 11., 12., 13., 18., 19., 20., 21., 26., 27., 28., 29.], [ 4., 5., 6., 7., 12., 13., 14., 15., 20., 21., 22., 23., 28., 29., 30., 31.], [16., 17., 18., 19., 24., 25., 26., 27., 32., 33., 34., 35., 40., 41., 42., 43.], [18., 19., 20., 21., 26., 27., 28., 29., 34., 35., 36., 37., 42., 43., 44., 45.], [20., 21., 22., 23., 28., 29., 30., 31., 36., 37., 38., 39., 44., 45., 46., 47.], [32., 33., 34., 35., 40., 41., 42., 43., 48., 49., 50., 51., 56., 57., 58., 59.], [34., 35., 36., 37., 42., 43., 44., 45., 50., 51., 52., 53., 58., 59., 60., 61.], [36., 37., 38., 39., 44., 45., 46., 47., 52., 53., 54., 55., 60., 61., 62., 63.]])
Dans col [0], la partie à laquelle le filtre est appliqué en premier est extraite des données d'entrée. col [1] est la partie où le filtre est appliqué en décalant la foulée 2 vers la droite de deux. En dessous, la partie sur laquelle le filtre est appliqué 9 fois est extraite et agencée.
Je ne suis pas sûr de ce que je fais, mais je peux comprendre le résultat.
Si vous remodelez le filtre 4x4 en une colonne et effectuez des opérations de col et de point, vous pouvez obtenir le résultat de l'application du filtre 9 fois en une seule opération.
#Convolution.forward
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T
out = np.dot(col, col_W) + self.b
out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
Compréhension complète de la fonction numpy.pad Manipulez librement le tableau bidimensionnel. [Initialisation / Référence / Extraction / Calcul / Déplacement]
Recommended Posts