Classifier les ensembles de données d'image CIFAR-10 à l'aide de divers modèles d'apprentissage en profondeur

Motivation

Je voulais utiliser un réseau de neurones profonds comme Residual Network. ImageNet a utilisé l'ensemble de données d'image CIFAR-10 pour classer les images car MNIST ne suffit pas et ImageNet semble difficile à collecter des données et prend du temps à apprendre.

Qu'est-ce que le jeu de données d'images CIFAR-10?

L'ensemble de données d'image CIFAR-10 est un ensemble de données d'image couleur de petite taille https://www.cs.toronto.edu/~kriz/cifar.html

Environnement d'exécution

Je l'ai exécuté dans l'environnement suivant

Code source et fonctionnalités

Le code source utilisé est ci-dessous. Les commandes décrites ci-dessous supposent que vous avez cloné ce code et que vous êtes à la racine de l'arborescence source. https://github.com/dsanno/chainer-cifar

Il a les fonctions suivantes

Mesure du taux d'erreur

Lors de la classification, le taux d'erreur a été mesuré comme suit.

Obtenez l'ensemble de données

Vous pouvez le télécharger à partir du lien «CIFAR-10 python version» à https://www.cs.toronto.edu/~kriz/cifar.html. Ou téléchargez le jeu de données avec la commande suivante. Le fichier du jeu de données fait 166 Mo et cela prend du temps si la ligne est fine.

$ python src/download.py

Décompressez l'ensemble de données téléchargé et vous verrez l'image suivante.

Le contenu de data_batch_1 etc. est dict, et les données brutes de l'image sont stockées dans "data" et les informations d'étiquette sont stockées dans "étiquettes". Voici un exemple qui enregistre les 100 premiers sous forme d'image tout en examinant data_batch_1.

$ python
>>> import cPickle as pickle
>>> f = open('dataset/cifar-10-batches-py/data_batch_1', 'rb')
>>> train_data = pickle.load(f)
>>> f.close()
>>> type(train_data['data'])
<type 'numpy.ndarray'>
>>> train_data['data'].shape #Obtenez la forme des données brutes
(10000L, 3072L)
>>> train_data['data'][:5]   #Obtenez les 5 premières données brutes
array([[ 59,  43,  50, ..., 140,  84,  72],
       [154, 126, 105, ..., 139, 142, 144],
       [255, 253, 253, ...,  83,  83,  84],
       [ 28,  37,  38, ...,  28,  37,  46],
       [170, 168, 177, ...,  82,  78,  80]], dtype=uint8)
>>> type(train_data['labels'])
<type 'list'>
>>> train_data['labels'][:10] #Obtenez les 10 premières données d'étiquette
[6, 9, 9, 4, 1, 1, 2, 7, 8, 3]
>>> from PIL import Image
>>> sample_image = train_data['data'][:100].reshape((10, 10, 3, 32, 32)).transpose((0, 3, 1, 4, 2)).reshape((320, 320, 3)) #Trier les 100 premières pièces en tuiles
>>> Image.fromarray(sample_image).save('sample.png')

Vous pouvez obtenir les images suivantes sample.png

Prétraitement d'image

Exécutez la commande suivante pour générer un ensemble de données avec 3 types de prétraitement.

$ python src/dataset.py

Pour "Valeur moyenne de l'image", nous avons utilisé la valeur moyenne des valeurs RVB de l'ensemble des données d'entraînement indépendamment de RVB. La normalisation du contraste a rendu le contraste uniforme en soustrayant la valeur moyenne de chaque image de la valeur RVB, puis en la multipliant par une constante de sorte que l'écart type devienne 1. Je ne comprends pas vraiment le blanchiment ZCA, mais Toki no Mori Wiki Dit qu'une "transformation pour que la matrice de covariance des données devienne une matrice unitaire" est effectuée. Le calcul spécifique du blanchiment est décrit en détail dans Mambo Diary "CIFAR-10 and ZCA whitening".

L'image après normalisation du contraste + blanchiment ZCA est la suivante. Si elle est laissée telle quelle, la distribution des valeurs RVB est étroite, elle est donc normalisée afin que la distribution des valeurs RVB s'étale dans la plage de 0 à 255 pour chaque image.

sample_norm_zca.png

Augmentation des données d'entraînement

Les augmentations suivantes sont effectuées dans tous les apprentissages. Au moment du test, l'augmentation n'est pas effectuée et les données de test prétraitées sont utilisées telles quelles.

Le code de la partie d'augmentation est le suivant.


import numpy as np

(Omission)

    def __trans_image(self, x):
        size = 32
        n = x.shape[0]
        images = np.zeros((n, 3, size, size), dtype=np.float32)
        offset = np.random.randint(-4, 5, size=(n, 2))
        mirror = np.random.randint(2, size=n)
        for i in six.moves.range(n):
            image = x[i]
            top, left = offset[i]
            left = max(0, left)
            top = max(0, top)
            right = min(size, left + size)
            bottom = min(size, left + size)
            if mirror[i] > 0:
                images[i,:,size-bottom:size-top,size-right:size-left] = image[:,top:bottom, left:right][:,:,::-1]
            else:
                images[i,:,size-bottom:size-top,size-right:size-left] = image[:,top:bottom,left:right]
        return images

Essayez de classer en utilisant un réseau relativement peu profond

Apprenons avec un réseau relativement peu profond comme celui ci-dessous. Utilisez une structure de type réseau utilisée dans le didacticiel Tensorflor (https://www.tensorflow.org/versions/r0.9/tutorials/deep_cnn/index.html). (Pas exactement la même chose, il existe des différences dans la composition des calques et les valeurs des paramètres initiaux) Après avoir empilé 3 couches de réseau neuronal à convolution (CNN) + ReLU + MaxPooling, il y a 2 couches de couche entièrement connectée. Après la couche entièrement connectée, une suppression est fournie pour supprimer le surapprentissage.

class CNN(chainer.Chain):
    def __init__(self):
        super(CNN, self).__init__(
            conv1=L.Convolution2D(3, 64, 5, stride=1, pad=2),
            conv2=L.Convolution2D(64, 64, 5, stride=1, pad=2),
            conv3=L.Convolution2D(64, 128, 5, stride=1,
            pad=2),
            l1=L.Linear(4 * 4 * 128, 1000),
            l2=L.Linear(1000, 10),
        )

    def __call__(self, x, train=True):
        h1 = F.max_pooling_2d(F.relu(self.conv1(x)), 3, 2)
        h2 = F.max_pooling_2d(F.relu(self.conv2(h1)), 3, 2)
        h3 = F.max_pooling_2d(F.relu(self.conv3(h2)), 3, 2)
        h4 = F.relu(self.l1(F.dropout(h3, train=train)))
        return self.l2(F.dropout(h4, train=train))

Exécutez l'apprentissage avec la commande suivante. Il a fallu environ 40 minutes pour fonctionner.

$ python src/train.py -g 0 -m cnn -b 128 -p cnn --optimizer adam --iter 300 --lr_decay_iter 100

La signification des options est la suivante

La courbe d'erreur ressemble à ceci, avec un taux d'erreur de test de 18,94%. En regardant la courbe d'erreur, si vous réduisez le taux d'apprentissage après avoir appris pendant un certain temps, l'apprentissage progressera à nouveau rapidement. C'est une technique utilisée pour programmer le taux d'apprentissage pour diminuer après un certain nombre de fois de cette manière.

cnn2_error.png

Utilisez un ensemble de données avec normalisation du contraste + blanchiment ZCA

Cette fois, nous nous entraînerons à l'aide d'un ensemble de données qui a subi une normalisation du contraste + un blanchiment ZCA lors du prétraitement. Exécutez l'apprentissage avec la commande suivante. Il a fallu environ 40 minutes pour fonctionner.

$ python src/train.py -g 0 -m cnn -b 128 -p cnn_zca --optimizer adam --iter 300 --lr_decay_iter 100 -d dataset/image_norm_zca.pkl

"-d dataset / image_norm_zca.pkl" spécifie l'ensemble de données qui a subi la normalisation du contraste + le blanchiment ZCA.

La courbe d'erreur ressemble à ceci, avec un taux d'erreur de test de 18,76%. Le résultat est que même s'il vaut mieux que de simplement soustraire la valeur moyenne, elle est presque inchangée.

cnn_zca2_error.png

Utiliser la normalisation par lots

La normalisation par lots est une méthode pour normaliser la sortie d'une couche spécifique à 0 en moyenne et à 1 sur la distribution pour chaque mini-lot. Le but est de faciliter l'apprentissage de la couche suivante en normalisant. L'algorithme est décrit en détail dans cet article. Vous pouvez utiliser chainer.links.BatchNormalization pour effectuer la normalisation par lots avec Chainer.

Le code réseau utilisé cette fois est indiqué ci-dessous. La configuration du réseau est presque la même que celle que nous avons utilisée précédemment, la seule différence est la présence ou l'absence de normalisation par lots.

class BatchConv2D(chainer.Chain):
    def __init__(self, ch_in, ch_out, ksize, stride=1, pad=0, activation=F.relu):
        super(BatchConv2D, self).__init__(
            conv=L.Convolution2D(ch_in, ch_out, ksize, stride, pad),
            bn=L.BatchNormalization(ch_out),
        )
        self.activation=activation

    def __call__(self, x, train):
        h = self.bn(self.conv(x), test=not train)
        if self.activation is None:
            return h
        return F.relu(h)

class CNNBN(chainer.Chain):
    def __init__(self):
        super(CNNBN, self).__init__(
            bconv1=BatchConv2D(3, 64, 5, stride=1, pad=2),
            bconv2=BatchConv2D(64, 64, 5, stride=1, pad=2),
            bconv3=BatchConv2D(64, 128, 5, stride=1, pad=2),
            l1=L.Linear(4 * 4 * 128, 1000),
            l2=L.Linear(1000, 10),
        )

    def __call__(self, x, train=True):
        h1 = F.max_pooling_2d(self.bconv1(x, train), 3, 2)
        h2 = F.max_pooling_2d(self.bconv2(h1, train), 3, 2)
        h3 = F.max_pooling_2d(self.bconv3(h2, train), 3, 2)
        h4 = F.relu(self.l1(F.dropout(h3, train=train)))
        return self.l2(F.dropout(h4, train=train))

Pour apprendre à utiliser la normalisation par lots, entrez la commande suivante: Il a fallu environ 50 minutes pour fonctionner.

$ python src/train.py -g 0 -m cnnbn -b 128 -p cnnbn --optimizer adam --iter 300 --lr_decay_iter 100

Utilisez le modèle avec normalisation par lots avec "-m cnnbn".

La courbe d'erreur ressemble à ceci, avec un taux d'erreur de 12,40%. Vous pouvez voir que le taux d'erreur est considérablement inférieur à celui sans normalisation par lots.

cnnbn2_error.png

J'ai également essayé d'utiliser les données d'entraînement de Contrast Normalization + ZCA Whitening. La commande est la suivante.

$ python src/train.py -g 0 -m cnnbn -b 128 -p cnnbn --optimizer adam --iter 300 --lr_decay_iter 100 -d dataset/image_norm_zca.pkl

Le taux d'erreur était de 12,27%, ce qui était légèrement supérieur à la simple soustraction de la moyenne.

cnnbn_zca2_error.png

Utilisez un modèle semblable à VGG

Utilisez un modèle similaire à la couche VGG 16 ou VGG 19. Le modèle VGG a une couche entièrement connectée après avoir répété plusieurs fois plusieurs CNN de taille de noyau 3 + Max Pooling. J'ai utilisé un réseau basé sur VGG le Jour "L'histoire de Kaggle CIFAR-10", j'ai donc utilisé un réseau similaire. Apprendre. Ce blog a obtenu un score élevé de 94,15% de taux de reconnaissance des données de test.

Les différences avec "L'histoire de Kaggle CIFAR-10" sont les suivantes.

Cette implémentation Kaggle CIFAR-10 histoires
Des données d'entrée 32 x 32px 24 x 24 px
Augmentation(Pendant l'apprentissage) Mouvement parallèle, inversion gauche-droite Mouvement parallèle, inversion gauche-droite、拡大
Augmentation(Au moment du test) Aucun Mouvement parallèle, inversion gauche-droite, agrandissement
Nombre de modèles 1 pièce 6(Utilisez la sortie moyenne de chaque modèle)
Batch Normaliztion Oui Aucun

Il se présente comme suit lorsqu'il est décrit à l'aide de Chainer.

class VGG(chainer.Chain):
    def __init__(self):
        super(VGG, self).__init__(
            bconv1_1=BatchConv2D(3, 64, 3, stride=1, pad=1),
            bconv1_2=BatchConv2D(64, 64, 3, stride=1, pad=1),
            bconv2_1=BatchConv2D(64, 128, 3, stride=1, pad=1),
            bconv2_2=BatchConv2D(128, 128, 3, stride=1, pad=1),
            bconv3_1=BatchConv2D(128, 256, 3, stride=1, pad=1),
            bconv3_2=BatchConv2D(256, 256, 3, stride=1, pad=1),
            bconv3_3=BatchConv2D(256, 256, 3, stride=1, pad=1),
            bconv3_4=BatchConv2D(256, 256, 3, stride=1, pad=1),
            fc4=L.Linear(4 * 4 * 256, 1024),
            fc5=L.Linear(1024, 1024),
            fc6=L.Linear(1024, 10),
        )

    def __call__(self, x, train=True):
        h = self.bconv1_1(x, train)
        h = self.bconv1_2(h, train)
        h = F.dropout(F.max_pooling_2d(h, 2), 0.25, train=train)
        h = self.bconv2_1(h, train)
        h = self.bconv2_2(h, train)
        h = F.dropout(F.max_pooling_2d(h, 2), 0.25, train=train)
        h = self.bconv3_1(h, train)
        h = self.bconv3_2(h, train)
        h = self.bconv3_3(h, train)
        h = self.bconv3_4(h, train)
        h = F.dropout(F.max_pooling_2d(h, 2), 0.25, train=train)
        h = F.relu(self.fc4(F.dropout(h, train=train)))
        h = F.relu(self.fc5(F.dropout(h, train=train)))
        h = self.fc6(h)
        return h

Exécutez-le avec la commande suivante. Un modèle de type VGG est spécifié avec "-m vgg". Il a fallu environ cinq heures et demie pour fonctionner.

$ python src/train.py -g 0 -m vgg -b 128 -p vgg_adam --optimizer adam --iter 300 --lr_decay_iter 100 

Le taux d'erreur est de 7,65%, améliorant la précision de la reconnaissance.

vgg_adam2_error.png

Utiliser le réseau résiduel

Residual Network est un réseau qui combine une transformation uniforme et plusieurs couches de CNN afin que l'apprentissage puisse être bien effectué même si la hiérarchie est approfondie. Il est décrit en détail dans cet article.

Le réseau mis en œuvre cette fois a un total de 74 couches et a la configuration suivante. En ce qui concerne la façon de compter les couches, étant donné que le bloc résiduel utilise 2 couches de CNN, un bloc résiduel est compté comme 2 couches.

Je voulais vraiment en faire une couche 110, mais je ne pouvais pas l'exécuter en raison du manque de mémoire GPU. Nous avons confirmé que si l'image d'entrée est réduite à 24 x 24px, elle peut être exécutée avec 110 couches.

L'implémentation réseau dans Chainer ressemble à ceci:

class ResidualBlock(chainer.Chain):
    def __init__(self, ch_in, ch_out, stride=1, swapout=False, skip_ratio=0, activation1=F.relu, activation2=F.relu):
        w = math.sqrt(2)
        super(ResidualBlock, self).__init__(
            conv1=L.Convolution2D(ch_in, ch_out, 3, stride, 1, w),
            bn1=L.BatchNormalization(ch_out),
            conv2=L.Convolution2D(ch_out, ch_out, 3, 1, 1, w),
            bn2=L.BatchNormalization(ch_out),
        )
        self.activation1 = activation1
        self.activation2 = activation2
        self.skip_ratio = skip_ratio
        self.swapout = swapout

    def __call__(self, x, train):
        skip = False
        if train and self.skip_ratio > 0 and np.random.rand() < self.skip_ratio:
            skip = True
        sh, sw = self.conv1.stride
        c_out, c_in, kh, kw = self.conv1.W.data.shape
        b, c, hh, ww = x.data.shape
        if sh == 1 and sw == 1:
            shape_out = (b, c_out, hh, ww)
        else:
            hh = (hh + 2 - kh) // sh + 1
            ww = (ww + 2 - kw) // sw + 1
            shape_out = (b, c_out, hh, ww)
        h = x
        if x.data.shape != shape_out:
            xp = chainer.cuda.get_array_module(x.data)
            n, c, hh, ww = x.data.shape
            pad_c = shape_out[1] - c
            p = xp.zeros((n, pad_c, hh, ww), dtype=xp.float32)
            p = chainer.Variable(p, volatile=not train)
            x = F.concat((p, x))
            if x.data.shape[2:] != shape_out[2:]:
                x = F.average_pooling_2d(x, 1, 2)
        if skip:
            return x
        h = self.bn1(self.conv1(h), test=not train)
        if self.activation1 is not None:
            h = self.activation1(h)
        h = self.bn2(self.conv2(h), test=not train)
        if not train:
            h = h * (1 - self.skip_ratio)
        if self.swapout:
            h = F.dropout(h, train=train) + F.dropout(x, train=train)
        else:
            h = h + x
        if self.activation2 is not None:
            return self.activation2(h)
        else:
            return h

class ResidualNet(chainer.Chain):
    def __init__(self, depth=18, swapout=False, skip=True):
        super(ResidualNet, self).__init__()
        links = [('bconv1', BatchConv2D(3, 16, 3, 1, 1), True)]
        skip_size = depth * 3 - 3
        for i in six.moves.range(depth):
            if skip:
                skip_ratio = float(i) / skip_size * 0.5
            else:
                skip_ratio = 0
            links.append(('res{}'.format(len(links)), ResidualBlock(16, 16, swapout=swapout, skip_ratio=skip_ratio, ), True))
        links.append(('res{}'.format(len(links)), ResidualBlock(16, 32, stride=2, swapout=swapout), True))
        for i in six.moves.range(depth - 1):
            if skip:
                skip_ratio = float(i + depth) / skip_size * 0.5
            else:
                skip_ratio = 0
            links.append(('res{}'.format(len(links)), ResidualBlock(32, 32, swapout=swapout, skip_ratio=skip_ratio), True))
        links.append(('res{}'.format(len(links)), ResidualBlock(32, 64, stride=2, swapout=swapout), True))
        for i in six.moves.range(depth - 1):
            if skip:
                skip_ratio = float(i + depth * 2 - 1) / skip_size * 0.5
            else:
                skip_ratio = 0
            links.append(('res{}'.format(len(links)), ResidualBlock(64, 64, swapout=swapout, skip_ratio=skip_ratio), True))
        links.append(('_apool{}'.format(len(links)), F.AveragePooling2D(8, 1, 0, False, True), False))
        links.append(('fc{}'.format(len(links)), L.Linear(64, 10), False))

        for name, f, _with_train in links:
            if not name.startswith('_'):
                self.add_link(*(name, f))
        self.layers = links

    def __call__(self, x, train=True):
        h = x
        for name, f, with_train in self.layers:
            if with_train:
                h = f(h, train=train)
            else:
                h = f(h)
        return h

Il existe un paramètre appelé swapout, mais c'est une implémentation expérimentale, alors ignorez-le pour le moment. Dans le bloc résiduel, lorsque les tailles d'entrée et de sortie sont différentes (la largeur et la hauteur sont plus petites en sortie, le nombre de canaux est le même ou la sortie est plus grande), la partie de conversion d'égalité est la suivante.

Exécutez-le avec la commande suivante.

python src/train.py -g 0 -m residual -b 128 -p residual --res_depth 12 --optimizer sgd --lr 0.1 --iter 300 --lr_decay_iter 100

«-m residuel» est la spécification du réseau résiduel. --optimizer sgd est une spécification qui utilise Momentum SGD, ce qui donne de meilleurs résultats que l'utilisation d'Adam. La valeur initiale du taux d'apprentissage semble être de 0,1. Dans la mise en œuvre de la classification d'image CIFAR-10 à l'aide du réseau résiduel répertorié ci-dessous, le taux d'apprentissage initial était de 0,1.

Il a fallu environ 10 heures pour fonctionner. Le taux d'erreur de test était de 8,06%, ce qui était pire que le modèle de type VGG.

residual_noskip_error.png

Utiliser la profondeur stochastique

La profondeur stochastique est une méthode permettant de sauter de manière probabiliste un bloc résiduel pendant l'apprentissage. L'explication est détaillée dans cet article.

Le code de réseau est publié dans Utilisation du réseau résiduel (en utilisant le # réseau-résiduel). Donnez à ReisualBlock une propriété appelée skip_ratio, et empêchez la partie CNN de Residual Block d'être exécutée avec la probabilité spécifiée dans skip_ratio pendant l'apprentissage. Lors du test, utilisez la valeur obtenue en multipliant la partie CNN par (1 --skip_ratio). Le skip_ratio est incliné de sorte que plus le bloc résiduel est profond, plus le skip_ratio est grand. Cette fois, skip_ratio vaut 0 pour le premier bloc résiduel, 0,5 pour le skip_ratio le plus profond, et les blocs intermédiaires sont modifiés linéairement.

Exécutez-le avec la commande suivante.

python src/train.py -g 0 -m residual -b 128 -p residual_skip --skip_depth --res_depth 12 --optimizer sgd --lr 0.1 --iter 300 --lr_decay_iter 100

--skip_depth est une option pour utiliser la profondeur stochastique.

Il a fallu environ 9 heures pour apprendre. Le taux d'erreur de test est maintenant de 7,42%, améliorant la précision.

residual_error.png

Résumé

Nous avons classé l'ensemble de données d'image CIFAR-10 à l'aide de divers modèles. Nous avons pu confirmer la différence de taux de reconnaissance due à la différence de prétraitement, à la présence ou à l'absence de normalisation des lots et à la différence des modèles.

Cette fois, je l'ai implémenté avec Chainer, mais par exemple, Implementation of Stochastic Depth by Torch7 est plus rapide et économise de la mémoire que l'implémentation utilisée cette fois, et le même environnement (https://github.com/yueatsprograms/Stochastic_Depth) J'ai pu former 56layer Residual Network sur Ubuntu). Si vous souhaitez exécuter un modèle plus profond, vous pouvez envisager un autre framework.

Les références

Recommended Posts

Classifier les ensembles de données d'image CIFAR-10 à l'aide de divers modèles d'apprentissage en profondeur
Modèle de reconnaissance d'image utilisant l'apprentissage profond en 2016
Implémentation du modèle Deep Learning pour la reconnaissance d'images
Deep learning 1 Pratique du deep learning
Collecte et automatisation d'images érotiques à l'aide du deep learning
[PyTorch] Classification des images du CIFAR-10
Examen de la méthode de prévision des échanges utilisant le Deep Learning et la conversion en ondelettes - Partie 2
Deep running 2 Réglage de l'apprentissage profond
Importance des ensembles de données d'apprentissage automatique
Apprentissage par renforcement profond 2 Mise en œuvre de l'apprentissage par renforcement
[Détection d'anomalies] Essayez d'utiliser la dernière méthode d'apprentissage à distance
Capture d'image de Firefox en utilisant Python
Jugement de l'image rétroéclairée avec OpenCV
Implémentation du modèle de reconnaissance d'images d'apprentissage en profondeur 2
J'ai essayé l'apprentissage en profondeur avec Theano
Reconnaissance d'image des fruits avec VGG16
[AI] Apprentissage en profondeur pour le débruitage d'image
Analyser émotionnellement les données de bouche-à-oreille d'entreprise des réunions de changement de carrière à l'aide de l'apprentissage en profondeur
L'histoire d'un débutant en apprentissage profond essayant de classer les guitares avec CNN
J'ai essayé l'histoire courante de l'utilisation du Deep Learning pour prédire la moyenne Nikkei
J'ai essayé l'histoire courante de prédire la moyenne Nikkei à l'aide du Deep Learning (backtest)
Examen de la méthode de prévision des échanges utilisant le Deep Learning et la conversion en ondelettes
Python: principes de base de la reconnaissance d'image à l'aide de CNN
Deep learning 2 appris par l'implémentation (classification d'images)
Classez les visages d'anime avec l'apprentissage en profondeur avec Chainer
Othello-De la troisième ligne de "Implementation Deep Learning" (3)
Python: Application de la reconnaissance d'image à l'aide de CNN
Prévision du cours des actions à l'aide du Deep Learning (TensorFlow)
Essayez l'apprentissage profond de la génomique avec Kipoi
Visualisez les effets de l'apprentissage profond / de la régularisation
Analyse émotionnelle des tweets avec apprentissage en profondeur
Alignement d'image: du SIFT au deep learning
Reconnaissance d'image en apprentissage profond 3 après la création du modèle
Enregistrement d'apprentissage de la lecture "Deep Learning from scratch"
Othello-De la troisième ligne de "Implementation Deep Learning" (2)
Apprentissage automatique: reconnaissance d'image de MNIST à l'aide de PCA et de Gaussian Native Bayes
[Deep Learning from scratch] Valeur initiale du poids du réseau neuronal utilisant la fonction sigmoïde