Le thème du chapitre 7 est le réseau de neurones convolutifs: ** CNN **
CNN est le même que le réseau de neurones que nous avons vu jusqu'à présent, et il peut être créé en combinant des couches comme un bloc lego. Le suivant est nouveau ・ "Couche de convolution" ・ "Couche de mise en commun"
Fonctionnalités générales de CNN ・ Flux de "Convolution --ReLU- (Regroupement)" ・ La couche de regroupement peut être omise ・ La combinaison "Affine --ReLU" est utilisée dans le calque proche de la sortie. ・ La dernière couche de sortie est une combinaison de "Affine --Softmax"
Les termes suivants sont apparus · En mettant ·foulée
De plus, des données tridimensionnelles sont apparues
Le problème avec les jointures complètes est que ** la structure des données est "ignorée" **
Par exemple, dans le cas d'une image, il s'agit généralement d'une forme tridimensionnelle dans les directions verticale, horizontale et de canal. Cette forme contient des informations spatiales importantes Par exemple -Les pixels proches spatiaux peuvent avoir des valeurs similaires ・ Il existe une relation étroite entre chaque canal de RBG ・ Les pixels éloignés ne sont pas si liés Il y a un motif essentiel à saisir dans la forme tridimensionnelle.
La couche entièrement connectée ignore la forme ci-dessus et les traite tous comme des neurones équivalents (neurones de même dimension). D'autre part, la couche convolutive conserve sa forme
Dans CNN, les données d'entrée / sortie de la couche de convolution sont ** feature map ** (feature map). Données d'entrée ** Carte des caractéristiques d'entrée ** (carte des caractéristiques) Données de sortie ** Carte des caractéristiques de sortie ** (carte des caractéristiques) Parfois je dis.
"Opération de pliage" Équivalent au "traitement de filtre" dans le traitement d'image Dans certaines publications, le terme «filtre» est parfois appelé «noyau».
Les paramètres utilisés pour ce filtre correspondent aux "poids" dans le réseau neuronal entièrement couplé.
Exemple de calcul
Opération biaisée
Mettre: Remplir des données fixes (par exemple 0) autour des données d'entrée
La figure ci-dessous est remplie avec 1 pixel 0 de largeur
Stride: La distance entre les positions où le filtre est appliqué.
Taille d'entrée (H, W) Taille de filtre (FH, FW) Taille de sortie (OH, OW) Rembourrage P Foulée S La taille de sortie est la suivante
OH = \frac{H + 2P - FH}{S} + 1\\
OW = \frac{W + 2P - FW}{S} + 1
Compte tenu de l'opération de convolution tridimensionnelle dans un bloc rectangulaire facile à comprendre, c'est comme suit.
Ce qui précède est une carte des caractéristiques avec une sortie. En d'autres termes, il s'agit d'une carte de caractéristiques avec un canal.
Ce qui suit est un diagramme en cas de méthodes de canaux multiples.
L'ajout du terme de biais est le suivant.
Lors du traitement par lots de N éléments de données, la forme des données est la même
Pooling: calcul pour réduire la vue verticale et horizontale du ciel
Dans la figure ci-dessous, la taille de l'espace est réduite en effectuant un traitement qui regroupe la zone 2x2 en un seul élément.
Dans cet exemple, il s'agit du processus lors de la mise en pool 2x2 Max sur la diapositive 2.
Pooling max: calcul pour prendre la valeur maximale dans la zone De plus, en général, la taille de la fenêtre de regroupement et la diapositive sont définies sur la même valeur.
En plus de la mise en commun maximale, il existe une mise en commun moyenne qui prend la valeur moyenne de la zone.
・ Il n'y a pas de paramètres à apprendre
La mise en commun est un processus qui ne prend que la valeur maximale (ou la valeur moyenne) de la cible, il n'y a donc pas de paramètres à apprendre.
・ Le nombre de canaux ne change pas
Le calcul de regroupement ne modifie pas le nombre de canaux de données d'entrée et de données de sortie. (OH et OW changent, mais FN ne le fait pas)
・ Robuste contre les changements de position infimes
La mise en commun renvoie des résultats similaires pour de petits écarts dans les données d'entrée. Par conséquent, il est robuste contre de légers écarts dans les données d'entrée.
#Générer des données de manière aléatoire
x = np.random.rand(10,1,28,28)
x.shape
# (10, 1, 28, 28)
x[0].shape
# (1, 28, 28)
x[1].shape
# (1, 28, 28)
x[0, 0].shape # x[0][0]Mais d'accord
# (28, 28)
Si vous implémentez la convolution comme indiqué dans la figure précédente, vous devez combiner plusieurs instructions for. En outre, NumPy est lent lors de l'utilisation de l'instruction for.
Par conséquent, implémentez en utilisant une fonction appelée im2col au lieu d'une instruction for. im2col est une fonction qui étend les données d'entrée en fonction du filtre.
Dans cette figure, nous avons souligné la facilité de compréhension et donné un exemple dans lequel les zones de filtre ne se chevauchent pas.
・ Avantages Im2col Inconvénients Avantage: Il est possible d'utiliser efficacement la bibliothèque d'algèbre linéaire car elle peut être réduite au calcul matriciel. Inconvénients: consomme plus de mémoire que d'habitude
#----------------------------------------------------
# Parameters
# input_data : (Le nombre de données,Canal,la taille,Largeur)Données d'entrée constituées d'un tableau à 4 dimensions de
# filter_h :Hauteur du filtre
# filter_w :Largeur du filtre
# stride :foulée
# pad :Rembourrage
# Returns
# col :Un tableau bidimensionnel
#----------------------------------------------------
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
Afficher avec im2col
import sys, os
sys.path.append(os.pardir)
from common.util import im2col
x1 = np.random.rand(1, 3, 7, 7)
col1 = im2col(x1, 5, 5, stride=1, pad=0)
print(col1.shape)
x2 = np.random.rand(10, 3, 7, 7)
col2 = im2col(x2, 5, 5, stride=1, pad=0)
print(col2.shape)
résultat (9, 75) (90, 75)
x1 correspond à des données 7x7 avec une taille de lot 1 et 3 canaux x2 correspond à des données 7x7 avec une taille de lot de 10 et 3 canaux
Dans les deux cas, le nombre d'éléments dans la deuxième dimension est de 75, qui est la somme du nombre d'aspects du filtre. (Canal 3, taille 5 x 5)
Après avoir développé les données avec im2col, tout ce que vous avez à faire est de développer le filtre (poids) de la couche de convolution dans une colonne et de calculer le produit interne des deux matrices. C'est presque la même chose que ce que nous avons fait dans la couche Affine de la couche entièrement connectée.
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)
#à la fonction de remodelage-Si 1 est spécifié, le nombre d'éléments sera résumé afin que le Tsuji du tableau multidimensionnel corresponde.
col_W = self.W.reshape(FN, -1).T
out = np.dot(col, col_W) + self.b
#Enfin, façonnez la taille de sortie à la forme appropriée
#reshape reconfigure la taille de sortie spécifiée
#transposer change l'ordre des axes
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)
#Le calcul de la matrice inverse elle-même se fait dans les deux lignes suivantes, ce qui est le même que dans Affine, la seule différence est l'ajustement des dimensions de la matrice.
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)
#Traitement inverse de im2col
dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)
return dx
Comme pour la couche Convolution, utilisez im2col pour développer et implémenter les données d'entrée Cependant, dans le cas du pooling, la différence est qu'ils sont indépendants dans le sens du canal.
class Pooling:
def __init__(self, pool_h, pool_w, stride=1, pad=0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
self.x = None
self.arg_max = None
def forward(self, x):
N, C, H, W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h*self.pool_w)
arg_max = np.argmax(col, axis=1)
out = np.max(col, axis=1)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
self.x = x
self.arg_max = arg_max
return out
def backward(self, dout):
dout = dout.transpose(0, 2, 3, 1)
pool_size = self.pool_h * self.pool_w
dmax = np.zeros((dout.size, pool_size))
#flatten réinsère la structure dans un tableau unidimensionnel
dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
dmax = dmax.reshape(dout.shape + (pool_size,))
dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
return dx
# 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
#ConvNet simple
# conv - relu - pool - affine - relu - affine - softmax
class SimpleConvNet:
#----------------------------------------------------
# Parameters
# input_size :Taille d'entrée (784 pour MNIST)
# hidden_size_list :Liste des nombres de neurones de la couche cachée (e.g. [100, 100, 100])
# output_size :Taille de sortie (10 pour MNIST)
# activation : 'relu' or 'sigmoid'
# weight_init_std :Spécifiez l'écart type du poids (e.g. 0.01)
# 'relu'Ou'he'Si est spécifié, "Valeur initiale de He" est définie.
# 'sigmoid'Ou'xavier'Si est spécifié, "Valeur initiale de Xavier" est défini.
#----------------------------------------------------
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):
#Initialisation des poids, calcul de la taille de sortie de la couche de convolution
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()
#Faites une déduction
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
#Trouvez la fonction de perte
def loss(self, x, t):
"""Trouvez la fonction de perte
L'argument x est les données d'entrée, t est l'étiquette de l'enseignant
"""
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 numerical_gradient(self, x, t):
"""Trouver le dégradé (différenciation numérique)
Parameters
----------
x :Des données d'entrée
t :Label enseignant
Returns
-------
Variable de dictionnaire avec gradient de chaque couche
grads['W1']、grads['W2']、...Est le poids de chaque couche
grads['b1']、grads['b2']、...Est-ce que le biais de chaque couche
"""
loss_w = lambda w: self.loss(x, t)
grads = {}
for idx in (1, 2, 3):
grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)])
grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)])
return grads
def gradient(self, x, t):
"""Trouvez le dégradé (méthode de propagation de retour d'erreur)
Parameters
----------
x :Des données d'entrée
t :Label enseignant
Returns
-------
Variable de dictionnaire avec gradient de chaque couche
grads['W1']、grads['W2']、...Est le poids de chaque couche
grads['b1']、grads['b2']、...Est-ce que le biais de chaque couche
"""
# 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)]
Le fait est qu'il peut être implémenté simplement en augmentant le nombre de couches et en augmentant la valeur de l'hyper paramètre utilisé dans la couche cachée.
Effectuer l'apprentissage De plus, mon Macbook Air utilisait beaucoup le processeur, je n'ai donc pas commenté la réduction des données et l'ai exécutée.
# coding: utf-8
import sys, os
sys.path.append(os.pardir) #Paramètres d'importation des fichiers dans le répertoire parent
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from 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)
#Réduisez les données si le traitement prend du temps
#x_train, t_train = x_train[:5000], t_train[:5000]
#x_test, t_test = x_test[:1000], t_test[:1000]
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)
trainer.train()
#Enregistrer les paramètres
network.save_params("params.pkl")
print("Saved Network Parameters!")
#Dessiner un graphique
markers = {'train': 'o', 'test': 's'}
x = np.arange(max_epochs)
plt.plot(x, trainer.train_acc_list, marker='o', label='train', markevery=2)
plt.plot(x, trainer.test_acc_list, marker='s', label='test', markevery=2)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
…… train loss:0.0145554384445 train loss:0.0275851756417 train loss:0.00785021651885 train loss:0.00986611950473 =============== Final Test Accuracy =============== test acc:0.956 Saved Network Parameters!
Avant l'apprentissage: Il n'y a pas de régularité dans les tons noir et blanc car le filtre est initialisé aléatoirement.
Après l'apprentissage: régulier
Qu'est-ce qu'un filtre avec une telle régularité «regarde»? ・ ** Bord **: Bordure où la couleur change ・ ** Blob **: zone localement grumeleuse
Convolution de la 1ère couche: extrait les informations de bas niveau telles que les bords et les gouttes Empilez plusieurs couches de convolution: extrayez des informations plus complexes et abstraites
La DEMO 1 suivante a été citée http://vision03.csail.mit.edu/cnn_art/index.html#v_single
Dans la démo, c'était comme suit. Cov1: Bord, Blob (Bord + Blob) Cov3: Texture Cov5: Parties d'objets Fc8: Classes d'objets comme les chiens et les chats
Par conséquent, à mesure que la couche s'approfondit, les neurones passent de formes simples à des informations "avancées" **. En d'autres termes, c'est le jour où l'objet qui réagit change pour que vous puissiez comprendre le «sens» des choses.
Ce livre explique ce qui suit ・ CNN, qui a été proposé pour la première fois en 1998, est également le LeNet original ・ AlexNet en 2012, lorsque l'apprentissage profond a attiré l'attention
LeNet
Les points suivants sont différents par rapport à "CNN actuel" ・ Utilisez la fonction sigmoïde pour la fonction d'activation (Actuellement fonction ReLU) ・ La taille des données intermédiaires est réduite par le sous-échantillonnage. (Actuellement Max Pooling) http://dx.doi.org/10.1109/5.726791
AlexNet
AlexNet empile la couche de convolution et la couche de pooling, et renvoie finalement le résultat via la couche entièrement connectée. Les points suivants sont différents de LeNet -Utiliser la fonction ReLU comme fonction d'activation ・ Utilisez une couche qui effectue une normalisation locale appelée LRN (Local Response Normalization) ・ Utiliser Dropout
Il n'y a pas de grande différence entre LeNet et AlexNe dans la configuration du réseau, mais il y a eu des progrès majeurs dans la technologie informatique. En particulier ・ Une grande quantité de données est désormais accessible à tous ・ Les GPU spécialisés dans de grandes quantités de calculs parallèles se sont généralisés, ce qui permet d'effectuer de grandes quantités d'opérations à grande vitesse.
Recommended Posts