[PYTHON] Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 11) CNN

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.

Couche pliante, rembourrage, foulée

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é. OH = \frac{H + 2P - FH}{S} + 1 OW = \frac{W + 2P - FW}{S} + 1

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 p7-1.jpg p7-2.jpg

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

Formation et test des données MNIST

À 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 p6-.jpg

p6-2.jpg

p6-3.jpg

p6-4.jpg

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.

Classe SimpleConvNet

# 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.

Confirmation de la taille des données de test de ces données d'entrée MNIST

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

Vérifiez la taille du filtre W1

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

Confirmation du rembourrage et de la foulée

                        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.

Taille de sortie de la couche de convolution

OH = \frac{H + 2P - FH}{S} + 1 = (28 + 0 - 5)/1 +1 = 24 OW = \frac{W + 2P - FW}{S} + 1 = (28 + 0 - 5)/1 +1 = 24 Devrait être.

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

Suivi des couches de convolution

        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.

p7-3.jpg p7-4.jpg

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)

référence

Compréhension complète de la fonction numpy.pad Manipulez librement le tableau bidimensionnel. [Initialisation / Référence / Extraction / Calcul / Déplacement]

Partie 10

Recommended Posts

Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 11) CNN
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 19) Augmentation des données
Mémo d'auto-apprentissage "Deep Learning from scratch" (partie 12) Deep learning
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 18) One! Miaou! Grad-CAM!
Mémo d'auto-apprentissage "Deep Learning from scratch" (n ° 15) Tutoriel pour débutants TensorFlow
Mémo d'auto-apprentissage "Deep Learning from scratch" (glossaire illisible)
"Deep Learning from scratch" Mémo d'auto-apprentissage (n ° 9) Classe MultiLayerNet
Mémo d'auto-apprentissage «Deep Learning from scratch» (10) Classe MultiLayerNet
Mémo d'auto-apprentissage «Deep Learning from scratch» (n ° 10-2) Valeur initiale du poids
Apprentissage profond à partir de zéro
[Mémo d'apprentissage] Le Deep Learning fait de zéro [Chapitre 7]
Deep learning / Deep learning made from scratch Chapitre 6 Mémo
[Mémo d'apprentissage] Deep Learning fait de zéro [Chapitre 5]
[Mémo d'apprentissage] Le Deep Learning fait de zéro [Chapitre 6]
[Mémo d'apprentissage] Deep Learning fait de zéro [~ Chapitre 4]
"Deep Learning from scratch" Mémo d'auto-apprentissage (n ° 16) J'ai essayé de créer SimpleConvNet avec Keras
"Deep Learning from scratch" Mémo d'auto-apprentissage (n ° 17) J'ai essayé de créer DeepConvNet avec Keras
Deep Learning from scratch Chapter 2 Perceptron (lecture du mémo)
[Mémo d'apprentissage] Apprentissage profond à partir de zéro ~ Mise en œuvre de l'abandon ~
Apprentissage profond à partir de zéro 1 à 3 chapitres
"Deep Learning from scratch" Mémo d'auto-apprentissage (n ° 14) Exécutez le programme du chapitre 4 sur Google Colaboratory
Deep learning / Deep learning from scratch 2 Chapitre 4 Mémo
Deep learning / Deep learning made from scratch Chapitre 3 Mémo
Apprentissage profond à partir de zéro (calcul des coûts)
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 7 Mémo
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 8 Mémo
Deep learning / Deep learning made from scratch Chapitre 5 Mémo
Deep learning / Deep learning made from scratch Chapitre 4 Mémo
Deep learning / Deep learning from scratch 2 Chapitre 3 Mémo
Mémo d'apprentissage profond créé à partir de zéro
Deep Learning / Deep Learning à partir de Zero 2 Chapitre 6 Mémo
Apprentissage profond à partir de zéro (propagation vers l'avant)
"Deep Learning from scratch" avec Haskell (inachevé)
[Windows 10] Construction de l'environnement "Deep Learning from scratch"
Enregistrement d'apprentissage de la lecture "Deep Learning from scratch"
[Deep Learning from scratch] À propos de l'optimisation des hyper paramètres
Pourquoi ModuleNotFoundError: Aucun module nommé'dataset.mnist 'n'apparaît dans "Deep Learning from scratch".
Mémo d'auto-apprentissage "Deep Learning from scratch" (partie 8) J'ai dessiné le graphique du chapitre 6 avec matplotlib
Deep Learning from scratch ① Chapitre 6 "Techniques liées à l'apprentissage"
GitHub du bon livre "Deep Learning from scratch"
Django memo n ° 1 à partir de zéro
Résumé Python vs Ruby "Deep Learning from scratch"
[Deep Learning from scratch] J'ai implémenté la couche Affine
Application de Deep Learning 2 à partir de zéro Filtre anti-spam
<Cours> Apprentissage en profondeur: Day2 CNN
Apprentissage profond / code de travail LSTM
[Deep Learning from scratch] Implémentation de la méthode Momentum et de la méthode AdaGrad
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 5
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 2
Créez un environnement pour "Deep Learning from scratch" avec Docker
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 3
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 7
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 5
Un amateur a trébuché dans le Deep Learning ❷ fait de zéro Note: Chapitre 1
Un amateur a trébuché dans le Deep Learning ❷ fait à partir de zéro Note: Chapitre 4
Un amateur a trébuché dans le Deep Learning à partir de zéro.
Un amateur a trébuché dans le Deep Learning à partir de zéro Note: Chapitre 2
J'ai essayé d'implémenter Perceptron Part 1 [Deep Learning from scratch]