[PYTHON] Deep Learning Gaiden ~ Programmation GPU ~

Aperçu

L'article précédent était ici Dans l'article précédent, j'ai écrit que "cela prend plusieurs heures pour apprendre avec l'ensemble de données MNIST de Keras." Je ne peux pas faire cela avec un petit jeu de données comme le jeu de données MNIST, donc je vais l'accélérer. Parlant d'accélération du deep learning, c'est l'utilisation du GPU et du TPU ~ Donc, dans cet article, nous allons faire de la programmation GPU pour utiliser les GPU NVIDIA. Le package utilisé est «CuPy». La raison sera plus tard ...

table des matières

  • [Speed up pooling layer](# Speed up pooling layer)
  • Conclusion
  • Accélérez l'apprentissage en profondeur

    Remarque: vous pouvez l'ignorer car il parle paresseusement.

    Le développement de l'architecture informatique au cours des dernières années progresse à un niveau sans précédent. Par exemple, quand j'étais enfant, Game Boy Advance était populaire, mais la capacité de données des jeux en cours d'exécution était de ** 32 Mo ** au maximum. Cependant, de nos jours, en ce qui concerne les jeux fonctionnant sur du matériel de jeu extrêmement spécifique tel que PS4, PS5 et Switch, il semble qu'il existe une capacité de données de ** 10 Go ** bien sûr. Étant donné que Go représente 1024 fois celui de Mo, cela signifie que ** il est devenu possible de traiter près de 1000 fois la quantité de données en seulement 10 ans **. À partir de là, vous pouvez voir la progression des supports de stockage tels que le disque dur et le SSD.

    Bien sûr, à mesure que la quantité de données traitées augmente, le nombre d'instructions traitées par l'ordinateur augmente également. Je ne sais pas si la puissance de traitement nécessaire au CPU est épuisée, mais pour y répondre, le développement du CPU suit une règle empirique appelée "** Loi de Moore **" ** Doublé en 18 mois (récemment 24 mois) Il est devenu ** [^ 1]. Cela signifie que ** 15 ans augmenteront les performances de traitement de votre ordinateur de 1024 fois **. C'est incroyable ~

    Cependant, comme mentionné précédemment, la puissance de traitement requise d'un processeur est requise pour le plafond bleu chaque fois que les performances du processeur s'améliorent et que davantage de choses peuvent être faites. Pour cette raison, le manque de performance a toujours été déploré.

    Il ne fait aucun doute que le contexte de l'apprentissage profond sous les feux de la rampe est l'amélioration des performances du processeur, et il existe de nombreuses situations dans lesquelles le manque de performances du processeur est déploré. L'un d'eux est la reconnaissance d'image et le ** réseau de neurones convolutifs (CNN) **. Étant donné que les données d'image sont bidimensionnelles, si vous essayez d'utiliser un ensemble de données d'image légèrement volumineux pour l'apprentissage, elles deviendront rapidement un tenseur avec plus de 10000 unités d'éléments, et le processeur actuel est extrêmement insuffisant en performances. Article précédent ne le mentionne pas spécifiquement, mais quand j'ai expérimenté, quand j'ai essayé d'apprendre en utilisant le jeu de données MNIST de Keras, c'était sur google colaboratory. Cela prend également ** 1 époque 30 minutes **. Étant donné que google colaboratory a une limite de 12 heures, vous ne pouvez apprendre que 24 époques (sauf si vous reprenez l'apprentissage de l'enregistrement et du rechargement temporaires). Eh bien, vous pouvez toujours apprendre avec une précision suffisante avec l'ensemble de données MNIST.

    Quoi qu'il en soit, ce n'est pas facile d'expérimenter cela. Le GPU était au centre de l'attention.

    GPU Le CPU est l'unité centrale de traitement, tandis que le GPU est appelé l'unité de traitement graphique. Comme son nom l'indique, il s'agit d'un ** processeur semi-conducteur spécialisé dans les calculs pour le dessin d'écran **. ** Le processeur est excellent pour le calcul à usage général **, tandis que ** le GPU est spécialisé pour le calcul pour le traitement d'image **, donc sa vitesse est écrasante. Étant donné que le calcul massivement parallèle est effectué avec des milliers de cœurs ou plus, le dessin d'écran est essentiellement effectué sans décalage. Et voici le miso, mais ce calcul super parallèle et ce calcul matriciel ont une affinité. cpu_vs_gpu.png Veuillez noter que ce chiffre n'est pas qu'une métaphore, il ne le fait pas réellement sur le GPU. Ce que je voudrais que vous lisiez sur cette figure, c'est que ** le calcul matriciel peut être exécuté en parallèle **. À propos, la figure ci-dessus peut être réalisée en parallélisant les processeurs, mais l'échelle des GPU est d'un ordre de grandeur.

    Les GPU qui se sont concentrés sur l'apprentissage profond comme celui-ci apporteront une grande contribution au développement de GPGPU: General-Purpose Computing on Graphics Processing Units: avec l'avènement de la technologie qui utilise des dispositifs de traitement graphique pour le calcul général.

    TPU Aujourd'hui, avec l'avènement des GPU et des GPGPU, l'apprentissage profond a fait des progrès rapides, mais c'est la nature humaine qui ne suffit pas. C'est pourquoi TPU: Tensor Processing Unit: Tensol Processing Device a été introduit.

    Le GPU a été conçu uniquement pour les graphiques, mais à partir de là, le TPU ** a été conçu pour réaliser un calcul de tenseur à grande vitesse pour l'apprentissage en profondeur. Au détriment de la polyvalence et d'un peu de précision de calcul, nous avons atteint des vitesses qui submergent le GPU, et même atteignent nos pieds.

    Comme il est spécialisé dans le calcul tenseur, il est moins polyvalent que le GPU, et il est accéléré en réduisant le calcul de 32 bits ou 64 bits à 8 bits ou 16 bits. De plus, afin de réduire même l'écriture dans la mémoire cache, des données sont échangées dans le circuit arithmétique, et de toute façon, le calcul du tenseur est conçu à grande vitesse.

    Alpha Go Zero est un exemple typique de sa puissance écrasante. Le montant du calcul qui prendrait ** 30 000 ans ** une fois converti en CPU par un simple calcul a été effectué en ** 3 jours ** en utilisant plusieurs TPU. Je ne comprends pas le sens. Lol

    En tant que tel, les avantages du calcul massivement parallèle sur l'apprentissage profond sont énormes.

    Programmation GPU avec CuPy

    Maintenant, entrons dans le sujet principal. Dans cet article, nous utiliserons CuPy pour la programmation GPU. Il semble que CuPy était à l'origine un package développé pour l'implémentation de programme GPU (programmation CUDA) dans Chainer. Le plus grand avantage est qu'il suit numpy, donc la plupart du code réécrit simplement np (ʻimport numpy as np) en cp (ʻimport cupy as cp). C'est pourquoi j'ai décidé d'utiliser CuPy dans cet article. Facile c'est génial! Lol

    J'y pense rarement. Ou plutôt, c'est sale parce que je l'ai implémenté sous forme de prototype ... Je vais le régler bientôt. Je me demande si je devrais utiliser un décorateur ... Faites-moi savoir si vous avez une bonne idée. Le code est ici.

    ** À propos, pour utiliser GPU avec google colaboratory, vous devez sélectionner GPU comme type d'exécution. ** **

    Installation et confirmation de CuPy

    Pour installer CuPy, exécutez la cellule dans laquelle vous avez entré le code suivant.

    !curl https://colab.chainer.org/install | sh -
    
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100  1580  100  1580    0     0   6666      0 --:--:-- --:--:-- --:--:--  6666
    + apt -y -q install cuda-libraries-dev-10-0
    Reading package lists...
    Building dependency tree...
    Reading state information...
    cuda-libraries-dev-10-0 is already the newest version (10.0.130-1).
    0 upgraded, 0 newly installed, 0 to remove and 11 not upgraded.
    + pip install -q cupy-cuda100  chainer 
         |████████████████████████████████| 348.0MB 51kB/s 
    + set +ex
    Installation succeeded!
    

    Cela installera automatiquement la version requise de CuPy. Aussi «Chainer». Je ne l'utilise pas, mais ça va. Vous pouvez vérifier s'il est correctement installé avec le code suivant.

    !python -c 'import chainer; chainer.print_runtime_info()'
    
    Platform: Linux-4.19.112+-x86_64-with-Ubuntu-18.04-bionic
    Chainer: 7.4.0
    ChainerX: Not Available
    NumPy: 1.18.5
    CuPy: Not Available
    iDeep: 2.0.0.post3
    

    Ce n'est pas grave si vous pouvez confirmer la sortie de cette manière.

    Programmation CuPy

    Prenons (une partie de) la fonction d'activation comme exemple.

    activator.py

    
    import numpy as np
    import cupy as cp
    
    
    class Activator():
        def __init__(self, *args, mode="cpu", **kwds):
            self.mode = mode
    
            if self.mode == "cpu":
                self.forward = self.cpu_forward
                self.backward = self.cpu_backward
                self.update = self.cpu_update
            elif self.mode == "gpu":
                self.forward = self.gpu_forward
                self.backward = self.gpu_backward
                self.update = self.gpu_update
        
    
        def cpu_forward(self, *args, **kwds):
            raise NotImplemented
    
        def gpu_forward(self, *args, **kwds):
            raise NotImplemented
    
    
        def cpu_backward(self, *args, **kwds):
            raise NotImplemented
        
        def gpu_backward(self, *args, **kwds):
            raise NotImplemented
    
    
        def cpu_update(self, *args, **kwds):
            raise NotImplemented
    
        def gpu_update(self, *args, **kwds):
            raise NotImplemented
    
    
    class step(Activator):
        def cpu_forward(self, x, *args, **kwds):
            return np.where(x > 0, 1, 0)
        
        def gpu_forward(self, x, *args, **kwds):
            return cp.where(x > 0, 1, 0)
    
    
        def cpu_backward(self, x, *args, **kwds):
            return np.zeros_like(x)
        
        def gpu_backward(self, x, *args, **kwds):
            return cp.zeros_like(x)
    

    J'écris avec la mort cérébrale. D'une manière ou d'une autre, il doit y avoir un moyen plus intelligent ... Ce que nous faisons, c'est créer des branches en tirant parti de la capacité de Python à assigner des fonctions comme une sorte d'objet. La partie implémentation de la fonction est également différente de np et cp! C'est la bonne chose à propos de «CuPy». Vous pouvez facilement et commodément effectuer la programmation GPU ~

    Confirmation d'effet

    Expérimentons avec l'ensemble de données MNIST de Keras. L'exécution exécute toutes les cellules jusqu'au code expérimental, exécute la cellule qui lit les données Keras et enfin exécute le corps du code expérimental CNN.

    cnn_main.py

    
    %matplotlib inline
    #Créer une couche de convolution et une couche de sortie
    M, F_h, F_w = 10, 3, 3
    lm = LayerManager((x_train, x_test), (t_train, t_test), mode="gpu")
    #lm.append(name="c", I_shape=(C, I_h, I_w), F_shape=(M, F_h, F_w), pad=1,
    #          wb_width=0.1, opt="AdaDelta", opt_dic={"eta": 1e-2})
    lm.append(name="c", I_shape=(C, I_h, I_w), F_shape=(M, F_h, F_w), pad=1)
    lm.append(name="p", I_shape=lm[-1].O_shape, pool=2)
    #lm.append(name="m", n=100, wb_width=0.1,
    #          opt="AdaDelta", opt_dic={"eta": 1e-2})
    lm.append(name="m", n=100)
    #lm.append(name="o", n=n_class, act="softmax", err_func="Cross", wb_width=0.1,
    #          opt="AdaDelta", opt_dic={"eta": 1e-2})
    lm.append(name="o", n=n_class, act="softmax", err_func="Cross")
    
    #Apprendre
    epoch = 5
    threshold = 1e-8
    n_batch = 8
    lm.training(epoch, threshold=threshold, n_batch=n_batch, show_train_error=True)
    
    #Prédire
    print("training dataset")
    _ = lm.predict(x=lm.x_train, y=lm.y_train)
    print("test dataset")
    if lm.mode == "cpu":
        y_pred = lm.predict()
    elif lm.mode == "gpu":
        y_pred = lm.predict().get()
    
    progress:[XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]483s/514s
    training dataset
    correct: [5 0 4 1 9 2 1 3 1 4 3 5 3 6 1 7]
    predict: [5 0 4 1 9 2 1 3 1 4 3 5 3 6 1 7]
    accuracy rate: 98.58 % (59148/60000)
    test dataset
    correct: [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1 5]
    predict: [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1 5]
    accuracy rate: 97.58 % (9758/10000)
    

    CNN_test_error_transition.png Cela ne veut rien dire, mais c'est par défaut la fonction d'activation, la plage de poids wb_width, l'optimiseur, etc. En d'autres termes, la fonction d'activation est ReLU, wb_width vaut 0,05 et l'optimiseur est Adam. L'époque d'apprentissage est définie sur 5.

    Le résultat de l'exécution est d'environ 100 secondes par époque! Nous avons réussi à accélérer 18 fois. C'est encore lent, mais cela devrait être pratique. Sauf pour MNIST ... (yeux lointains)

    Pour accélérer encore

    À propos, au bas du code de test se trouve le code d'entraînement de l'ensemble de données MNIST dans Keras. Je l'ai copié depuis ici.

    mnist_cnn.py

    mnist_cnn.py

    
    '''Trains a simple convnet on the MNIST dataset.
    Gets to 99.25% test accuracy after 12 epochs
    (there is still a lot of margin for parameter tuning).
    16 seconds per epoch on a GRID K520 GPU.
    '''
     
    from __future__ import print_function
    import keras
    from keras.datasets import mnist
    from keras.models import Sequential
    from keras.layers import Dense, Dropout, Flatten
    from keras.layers import Conv2D, MaxPooling2D
    from keras import backend as K
     
    batch_size = 128
    num_classes = 10
    epochs = 12
     
    # input image dimensions
    img_rows, img_cols = 28, 28
     
    # the data, split between train and test sets
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
     
    if K.image_data_format() == 'channels_first':
        x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
        x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
        input_shape = (1, img_rows, img_cols)
    else:
        x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
        x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
        input_shape = (img_rows, img_cols, 1)
     
    x_train = x_train.astype('float32')
    x_test = x_test.astype('float32')
    x_train /= 255
    x_test /= 255
     
    print('x_train shape:', x_train.shape)
    print(x_train.shape[0], 'train samples')
    print(x_test.shape[0], 'test samples')
     
    # convert class vectors to binary class matrices
    y_train = keras.utils.to_categorical(y_train, num_classes)
    y_test = keras.utils.to_categorical(y_test, num_classes)
     
    model = Sequential()
    model.add(Conv2D(32, kernel_size=(3, 3),
                     activation='relu',
                     input_shape=input_shape))
    model.add(Conv2D(64, (3, 3), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))
    model.add(Flatten())
    model.add(Dense(128, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(num_classes, activation='softmax'))
     
    model.compile(loss=keras.losses.categorical_crossentropy,
                  optimizer=keras.optimizers.Adadelta(),
                  metrics=['accuracy'])
     
    model.fit(x_train, y_train,
              batch_size=batch_size,
              epochs=epochs,
              verbose=1,
              validation_data=(x_test, y_test))
    score = model.evaluate(x_test, y_test, verbose=0)
    print('Test loss:', score[0])
    print('Test accuracy:', score[1])
    
    Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
    11493376/11490434 [==============================] - 0s 0us/step
    x_train shape: (60000, 28, 28, 1)
    60000 train samples
    10000 test samples
    Epoch 1/12
    469/469 [==============================] - 4s 9ms/step - loss: 2.2889 - accuracy: 0.1426 - val_loss: 2.2611 - val_accuracy: 0.2889
    Epoch 2/12
    469/469 [==============================] - 4s 9ms/step - loss: 2.2432 - accuracy: 0.2350 - val_loss: 2.2046 - val_accuracy: 0.4885
    Epoch 3/12
    469/469 [==============================] - 4s 9ms/step - loss: 2.1837 - accuracy: 0.3312 - val_loss: 2.1279 - val_accuracy: 0.5908
    Epoch 4/12
    469/469 [==============================] - 4s 9ms/step - loss: 2.1039 - accuracy: 0.4035 - val_loss: 2.0235 - val_accuracy: 0.6492
    Epoch 5/12
    469/469 [==============================] - 4s 9ms/step - loss: 1.9959 - accuracy: 0.4669 - val_loss: 1.8864 - val_accuracy: 0.6989
    Epoch 6/12
    469/469 [==============================] - 4s 9ms/step - loss: 1.8604 - accuracy: 0.5193 - val_loss: 1.7149 - val_accuracy: 0.7420
    Epoch 7/12
    469/469 [==============================] - 4s 9ms/step - loss: 1.6990 - accuracy: 0.5681 - val_loss: 1.5179 - val_accuracy: 0.7688
    Epoch 8/12
    469/469 [==============================] - 4s 9ms/step - loss: 1.5315 - accuracy: 0.6014 - val_loss: 1.3180 - val_accuracy: 0.7912
    Epoch 9/12
    469/469 [==============================] - 4s 9ms/step - loss: 1.3717 - accuracy: 0.6327 - val_loss: 1.1394 - val_accuracy: 0.8029
    Epoch 10/12
    469/469 [==============================] - 4s 9ms/step - loss: 1.2431 - accuracy: 0.6562 - val_loss: 0.9945 - val_accuracy: 0.8171
    Epoch 11/12
    469/469 [==============================] - 4s 9ms/step - loss: 1.1369 - accuracy: 0.6757 - val_loss: 0.8818 - val_accuracy: 0.8263
    Epoch 12/12
    469/469 [==============================] - 4s 9ms/step - loss: 1.0520 - accuracy: 0.6957 - val_loss: 0.7949 - val_accuracy: 0.8356
    Test loss: 0.7948545217514038
    Test accuracy: 0.8356000185012817
    

    C'est rapide ... et 20 fois plus rapide ... cela signifie qu'il y a encore de la place pour la vitesse!

    Voyons maintenant où se trouve le goulot d'étranglement de la vitesse de calcul dans mon code actuel. Le temps de traitement est mesuré à l'aide de la magie «timeit». Si vous l'utilisez, il mesurera bien le temps de traitement.

    Mesure du temps du calcul d'erreur

    Premièrement, mesurons le temps nécessaire au calcul des erreurs.

    search_bottleneck.py

    
    #Calcul de l'erreur d'entraînement
    %%timeit
    lm.forward(lm.x_train)
    error = lm[-1].get_error(lm.y_train)
    
    #----------output----------
    # 1 loop, best of 3: 957 ms per loop
    #--------------------------
    
    
    #Calcul de l'erreur de test
    %%timeit
    lm.forward(lm.x_test)
    error = lm[-1].get_error(lm.y_test)
    
    #----------output----------
    # 10 loops, best of 3: 160 ms per loop
    #--------------------------
    

    La quantité de données pour le calcul d'erreur des données d'entraînement est de 60 000, donc ce sera comme ça. Je pense que c'est normal d'en avoir moins ... Je pense que nous pouvons nous améliorer ici. Comme pour les données de test, si vous les réduisez à 10 000, elles seront raccourcies d'environ 0,8 seconde par époque. Eh bien, c'est une sorte d'erreur. Compte tenu de l'ensemble (100 secondes par époque), le taux d'erreur de calcul est d'environ 1% dans son ensemble, ce n'est donc pas un goulot d'étranglement. La partie apprentissage semble donc être un problème.

    Mesure du temps de la partie d'apprentissage

    Nous mesurerons le temps de traitement de la partie d'apprentissage. Quelque part dans l'apprentissage, 99% du temps de traitement par époque devrait être ...

    search_bottleneck.py

    
    #Les données d'un mini-lot constituent la cible de mesure.
    rand_index = np.arange(lm.x_train.get().shape[0])
    np.random.shuffle(rand_index)
    rand = rand_index[0 : n_batch]
    
    #Calcul de la propagation vers l'avant
    %%timeit
    lm.forward(lm.x_train[rand])
    
    #----------output----------
    # 1000 loops, best of 3: 1.32 ms per loop
    #--------------------------
    
    #Calcul de rétropropagation
    %%timeit
    lm.backward(lm.y_train[rand])
    
    #----------output----------
    # 100 loops, best of 3: 10.3 ms per loop
    #--------------------------
    
    #Calcul de mise à jour du poids
    %%timeit
    lm.update()
    
    #----------output----------
    # 1000 loops, best of 3: 1.64 ms per loop
    #--------------------------
    

    De toute évidence, seule la rétropropagation prend un temps inhabituel. La propagation vers l'avant et les mises à jour de poids prennent 10 fois plus de temps. Étant donné que les données d'entraînement sont de 60000 cette fois et que la taille du mini-lot est de 8, ce processus de calcul sera répété 7500 fois, donc au total ~~ $ (1,32 + 10,3 + 1,64) \ fois 7500 \ fois 10 ^ {- Cela coûtera 3} = 23,92s $. C'est moins que ce à quoi je m'attendais ... Cela a pris assez de temps, mais ce n'est toujours pas assez ... Eh bien, il y a beaucoup de fluctuations, alors ne vous inquiétez pas pour le moment. ~~ C'était juste une erreur de calcul ... Je dois bien comprendre les spécifications de la calculatrice. (1.32+10.3+1.64) \times 7500 \times 10^{-3} = 99.45s Quoi qu'il en soit, la rétro-propagation est anormalement lente, nous allons donc la mesurer plus en détail.

    Mesure du temps de rétropropagation

    Donc, nous allons diviser le processus de propagation arrière et le mesurer.

    search_bottleneck.py

    
    #Préparation préalable
    err3 = lm[3].backward(lm.y_train[rand])
    err2 = lm[2].backward(err3)
    err2 = err2.reshape(n_batch, *lm[1].O_shape)
    err1 = lm[1].backward(err2)
    err0 = lm[0].backward(err1)
    
    #Rétropropagation de la couche de sortie
    %%timeit
    err3 = lm[3].backward(lm.y_train[rand])
    
    #----------output----------
    # 10000 loops, best of 3: 152 µs per loop
    #--------------------------
    
    #Rétropropagation de la couche intermédiaire
    %%timeit
    err2 = lm[2].backward(err3)
    err2 = err2.reshape(n_batch, *lm[1].O_shape)
    
    #----------output----------
    # 1000 loops, best of 3: 224 µs per loop
    #--------------------------
    
    #Rétropropagation de la couche de mise en commun
    %%timeit
    err1 = lm[1].backward(err2)
    
    #----------output----------
    # 1000 loops, best of 3: 9.72 ms per loop
    #--------------------------
    
    #Rétropropagation de la couche convolutive
    %%timeit
    err0 = lm[0].backward(err1)
    
    #----------output----------
    # 1000 loops, best of 3: 442 µs per loop
    #--------------------------
    

    Il s'avère que la couche de mise en commun est des ordres de grandeur plus lente. Le temps de traitement de la couche de pooling représente environ 93,6% du temps de calcul de la rétro-propagation. Au fait, si vous ajoutez ceci, ce sera environ 10 ms, donc c'est presque la même chose.

    Mesure du temps de rétro-propagation de la couche de mise en commun

    Examinons donc de plus près la rétropropagation de la couche de pooling en question.

    search_bottleneck.py

    
    #Préparation préalable
    B, C, O_h, O_w = n_batch, *lm[1].O_shape
    grad = err2.transpose(0, 2, 3, 1).reshape(-1, 1)
    grad_x = cp.zeros((grad.size, lm[1].pool*lm[1].pool))
    grad_x1 = grad_x.copy()
    grad_x1[:, lm[1].max_index] = grad
    grad_x2 = grad_x1.reshape(B*O_h*O_w, C*lm[1].pool*lm[1].pool).T
    
    #Echange dimensionnel et transformation des erreurs
    %%timeit
    grad = err2.transpose(0, 2, 3, 1).reshape(-1, 1)
    
    #----------output----------
    # 100000 loops, best of 3: 17.1 µs per loop
    #--------------------------
    
    #Génération de matrice vide
    %%timeit
    grad_x = cp.zeros((grad.size, lm[1].pool*lm[1].pool))
    
    #----------output----------
    # 100000 loops, best of 3: 7.89 µs per loop
    #--------------------------
    
    #Remplissage de valeur
    %%timeit
    grad_x1[:, lm[1].max_index] = grad
    
    #----------output----------
    # 1000 loops, best of 3: 9.5 ms per loop
    #--------------------------
    
    #Déformation et translocation
    %%timeit
    grad_x2 = grad_x1.reshape(B*O_h*O_w, C*lm[1].pool*lm[1].pool).T
    
    #----------output----------
    # 1000000 loops, best of 3: 1.86 µs per loop
    #--------------------------
    
    # col2im
    %%timeit
    grad_x3 = lm[1].col2im(grad_x2, (n_batch, *lm[1].I_shape), lm[1].O_shape,
                           stride=lm[1].pool, pad=lm[1].pad_state)
    
    #----------output----------
    # 10000 loops, best of 3: 112 µs per loop
    #--------------------------
    

    Le remplissage des prix est extrêmement lent que les autres ... C'est le goulot d'étranglement ici. Le rapport entre le remplissage de valeur et la rétropropagation de la couche de regroupement est en fait d'environ 98,6%.

    Le GPU est puissant pour les calculs simples, mais quand il s'agit d'un traitement aussi peu compliqué, il ralentit immédiatement et vous ne pouvez pas utiliser pleinement les performances. Pensons donc à un plan d'amélioration.

    Accélérer la couche de pooling

    Je me suis demandé s'il y avait un bon moyen de renseigner le prix lors de l'accélération. La première chose à laquelle j'ai pensé était que ce type de traitement compliqué est mieux adapté au processeur, il devrait donc être traité par le processeur au lieu du GPU. Cependant, des expériences ont montré que le goulot d'étranglement lorsque l'ensemble du processus est traité par le processeur est également dans la même partie, donc cette idée a été rejetée.

    L'idée suivante était de réécrire cette partie du processus sous une autre forme. En d'autres termes, j'ai pensé: "Remplaçons ce processus d'affectation par un processus de calcul dans lequel le GPU est bon." Cela signifie qu'au lieu de conserver l'index, vous devez conserver une matrice éparse qui a la même forme que l'entrée (renvoyée à la fonction ʻim2col`). 1 uniquement lorsque la valeur maximale correspond, 0 dans le cas contraire. La quantité de mémoire requise est le double du $ pool ^ 2 $ normal, mais $ pool $ est généralement petit, ce qui est très bien.

    pool.py

    
    import numpy as np
    import cupy as cp
    
    
    class PoolingLayer(BaseLayer):
        def __init__(self, *, mode="cpu",
                     I_shape=None, pool=1, pad=0,
                     name="", **kwds):
            self.mode = mode
    
            self.name = name
            
            if I_shape is None:
                raise KeyError("Input shape is None.")
            
            if len(I_shape) == 2:
                C, I_h, I_w = 1, *I_shape
            else:
                C, I_h, I_w = I_shape
            self.I_shape = (C, I_h, I_w)
    
            #Contient la fonction im2col et la fonction col2im
            if self.mode == "cpu":
                self.im2col = cpu_im2col
                self.col2im = cpu_col2im
            elif self.mode == "gpu":
                self.im2col = gpu_im2col
                self.col2im = gpu_col2im
            
            if self.mode == "cpu":
                _, O_shape, self.pad_state = self.im2col(
                    np.zeros((1, *self.I_shape)),
                    (pool, pool),
                    stride=pool, pad=pad)
            elif self.mode == "gpu":
                _, O_shape, self.pad_state = self.im2col(
                    cp.zeros((1, *self.I_shape)),
                    (pool, pool),
                    stride=pool, pad=pad)
            self.O_shape = (C, *O_shape)
            
            self.n = np.prod(self.O_shape)
            
            self.pool = pool
            self.F_shape = (pool, pool)
        
        
        def forward(self, x):
            B = x.shape[0]
            C, O_h, O_w = self.O_shape
            
            self.x, _, self.pad_state = self.im2col(x, self.F_shape,
                                                    stride=self.pool,
                                                    pad=self.pad_state)
            self.x = self.x.T.reshape(B*O_h*O_w*C, -1)
    
            if self.mode == "cpu":
                #self.max_index = np.argmax(self.x, axis=1)
                self.y = np.max(self.x, axis=1, keepdims=True)
                self.max_index = np.where(self.y == self.x, 1, 0)
                self.y = self.y.reshape(B, O_h, O_w, C).transpose(0, 3, 1, 2)
            elif self.mode == "gpu":
                #self.max_index = cp.argmax(self.x, axis=1)
                self.y = cp.max(self.x, axis=1, keepdims=True)
                self.max_index = cp.where(self.y == self.x, 1, 0)
                self.y = self.y.reshape(B, O_h, O_w, C).transpose(0, 3, 1, 2)
            
            return self.y
        
        
        def backward(self, grad):
            B = grad.shape[0]
            I_shape = B, *self.I_shape
            C, O_h, O_w = self.O_shape
            
            grad = grad.transpose(0, 2, 3, 1).reshape(-1, 1)
            if self.mode == "cpu":
                self.grad_x = np.zeros((grad.size, self.pool*self.pool))
            elif self.mode == "gpu":
                self.grad_x = cp.zeros((grad.size, self.pool*self.pool))
            #self.grad_x[:, self.max_index] = grad
            self.grad_x = self.max_index*grad
            self.grad_x = self.grad_x.reshape(B*O_h*O_w, C*self.pool*self.pool).T
            self.grad_x = self.col2im(self.grad_x, I_shape, self.O_shape,
                                      stride=self.pool, pad=self.pad_state)
            
            return self.grad_x
        
        
        def update(self, **kwds):
            pass
    

    Expérimentons.

    search_bottleneck.py

    
    #Rétropropagation de la couche de mise en commun
    %%timeit
    err1 = lm[1].backward(err2)
    
    #----------output----------
    # 1000 loops, best of 3: 280 µs per loop
    #--------------------------
    
    #Remplissage de valeur
    %%timeit
    grad_x1 = lm[1].max_index*grad
    
    #----------output----------
    # 100000 loops, best of 3: 16.3 µs per loop
    #--------------------------
    

    D'ailleurs, on pense que les résultats ci-dessus sont attribués à un GPU différent des résultats expérimentaux précédents, il est donc délicat de comparer les résultats en général, mais pour le moment, nous avons réussi à accélérer. Il n'y aucun doute à propos de ça. Dans ce cas, la fonction col2im, etc. deviendra un problème cette fois, il y a donc encore de la place pour accélérer autour de cela.

    Aussi, dans son ensemble

    progress:[XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX]287s/285s
    training dataset
    correct: [5 0 4 1 9 2 1 3 1 4 3 5 3 6 1 7]
    predict: [5 0 4 1 9 2 1 3 1 4 3 5 3 6 1 7]
    accuracy rate: 99.21333333333334 % (59528/60000)
    test dataset
    correct: [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1 5]
    predict: [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1 5]
    accuracy rate: 98.03 % (9803/10000)
    

    De cette façon, nous avons pu le réduire à environ 50 s par époque! De plus, comme le temps d'apprentissage par mini-lot est d'environ 6 ms, le temps d'apprentissage par époque est de 6 $ \ times 7500 \ times 10 ^ {-3} = 45s $. ~~ Et la précédente discordance a été résolue ... Qu'est-ce que c'était après tout? J'aurais dû expérimenter en attribuant au même GPU ... eh bien. ~~

    en conclusion

    Avec ce genre de sentiment, nous allons rechercher la partie goulot d'étranglement, l'améliorer et l'accélérer. Nous continuerons de l'améliorer de temps en temps.

    Lorsque la taille du mini-lot P.S. était définie sur 128, le temps d'exécution était presque le même que celui de l'expérience avec Keras. C'était bon.

    Série d'apprentissage en profondeur

    [^ 1]: Strictement parlant, "le taux d'intégration des semi-conducteurs double en 18 mois (24 mois)".

    Recommended Posts

    Deep Learning Gaiden ~ Programmation GPU ~
    L'apprentissage en profondeur
    Apprentissage profond pour démarrer sans GPU
    Mémorandum d'apprentissage profond
    Commencer l'apprentissage en profondeur
    Apprentissage en profondeur Python
    Apprentissage profond × Python
    Premier apprentissage profond ~ Lutte ~
    Python: pratique du Deep Learning
    Fonctions d'apprentissage en profondeur / d'activation
    Apprentissage profond à partir de zéro
    Deep learning 1 Pratique du deep learning
    Apprentissage profond / entropie croisée
    Premier apprentissage profond ~ Préparation ~
    Enregistrement d'apprentissage de la programmation 2ème jour
    Première solution d'apprentissage en profondeur ~
    [AI] Apprentissage métrique profond
    J'ai essayé le deep learning
    Python: réglage du Deep Learning
    Technologie d'apprentissage en profondeur à grande échelle
    Fonction d'apprentissage profond / softmax
    PROGRAMMATION PROFONDE PROBABILISTE --- Bibliothèque "Deep Learning + Bayes" --- Présentation d'Edward
    Une scène où le GPU est utile pour le deep learning?
    estimation personnelle en temps réel (apprentissage en utilisant le GPU localement)
    Apprentissage profond à partir de zéro 1 à 3 chapitres
    Essayez l'apprentissage en profondeur avec TensorFlow
    <Cours> Apprentissage en profondeur: Day2 CNN
    Reconnaissance d'image par apprentissage profond 1 théorie
    Deep running 2 Réglage de l'apprentissage profond
    Apprentissage profond / code de travail LSTM
    (Maintenant) Construisez un environnement GPU Deep Learning avec GeForce GTX 960
    <Cours> Apprentissage en profondeur: Jour 1 NN
    Apprentissage profond du noyau avec Pyro
    Essayez le Deep Learning avec FPGA
    Apprentissage par renforcement 5 Essayez de programmer CartPole?
    Apprentissage profond pour la formation composée?
    Présentation d'Udacity Deep Learning Nanodegree
    Sujets> Deep Learning: Day3 RNN
    Programmation Python Machine Learning> Mots-clés
    Introduction au Deep Learning ~ Règles d'apprentissage ~
    Apprentissage par renforcement profond 1 Introduction au renforcement de l'apprentissage
    Premier mois d'apprentissage en programmation
    Apprentissage par renforcement profond 2 Mise en œuvre de l'apprentissage par renforcement
    Générez des Pokémon avec Deep Learning
    Introduction au Deep Learning ~ Rétropropagation ~
    Je n'ai pas de GPU, mais je vais essayer le Deep Learning
    Apprentissage automatique avec docker (42) Programmation PyTorch pour l'apprentissage en profondeur par Ian Pointer
    La gestion du tensorflow a soudainement cessé de fonctionner en utilisant le GPU dans l'apprentissage en profondeur
    Deep learning / Deep learning from scratch 2 Chapitre 4 Mémo
    Essayez le Deep Learning avec les concombres FPGA-Select
    Identification de la race de chat avec Deep Learning
    Deep learning / Deep learning made from scratch Chapitre 3 Mémo
    Faites de l'art ASCII avec l'apprentissage en profondeur
    Deep Learning / Deep Learning à partir de Zero 2 Chapitre 5 Mémo
    Implémenter le deep learning / VAE (Variational Autoencoder)
    Introduction à l'apprentissage en profondeur ~ Approximation des fonctions ~
    Essayez l'apprentissage en profondeur avec TensorFlow Partie 2
    Apprentissage profond à partir de zéro (calcul des coûts)
    À propos de la gestion de projet de deep learning (DNN)