畳み込みニューラルネットワーク (CNN)に対する画像の回転・拡大・色などの影響

畳み込みニューラルネットワーク (CNN)と、他の機械学習手法（勾配ブースティング・多層パーセプトロン）の特徴を理解することを目的に、画像に回転・拡大・色の変更などの処理を行った時に分類性能がどう変わるかを試してみました。

画像は [コアラとクマの画像を自動生成する](https://qiita.com/maskot1977/items/8d5fe2492e9fba2c7d90) の方法で生成しました。コアラさんとクマさんをシルエットで区別できるでしょうか？

# コード

## 画像データ読み込み

```python
from PIL import Image

koalas = []
for i in range(num_data):
    koala = Image.open("koala_or_bear/koala_{}.jpg".format(i))
    koalas.append(koala)

bears = []
for i in range(num_data):
    bear = Image.open("koala_or_bear/bear_{}.jpg".format(i))
    bears.append(bear)
```

## 先頭データ表示

```python
%matplotlib inline
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(10,10))
for i in range(16):
    ax = fig.add_subplot(4, 4, i+1)
    ax.axis('off')
    if i < 8:
        ax.set_title('koala_{}'.format(i))
        ax.imshow(koalas[i],cmap=plt.cm.gray, interpolation='none')
    else:
        ax.set_title('bear_{}'.format(i - 8))
        ax.imshow(bears[i - 8],cmap=plt.cm.gray, interpolation='none')
plt.show()
```

## 説明変数・目的変数


```python
import numpy as np

X = [] # 説明変数
Y = [] # 目的変数

index = 0
for koala in koalas:
    resize_img = koala.resize((128, 128))
    r, g, b = resize_img.split()
    r_resize_img = np.asarray(np.float32(r)/255.0)
    g_resize_img = np.asarray(np.float32(g)/255.0)
    b_resize_img = np.asarray(np.float32(b)/255.0)
    rgb_resize_img = np.asarray([r_resize_img, g_resize_img, b_resize_img])
    X.append(rgb_resize_img)
    Y.append(0)
    index += 1
    if index >= num_data:
        break

index = 0
for bear in bears:
    resize_img = bear.resize((128, 128))
    r, g, b = resize_img.split()
    r_resize_img = np.asarray(np.float32(r)/255.0)
    g_resize_img = np.asarray(np.float32(g)/255.0)
    b_resize_img = np.asarray(np.float32(b)/255.0)
    rgb_resize_img = np.asarray([r_resize_img, g_resize_img, b_resize_img])
    X.append(rgb_resize_img)
    Y.append(1)
    index += 1
    if index >= num_data:
        break

X = np.array(X, dtype='float32')
Y = np.array(Y, dtype='int64')
```

## 教師セット・テストセットに分離

```python
from sklearn import model_selection
X_train, X_test, Y_train, Y_test = model_selection.train_test_split(
    X, Y, test_size=0.1
)
```

## scikit-learn用にデータ型変換

```python
d1, d2, d3, d4 = X_train.shape
X_train_a = X_train.reshape((d1, d2 * d3 * d4))
Y_train_onehot = np.identity(2)[Y_train]
d1, d2, d3, d4 = X_test.shape
X_test_a = X_test.reshape((d1, d2 * d3 * d4))
Y_test_onehot = np.identity(2)[Y_test]
```

## PyTorch用にデータ型変換

```python
import torch
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader

X_train_t = torch.from_numpy(X_train).float()
Y_train_t = torch.from_numpy(Y_train).long()

X_train_v = torch.autograd.Variable(X_train_t)
Y_train_v = torch.autograd.Variable(Y_train_t)

X_test_t = torch.from_numpy(X_test).float()
Y_test_t = torch.from_numpy(Y_test).long()

X_test_v = torch.autograd.Variable(X_test_t)
Y_test_v = torch.autograd.Variable(Y_test_t)

train = TensorDataset(X_train_t, Y_train_t)
train_loader = DataLoader(train, batch_size=32, shuffle=True)
```

## 勾配ブースティング

```python
%%time
from sklearn.ensemble import GradientBoostingClassifier

classifier = GradientBoostingClassifier()
classifier.fit(X_train_a, Y_train)
print("Accuracy score (train): ", classifier.score(X_train_a, Y_train))
print("Accuracy score (test): ", classifier.score(X_test_a, Y_test))
```

## 多層パーセプトロン（中間１層）

```python
%%time
from sklearn.neural_network import MLPClassifier

classifier = MLPClassifier(max_iter=10000, early_stopping=True)
classifier.fit(X_train_a, Y_train)
print("Accuracy score (train): ", classifier.score(X_train_a, Y_train))
print("Accuracy score (test): ", classifier.score(X_test_a, Y_test))
```

## 多層パーセプトロン（中間２層）


```python
%%time
from sklearn.neural_network import MLPClassifier
classifier = MLPClassifier(max_iter=10000, early_stopping=True,
                           hidden_layer_sizes=(100, 100))
classifier.fit(X_train_a, Y_train)
print("Accuracy score (train): ", classifier.score(X_train_a, Y_train))
print("Accuracy score (test): ", classifier.score(X_test_a, Y_test))
```


## 畳み込みニューラルネットワーク (CNN)

### ネットワークの定義

```python
class CNN(torch.nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = torch.nn.Conv2d(3, 10, 5)
        self.conv2 = torch.nn.Conv2d(10, 20, 5)
        self.fc1 = torch.nn.Linear(20 * 29 * 29, 50)
        self.fc2 = torch.nn.Linear(50, 2)
    
    def forward(self, x):
        x = torch.nn.functional.relu(self.conv1(x))
        x = torch.nn.functional.max_pool2d(x, 2)
        x = torch.nn.functional.relu(self.conv2(x))
        x = torch.nn.functional.max_pool2d(x, 2)
        x = x.view(-1, 20 * 29 * 29)
        x = torch.nn.functional.relu(self.fc1(x))
        x = torch.nn.functional.log_softmax(self.fc2(x), 1)
        return x
```

### ネットワーク構造の確認

```python
from torchsummary import summary
model = CNN()
summary(model, X[0].shape)
```

    ----------------------------------------------------------------
            Layer (type)               Output Shape         Param #
    ================================================================
                Conv2d-1         [-1, 10, 124, 124]             760
                Conv2d-2           [-1, 20, 58, 58]           5,020
                Linear-3                   [-1, 50]         841,050
                Linear-4                    [-1, 2]             102
    ================================================================
    Total params: 846,932
    Trainable params: 846,932
    Non-trainable params: 0
    ----------------------------------------------------------------
    Input size (MB): 0.19
    Forward/backward pass size (MB): 1.69
    Params size (MB): 3.23
    Estimated Total Size (MB): 5.11
    ----------------------------------------------------------------

### 実行のための関数

```python
def learn(model, criterion, optimizer, n_iteration):
    for epoch in range(n_iteration):
        total_loss = np.array(0, dtype='float64')
        for x, y in train_loader:
            x = torch.autograd.Variable(x)
            y = torch.autograd.Variable(y)
            optimizer.zero_grad()
            y_pred = model(x)
            loss = criterion(y_pred, y)
            loss.backward()
            optimizer.step()
            total_loss += loss.data.numpy()
        
        if (epoch + 1) % 10 == 0:
            print(epoch + 1, total_loss)
        
        if total_loss == np.array(0, dtype='float64'):
            break
        
        loss_history.append(total_loss)
    
    return model
```

### 実行用コード

```python
#%%time
model = CNN()
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
loss_history = []
model = learn(model, criterion, optimizer, 300)
```

### 損失履歴表示

```python
ax = plt.subplot(2, 1, 1)
ax.plot(loss_history)
ax.grid()
ax = plt.subplot(2, 1, 2)
ax.plot(loss_history)
ax.set_yscale('log')
ax.grid()
```

### 正答率

正答率（教師セット）

```python
Y_pred = torch.max(model(X_train_v).data, 1)[1]
accuracy = sum(Y_train == Y_pred.numpy()) / len(Y_train)
print(accuracy)
```

正答率（テストセット）

```python
Y_pred = torch.max(model(X_test_v).data, 1)[1]
accuracy = sum(Y_test == Y_pred.numpy()) / len(Y_test)
print(accuracy)
```

# 結果

まずは、コアラさんとクマさんを区別する一番簡単な問題です。

![基本](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/67dabe95-b12f-0352-52e3-f7df0722a10a.png)

<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>1min 2s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>50.9 s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>35.1 s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>1.0</td><td>1.0</td>
</tr>
</table>

どの予測法も完璧に答えることができました。


## 拡大

コアラさんとクマさんをランダムな倍率で拡大してみましょう。このときついでに、少しだけ縦横にずらしてみます。

![拡大](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/99459f6b-1ad8-b34c-69b0-fa31d0ec86ba.png)

<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>5min 11s</td><td>1.0</td><td>0.975</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>1min 4s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>1min 4s</td><td>0.977</td><td>1.0</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>1.0</td><td>1.0</td>
</tr>
</table>

勾配ブースティングの正答率が、ほんの少しだけ落ちましたね。畳み込みニューラルネットワーク (CNN)は完璧な正答率です。

## 邪魔物あり

背景に、邪魔になりそうなものを描き込みましょう。

![邪魔物あり](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/5eedac12-d21a-fdfa-3027-86a91af20280.png)

<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>1min 23s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>38.3 s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>1min 3s</td><td>0.983</td><td>1.0</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>1.0</td><td>1.0</td>
</tr>
</table>

ほとんど影響ないようです。

## 背景だけ彩色

背景をカラフルにしてみます。

![背景だけ彩色](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/a6d11aeb-8c10-c388-eaef-091e32479786.png)


<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>1min 30s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>43.9 s</td><td>0.9916</td><td>1.0</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>41.6 s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>1.0</td><td>1.0</td>
</tr>
</table>

これもほとんど影響ないようです。


## 動物だけ彩色

コアラさんとクマさんをカラフルにしてみましょう。

![動物だけ彩色](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/5b9f42d7-7d0d-b439-afe9-cea7a4403bb1.png)


<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>4min 26s</td><td>1.0</td><td>0.975</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>27.8 s</td><td>0.494</td><td>0.55</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>1min 9s</td><td>0.816</td><td>0.775</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>0.505</td><td>0.45</td>
</tr>
</table>

予測精度がかなり落ちました。畳み込みニューラルネットワーク (CNN)の性能がガタ落ち。あれ？意外にも多層パーセプトロン（中間２層）が健闘してる。そして、勾配ブースティングは、ほとんど性能が落ちてない。すごいぞ。

## 拡大・彩色・邪魔物あり

![拡大・彩色・邪魔物](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/11454ad5-32da-e5f2-fe7f-8d4d7b3af00b.png)

<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>7min 24s</td><td>1.0</td><td>0.9</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>42.4 s</td><td>0.6861</td><td>0.6</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>1min 50s</td><td>0.925</td><td>0.75</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>0.5</td><td>0.5</td>
</tr>
</table>

難易度が上がったけど、勾配ブースティングはかなり健闘してる。畳み込みニューラルネットワーク (CNN)は全く使い物になってない。

## 拡大・彩色・動物だけ黒・邪魔物あり

![拡大・彩色・動物だけ黒・邪魔物あり](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/bb6595f4-f1ad-7807-44f7-30b04c9b6a2b.png)

<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>6min 12s</td><td>1.0</td><td>0.975</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>1min 1s</td><td>0.9916</td><td>0.975</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>1min 12s</td><td>1.0</td><td>1.0</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>1.0</td><td>1.0</td>
</tr>
</table>

コアラさん・クマさんの色を黒に統一するだけで、全ての予測性能が回復した。やっぱりそこが判断基準なわけね。


## 回転

まわるまわるよ コアラは回る クマも回る

![回転](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/ba26280c-1336-f5f7-14ec-28f52e07cf47.png)


<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>3min 10s</td><td>1.0</td><td>0.925</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>27.4 s</td><td>0.5</td><td>0.5</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>1min 20s</td><td>0.994</td><td>1.0</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>1.0</td><td>1.0</td>
</tr>
</table>

多層パーセプトロン（中間１層）はダメでしたが他の皆さんは回転についてこれたようです。勾配ブースティングは回転が少し苦手っぽい。

## 回転・拡大

回転しながら拡大もしてみましょう。

![回転・拡大・グレースケール](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/112a55f7-e158-dc63-ba76-24535fc799d4.png)

<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>5min 28s</td><td>1.0</td><td>0.775</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>1min 33s</td><td>0.825</td><td>0.7</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>30.9 s</td><td>0.65</td><td>0.675</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>0.505</td><td>0.45</td>
</tr>
</table>

回転だけなら平気、拡大だけなら平気。だけど、回転しながら拡大だと、みなさん混乱してしまうようですね。それでも、勾配ブースティング、よく頑張ってる。

## 回転・拡大・彩色・動物だけ黒・邪魔物あり

![回転・拡大・彩色・動物だけ黒・邪魔物あり](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/77b52dfe-e0e2-a8a0-bc37-1877d132187e.png)

<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>7min 6s</td><td>1.0</td><td>0.6</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>29.5 s</td><td>0.5194</td><td>0.325</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>33 s</td><td>0.572</td><td>0.65</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>0.5194</td><td>0.325</td>
</tr>
</table>

いろんな要因（回転・拡大・邪魔物・色）が個別で攻めてくると、大した問題ではなくても、それらの要因が混ざって攻めてくると難しくなるようですね。

## いろんな要因全部のせ

![いろんな要因全部のせ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211162/ae100b03-04d0-8d6e-9266-a52cca7c5cd4.png)

<table>
<tr><th>手法</th><th>学習時間</th><th>正答率（教師セット）</th><th>正答率（テストセット）</th></tr>
<tr>
<td>勾配ブースティング</td><td>7min 55s</td><td>1.0</td><td>0.45</td>
</tr>
<tr>
<td>多層パーセプトロン（中間１層）</td><td>31.8 s</td><td>0.505</td><td>0.45</td>
</tr>
<tr>
<td>多層パーセプトロン（中間２層）</td><td>55.7 s</td><td>0.6027</td><td>0.45</td>
</tr>
<tr>
<td>畳み込みニューラルネットワーク (CNN)</td><td>-</td><td>0.505</td><td>0.45</td>
</tr>
</table>

いろんな要因を全部乗っけました。これはどの方法でもお手上げのようです。

# まとめ

畳み込みニューラルネットワーク (CNN)について、感覚を掴みたかったので、<b>「CNNの一人勝ち！！！」</b>な結果を出したかったのですが、結論は<b>「勾配ブースティングすげええええ！」</b>になってしまいました。（ヽ´ω`）