본문 바로가기

AI 인공지능

인공지능 수첩 ( 기울기 소실 / Skip Connection / AlexNet / VGG-16 / ResNet-50 / Degradation Problem / Res-Net / Dense-Net / SE-Net / NAS-Net / Efficient-Net / VGG )

반응형

 

기울기 소실 (Vanishing Gradient)

- 모델이 깊어질수록 학습을 위한 Gradient가 사라지는 현상

- 활성화 함수를 변경하거나 가중치 초기화 방법을 통해서 기울기 소실 문제를 완화할 수 있음.


Skip Connection

- 레이어의 입력을 다른 곳에 이어서 Gradient가 깊은 곳까지 이어지도록 함.

- 레이어와 Skip Connection이 있는 블록을 Residual Block이라고 함.

- segmentation에서 활용되는 U-Net의 구조도 일종의 Skip connection으로 볼 수 있음.


AlexNet

- 기존에 있던 Le-Net에 CNN과 더 많은 class를 갖고 있는 네트워크

- ReLU함수, 드롭아웃, 오버래핑 풀링(Overlapping pooling) 등이 적용됨.


 

VGG-16

- 합성곱층 13개와 밀집층 3개로 구성됨.

- 3x3 커널을 사용해서 더 많은 레이어를 쌓고, 이미지의 비선형적 특성을 더 잘 잡아냄.

import tensorflow as tf

from tensorflow import keras

from tensorflow.keras import layers

# CIFAR100 데이터셋을 가져옴.

cifar100 = keras.datasets.cifar100

(x_train, y_train), (x_test, y_test) = cifar100.load_data()

x_train, x_test = x_train / 255.0, x_test / 255.0

print("x_train:", len(x_train), "x_test:", len(x_test))

img_input = keras.Input(shape=(32, 32, 3))

x = keras.layers.Conv2D(16, 3, activation='relu')(img_input)

x = keras.layers.MaxPool2D((2,2))(x)

x = keras.layers.Conv2D(32, 3, activation='relu')(x)

x = keras.layers.MaxPool2D((2,2))(x)

x = keras.layers.Flatten()(x)

x = keras.layers.Dense(256, activation='relu')(x)

predictions = keras.layers.Dense(100, activation='softmax')(x)

model = keras.Model( inputs=img_input, outputs=predictions )

model.summary()

# 모델 학습

model.compile(optimizer='adam',

loss='sparse_categorical_crossentropy',

metrics=['accuracy'])

model.fit(x_train, y_train, epochs=1)

# 첫 번째 블록

x = layers.Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(img_input)

x = layers.Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x)

x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)

# 두 번째 블록

x = layers.Conv2D(128, (3, 3), activation='relu', padding='same', name='block2conv1')(x)

x = layers.Conv2D(128, (3, 3), activation='relu', padding='same', name='block2conv2')(x)

x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)

# 세 번째 블록

x = layers.Conv2D( 256, (3, 3), activation='relu', padding='same', name='block3conv1')(x)

x = layers.Conv2D( 256, (3, 3), activation='relu', padding='same', name='block3conv2')(x)

x = layers.Conv2D( 256, (3, 3), activation='relu', padding='same', name='block3conv3')(x)

x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block3pool')(x)

# 네 번째 블록

x = layers.Conv2D( 512, (3, 3), activation='relu', padding='same', name='block4conv1')(x)

x = layers.Conv2D( 512, (3, 3), activation='relu', padding='same', name='block4conv2')(x)

x = layers.Conv2D( 512, (3, 3), activation='relu', padding='same', name='block4conv3')(x)

x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block4pool')(x)

# 다섯 번째 블록

x = layers.Conv2D( 512, (3, 3), activation='relu', padding='same', name='block5conv1')(x)

x = layers.Conv2D( 512, (3, 3), activation='relu', padding='same', name='block5conv2')(x)

x = layers.Conv2D( 512, (3, 3), activation='relu', padding='same', name='block5conv3')(x)

x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block5pool')(x)

# 여섯 번째 블록

x = layers.Flatten(name='flatten')(x)

x = layers.Dense(4096, activation='relu', name='fc1')(x)

x = layers.Dense(4096, activation='relu', name='fc2')(x)

classes=100

x = layers.Dense(classes, activation='softmax', name='predictions')(x) # CIFAR100을 위한 모델

model = keras.Model(name="VGG-16", inputs=img_input, outputs=x)

model.summary()


ResNet-50

- conv_blockidentity_block을 잘 활용하여, 50개의 레이어 구조를 간결하게 표현함.

- feature의 크기가 서로 다른 4개의 Stage로 구분됨. (하나의 Stage안에서 kernel 사이즈와 channel 수는 동일)

- Skip Connection 구조를 사용해서 기울기 소실 문제를 해결함.

from tensorflow.keras import backend, regularizers, initializers, models

# block 안에 반복적으로 활용되는 L2 regularizer를 선언해줌.

def _gen_l2_regularizer( use_l2_regularizer=True, l2_weight_decay=1e-4 ):

return regularizers.l2(l2_weight_decay) if use_l2_regularizer else None

def conv_block(input_tensor,

kernel_size,

filters,

stage,

block,

strides=(2, 2),

use_l2_regularizer=True,

batch_norm_decay=0.9,

batch_norm_epsilon=1e-5):

filters1, filters2, filters3 = filters

if backend.image_data_format() == 'channels_last':

bn_axis = 3

else:

bn_axis = 1

conv_name_base = 'res' + str(stage) + block + '_branch'

bn_name_base = 'bn' + str(stage) + block + '_branch'

x = layers.Conv2D(

filters1, (1, 1),

use_bias=False,

kernel_initializer='he_normal',

kernel_regularizer=_gen_l2_regularizer(use_l2_regularizer),

name=conv_name_base + '2a') (input_tensor)

x = layers.BatchNormalization(

axis=bn_axis,

momentum=batch_norm_decay,

epsilon=batch_norm_epsilon,

name=bn_name_base + '2a')(x)

x = layers.Activation('relu')(x)

def identity_block(input_tensor,

kernel_size,

filters,

stage,

block,

use_l2_regularizer=True,

batch_norm_decay=0.9,

batch_norm_epsilon=1e-5):

def resnet50(num_classes,

batch_size=None,

use_l2_regularizer=True,

rescale_inputs=False,

batch_norm_decay=0.9,

batch_norm_epsilon=1e-5):

input_shape = (32, 32, 3) # CIFAR100을 위한 input_shape 조정입니다.

img_input = layers.Input(shape=input_shape, batch_size=batch_size)

if rescale_inputs:

x = layers.Lambda(

lambda x: x * 255.0 - backend.constant(

imagenet_preprocessing.CHANNEL_MEANS,

shape=[1, 1, 3],

dtype=x.dtype),

name='rescale')(img_input)

else:

x = img_input

if backend.image_data_format() == 'channels_first':

x = layers.Permute((3, 1, 2))(x)

bn_axis = 1

else:

bn_axis = 3

block_config = dict(

use_l2_regularizer=use_l2_regularizer,

batch_norm_decay=batch_norm_decay,

batch_norm_epsilon=batch_norm_epsilon)

x = layers.ZeroPadding2D(padding=(3, 3), name='conv1_pad')(x)

x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1), **block_config)

x = identity_block(x, 3, [64, 64, 256], stage=2, block='b', **block_config)

x = identity_block(x, 3, [64, 64, 256], stage=2, block='c', **block_config)

x = layers.GlobalAveragePooling2D()(x)

x = layers.Dense(num_classes,

kernel_initializer=initializers.RandomNormal(stddev=0.01),

kernel_regularizer=_gen_l2_regularizer(use_l2_regularizer),

bias_regularizer=_gen_l2_regularizer(use_l2_regularizer),

name='fc1000')(x)

x = layers.Activation('softmax', dtype='float32')(x)

return models.Model(img_input, x, name='resnet50')

model = resnet50(num_classes=100)

model.summary()


 

Degradation Problem

- 딥러닝 모델의 레이어가 깊어졌을 때 모델이 수렴했음에도 불구하고, 오히려 레이어 개수가 적을 때보다 모델의 training error 및 test error가 더 커지는 문제

- 오버피팅 때문이 아니라, 네트워크 구조상 레이어를 깊이 쌓았을 때 최적화가 잘 안되기 때문에 발생하는 문제

- ResNet 논문에서 이 문제를 해결하기 위한 Residual Block이라는 솔루션을 제시함.


 

Res-Net

- 깊은 네트워크의 학습이 어려운 점을 해결하기 위해 레이어의 입력값 x를 활용함.

- 입력값 x를 identity mapping으로 감싸서 정보가 손실되지 않게 출력값으로 전달함.

- 학습해야할 레이어 H(x)F(x) + x로 만들면 아무리 F(x)가 zero mapping이 될지라도, 최종 H(x)는 identity mapping이라도 되므로 성능 저하가 발생하지 않게 됨.

- 실제로 학습해야 할 F(x)는 학습해야 할 레이어인 H(x)에다 입력값 x를 뺀 형태인 Residual function이 됨.

- 일종의 지름길인 shortcut connection을 통해서 레이어가 입력값을 직접 참조하도록 레이어를 변경함.

- Shortcut connection은 앞에서 입력으로 들어온 값을 네트워크의 출력층에 곧바로 더해줌.

- shortcut connection을 가진 기본 블록을 Residual Block이라고 함.

- shortcut이 적용된 레이어는 깊어져도 학습이 잘 되는 효과를 볼 수 있음.


 

Dense-Net (Densely Connected Convolutional Networks)

- Res-Net의 shortcut connection을 마치 Fully Connected Layer처럼 촘촘하게 하여 성능을 더욱 개선함.

- L개의 레이어가 있을 때 레이어 간에 L(L+1) / 2개의 직접적인 연결인 dense connectivity를 만듦.

- dense connectivityHl로 표기하고, 이를 합성 함수(composite function)라고 부름.

- 각 레이어는 이전 레이어들에서 나온 특성맵 전부를 입력값으로 받음.

- 특성맵들을 채널 축을 따라 연결(concatenate)하여 하나의 텐서로 만듦.

- Hl은 이 텐서에 대해 배치 정규화, ReLU 활성화 함수, 3x3 컨볼루션 레이어를 통해서 pre-activation을 수행함.

- 특성 맵을 채널 방향으로 쌓아서 사용함.

- growth rate라는 값을 조정하여 채널의 개수를 조절함.


 

SE-Net (Squeeze-and-Excitation Networks)

- Squeeze는 특성에서 중요한 정보를 짜내는 과정임.

- 채널 방향으로 Global Average Pooling(GAP)을 적용하여 중요한 채널이 활성화되도록 함.

- CNN에서 나온 특성 맵의 채널에 어텐션(attention) 매커니즘을 적용한 블록을 만들어냄.

- Ftr이라는 컨볼루션 레이어를 거치면 'H x W x C'의 크기를 가진 특성 맵 U 가 나옴.

- Fsq함수에서 Squeeze가 일어나며, 특성맵은 '1 x 1 x C'의 크기로 변함.

- 얻어진 '1 x 1 x C'의 벡터는 채널별 정보를 압축해 담고 있음.

- Excitate는 채널별 정보에 따라 어떤 채널을 강조해야 할지 판단하는 과정임.

- 여러 채널들이 서로 다른 정도로 활성화되도록 하기 위해 시그모이드를 사용함.


NAS-Net

- NAS (neural architecture search): 신경망을 사용해 최적화된 모델의 구조를 탐색하는 접근 방법

- 딥러닝에서 모델을 탐색하기 위해 강화학습 모델하이퍼파라미터를 조정하면서 최적의 성능을 내도록 함.

- 레이어의 세부 구성, CNN의 필터 크기, 채널의 개수, connection 등이 조정할 수 있는 변수가 됨.

- 네트워크 구성에 대한 요소들을 조합할 수 있는 범위를 탐색 공간(search space)라고 함.

- 탐색 공간을 줄이기 위해 모듈(cell) 단위의 최적화를 하고, 그 모듈을 조합하는 방식을 채택함.

- Convolution cellnormal cellreduction cell로 구분됨.

- normal cell은 특성 맵의 가로, 세로가 유지되도록 stride를 1로 고정함.

- reduction cell stride를 1 또는 2로 가져가서 특성 맵의 크기가 줄어들도록 함.


 

Efficient-Net

- CNN을 효율적으로 사용할 수 있도록 네트워크의 형태를 조정할 수 있는 width, depth, resolution이라는 세 가지 scaling factor에 집중함.

* width: CNN의 채널 (채널을 늘려줄수록 CNN의 파라미터와 특성을 표현하는 차원의 크기를 키울 수 있음.)

* depth: 네트워크의 깊이

* resolution: 입력값의 너비(w)와 높이(h)

- width, depth, resolution를 동시에 고려하는 compound scaling을 제안함.

- compound coefficient ϕ는 모델의 크기를 조정하기 위한 계수임.

- ϕ를 고정시키면 보다 일정한 규칙에 따라 모델의 구조가 조절됨.

 


 

VGG (Visual Geometry Group)

- 심층 합성곱 신경망 아키텍처

- 이미지 분류(classification) 및 인식(recognition)을 위해 개발됨.

- 모든 컨볼루션 레이어와 맥스풀링 레이어에 작은 3x3 필터가 있음.

- 객체 감지(object detection), 세분화(segmentation), 자연어 처리와 같은 다양한 애플리케이션에서 다

른 딥러닝 모델의 빌딩 블록으로 사용됨.

# CIFAR-10 데이터셋 준비하기

import tensorflow as tf

from tensorflow import keras

import tensorflow_datasets as tfds

import urllib3

(ds_train, ds_test), ds_info = tfds.load(

'cifar10',

split = ['train', 'test'],

shuffle_files = True,

with_info = True,

)

def normalize_and_resize_img( image, label ):

return tf.cast( image, tf.float32 ) / 255., label

def apply_normalize_on_dataset( ds, is_test = False, batch_size = 16 ):

ds = ds.map(

normalize_and_resize_img,

num_parallel_calls = 1

)

ds = ds.batch( batch_size )

if not is_test:

ds = ds.repeat()

ds = ds.shuffle(200)

ds = ds.prefetch( tf.data.experimental.AUTOTUNE )

return ds

fig = tfds.show_examples(ds_train, ds_info)

fig = tfds.show_examples(ds_test, ds_info)

# 블록 구성하기

def build_vgg_block( input_layer,

num_cnn = 3,

channel = 64,

block_num = 1,

)

# 입력 레이어

x = input_layer

# CNN 레이어

for cnn_num in range( num_cnn ):

x = keras.layers.Conv2D(

filters = channel,

kernel_size = (3,3),

activation = 'relu',

kernel_initializer = 'he_normal',

padding = 'same',

name = f'block{ block_num }_conv{ cnn_num }'

)(x)

# Max Pooling 레이어

x = keras.layers.MaxPooling2D(

pool_size=(2, 2),

strides=2,

name=f'block{block_num}_pooling'

)(x)

return x

vgg_input_layer = keras.layers.Input( shape=(32,32,3) ) # 입력 레이어 생성

vgg_block_output = build_vgg_block( vgg_input_layer ) # VGG 블록 생성

# 블록 1개짜리 모델 생성

model = keras.Model( inputs = vgg_input_layer, outputs = vgg_block_output )

model.summary()

# VGG-16

def build_vgg( input_shape = (32,32,3),

num_cnn_list = [2,2,3,3,3],

channel_list = [64,128,256,512,512],

num_classes = 10)

# 모델을 만들기 전에 config list들이 같은 길이인지 확인함.

assert len( num_cnn_list ) == len( channel_list )

input_layer = keras.layers.Input( shape=input_shape ) # input layer를 만들어둡니다.

output = input_layer

 

# config list들의 길이만큼 반복해서 블록을 생성함.

for i, (num_cnn, channel) in enumerate( zip( num_cnn_list, channel_list ) ):

output = build_vgg_block(

output,

num_cnn = num_cnn,

channel = channel,

block_num = i

)

 

output = keras.layers.Flatten(name='flatten')(output)

output = keras.layers.Dense(4096, activation='relu', name='fc1')(output)

output = keras.layers.Dense(4096, activation='relu', name='fc2')(output)

output = keras.layers.Dense(num_classes, activation='softmax', name='predictions')(output)

 

model = keras.Model(

inputs = input_layer,

outputs = output

)

return model

vgg_16 = build_vgg()

vgg_19 = build_vgg(

num_cnn_list = [2,2,4,4,4],

channel_list=[64,128,256,512,512]

)

BATCH_SIZE = 256

EPOCH = 15

(ds_train, ds_test), ds_info = tfds.load(

'cifar10',

split=['train', 'test'],

as_supervised=True,

shuffle_files=True,

with_info=True,

)

ds_train = apply_normalize_on_dataset( ds_train, batch_size = BATCH_SIZE )

ds_test = apply_normalize_on_dataset( ds_test, batch_size = BATCH_SIZE )

vgg_16.compile(

loss='sparse_categorical_crossentropy',

optimizer=tf.keras.optimizers.SGD( lr=0.01, clipnorm=1. ),

metrics=['accuracy'],

)

history_16 = vgg_16.fit(

ds_train,

steps_per_epoch = int( ds_info.splits['train'].num_examples / BATCH_SIZE ),

validation_steps = int( ds_info.splits['test'].num_examples / BATCH_SIZE ),

epochs = EPOCH,

validation_data = ds_test,

verbose = 1,

use_multiprocessing = True,

)

vgg_19.compile(

loss = 'sparse_categorical_crossentropy',

optimizer = tf.keras.optimizers.SGD( lr=0.01, clipnorm=1. ),

metrics=['accuracy'],

)

history_19 = vgg_19.fit(

ds_train,

steps_per_epoch=int( ds_info.splits['train'].num_examples/BATCH_SIZE ),

validation_steps=int( ds_info.splits['test'].num_examples/BATCH_SIZE ),

epochs=EPOCH,

validation_data=ds_test,

verbose=1,

use_multiprocessing=True,

)

반응형