DevelopHyun

Data Science & Algorith with Computer Science

resNet[4] - Implementation

20 Feb 2018 » deeplearning, cnn, resnet, implementation, tensorflow

1. Summary

resNet의 성능을 확인하기 위하여 original resNetproposed resNet을 구현했다. 비교를 위하여 같은 layer 개수를 가진 CNN도 구현했다. 구현은 tensorflow로 했으며, 컴퓨팅 파워가 좋지는 않아서 학습시간은 대략 40시간정도 걸린 것 같다.


2. Data set

원래 논문에서 사용했던 CIFAR10 데이터를 사용했다. 32X32X3 크기의 데이터이며 train set 50k, test set 10k로 구성되어 있으며, train set 중 10k를 validation set으로 사용했다.


3. Setting

  • learning_rate = 0.005
  • training_epochs = 50
  • batch_size = 100

4. Model

resNet

  • 3개의 model 모두 32-layer로 구성했다.

4.1 original resNet

def residual_block(self, X_input, num_filter, chg_dim) :
    stride = 1
        
    #stride=2일 경우
    if chg_dim :
        stride = 2
        pool1 = tf.layers.max_pooling2d(inputs= X_input, strides=2, pool_size=[1,1])
        pad1 = tf.pad(pool1, [[0,0], [0,0], [0,0], [int(num_filter/4),int(num_filter/4)]])
        shortcut = pad1
    else :
        shortcut = X_input
            
    conv1 = tf.layers.conv2d(inputs = X_input, filters=num_filter, kernel_size=[3, 3], padding="SAME", strides=stride, kernel_initializer=tf.contrib.layers.xavier_initializer())
    bm1 = tf.layers.batch_normalization(inputs = conv1)
    relu1 = tf.nn.relu(bm1)
        
    conv2 = tf.layers.conv2d(inputs = relu1, filters=num_filter, kernel_size=[3, 3], padding="SAME", strides=1, kernel_initializer=tf.contrib.layers.xavier_initializer())
    bm2 = tf.layers.batch_normalization(inputs = conv2)
        
    X_output = tf.nn.relu(bm2 + shortcut)
        
    return X_output

def build(self) :
    with tf.variable_scope(self.name) :
            
        ###Input Layer
        #input : ? * 32 * 32 * 3
        #ouput : ? * 32 * 32 * 3
        self.X = tf.placeholder(tf.float32, [None, 32, 32, 3])
        self.Y = tf.placeholder(tf.float32, [None, 10])
        self.training = tf.placeholder(tf.bool)
            
        ###Hidden Layer
        #input : ? * 32 * 32 * 3
        #ouput : ? * 32 * 32 * 16
        conv = tf.layers.conv2d(inputs = self.X, filters = 16, kernel_size=[3,3], padding="SAME", strides=1)
        bm = tf.layers.batch_normalization(inputs = conv)
        relu = tf.nn.relu(bm)
            
        #input : ? * 32 * 32 * 3
        #ouput : ? * 32 * 32 * 16
        res1 = self.residual_block(relu, 16, False)
        res2 = self.residual_block(res1, 16, False)
        res3 = self.residual_block(res2, 16, False)
        res4 = self.residual_block(res3, 16, False)
        res5 = self.residual_block(res4, 16, False)

        #input : ? * 32 * 32 * 16
        #ouput : ? * 16 * 16 * 32
        res6 = self.residual_block(res5, 32, True)
        res7 = self.residual_block(res6, 32, False)
        res8 = self.residual_block(res7, 32, False)
        res9 = self.residual_block(res8, 32, False)
        res10 = self.residual_block(res9, 32, False)

        #input : ? * 16 * 16 * 32
        #ouput : ? * 8 * 8 * 64
        res11 = self.residual_block(res10, 64, True)
        res12 = self.residual_block(res11, 64, False)
        res13 = self.residual_block(res12, 64, False)
        res14 = self.residual_block(res13, 64, False)
        res15 = self.residual_block(res14, 64, False)
            
        ###Global Average Pooling
        #input : ? * 8 * 8 * 64
        #ouput : ? * 1 * 1 * 64
        gap = tf.reduce_mean(res15, [1, 2], keep_dims=True)
            
        ###Output Layer
        #input : ? * 1 * 1 * 64
        #ouput : ? * 1 * 1 * 64
        shape = gap.get_shape().as_list()
        dimension = shape[1] * shape[2] * shape[3]
        flat = tf.reshape(gap, [-1, dimension])
            
        fc = tf.layers.dense(inputs=flat, units=10, kernel_initializer=tf.contrib.layers.xavier_initializer())
        self.logits = fc

        self.cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=self.logits, labels=self.Y))
        update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS, scope=self.name)
        with tf.control_dependencies(update_ops):
            self.optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(self.cost)

        correct_prediction = tf.equal(tf.argmax(self.logits, 1), tf.argmax(self.Y, 1))     
        self.accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
  • 총 31개의 convolution layer와 1개의 fully connected layer
  • residual block
    • conv -> batch norm -> relu -> conv -> batch norm까지 연산하고, shortcut path의 값을 더해준 후 다시 relu를 취해줬다.
    • stride = 2로 할때 input data size가 줄어들게 하였고, 그때마다 filter의 수를 2배로 늘려줬다.
    • input data size가 변할 때, shortcut path data도 바꿔줘야 하는데, pooling과 padding을 통해 dimension을 맞춰줬다.
  • convolution이 끝나고, FC layer를 구성하기 전에 Gloval average pooling을 사용했다.

4.2 proposed resNet

def residual_block(self, X_input, num_filter, chg_dim) :
    stride = 1
        
    #stride=2일 경우
    if chg_dim :
        stride = 2
        pool1 = tf.layers.max_pooling2d(inputs= X_input, strides=2, pool_size=[1,1])
        pad1 = tf.pad(pool1, [[0,0], [0,0], [0,0], [int(num_filter/4),int(num_filter/4)]])
        shortcut = pad1
    else :
        shortcut = X_input
        
    bm1 = tf.layers.batch_normalization(inputs = X_input)
    relu1 = tf.nn.relu(bm1)
    conv1 = tf.layers.conv2d(inputs = relu1, filters=num_filter, kernel_size=[3, 3], padding="SAME", strides=stride, kernel_initializer=tf.contrib.layers.xavier_initializer())

    bm2 = tf.layers.batch_normalization(inputs = conv1)
    relu2 = tf.nn.relu(bm2)
    conv2 = tf.layers.conv2d(inputs = relu2, filters=num_filter, kernel_size=[3, 3], padding="SAME", strides=1, kernel_initializer=tf.contrib.layers.xavier_initializer())
        
    X_output = conv2 + shortcut
        
    return X_output
  • 총 31개의 convoltuon layer와 1개의 fully connected layer
  • residual block을 제외한 나머지 부분은 original resNet과 동일
  • residual block
    • 연산을 해주는 순서를 변경
    • batch norm -> relu -> conv -> batch norm -> relu -> conv
    • shortcut path는 차원을 줄여줄때 pooling과 padding을 해주는 것 이외에는 건들지 않는다.

4.3 32-layer CNN

def cnn_block(self, X_input, num_filter, num_iter) :
        
    for idx in range(num_iter) : 
        conv = tf.layers.conv2d(inputs=X_input, filters=num_filter, kernel_size=[3, 3],padding="SAME")
        bm = tf.layers.batch_normalization(inputs = conv)
        relu = tf.nn.relu(bm)
        X_input = relu

    return relu
  • 총 31개의 convolution layer와 1개의 fully connected layer
  • conv -> batch norm -> relu를 해주는 block 31개를 쌓았다.

5. Result

5.1 Loss

loss

  • resNet의 경우 꾸준히 loss가 줄어들었지만, CNN의 경우 학습이 거의 되지 않았다.
    • CNN의 경우 layer수를 너무 많이 쌓아 학습이 잘 이뤄지지 않았다고 추정된다.
    • vanishing gradient나 local minimum 문제가 발생한 것 같다.
  • train loss의 경우 proposed resNet이 더 작았다.

  • valid loss의 경우 서로 비슷했다. 특이한 점은 20 epoch부터는 증가하는 모양새를 보여준다.
    • overfitting이 발생해서 그런 것으로 추정된다.

5.2 Accuracy

acc

  • CNN의 경우 학습이 잘 되지 않았다.
  • proposed resNet이 꾸준히 성능이 좋았다.

6. Comment

처음에 논문의 Experiment 부분을 잘못읽어서 데이터의 크기가 훨씬 컸던 ImageNet 데이터에 맞는 모델을 만들어서 성능이 좋지않았다. 하지만 논문에서 CIFAR10 데이터를 대상으로 모델을 구현한대로 paramter의 수를 조정했더니, 그제서야 성능이 괜찮게 나왔다. 처음 구현해본 것이라 중간에 실수도 많았지만, 결과는 비교적 괜찮게 나온 것 같다.


7. 코드

https://github.com/hwkim94/hwkim94.github.io/tree/master/Implementation/resNet