Deep Learning Tutorial

chainer and python

画像セットを増やす「前処理」 精度向上するのか??

CIFAR-10のaccuracy96%をchainerで目指すため色々頑張ってみるシリーズ第1弾

テーマ:画像に色々とテキトーな処理をしてデータを増やす

元画像(船)

32(px)*32(px)
f:id:tsuruchan_0827:20160305144046p:plain

ガンマ変換

{
f(x)= {255}({x}/{255})^{{1}/{γ}}
}
明るさを調整する。
f:id:tsuruchan_0827:20160305144052p:plain

gamma = 1.5
look_up_table = np.ones((256, 1), dtype = 'uint8' ) * 0
for i in range(256):
    look_up_table[i][0] = 255 * pow(float(i) / 255, 1.0 / gamma)
X = []
for i in range(50000):
    img = X_train[i].transpose(1, 2, 0)
    img_gamma = cv2.LUT(img, look_up_table)
    X.append(img_gamma.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))

ガンマの値を0.75にすると
f:id:tsuruchan_0827:20160305144059p:plain

平滑化

滑らかにする。
f:id:tsuruchan_0827:20160305144105p:plain

average_square = (10,10)
X = []
for i in range(50000):
    img = X_train[i].transpose(1, 2, 0)
    blur_img = cv2.blur(img, average_square)
    X.append(blur_img.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))

ここで大きなミス

average_square = (10,10)

これはやばい。32*32しかないのに、それを10*10で平滑化したらほぼ何も見えない笑

average_square = (3,3)

ぐらいが適正かと思われる。

コントラスト

ハイコントラスト

f:id:tsuruchan_0827:20160305144112p:plain

ローコントラスト

f:id:tsuruchan_0827:20160305144117p:plain

min_table = 50
max_table = 205
diff_table = max_table - min_table

LUT_HC = np.arange(256, dtype = 'uint8' )
LUT_LC = np.arange(256, dtype = 'uint8' )

for i in range(0, min_table):
    LUT_HC[i] = 0
for i in range(min_table, max_table):
    LUT_HC[i] = 255 * (i - min_table) / diff_table
for i in range(max_table, 255):
    LUT_HC[i] = 255
for i in range(256):
    LUT_LC[i] = min_table + i * (diff_table) / 255
X = []
for i in range(50000):
    img = X_train[i].transpose(1, 2, 0)
    high_cont_img = cv2.LUT(img, LUT_HC)
    X.append(high_cont_img.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))

ガウシアンノイズ

各画素にガウス分布に基づく生成値によりノイズを加える。
f:id:tsuruchan_0827:20160305144122p:plain

X = []
for i in range(50000):
    img = X_train[i].transpose(1, 2, 0)
    row,col,ch= img.shape
    mean = 0
    sigma = 15
    gauss = np.random.normal(mean,sigma,(row,col,ch))
    gauss = gauss.reshape(row,col,ch)
    gauss_img = img + gauss
    X.append(gauss_img.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))

少し気持ち悪い。
普通に見て参考に良さそうな画像データではないかも笑

ソルト&ペッパーノイズ

白い点と黒い点のノイズを加える。
f:id:tsuruchan_0827:20160305144127p:plain

X = []
for i in range(50000):
    img = X_train[i].reshape(3, 32, 32).transpose(1, 2, 0)
    row,col,ch= img.shape
    s_vs_p = 0.5
    amount = 0.004
    sp_img = img.copy()
    num_salt = np.ceil(amount * img.size * s_vs_p)
    coords = [np.random.randint(0, i-1 , int(num_salt)) for i in img.shape]
    sp_img[coords[:-1]] = (255,255,255)
    num_salt = np.ceil(amount * img.size * s_vs_p)
    coords = [np.random.randint(0, i-1 , int(num_salt)) for i in img.shape]
    sp_img[coords[:-1]] = (0,0,0)
    X.append(sp_img.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))

左右反転

途中で思ったけど下のような書き方のほうがpythonらしいし、速度とメモリ面に関しても優秀かも...
(いまいちよくわかってない)

def flick(X):
    img = X.transpose(1,2,0)
    return cv2.flip(img, 1).transpose(2,0,1)

X = []
X = [flick(X_train[i]) for i in range(50000)]
X_train = np.vstack((X_train, X))

いろいろ試した結果、
とりあえずガンマ変換(1.5,0.75)とハイコントラスト・ローコントラストを増やした約25万件のデータで学習することにしました。


そして、NNをすこし変更しました。

input
conv1
relu
conv2
relu
max_pooling_2d
dropout
conv3
relu
conv4
relu
max_pooling_2d
dropout
conv5
relu
conv6
relu
max_pooling_2d
linear
relu
dropout
linear
softmax

今回は96%超えたいので200エポック回します笑
コードはこちら。
結果は下にあります。
ちなみにGeForce GTX 960で約4時間かかりました。

import cv2
import sys
import pickle
import time
import argparse
import numpy as np
from tqdm import tqdm
import chainer
from chainer import cuda, Function, gradient_check, Variable, optimizers, serializers, utils
from chainer import Link, Chain, ChainList, FunctionSet
import chainer.functions as F
import chainer.links as L
import matplotlib.pyplot as plt
from sklearn.cross_validation import train_test_split

# GPU設定
parser = argparse.ArgumentParser(description='Chainer example: CIFAR-10')
parser.add_argument('--gpu', '-gpu', default=-1, type=int,
                    help='GPU ID (negative value indicates CPU)')


# GPUが使えるか確認
args = parser.parse_args()
if args.gpu >= 0:
    cuda.check_cuda_available()
xp = cuda.cupy if args.gpu >= 0 else np

# CIFAR-10データを読み込む関数
def unpickle(file):
    fp = open(file, 'rb')
    if sys.version_info.major == 2:
        data = pickle.load(fp)
    elif sys.version_info.major == 3:
        data = pickle.load(fp, encoding='latin-1')
    fp.close()
    return data


# 5つに分かれているデータを全て読み込み一つにする
X_train = None
y_train = []

for i in range(1,6):
    data_dic = unpickle("cifar-10-batches-py/data_batch_{}".format(i))
    if i == 1:
        X_train = data_dic['data']
    else:
        X_train = np.vstack((X_train, data_dic['data']))
    y_train += data_dic['labels']


test_data_dic = unpickle("cifar-10-batches-py/test_batch")
X_test = test_data_dic['data']
X_test = X_test.reshape(len(X_test),3,32,32)
y_test = np.array(test_data_dic['labels'])
X_train = X_train.reshape((len(X_train),3, 32, 32))

# 訓練データを増やす
#---   y_train   ---#
temp = y_train
for i in range(4):
    y_train = np.r_[y_train, temp]

#---   X_train   ---#

# ガンマ変換(1.5・0.75)
gamma = 1.5
look_up_table = np.ones((256, 1), dtype = 'uint8' ) * 0
for i in range(256):
    look_up_table[i][0] = 255 * pow(float(i) / 255, 1.0 / gamma)
X = []
for i in range(50000):
    img = X_train[i].transpose(1, 2, 0)
    img_gamma = cv2.LUT(img, look_up_table)
    X.append(img_gamma.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))

gamma = 0.75
look_up_table = np.ones((256, 1), dtype = 'uint8' ) * 0
for i in range(256):
    look_up_table[i][0] = 255 * pow(float(i) / 255, 1.0 / gamma)
X = []
for i in range(50000):
    img = X_train[i].transpose(1, 2, 0)
    img_gamma = cv2.LUT(img, look_up_table)
    X.append(img_gamma.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))

# コントラスト
min_table = 50
max_table = 205
diff_table = max_table - min_table

LUT_HC = np.arange(256, dtype = 'uint8' )
LUT_LC = np.arange(256, dtype = 'uint8' )

for i in range(0, min_table):
    LUT_HC[i] = 0
for i in range(min_table, max_table):
    LUT_HC[i] = 255 * (i - min_table) / diff_table
for i in range(max_table, 255):
    LUT_HC[i] = 255
for i in range(256):
    LUT_LC[i] = min_table + i * (diff_table) / 255
X = []
for i in range(50000):
    img = X_train[i].transpose(1, 2, 0)
    high_cont_img = cv2.LUT(img, LUT_HC)
    X.append(high_cont_img.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))

X = []
for i in range(50000):
    img = X_train[i].transpose(1, 2, 0)
    low_cont_img = cv2.LUT(img, LUT_LC)
    X.append(low_cont_img.transpose(2,0,1))

X = np.array(X)
X_train = np.vstack((X_train, X))


# INPUTデータはnp.float型・教師データはnp.int32型へ変換
y_train = np.array(y_train)
X_train = X_train.astype(np.float32)
X_test = X_test.astype(np.float32)
X_train /= 255
X_test /= 255
y_train = y_train.astype(np.int32)
y_test = y_test.astype(np.int32)

# 訓練データをシャッフル、その中の9.9割のデータを使用
X_train, temp1, y_train, temp2 = train_test_split(X_train, y_train, train_size=0.99, random_state=0)


# 畳み込み6層
model = chainer.FunctionSet(conv1=F.Convolution2D(3, 32, 3, pad=1),
                            conv2=F.Convolution2D(32, 32, 3, pad=1),
                            conv3=F.Convolution2D(32, 32, 3, pad=1),
                            conv4=F.Convolution2D(32, 32, 3, pad=1),
                            conv5=F.Convolution2D(32, 32, 3, pad=1),
                            conv6=F.Convolution2D(32, 32, 3, pad=1),
                            l1=F.Linear(512, 512),
                            l2=F.Linear(512, 10))


# GPU使用のときはGPUにモデルを転送
if args.gpu >= 0:
    cuda.get_device(args.gpu).use()
    model.to_gpu()

def forward(x_data, y_data, train=True):
    x, t = chainer.Variable(x_data), chainer.Variable(y_data)
    h = F.relu(model.conv1(x))
    h = F.max_pooling_2d(F.relu(model.conv2(h)), 2)
    h = F.dropout(h, ratio=0.2, train=train)
    h = F.relu(model.conv3(h))
    h = F.max_pooling_2d(F.relu(model.conv4(h)), 2)
    h = F.dropout(h, ratio=0.2, train=train)
    h = F.relu(model.conv5(h))
    h = F.max_pooling_2d(F.relu(model.conv6(h)), 2)
    h = F.dropout(F.relu(model.l1(h)), ratio=0.2, train=train)
    y = model.l2(h)
    
    return F.softmax_cross_entropy(y, t), F.accuracy(y, t)

# optimizerの設定(今回はAdamを使用)
optimizer = optimizers.Adam()
optimizer.setup(model)

# 結果を保存する配列
train_loss = np.array([])
train_acc  = np.array([])
test_loss = np.array([])
test_acc  = np.array([])

N = 247500
N_test = 10000
batch_size = 100

# 時間計測スタート
start_time = time.clock()

# エポック数40で回していく
for epoch in range(200):
    print("epoch", epoch+1)
    
    perm = np.random.permutation(N)
    
    # CPUで計算
    sum_accuracy = 0
    sum_loss = 0
    
    # 訓練
    # tadmを使うことにより、経過が視覚化できる
    for i in tqdm(range(0, N, batch_size)):
        # GPU使用可能の場合はGPUを使用
        X_batch = xp.asarray(X_train[perm[i:i+batch_size]])
        y_batch = xp.asarray(y_train[perm[i:i+batch_size]])
        
        # 勾配を初期化
        optimizer.zero_grads()
        # 順伝播させて誤差と精度を計算
        loss, acc = forward(X_batch, y_batch)
        # バックプロパゲーション
        loss.backward()
        # 最適化ルーティーンを実行
        optimizer.update()

        # pythonのlist.appendと同じような関数(np.concatenate)
        train_loss = np.concatenate((train_loss, [loss.data]))
        train_acc = np.concatenate((train_acc, [acc.data]))
        sum_loss     += float(cuda.to_cpu(loss.data)) * batch_size
        sum_accuracy += float(cuda.to_cpu(acc.data)) * batch_size
        
    # 訓練データの誤差と、正解精度を表示
    print("train mean loss={}, accuracy={}".format(sum_loss/N, sum_accuracy/N))

    # テスト評価
    sum_accuracy = 0
    sum_loss = 0
    
    for i in tqdm(range(0, N_test, batch_size)):
        X_batch = xp.asarray(X_train[perm[i:i+batch_size]])
        y_batch = xp.asarray(y_train[perm[i:i+batch_size]])
        
        # 順伝播させて誤差と精度を計算
        loss, acc = forward(X_batch, y_batch)
       
        test_loss = np.concatenate((train_loss, [loss.data]))
        test_acc = np.concatenate((train_acc, [acc.data]))
        sum_loss     += float(cuda.to_cpu(loss.data)) * batch_size
        sum_accuracy += float(cuda.to_cpu(acc.data)) * batch_size
        
    # 訓練データの誤差と、正解精度を表示
    print("test mean loss={}, accuracy={}".format(sum_loss/N_test, sum_accuracy/N_test))

end_time = time.clock()
print("total time : ", end_time - start_time)

結果

f:id:tsuruchan_0827:20160306234308p:plain

< 最高正答率 >
epoch 191
0.9702000087499618

なんと予想外に97%を超すことができました!!
平均値除去・正規化 ・白色化などについてはのちのちやろうと思います笑
目指せ98%!