CPU, GPU, Parallel GPUの速度評価with mnist(MLP), cifar-10(LeNet5), sine wave(LSTM)

##この記事の目的

Chainerを使って、自分のパソコンのCPU、GPUの性能を評価してみる。
+プログラミング練習

**コードはすべて[このレポ](https://github.com/AtomScott/cpugpu_speedtest/blob/master/speedtest.ipynb)にあります！！！**

##何をするのか

目的通りだが、

- cpu　　
- gpu　
- gpu 2つで

の3種類を使って、次のデータ（モデル）を使って、速度評価をします。

- mnist(MLP) →　全結合NN
- cifar10(LeNet5) →　CNN
- sine wave(LSTM)　→　RNN

みたいな感じでやっていきます。

##自分の開発環境

__Software__
OSはUbuntu使ってます。
Ubuntu, chainer, python, cudaのバージョンは以下のとおりです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/556cbab6-eddd-3ac2-62c5-ba4021c95a81.png)


__Hardware__
CPU - 
Intel(R) Core(TM) i7-6850K CPU @ 3.60GHz
6 core, 12 threads

GPU - 
gp102 titan x ２つ
https://www.techpowerup.com/gpu-specs/titan-x-pascal.c2863

##下準備
使うライブラリは以下のとおり：

```python
import chainer
import chainer.functions as F
import chainer.links as L
from chainer import training
from chainer.training import extensions
from chainer import Chain
```
##mnist(MLP)

__mnistについて__
>MNIST(Mixed National Institute of Standards and Technology database)とは、手書き数字画像60,000枚と、テスト画像10,000枚を集めた、画像データセットです。さらに、手書きの数字「0〜9」に正解ラベルが与えられるデータセットでもあり、画像分類問題で人気の高いデータセットです。
>https://udemy.benesse.co.jp/ai/mnist.html より

![image.png](https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/MnistExamples.png/220px-MnistExamples.png)

__MLPについて__
>多層パーセプトロン（たそうパーセプトロン、英: Multilayer perceptron、略称: MLP）は、順伝播型（英語版）ニューラルネットワークの一分類である。MLPは少なくとも3つのノードの層からなる。入力ノードを除けば、個々のノードは非線形活性化関数を使用するニューロンである。MLPは学習のために誤差逆伝播法（バックプロパゲーション）と呼ばれる教師あり学習手法を利用する[1][2]。その多層構造と非線形活性化関数が、MLPと線形パーセプトロンを区別している。MLPは線形分離可能ではないデータを識別できる[3]。
https://ja.wikipedia.org/wiki/多層パーセプトロン より

![](https://www.researchgate.net/profile/Mohamed_Zahran6/publication/303875065/figure/fig4/AS:371118507610123@1465492955561/A-hypothetical-example-of-Multilayer-Perceptron-Network.png)

ということで、Chainer docsにある[MLP](https://docs.chainer.org/en/stable/examples/mnist.html)を参考にして、次のようにモデルを定義しました。

```python
class MLP(chainer.Chain):

    def __init__(self, n_units, n_out):
        super(MLP, self).__init__()
        with self.init_scope():
            # Chainerがそれぞれの層を大きさを推測する
            self.l1 = L.Linear(None, n_units)  # n_in -> n_units
            self.l2 = L.Linear(None, n_units)  # n_units -> n_units
            self.l3 = L.Linear(None, n_out)  # n_units -> n_out

    def forward(self, x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)
```

__結果__
学習方法に関するコードはちょっと長く、いちいち出すとゴチャゴチャするので、最後に全部まとめて載せます。間違いがあったらぜひ、指摘してください。
上から、CPU、GPU1つ、GPU2つになっています。

```
GPU: -1, None
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
20          0.000392997  0.112276              1              0.9802                    131.866       

GPU: 0, None
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
20          0.000861154  0.0953411             1              0.9835                    34.7934       

GPU: 0, 1
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
20          0.00679889  0.0958061             1              0.9843                    75.3378 
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/fd86d974-c109-b425-a934-6fe011aaa6de.png)


##cifar-10(LeNet5)
__cifar-10について__
> CIFAR-101)はAlexNetで有名なAlexさんらが構築したもので80 million tiny imagesから
・飛行機、犬など10クラス
・学習用データ5万枚
・評価用データ1万枚
を抽出したデータセットです。
http://starpentagon.net/analytics/cifar-10_dataset/ より

![](http://cdn-ak.f.st-hatena.com/images/fotolife/a/aidiary/20151014/20151014211729.png)



__LeNet5について__
>LeNet(5)とは1990年代に作成された一番初めにConvolutional Neural Netowrkの基礎にあたるものを提案し使用したネットワーク.MNISTにおいて当時から高い精度を出していた.
http://www.thothchildren.com/chapter/59bf6f7ee319b7394d662311 より

ということで、Chainer docsにある[LeNet5](https://docs.chainer.org/en/stable/examples/cnn.html)を参考にして、次のようにモデルを定義しました。
Mnistに使うって書いてあるのになんで、cifar-10かというと、ただただ気分です。あとでResNetも使って実装しますから、怒らないでください笑

```python
class LeNet5(Chain):
    def __init__(self):
        super(LeNet5, self).__init__()
        with self.init_scope():
            self.conv1 = L.Convolution2D(
                in_channels=None, out_channels=6, ksize=5, stride=1)
            self.conv2 = L.Convolution2D(
                in_channels=6, out_channels=16, ksize=5, stride=1)
            self.conv3 = L.Convolution2D(
                in_channels=16, out_channels=120, ksize=4, stride=1)
            self.fc4 = L.Linear(None, 84)
            self.fc5 = L.Linear(84, 10)

    def forward(self, x):
        h = F.sigmoid(self.conv1(x))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.sigmoid(self.conv2(h))
        h = F.max_pooling_2d(h, 2, 2)
        h = F.sigmoid(self.conv3(h))
        h = F.sigmoid(self.fc4(h))
        if chainer.config.train:
            return self.fc5(h)
        return F.softmax(self.fc5(h))
```

__結果__
学習方法に関するコードはちょっと長く、いちいち出すとゴチャゴチャするので、最後に全部まとめて載せます。間違いがあったらぜひ、指摘してください。
上から、CPU、GPU1つ、GPU2つになっています。

```
GPU: -1, None
# Minibatch-size: 100
# epoch: 50
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
50          1.00791     1.92314               0.64           0.6087                    1313.48       

GPU: 0, None
# Minibatch-size: 100
# epoch: 50
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
50          1.26838     1.9345                0.62           0.6039                    127.483       

GPU: 0, 1
# Minibatch-size: 100
# epoch: 50
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
50          1.02486     1.94677               0.6            0.5919                    273.38        
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/67385944-daf0-2d51-6b81-6357241af652.png)


##Sine Wave(LSTM)
ここのパートは以前書いた記事https://qiita.com/AtomJamesScott/items/41c4a3bd85a851f02770 の実装結果を使っていきたいと思います。

__Sine Waveについて__
サイン波です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/3a3cd89c-08cc-20d3-0538-a07f99f95b17.png)


__LSTMについて__
LSTMとは時系列のデータを扱うRNN（再起的ニューラルネットワーク）の一種で、普通のRNNだと起こる勾配が爆発・消失する問題がないので、よく使われている。
![図1.png](https://qiita-image-store.s3.amazonaws.com/0/295666/8d9391e1-8f06-3201-bca3-1ae3473578a1.png)

```python
class LSTM(Chain):
    n_input  = 1
    n_output = 1
    n_units  = 5

    def __init__(self):
        super(MLP, self).__init__(
            l1 = L.Linear(self.n_input, self.n_units),
            l2 = L.LSTM(self.n_units, self.n_units),
            l3 = L.Linear(self.n_units, self.n_output),
        )

    def reset_state(self):
        self.l2.reset_state()

    def __call__(self, x):
        h1 = self.l1(x)
        h2 = self.l2(h1)
        return self.l3(h2)
```

__結果__
学習方法に関するコードはちょっと長く、いちいち出すとゴチャゴチャするので、最後に全部まとめて載せます。間違いがあったらぜひ、指摘してください。特にLSTMの学習はBPTTがある関係でややこしく、ミスってる可能性あります。

上から、CPU、GPU1つ、GPU2つになっています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/c29a7b89-d8b3-50d7-832d-aa00d5f02f0b.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/3c43b4ea-c591-6623-0cc4-a9f41959cfc7.png)

##補足　cifar-10(Resnet152)


__Resnet152について__
>ResNetは、Microsoft Research(現Facebook AI Research)のKaiming He氏が2015年に考案したニューラルネットワークのモデルである。
CNNにおいて層を深くすることは重要な役割を果たす。層を重ねるごとに、より高度で複雑な特徴を抽出していると考えられているからだ。
Convolution層はフィルタを持ち、Pooling層と組み合わせて何らかの特徴を検出する役割を持っている。低・中・高レベルの特徴を多層形式で自然に統合し、認識レベルを強化することができる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/0016c3f6-faa5-27c9-9152-5c3e8721a3fd.png)
https://deepage.net/deep_learning/2016/11/30/resnet.html より

モデルに関してこちらを参考にしてください。
https://docs.chainer.org/en/stable/examples/cnn.html
__結果__

```
GPU: 0, None
# Minibatch-size: 2048
# epoch: 50
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
50          0.0734697   6.44621               0.975586       0.474124                  1211.89
       
GPU: 0, 1
# Minibatch-size: 2048
# epoch: 50
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
50          0.0985431   6.45934               0.962891       0.459237                  789.439       
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/97104aae-87b4-5c20-595e-3b675bea2630.png)

##結論
複数のGPUを有効活用するのは難しいことが分かりました。
[調べた](https://medium.com/@c_61011/why-multi-gpu-training-is-not-faster-f439fe6dd6ec)ところ

- Use a very small network. 小さいネットワークを使う
- Use very small batch size.　小さいバッチサイズを使う
- Create overhead for updating parameters across multiple GPUs.　パラメータ更新のオーバーヘッドを大きくする

という3つのことをやってしまうと、複数のGPUを有効活用できないそうだ。
この記事でも最後に使ったResNet152が一番大きなネットワークになっており、唯一複数GPUを使ったほうが計算が早く終了した。
（CPUで実験をしなかったのはLeNet5の例から分かるとおり、conv netでCPUを使うとすごく時間かかるからです。）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/295666/210ffecd-d210-7427-8a0e-f31e7722a738.png)