Leaf Classificationで良精度を出すまで＿3日目

ついに実装していきます。
今回はChainerで実装しております。
**chainerは2.0.0**を使用しました。
なお、chainerのバージョンは

```py3
import chainer
chainer.__version__
```
とすることで確認することができます。


##構築
###前処理おさらい
まずは簡単におさらいしておきます。
画像データなどはKaggleのLeafClassificationのものをそのままダウンロードしてimagesというフォルダをimages2に名前だけ変更しています。

####リサイズ
下記コードの前処理ではimages2というフォルダに入っている1584枚の画像データを32x32の画像サイズに変更したのち、imagesというフォルダに対して保存しています。

```py3

images=[]
for i in range(1, 1585):
    image = cv2.imread("images2/%d.jpg"%i) #images2から取り出す
    grayed = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    images.append(grayed) #imagesに格納する

def resize_image(img):
    end_size = 32
    max_ = np.maximum(img.shape[0], img.shape[1])
    scale = end_size / max_
    height, width = img.shape
    size = (int(width*scale), int(height*scale))

    rescale_image = cv2.resize(img, size, interpolation=cv2.INTER_CUBIC)

    height, width = rescale_image.shape

    if width > height:
        z_pad = np.zeros([int((width-height)/2), width], dtype=np.uint8)
        end_image = np.vstack((z_pad, rescale_image, z_pad))
    else:
        z_pad = np.zeros([height, int((height - width)/2)], dtype=np.uint8)
        end_image = np.hstack((z_pad, rescale_image, z_pad))

    end_image = cv2.resize(end_image, (end_size, end_size))
    
    return end_image

for i,img in enumerate(images):
    cv2.imwrite("images/%d.jpg"%(i+1), resize_image(img)) #imagesというフォルダに画像を格納する

```

####データセットの作成
では次にトレーニングデータからトレーニングに使用可能なデータセットを作成します。

```py3:dataset.py
import numpy as np
import pandas as pd
import cv2

#Kaggleにあるtrain.csvをダウンロードし、それを読み込ませる
#(train.csvにはラベルのついているものしかないから)
train = pd.read_csv("train.csv")
labels=train["species"].values

#labelsは今文字列が入っているので、それをscikit-learnの関数を使って数字のラベルに変える
from sklearn import preprocessing
le = preprocessing.LabelEncoder()
le.fit(labels)
labels = le.transform(labels)
labels = labels.astype(np.int32)

#imagesフォルダから、train.csvのidカラムから、リサイズされた画像を持ってくる。
resized_images = []
for i in train.id:
    image = cv2.imread("images/%d.jpg"%i)
    grayed = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    resized_images.append(grayed)

#chainerで読み込ませられる型に変換する。
resized_images = np.array([resized_images])
resized_images = resized_images.reshape(-1, 1024).astype(np.float32)
resized_images /=255 #0~255のデータを0~1のデータに変える

#train_test_splitはscikit-learnのバージョンが新しければ、cross_validationではなくmodel_selectionの中に入っている。
#画像の行列データとラベルデータから、トレーニング用のデータセットを作成する。
from sklearn.cross_validation import train_test_split 
X_train, X_test, y_train, y_test = train_test_split(resized_images, labels, test_size=0.2, random_state=0)

#後で使用する数字を指定しておく。
N_train = y_train.size
N_test = y_test.size
```

こうすることで、データセットを作成することができました。


###NNで分類する
まずはCNNではなくNNから作ってみます。

```py3:NN_class.py
class MLP(chainer.Chain):
    
    def __init__(self):
        super().__init__(
            l1 = L.Linear(1024, 1000),
            l2 = L.Linear(1000, 900),
            l3 = L.Linear(900, 500),
            l4 = L.Linear(500, 99),
        )
        
    def __call__(self, x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        h3 = F.relu(self.l3(h2))
        return self.l4(h3)
```

ここで、

```
__call__
```
部分の説明は[Pythonのクラスにおける__call__メソッドの使い方](http://qiita.com/kyo-bad/items/439d8cc3a0424c45214a)に載っています。

とりあえず、inputが1024(32x32),outputが99(99クラス分類)
で、NN(ニューラルネット)をつなぎ、活性化関数にrelu関数を用いている、という感じですね。

####モデルの構築

```py3:model.py
model = L.Classifier(MLP())

from chainer import optimizers
optimizer = optimizers.Adam()
optimizer.setup(model)
```

これによりモデルを作成できました。
僕のPCだと**なぜかoptimizer=chainer.optimizers.Adam()とするとエラーが出た**のでその場合はこのように書けば大丈夫だと思います。

####実行

```py3:NN_実行.py

batchsize = 99 #一回のバッチサイズ
n_epoch = 8 #学習の繰り返し回数

train_loss = []
train_accuracy = []
test_loss = []
test_accuracy = []

for epoch in range(n_epoch):
    print("epoch", epoch+1)
    
    perm = np.random.permutation(N_train)
    
    sum_accuracy, sum_loss = 0,0
    
    for i in range(0, N_train, batchsize):
        X_batch = X_train[perm[i:i+batchsize]]
        y_batch = y_train[perm[i:i+batchsize]]
        
        
        optimizer.update(model, X_batch, y_batch)
        
        sum_loss += float(model.loss.data)
        sum_accuracy += float(model.accuracy.data)
        
        
    mean_loss = sum_loss/(N_train/batchsize)
    mean_accuracy = sum_accuracy/(N_train/batchsize)
    print("train_mean_loss={:.3f}, train_mean_accuracy={:.3f}".format(mean_loss, mean_accuracy))
    
    train_loss.append(model.loss.data)
    train_accuracy.append(model.accuracy.data)
    
    #少ないから、全部でテストしよう
    sum_loss, sum_accuracy= 0,0
    
    for i in range(0, N_test, batchsize):
        X_batch = X_test[i:i+batchsize]
        y_batch = y_test[i:i+batchsize]
        
        loss = model(X_batch, y_batch)
        
        sum_loss += float(model.loss.data)
        sum_accuracy += float(model.accuracy.data)
        
    mean_loss = sum_loss/(N_test/batchsize)
    mean_accuracy = sum_accuracy/(N_test/batchsize)
    print("test_mean_loss={:.3f}, test_mean_accuracy={:.3f}".format(mean_loss, mean_accuracy))
    
    test_loss.append(model.loss.data)
    test_accuracy.append(model.accuracy.data)
```

ここの部分のコードの部分の詳しい説明は参考にしたサイト様が非常にわかりやすく解説してくださっているので、割愛します。

書き換えた部分は、N_train.size=792だったのでbatchsize=99として、n_epoch=8にしたことくらいです。

というわけで実行結果はこうなりました。

<img width="381" alt="スクリーンショット 2017-07-19 1.59.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/190157/aaedc279-5ade-6214-ff65-7ed452044a8a.png">

まぁ、わかったいたことですが**99クラス分類を各10毎のデータで行うのは無理がありました。**
一応CNNでも実装をしてみます。

###CNN
####モデルの構築

```py3:model_CNN.py

class CNN(chainer.Chain):
    
    def __init__(self):
        super(CNN, self).__init__(
            conv1 = L.Convolution2D(1, 32, 5),
            conv2 = L.Convolution2D(32, 64, 5),
            conv3 = L.Convolution2D(64, 64, 5),
            l1 = L.Linear(None, 500),
            l2 = L.Linear(500, 99)
        )
        
    def __call__(self, x):
        x = x.reshape((len(x.data), 1, 32, 32))
        
        h = F.relu(self.conv1(x))
        h = F.max_pooling_2d(h, 2)
        h = F.relu(self.conv2(h))
        h = F.max_pooling_2d(h, 2)
        h = F.relu(self.conv3(h))
        h = F.max_pooling_2d(h, 2)
        h = F.dropout(F.relu(self.l1(h)))
        y = self.l2(h)
        return y

model = L.Classifier(CNN())

from chainer import optimizers
optimizer = optimizers.Adam()
optimizer.setup(model)
```

これも、参考にしたサイト様を見た方がわかりやすいと思うので、説明は割愛します。
そして**実行のコードはNNの方と全く同じ**なので、結果だけ載せます。

<img width="396" alt="スクリーンショット 2017-07-19 2.04.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/190157/6f3dae19-fa80-78fc-f4e3-d3dbe4332c89.png">

**、、、あれっ？**
こんなにも**正答率が低くなってしまう**のかと、感服でした。
やはり、**データセットの数を増やさなければならない**ようです。
というわけで４日目に続きます。


##参考にしたサイト


[【機械学習】ディープラーニングフレームワークChainerを試しながら解説してみる。](http://qiita.com/kenmatsu4/items/7b8d24d4c5144a686412)
とてもわかりやすいです。FunctionSetなどが2.0.0にはないので、そこは他のサイトをみながらやればいいと思うのですが、relu関数などの説明がとても丁寧です。
訓練のコードはここから拝借しました。


[ChainerのMNISTサンプルを試す](http://bunji2.hatenablog.com/entry/2016/05/22/213114)
この方はモデルの構築を引数の初期値として与える書き方をしているので、何をしているのかがなんとなく掴むことができました。
CNNのモデル構築の書き方は一部ここから拝借しました。

[雑記chainerエンジョイ勢のためのトラブルシューティング](http://segafreder.hatenablog.com/entry/2016/01/17/234707)
データのエラーコードについて調べた時に見つけたサイトです。