├── .gitignore ├── README.md ├── global-average-pooling.ipynb └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Project 2 | .idea 3 | .ipynb_checkpoints 4 | 5 | # Python 6 | *.pyc 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # global-average-pooling 2 | Global Average Pooling Implemented in TensorFlow 3 | 4 | At this point, **this repository is in development**.
5 | I made ResNet with global average pooling instead of traditional fully-connected layer.
6 | But the model will be replaced by simpler model for you to understand GAP easily. 7 | 8 | 9 | ### GAP Example Code 10 | 11 | The input tensor to GAP is (4, 4, 128).
12 | In this example, I used 1 x 1 convolution to reduce filter size and then compute average pooling to 4 x 4 size. 13 | Lastly, the output tensor of the average pooling layer is flattened by tf.reduce_mean. 14 | 15 | The [Network In Network](https://arxiv.org/pdf/1312.4400.pdf) paper is not obvious. 16 | So.. please let me know if something is wrong. 17 | 18 | ``` 19 | gap_filter = resnet.create_variable('filter', shape=(1, 1, 128, 10)) 20 | h = tf.nn.conv2d(h, filter=gap_filter, strides=[1, 1, 1, 1], padding='SAME') 21 | h = tf.nn.avg_pool(h, ksize=[1, 4, 4, 256], strides=[1, 1, 1, 1], padding='VALID') 22 | h = tf.reduce_mean(h, axis=[1, 2]) 23 | ``` 24 | -------------------------------------------------------------------------------- /global-average-pooling.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Global Average Pooling\n", 8 | "\n", 9 | "Global Average Pooling에 대한 소개는 [Network In Network](https://arxiv.org/pdf/1312.4400.pdf) 라는 페이퍼에서 소개가 됩니다.
\n", 10 | "Network In Network의 논문을 정말 간단하게 말하면 기존 kernel/filter 를 일종의 generalized linear model로서 보는데, linearly separable한 것에 좋다고 설명합니다. 하지만 대부분의 데이터는 nonlinear manifold안에 존재하며 추상화를 높이기 위해서 MLPConv Layer를 추가합니다. (대충 짐작이 가듯이 MLP는 multi-layer perceptron을 가르킵니다.) 즉 receptive field를 연산후 output을 낼때 여기에 다시 MLP를 태워서 nonlinear function으로 만드는게 핵심입니다. \n", 11 | "\n", 12 | "하여튼.. 해당문서에서는 Network In Network가 중요한것이 아니고 바로 Global Average Pooling을 다뤄보겠습니다.
\n", 13 | "참고한 문서는 [Network In Network](https://arxiv.org/pdf/1312.4400.pdf) 페이퍼안의 *3.2 Global Average Pooling* 을 참고하였습니다.
\n", 14 | "(Network In Network는 제가 정말 시간이 날때.. 게임해야 되니 다루지 않겠습니다.) " 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "일반적으로 convolutional neural network 모델을 만들게 되면 output에서 fully-connected neural network를 태웁니다.
\n", 22 | "이러한 방법은 convolution layers를 일종의 feature extractors로 보며, 뽑혀진 feature는 전통적인 방식으로 FC layer에서 classfication이 진행이 됩니다.\n", 23 | "\n", 24 | "Fully-connected layer에는 다소 문제가 있습니다.
\n", 25 | "\n", 26 | "1. Overfitting에 취약 -> 물론 Hinotn교수님에 의해서 제안된 Dropout을 사용해 overfitting을 줄일수 있습니다.\n", 27 | "2. Positional data를 손실하게 됨 (Flatten하게 되면서 완벽한 손실. 물론 pooling에서도 손실은 여전히 있음) \n", 28 | "3. FC에서 느려진다는 말도 있는데. 이건 모델의 구조를 어떻게 했느냐에 따라서 달라지기 때문에 노 인정\n", 29 | "\n", 30 | "GAP의 구현은 매우 쉽습니다.
\n", 31 | "각각의 feature map을 분류하려는 category에 맞추면 됩니다. 예를 들어서 MNIST의 경우 10개의 categories가 있는데 이경우 feature map도 10개가 되면 됩니다.\n", 32 | "\n", 33 | "예를 들어서 마지막 tensor의 shape (8, 8, 128) 이라면, 원래는 8 x 8 x 128로 flatten시켜야 합니다.
\n", 34 | "이후 1개 이상의 fully-connected layers를 추가시키게 됩니다. (중간중간 dropout도 들어가고 BN도 들어가고.. )
\n", 35 | "layers타면서 dimension은 감소하게 되고 10개로 남기고 이후 softmax를 태웁니다.
\n", 36 | "(페이퍼에서는 마지막 activation으로 softmax를 지칭했습니다.)\n", 37 | "\n", 38 | "Global average pooling을 사용시 (8, 8, 10) tensor를 갖고 있다면, (8, 8)부분에 평균값을 구하고 결과값은 (1, 1, 10) tensor를 갖게 됩니다. 이후 해당 tensor를 1D vector로 reshape을 시키고 마지막으로 softmax activation을 태우게 됩니다. 이때 중간에 다른 레이어나 다른 연산이 들어가면 안됩니다. 그냥 이걸로 끝내는 것입니다.\n", 39 | "\n", 40 | "즉 $ y = \\text{softmax}(W * \\text{flatten}(\\text{GAP}(x) + b)) $ 이게 아니고 \n", 41 | "$ y = \\text{softmax}(\\text{flatten}(\\text{GAP}(x))) $ 이게 맞습니다.\n" 42 | ] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.6.3" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 2 66 | } 67 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | from collections import deque 4 | 5 | import tensorflow as tf 6 | from datetime import datetime 7 | from keras.preprocessing.image import ImageDataGenerator 8 | from resnet.model import ResNet 9 | import numpy as np 10 | 11 | # Parse Arguments 12 | from resnet.tool import load_data 13 | 14 | parser = argparse.ArgumentParser(description="CIFAR-10 Classification with Deep Residual Neural Network") 15 | parser.add_argument('--mode', default='train', type=str, help='"train" or "test"') 16 | parser.add_argument('--datapath', default='/tmp/cifar10', type=str, help='the directory path to store Iris data set') 17 | parser.add_argument('--epoch', default=30, type=int, ) 18 | parser.add_argument('--batch', default=64, type=int, help='batch size') 19 | parser.add_argument('--save_interval', default=5000, type=int, 20 | help='Automatically save the model after specific time interval') 21 | parser.add_argument('--visualize_interval', default=100, type=int, help='The interval value to print status like loss') 22 | parser = parser.parse_args() 23 | 24 | # Logging 25 | logger = logging.getLogger('resnet') 26 | logger.setLevel(logging.INFO) 27 | formatter = logging.Formatter('%(asctime)s - %(message)s') 28 | 29 | stream_handler = logging.StreamHandler() 30 | stream_handler.setLevel(logging.DEBUG) 31 | stream_handler.setFormatter(formatter) 32 | logger.addHandler(stream_handler) 33 | logger.propagate = False 34 | 35 | 36 | def create_model(resnet): 37 | with tf.variable_scope('input_scope'): 38 | h = resnet.init_block(filter=[7, 7], channel=[3, 32], max_pool=False) 39 | 40 | with tf.variable_scope('residual01'): 41 | h = resnet.max_pool(h, kernel=[2, 2], stride=[2, 2]) 42 | h = resnet.residual_block(h, filter=[3, 3], channel=[32, 32]) 43 | h = resnet.residual_block(h, filter=[3, 3], channel=[32, 32]) 44 | h = resnet.residual_block(h, filter=[3, 3], channel=[32, 32]) 45 | h = resnet.residual_block(h, filter=[3, 3], channel=[32, 32]) 46 | h = resnet.residual_block(h, filter=[3, 3], channel=[32, 32]) 47 | h = resnet.residual_block(h, filter=[3, 3], channel=[32, 32]) 48 | 49 | with tf.variable_scope('residual02'): 50 | h = resnet.max_pool(h, kernel=[2, 2], stride=[2, 2]) 51 | h = resnet.residual_block(h, filter=[3, 3], channel=[32, 64]) 52 | h = resnet.residual_block(h, filter=[3, 3], channel=[64, 64]) 53 | h = resnet.residual_block(h, filter=[3, 3], channel=[64, 64]) 54 | h = resnet.residual_block(h, filter=[3, 3], channel=[64, 64]) 55 | 56 | h = resnet.residual_block(h, filter=[3, 3], channel=[64, 64]) 57 | h = resnet.residual_block(h, filter=[3, 3], channel=[64, 64]) 58 | h = resnet.residual_block(h, filter=[3, 3], channel=[64, 64]) 59 | h = resnet.residual_block(h, filter=[3, 3], channel=[64, 64]) 60 | 61 | with tf.variable_scope('residual04'): 62 | h = resnet.max_pool(h, kernel=[2, 2], stride=[2, 2]) 63 | h = resnet.residual_block(h, filter=[3, 3], channel=[64, 128]) 64 | h = resnet.residual_block(h, filter=[3, 3], channel=[128, 128]) 65 | h = resnet.residual_block(h, filter=[3, 3], channel=[128, 128]) 66 | h = resnet.residual_block(h, filter=[3, 3], channel=[128, 128]) 67 | h = resnet.residual_block(h, filter=[3, 3], channel=[128, 128]) 68 | h = resnet.residual_block(h, filter=[3, 3], channel=[128, 128]) 69 | h = resnet.residual_block(h, filter=[3, 3], channel=[128, 128]) 70 | 71 | with tf.variable_scope('global_average_pooling'): 72 | print('xxxxxxxxxxxxx', h) 73 | gap_filter = resnet.create_variable('filter', shape=(1, 1, 128, 10)) 74 | h = tf.nn.conv2d(h, filter=gap_filter, strides=[1, 1, 1, 1], padding='SAME') 75 | print('before global avg:', h) 76 | h = tf.nn.avg_pool(h, ksize=[1, 4, 4, 1], strides=[1, 1, 1, 1], padding='VALID') 77 | print(h) 78 | h = tf.reduce_mean(h, axis=[1, 2]) 79 | resnet.layers.append(h) 80 | return h 81 | 82 | 83 | def train(resnet, interval=parser.visualize_interval): 84 | loss = resnet.loss() 85 | adam = tf.train.AdamOptimizer() 86 | train_op = adam.minimize(loss) 87 | resnet.compile() 88 | 89 | # Get Data 90 | train_x, train_y, test_x, test_y = load_data(parser.datapath) 91 | 92 | # Image Augmentation 93 | datagen = ImageDataGenerator(horizontal_flip=True, vertical_flip=True, featurewise_center=True, 94 | featurewise_std_normalization=True) 95 | datagen.fit(train_x) 96 | 97 | iter_count = 0 98 | _losses = deque(maxlen=interval) 99 | time_point = datetime.now() 100 | for epoch in range(1, parser.epoch + 1): 101 | sample_count = 0 102 | 103 | for i, (sample_x, sample_y) in enumerate(datagen.flow(train_x, train_y, batch_size=resnet.batch)): 104 | feed_dict = {resnet.x_ts: sample_x, resnet.y_ts: sample_y} 105 | _loss, _ = resnet.sess.run([loss, train_op], feed_dict=feed_dict) 106 | 107 | # Visualize 108 | _losses.append(_loss) 109 | if i % interval == 0: 110 | _loss = np.mean(_losses) 111 | time_diff = round((datetime.now() - time_point).total_seconds(), 2) 112 | time_point = datetime.now() 113 | logger.info(f'[epoch:{epoch:02}] loss:{_loss:<7.4}' 114 | f'time-taken:{time_diff:<7.4}' 115 | f'sample:{sample_count:<5}' 116 | f'iter:{iter_count:<5}') 117 | 118 | # Add up count 119 | iter_count += 1 120 | sample_count += 1 121 | 122 | # Save 123 | if iter_count % parser.save_interval == 0: 124 | logger.info(f'Model has been successfully saved at iteration = {iter_count}') 125 | resnet.save() 126 | 127 | 128 | def evaluate(resnet, batch_size=parser.batch): 129 | correct_prediction = tf.equal(tf.argmax(resnet.last_layer, 1), y=resnet.y_ts) 130 | accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) 131 | 132 | # Get Data 133 | train_x, train_y, test_x, test_y = load_data(parser.datapath) 134 | 135 | accuracies = list() 136 | for i in range(0, 10000, batch_size): 137 | if i + batch_size < 10000: 138 | _acc = resnet.sess.run(accuracy, feed_dict={ 139 | resnet.x_ts: test_x[i:i + batch_size], 140 | resnet.y_ts: test_y[i:i + batch_size]}) 141 | accuracies.append(_acc) 142 | 143 | logger.info('Accuracy', np.mean(accuracies)) 144 | 145 | 146 | def main(): 147 | resnet = ResNet(batch=parser.batch) 148 | create_model(resnet) 149 | resnet.compile() 150 | 151 | if parser.mode == 'train': 152 | train(resnet) 153 | elif parser.mode == 'test': 154 | resnet.restore() 155 | evaluate(resnet) 156 | 157 | 158 | if __name__ == '__main__': 159 | main() 160 | --------------------------------------------------------------------------------