[PYTHON] Deep Learning Gaiden ~ GPU-Programmierung ~

Überblick

Der vorherige Artikel war hier Im vorherigen Artikel schrieb ich: "Das Lernen mit dem MNIST-Datensatz von Keras dauert mehrere Stunden." Ich kann das mit einem kleinen Datensatz wie dem MNIST-Datensatz nicht machen, also werde ich es beschleunigen. Apropos schnelles Lernen zu beschleunigen, es ist die Verwendung von GPU und TPU ~ In diesem Artikel werden wir die GPU-Programmierung durchführen, um NVIDIA-GPUs zu verwenden. Das verwendete Paket ist "CuPy". Der Grund wird später sein ...

Inhaltsverzeichnis

  • [Pooling-Ebene beschleunigen](# Pooling-Ebene beschleunigen)
  • Schlussfolgerung
  • Beschleunigen Sie das tiefe Lernen

    Hinweis: Sie können es überspringen, da es langsam spricht.

    Die Entwicklung der Computerarchitektur in den letzten Jahren schreitet auf einem beispiellosen Niveau voran. Als ich ein Kind war, war Game Boy Advance zum Beispiel beliebt, aber die Datenkapazität der dort laufenden Spiele betrug maximal ** 32 MB **. Heutzutage haben PS4, PS5, Switch und andere Spiele, die auf extrem spezieller Spielehardware ausgeführt werden, selbstverständlich eine Datenkapazität von ** 10 GB **. Da GB das 1024-fache von MB beträgt, bedeutet dies, dass ** es in nur 10 Jahren möglich ist, fast das 1000-fache der Datenmenge zu verarbeiten **. Daraus können Sie den Fortschritt von Speichermedien wie HDD und SSD sehen.

    Mit zunehmender Datenmenge steigt natürlich auch die Anzahl der Anweisungen, die der Computer verarbeitet. Ich weiß nicht, dass die für die CPU erforderliche Rechenleistung erschöpft ist, aber um darauf zu reagieren, folgt die Entwicklung der CPU einer empirischen Regel namens "** Mooresches Gesetz **" ** Verdoppelt in 18 Monaten (vor kurzem 24 Monaten) Es wurde ** [^ 1]. Dies bedeutet, dass ** 15 Jahre die Verarbeitungsleistung Ihres Computers um das 1024-fache erhöhen **. Das ist unglaublich ~

    Wie bereits erwähnt, wird die für eine blaue CPU erforderliche Verarbeitungsleistung für die blaue Decke jedes Mal benötigt, wenn sich die Leistung der CPU verbessert und mehr getan werden kann. Aus diesem Grund wurde mangelnde Leistung immer beklagt.

    Es besteht kein Zweifel, dass der Hintergrund des tiefen Lernens im Rampenlicht die Verbesserung der CPU-Leistung ist, und es gibt viele Fälle, in denen die mangelnde CPU-Leistung beklagt wird. Eine davon ist die Bilderkennung und das ** Convolutional Neural Network (CNN) **. Da die Bilddaten zweidimensional sind und Sie versuchen, einen leicht großen Bilddatensatz zum Lernen zu verwenden, werden sie schnell zu einem Tensor mit mehr als 10.000 Elementeinheiten, und die Leistung der aktuellen CPU ist überwiegend unzureichend. Vorheriger Artikel gibt nicht speziell an, aber im Experiment, wenn Sie versuchen, die Verwendung des Kerist-MNIST-Datasets in Google Colaboratory zu erlernen Es dauert auch ** 1 Epoche 30 Minuten **. Da Google Colaboratory ein 12-Stunden-Limit hat, können Sie nur 24 Epochen lernen (es sei denn, Sie lernen weiter durch temporäres Speichern und erneutes Laden). Nun, mit dem MNIST-Datensatz können Sie immer noch mit ausreichender Genauigkeit lernen.

    Wie auch immer, es ist nicht einfach, damit zu experimentieren. Die GPU stand im Mittelpunkt der Aufmerksamkeit.

    GPU Die CPU ist die Zentraleinheit, während die GPU als Grafikverarbeitungseinheit bezeichnet wird. Wie der Name schon sagt, handelt es sich um einen ** Halbleiterprozessor, der sich auf Berechnungen für Siebzeichnungen ** spezialisiert hat. ** Die CPU eignet sich hervorragend für allgemeine Berechnungen **, während die ** GPU auf die Berechnung für die Bildverarbeitung spezialisiert ist **, sodass ihre Geschwindigkeit überwältigend ist. Da die massiv parallele Berechnung mit Tausenden oder mehr Kernen durchgeführt wird, wird die Bildschirmzeichnung grundsätzlich ohne Verzögerung durchgeführt. Und hier ist das Miso, aber diese superparallele Berechnung und Matrixberechnung haben eine Affinität. cpu_vs_gpu.png Bitte beachten Sie, dass diese Figur nicht nur eine Metapher ist, sondern dies auch nicht auf der GPU tut. Ich möchte, dass Sie aus dieser Abbildung lesen, dass die ** Matrixberechnung parallel ausgeführt werden kann **. Übrigens kann die obige Abbildung durch Parallelisieren von CPUs realisiert werden, aber der Maßstab der GPUs ist eine Größenordnung.

    Daher werden GPUs, die sich auf tiefes Lernen konzentriert haben, durch die Einführung von GPGPU einen großen Beitrag zu ihrer Entwicklung leisten: Allzweck-Computing auf Grafikverarbeitungseinheiten: Technologie, die Grafikverarbeitungsgeräte für allgemeine Berechnungen verwendet.

    TPU Mit dem Aufkommen von GPUs und GPGPUs hat Deep Learning schnelle Fortschritte gemacht, aber es ist die menschliche Natur, die nicht ausreicht. Aus diesem Grund wurde TPU: Tensor Processing Unit: Tensol Processing Device eingeführt.

    Die GPU wurde nur für Grafiken entwickelt, von dort aus wurde die TPU ** entwickelt, um eine Hochgeschwindigkeitstensorberechnung für tiefes Lernen zu realisieren. Auf Kosten der Vielseitigkeit und ein wenig Rechengenauigkeit haben wir Geschwindigkeiten erreicht, die die GPU überfordern und sogar unsere Füße erreichen.

    Da es auf die Tensorberechnung spezialisiert ist, ist es weniger vielseitig als die GPU und wird beschleunigt, indem die Berechnung von 32 Bit oder 64 Bit auf 8 Bit oder 16 Bit reduziert wird. Um ein gleichmäßiges Schreiben in den Cache-Speicher zu reduzieren, werden außerdem Daten in der arithmetischen Schaltung ausgetauscht, und die Tensorberechnung wird ohnehin mit hoher Geschwindigkeit durchgeführt.

    Alpha Go Zero ist ein typisches Beispiel für seine überwältigende Kraft. Der Berechnungsaufwand, der bei der Konvertierung in eine CPU durch einfache Berechnung ** 30.000 Jahre ** dauern würde, wurde in ** 3 Tagen ** mit mehreren TPUs abgeschlossen. Ich verstehe die Bedeutung nicht. Lol

    Daher sind die Vorteile einer massiv parallelen Berechnung beim Deep Learning enorm.

    GPU-Programmierung mit "CuPy"

    Kommen wir nun zum Hauptthema. In diesem Artikel verwenden wir "CuPy" für die GPU-Programmierung. Es scheint, dass "CuPy" ursprünglich ein Paket war, das für die Implementierung eines GPU-Programms (CUDA-Programmierung) in Chainer entwickelt wurde. Der größte Vorteil ist, dass es auf "numpy" folgt, sodass der meiste Code nur "np" ("numpy als np importieren") in "cp" ("cupy als cp importieren") umschreibt. Aus diesem Grund habe ich mich in diesem Artikel für die Verwendung von "CuPy" entschieden. Einfach ist toll! Lol

    Ich denke selten darüber nach. Oder besser gesagt, es ist schmutzig, weil ich es als Prototyp implementiert habe ... Ich werde es bald klären. Ich frage mich, ob ich einen Dekorateur verwenden sollte ... Bitte lassen Sie mich wissen, wenn Sie eine gute Idee haben. Der Code lautet hier.

    ** Um GPU mit Google Colaboratory verwenden zu können, müssen Sie übrigens GPU als Laufzeittyp auswählen. ** ** **

    Installation und Bestätigung von "CuPy"

    Um "CuPy" zu installieren, führen Sie die Zelle aus, in der Sie den folgenden Code eingegeben haben.

    !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!
    

    Dadurch wird automatisch die erforderliche Version von "CuPy" installiert. Auch Chainer. Ich benutze es nicht, aber es ist okay. Mit dem folgenden Code können Sie überprüfen, ob es ordnungsgemäß installiert ist.

    !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
    

    Es ist in Ordnung, wenn Sie die Ausgabe wie folgt bestätigen können.

    CuPy-Programmierung

    Nehmen Sie als Teil (einen Teil) der Aktivierungsfunktion.

    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)
    

    Ich schreibe mit Hirntod. Irgendwie muss es einen klügeren Weg geben ... Wir verzweigen, indem wir die Fähigkeit von Python nutzen, Funktionen als eine Art Objekt zuzuweisen. Der Teil der Funktionsimplementierung unterscheidet sich auch von np und cp! Das ist das Schöne an "CuPy". Sie können die GPU-Programmierung einfach und bequem durchführen ~

    Wirkungsbestätigung

    Experimentieren wir mit dem MNIST-Datensatz von Keras. Die Ausführung führt alle Zellen bis zum experimentellen Code aus, führt die Zelle aus, die die Keras-Daten liest, und führt schließlich den experimentellen CNN-Code-Body aus.

    cnn_main.py

    
    %matplotlib inline
    #Erstellen Sie eine Faltungsschicht und eine Ausgabeebene
    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")
    
    #Lernen
    epoch = 5
    threshold = 1e-8
    n_batch = 8
    lm.training(epoch, threshold=threshold, n_batch=n_batch, show_train_error=True)
    
    #Vorhersagen
    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 Es hat keine Bedeutung, aber standardmäßig Aktivierungsfunktion, Gewichtsbereich "wb_width", Optimierer usw. Mit anderen Worten, die Aktivierungsfunktion ist ReLU, "wb_width" ist 0,05 und der Optimierer ist Adam. Die Lernphase ist auf 5 eingestellt.

    Das Ausführungsergebnis beträgt ca. 100 Sekunden pro Epoche! Es ist uns gelungen, 18 Mal zu beschleunigen. Es ist immer noch langsam, aber es sollte praktisch sein. Außer MNIST ... (entfernte Augen)

    Zur weiteren Beschleunigung

    Am Ende des Testcodes befindet sich übrigens der MNIST-Dataset-Trainingscode in Keras. Ich habe es von [hier] kopiert (https://github.com/keras-team/keras/blob/master/examples/mnist_cnn.py).

    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
    

    Es ist schnell ... und 20 mal schneller ... das heißt, es gibt immer noch Raum für Geschwindigkeit!

    Lassen Sie uns nun herausfinden, wo der Engpass der Berechnungsgeschwindigkeit in meinem aktuellen Code liegt. Die Verarbeitungszeit wird mit "Timeit" -Magie gemessen. Wenn Sie dies verwenden, wird die Verarbeitungszeit gut gemessen.

    Zeitmessung der Fehlerberechnung

    Lassen Sie uns zunächst die für die Fehlerberechnung erforderliche Zeit messen.

    search_bottleneck.py

    
    #Berechnung des Trainingsfehlers
    %%timeit
    lm.forward(lm.x_train)
    error = lm[-1].get_error(lm.y_train)
    
    #----------output----------
    # 1 loop, best of 3: 957 ms per loop
    #--------------------------
    
    
    #Berechnung des Testfehlers
    %%timeit
    lm.forward(lm.x_test)
    error = lm[-1].get_error(lm.y_test)
    
    #----------output----------
    # 10 loops, best of 3: 160 ms per loop
    #--------------------------
    

    Die Datenmenge für die Fehlerberechnung von Trainingsdaten beträgt 60.000, also wird es so sein. Ich denke, es ist okay, weniger zu haben ... Ich denke, wir können uns hier verbessern. Wenn Sie die Testdaten auf 10.000 reduzieren, werden sie pro Epoche um etwa 0,8 Sekunden verkürzt. Nun, das ist eine Art Fehler. In Anbetracht des Ganzen (100 Sekunden pro Epoche) beträgt die Fehlerberechnungsquote insgesamt etwa 1%, sodass dies kein Engpass ist. Der Lernteil scheint also ein Problem zu sein.

    Zeitmessung des Lernteils

    Wir werden die Bearbeitungszeit des Lernteils messen. Irgendwo beim Lernen sollten 99% der Verarbeitungszeit pro Epoche ...

    search_bottleneck.py

    
    #Die Daten für eine Mini-Charge sind das Messziel.
    rand_index = np.arange(lm.x_train.get().shape[0])
    np.random.shuffle(rand_index)
    rand = rand_index[0 : n_batch]
    
    #Vorwärtsausbreitungsberechnung
    %%timeit
    lm.forward(lm.x_train[rand])
    
    #----------output----------
    # 1000 loops, best of 3: 1.32 ms per loop
    #--------------------------
    
    #Backpropagation-Berechnung
    %%timeit
    lm.backward(lm.y_train[rand])
    
    #----------output----------
    # 100 loops, best of 3: 10.3 ms per loop
    #--------------------------
    
    #Berechnung der Gewichtsaktualisierung
    %%timeit
    lm.update()
    
    #----------output----------
    # 1000 loops, best of 3: 1.64 ms per loop
    #--------------------------
    

    Offensichtlich nimmt nur die Rückausbreitung ungewöhnlich viel Zeit in Anspruch. Die Vorwärtsausbreitung und Gewichtsaktualisierungen dauern zehnmal länger. Da die Trainingsdaten diesmal 60.000 und die Mini-Batch-Größe 8 betragen, wird dieser Berechnungsprozess 7500 Mal wiederholt, also insgesamt ~~ $ (1,32 + 10,3 + 1,64) \ mal 7500 \ mal 10 ^ {- Es kostet 3} = 23,92s $. Das ist weniger als ich erwartet hatte ...? Es hat genug Zeit in Anspruch genommen, aber es ist immer noch nicht genug ... Nun, es gibt viele Schwankungen, also mach dir vorerst keine Sorgen. ~~ Es war nur ein Berechnungsfehler ... Ich muss die Spezifikationen des Rechners richtig verstehen. (1.32+10.3+1.64) \times 7500 \times 10^{-3} = 99.45s Wie auch immer, die Rückausbreitung ist ungewöhnlich langsam, daher werden wir sie genauer messen.

    Messung der Backpropagation-Zeit

    Also werden wir den Rückausbreitungsprozess aufteilen und messen.

    search_bottleneck.py

    
    #Vorbereitungen
    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)
    
    #Backpropagation der Ausgabeschicht
    %%timeit
    err3 = lm[3].backward(lm.y_train[rand])
    
    #----------output----------
    # 10000 loops, best of 3: 152 µs per loop
    #--------------------------
    
    #Rückausbreitung der Mittelschicht
    %%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
    #--------------------------
    
    #Backpropagation der Pooling-Schicht
    %%timeit
    err1 = lm[1].backward(err2)
    
    #----------output----------
    # 1000 loops, best of 3: 9.72 ms per loop
    #--------------------------
    
    #Rückausbreitung der Faltungsschicht
    %%timeit
    err0 = lm[0].backward(err1)
    
    #----------output----------
    # 1000 loops, best of 3: 442 µs per loop
    #--------------------------
    

    Es stellt sich heraus, dass die Pooling-Schicht um Größenordnungen langsamer ist. Die Verarbeitungszeit der Pooling-Schicht macht etwa 93,6% der Berechnungszeit der Rückausbreitung aus. Übrigens, wenn Sie dies hinzufügen, werden es ungefähr 10 ms sein, also ist es fast das gleiche.

    Zeitmessung der Rückausbreitung der Poolschicht

    Schauen wir uns also die Backpropagation der betreffenden Pooling-Schicht genauer an.

    search_bottleneck.py

    
    #Vorbereitungen
    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
    
    #Dimensionsaustausch und Transformation von Fehlern
    %%timeit
    grad = err2.transpose(0, 2, 3, 1).reshape(-1, 1)
    
    #----------output----------
    # 100000 loops, best of 3: 17.1 µs per loop
    #--------------------------
    
    #Leere Matrixerzeugung
    %%timeit
    grad_x = cp.zeros((grad.size, lm[1].pool*lm[1].pool))
    
    #----------output----------
    # 100000 loops, best of 3: 7.89 µs per loop
    #--------------------------
    
    #Wertfüllung
    %%timeit
    grad_x1[:, lm[1].max_index] = grad
    
    #----------output----------
    # 1000 loops, best of 3: 9.5 ms per loop
    #--------------------------
    
    #Verformung und Translokation
    %%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
    #--------------------------
    

    Die Preisfüllung ist überwältigend langsamer als bei anderen ... Das ist der Engpass hier. Das Verhältnis der Wertfüllung zur Rückausbreitung der Poolschicht beträgt tatsächlich etwa 98,6%.

    Die GPU ist stark für einfache Berechnungen, aber wenn es um eine so etwas komplizierte Verarbeitung geht, verlangsamt sie sich sofort und Sie können die Leistung nicht voll ausnutzen. Denken wir also über einen Verbesserungsplan nach.

    Beschleunigen der Pooling-Schicht

    Ich fragte mich, ob es eine gute Möglichkeit gab, den Preis beim Beschleunigen einzugeben. Das erste, woran ich dachte, war, dass diese Art der komplizierten Verarbeitung besser für die CPU geeignet ist, daher sollte sie von der CPU anstelle der GPU verarbeitet werden. Im Experiment wurde jedoch festgestellt, dass der Engpass bei der Verarbeitung des Ganzen durch die CPU ebenfalls im selben Teil lag, sodass diese Idee abgelehnt wurde.

    Die nächste Idee war, diesen Teil des Prozesses in eine andere Form umzuschreiben. Mit anderen Worten, ich dachte: "Ersetzen wir diesen Zuweisungsprozess durch einen Berechnungsprozess, in dem die GPU gut ist." Dies bedeutet, dass Sie anstelle des Index eine dünn besetzte Matrix halten sollten, die dieselbe Form wie die Eingabe hat (die in die Funktion im2col geworfen wird). 1 nur dort, wo der Maximalwert entspricht, sonst 0. Der erforderliche Speicherplatz ist doppelt so groß wie der normale $ pool ^ 2 $, aber $ pool $ ist normalerweise klein, was in Ordnung ist.

    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)
    
            #Enthält die Funktion im2col und 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
    

    Lass uns experimentieren.

    search_bottleneck.py

    
    #Backpropagation der Pooling-Schicht
    %%timeit
    err1 = lm[1].backward(err2)
    
    #----------output----------
    # 1000 loops, best of 3: 280 µs per loop
    #--------------------------
    
    #Wertfüllung
    %%timeit
    grad_x1 = lm[1].max_index*grad
    
    #----------output----------
    # 100000 loops, best of 3: 16.3 µs per loop
    #--------------------------
    

    Übrigens wird angenommen, dass die obigen Ergebnisse einer GPU zugeordnet sind, die sich von den vorherigen experimentellen Ergebnissen unterscheidet. Daher ist es schwierig, die Ergebnisse im Allgemeinen zu vergleichen, aber vorerst ist es uns gelungen, sie zu beschleunigen. Es besteht kein Zweifel daran. In diesem Fall wird die col2im -Funktion usw. diesmal zu einem Problem, sodass noch Raum für eine Beschleunigung besteht.

    Auch als Ganzes

    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)
    

    Auf diese Weise konnten wir es auf etwa 50 pro Epoche verkürzen! Da die Lernzeit pro Mini-Batch etwa 6 ms beträgt, beträgt die Lernzeit pro Epoche $ 6 \ mal 7500 \ mal 10 ^ {-3} = 45s $. ~~ Und das vorherige Missverhältnis wurde behoben ... Was war es schließlich? Ich hätte experimentieren sollen, während ich derselben GPU zugewiesen habe ... na ja. ~~

    abschließend

    Mit diesem Gefühl werden wir nach dem Engpass suchen, ihn verbessern und beschleunigen. Wir werden es von Zeit zu Zeit weiter verbessern.

    Wenn die P.S.-Minibatchgröße auf 128 eingestellt wurde, war die Ausführungszeit fast dieselbe wie beim Experiment mit Keras. Es war gut.

    Deep Learning-Serie

    [^ 1]: Genau genommen "verdoppelt sich die Integrationsrate von Halbleitern in 18 Monaten (24 Monaten)".

    Recommended Posts

    Deep Learning Gaiden ~ GPU-Programmierung ~
    Tiefes Lernen
    Tiefes Lernen, um ohne GPU zu beginnen
    Deep Learning Memorandum
    Starten Sie Deep Learning
    Python Deep Learning
    Deep Learning × Python
    Erstes tiefes Lernen ~ Kampf ~
    Python: Deep Learning-Praxis
    Deep Learning / Aktivierungsfunktionen
    Deep Learning von Grund auf neu
    Deep Learning 1 Übung des Deep Learning
    Deep Learning / Cross Entropy
    Erstes tiefes Lernen ~ Vorbereitung ~
    Programmieren des Lernprotokolls 2. Tag
    Erstes tiefes Lernen ~ Lösung ~
    [AI] Deep Metric Learning
    Ich habe versucht, tief zu lernen
    Python: Deep Learning Tuning
    Deep Learning Großtechnologie
    Deep Learning / Softmax-Funktion
    DEEP PROBABILISTIC PROGRAMMING --- Bibliothek "Deep Learning + Bayes" --- Einführung von Edward
    Eine Szene, in der GPU für tiefes Lernen nützlich ist?
    Echtzeit-Personalschätzung (Lernen mit lokaler GPU)
    Deep Learning von Grund auf 1-3 Kapitel
    Versuchen Sie es mit TensorFlow
    <Kurs> Tiefes Lernen: Day2 CNN
    Deep Learning Bilderkennung 1 Theorie
    Deep Running 2 Tuning von Deep Learning
    Deep Learning / LSTM Scratch Code
    (Jetzt) Erstellen Sie eine GPU Deep Learning-Umgebung mit GeForce GTX 960
    <Kurs> Tiefes Lernen: Tag 1 NN
    Deep Kernel Learning mit Pyro
    Versuchen Sie Deep Learning mit FPGA
    Verstärkungslernen 5 Versuchen Sie, CartPole zu programmieren?
    Deep Learning für die Bildung von Verbindungen?
    Einführung in Udacity Deep Learning Nanodegree
    Themen> Deep Learning: Tag 3 RNN
    Python Machine Learning Programming> Schlüsselwörter
    Einführung in Deep Learning ~ Lernregeln ~
    Tiefe Stärkung des Lernens 1 Einführung in die Stärkung des Lernens
    Erster Monat des Programmierlernens
    Tiefes Lernen der Verstärkung 2 Implementierung des Lernens der Verstärkung
    Generiere Pokemon mit Deep Learning
    Einführung in Deep Learning ~ Backpropagation ~
    Ich habe keine GPU, aber ich werde Deep Learning ausprobieren
    Maschinelles Lernen mit Docker (42) Programmieren von PyTorch für Deep Learning Von Ian Pointer
    Umgang mit Tensorflow mit GPU beim Deep Learning, das plötzlich nicht mehr funktioniert
    Deep Learning / Deep Learning von Grund auf neu 2 Kapitel 4 Memo
    Probieren Sie Deep Learning mit FPGA-Select-Gurken aus
    Identifikation der Katzenrasse mit Deep Learning
    Deep Learning / Deep Learning von Grund auf neu Kapitel 3 Memo
    Machen Sie ASCII-Kunst mit tiefem Lernen
    Deep Learning / Deep Learning von Null 2 Kapitel 5 Memo
    Implementieren Sie Deep Learning / VAE (Variational Autoencoder)
    Einführung in das tiefe Lernen ~ Funktionsnäherung ~
    Versuchen Sie es mit TensorFlow Part 2
    Tiefes Lernen von Grund auf neu (Kostenberechnung)
    Über das Projektmanagement von Deep Learning (DNN)