External Memory

プログラミング周辺知識の備忘録メイン

畳み込みニューラルネットワークのプログラム作成とCIFAR10分類

いろいろな畳み込みニューラルネットワークの構造を試すために、たたき台となるようなプログラムを作成した。
主にCIFAR-10の分類に使用すると思う。

作成したとはいっても、下記URL tensorflow CIFAR-10用のサンプルコードとネットワーク構造はほぼ同じで、重み、バイアス初期化やoptimizerを少し変更している。
models/tutorials/image/cifar10 at master · tensorflow/models · GitHub


このサンプルコードでは以下のような手法を使っている。

  • overlap pooling

pooling操作時にウィンドウサイズに対してスライド距離が小さい場合、それぞれのウィンドウ同士が重なる。
サンプルの場合ウィンドウサイズは3、スライド距離は2である。

  • data argumentation

データを増やすため、データの入力に対してランダムに反転、切り抜き抽出、正規化、輝度・コントラスト調整を行っている。

  • local response normalization

AlexNetでも使用されているlocal_response_normalizationは名前の通り、空間位置ごとの特徴マップ間活性化値の正規化である。
局所的なコントラストの違いを調整するような働きをすると思われるので自然画像のようなサンプルに適しているだろうと思う。
local_response_normalizationはAlexNetでは畳み込み層の直後だが、
サンプルコードでは最初のnormalizationはpooling層の直後だったので今回はこちらに従った。個人的にはpooling層の手前に配置するほうが、pooling層をうまく使えているような気がするが。

  • stepに対して学習率を減衰させる勾配降下法

収束を速めるために350 epochごとに学習率を0.1倍にまで減衰させている。
指数関数的減衰を使用している。350 epochで0.1倍に減衰する。

プログラム

実際に作成したプログラムは以下の通りである。
CIFAR-10データのinputに関するところは、サンプルコードと内容は同じなので省略。

import tensorflow as tf
import numpy as np
import os


NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000
NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = 10000

class Conv_net():
    
    def __init__(self,l_rate,num_label,istrain):
        self.l_rate = l_rate
        self.num_label = num_label
        self.istrain = istrain
        
    def conv2d(self,p_height,p_width,ch,next_ch,name,strides=[1,1,1,1],inpt=None):
        
        W = tf.Variable(tf.truncated_normal([p_height, p_width, ch, next_ch],
                            stddev=1.0 / tf.sqrt(float(p_height * p_width))),name=name+"weight")
        b = tf.Variable(tf.zeros([next_ch]),name=name+"bias")
        
        if inpt == None:
            self.y = tf.nn.relu(tf.nn.conv2d(self.y, W, strides=strides, padding='SAME')+ b)
        else:
            self.y = tf.nn.relu(tf.nn.conv2d(inpt, W, strides=strides, padding='SAME')+ b)

        
    def max_pool(self,ksize=[1,2,2,1],strides=[1,2,2,1]):
        self.y = tf.nn.max_pool(self.y, ksize=ksize,
            strides=strides, padding='SAME')
    
    def lrn(self,depth_radius,bias,alpha,beta):
        self.y = tf.nn.local_response_normalization(self.y,depth_radius=depth_radius,
                                                    bias=bias,alpha=alpha,beta=beta)
    
    def fully_connect(self,unit,next_unit,name):
        W = tf.Variable(tf.truncated_normal([unit, next_unit],
                        stddev=1.0 / tf.sqrt(float(unit))),name=name+"weight")
        b = tf.Variable(tf.zeros([next_unit]),name=name+"weight")
        self.y = tf.reshape(self.y, [-1, unit])
        self.y = tf.nn.relu(tf.matmul(self.y, W)+ b)
    
    def drop_out(self,rate):
            self.y = tf.layers.dropout(self.y, tf.constant(rate),training = self.istrain)
            
    def output(self,unit,name):
        W = tf.Variable(tf.truncated_normal([unit, self.num_label],
                    stddev=1.0 / tf.sqrt(float(unit))),name=name+"weight")
        b = tf.Variable(tf.zeros([self.num_label]),name=name+"bias")

        self.y = tf.matmul(self.y, W)+ b
        
    def train(self,epoch,labels,data_length,batch_size):
        
        cross_entropy = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(labels = labels,logits = self.y))
        train_op = tf.train.AdamOptimizer(self.l_rate).minimize(cross_entropy)
        
        saver = tf.train.Saver(max_to_keep=11)
        sess = tf.InteractiveSession()
        tf.global_variables_initializer().run()

        tf.train.start_queue_runners(sess=sess)
        for i in range(epoch*data_length//batch_size):
            _,loss = sess.run([train_op,cross_entropy])
            
            
            if i % (data_length*epoch//(batch_size*10)) == 0:
                print("{0:>7}{1:>13.4f}".format(i*batch_size//data_length,loss))
                saver.save(sess,"tmp/cifar10",global_step=i*batch_size//data_length)   
                
        print("{0:>7}{1:>13.4f}".format(epoch,sess.run(cross_entropy)))
        saver.save(sess,"tmp/cifar10",global_step=epoch)   
        
        
        return cross_entropy
    
    def print_accurency(self,p_epoch,labels,batch_size):
        cross_entropy = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(labels = labels,logits = self.y))
        top_k_op = tf.nn.in_top_k(self.y, labels, 1)
        
        num_iter = int(NUM_EXAMPLES_PER_EPOCH_FOR_EVAL / batch_size)
        true_count = 0
        total_loss = 0
        total_sample_count = num_iter * batch_size
        step = 0
        while step < num_iter:
            loss,predictions = sess.run([cross_entropy,top_k_op])
            true_count += np.sum(predictions)
            total_loss += loss
            step += 1
        precision = true_count / total_sample_count
        loss = total_loss / num_iter
        print("{0:>7}{1:>13.4f}{2:>13.4f}".format(p_epoch,loss,precision))

#----一部省略------

def inference(data,istrain):
    model = Conv_net(0.001,10,istrain)
    model.conv2d(5,5,3,64,"conv1",inpt=data)
    model.max_pool(ksize=[1,3,3,1])
    model.lrn(4,1.0,0.001/9.0,0.75)
    model.conv2d(5,5,64,64,"conv2")
    model.lrn(4,1.0,0.001/9.0,0.75)
    model.max_pool(ksize=[1,3,3,1])
    model.fully_connect(36*64,384,"fully1")
    #model.drop_out(0.5)
    model.fully_connect(384,192,"fully2")
    #model.drop_out(0.5)
    model.output(192,"output")
    
    return model
    
if __name__ == '__main__':
    #CIFAR-10
    epoch = 50
    
    train_data, train_label = distorted_inputs("cifar-10-batches-bin",50,
                                               img_size = 24,crop=True,flip=True,
                                               brightness=True,contrast=True)
    print("{0:>7}{1:>13}".format("epoch","train_loss"))
    print("-------------------------------------")
    train_cifar10 = inference(train_data,True)
    train_cifar10.train(epoch,train_label,NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN,50)
    
    
    print("{0:>7}{1:>13}{2:>13}".format("epoch","loss","accuracy"))
    print("------------------------------------")   
       
    for i in range(0,epoch+1,epoch//10):
        
        tf.reset_default_graph()
        
        test_data, test_labels = inputs(True,"cifar-10-batches-bin",50,img_size = 24)
        eval_cifar10 = inference(test_data,False)
        
        sess = tf.InteractiveSession()
        saver = tf.train.Saver()
        saver.restore(sess,"tmp/cifar10-{}".format(i))
        
        tf.train.start_queue_runners(sess=sess)
               
        eval_cifar10.print_accurency(i,test_labels,50)
        


sparse_softmax_cross_entropy_with_logits関数に関して、入力の形式が示している通りそれぞれのラベルが離散的でが確率に対して排他的なら使用可能である。
MNISTやCIFAR-10などのよくある離散的な分類問題では問題なく使えるだろう。
softmax_cross_entropy_with_logitsは排他的でなくても使える。
何故sparseと名付けているのかよくわからないが、排他的な確率前提の出力なら比較的にスパースっぽい気もする。

tf.nn.in_top_k関数はターゲットがpredictionのtop kにあるかによってbool値のtensorを返す。k=1の場合は、予測の最大値とlabelが一致していればTrueを返す。

CIFAR-10 学習結果

プログラムを実行して、CIFAR-10分類精度は以下のようになる。
上記の手法を使用したときの精度の差異を比較できるようにした。。
baseのネットワーク構造は単純なCNN (conv + maxpooling)*2 + 全結合層*2 である。

  epoch         loss     accuracy
------------------------------------
base
     45       3.0144       0.6869

base + dropout
     50       1.3974       0.7237

base + local_response_normalization
     45       2.8104       0.6932

base + overlap pooling
     30       2.3325       0.7137

base + data argumentation + overlap pooling
     50       0.6110       0.7947

base + data argumentation + overlap pooling + local_response_normalization
     50       0.6139       0.7880

baseの実行時にepochに対してlossが徐々に低下したため全結合層にdropoutしたものを付け加えた。

local_response_normalization適用時に、
train_lossが他の系よりかなり小さかったのに対し、testデータに対するlossは大きかった。原因としてコントラスト調整で過学習気味になりやすいというのはすぐ思いあたるが、AlexNetでは過学習の抑制に苦労したらしいので、このあたりの影響もあったのだろうか?

そこで過学習抑制のためのdata argumentationと組み合わせてlocal_response_normalizationを適用させたが、逆に精度は落ちた。
後で調べてみると、local_response_normalizationはあまり効果がなくてあまり使用されていないようである。
CS231n Convolutional Neural Networks for Visual Recognition


data argumentationを適用したものに関しては、epoch 50程度ではまだ精度は飽和していないようだった。ちなみにサンプルコードでは100 epoch程度以上で飽和するようなので、学習量は足りてない。これに関しては、別の検証時に行うことにする。


data argumentationを適用する前の入力データ量は32*32*3であり、適用後は24*24*3である。Core i5-7600 CPUでepoch 50の学習時間は32サイズで3時間弱ほどかかり、local_response_normalizationを適用するとさらに倍近くの時間がかかる。
24サイズでは時間短縮できるが、大抵のCNNはかなり深いので学習時間の問題もそれなりに考えなければならない。