[AI] ResNet -50Layer을 통한 반려묘 안구 질환 진단

DongHyeon·2023년 11월 4일
0

앞서 설명한 질병 A.각막궤양을 ResNet 50-Layer를 통해 학습, 모델을 작성하고, 해당 정확도를 분석

ResNet -50Layer

ResNet 50-Layer는 50개 층을 이루는 ResNet으로 기존의 모델들은 모델의 깊이가 깊어질수록 오히려 성능이 떨어지는 결과를 가져온다고 밝혀냈다.
gradient vanishing 은 오버피팅과는 조금 다른 느낌으로, Layer의 깊이가 깊어질수록 미분을 점점 더 많이 하기 때문에, 그만큼 미분값이 작아져, output에 영향을 끼치는 weight가 작아진다는 개념이다.

Input image

  • size: (224,224)
  • 구조

    ResNet은 Identity Block과 Convolution Block을 적용한 네트워크의 Layer가 깊어질수록 성능이 좋아지는지 확인하기 위해 많은 Layer단계(18,34,50,101,152Layer)를 구분하여 적용 - 50Layer만 서술할 예정

Identity Block

ResNet의 Identity Block으로 입력값 xx는 weight layer를 통과하여 나오는 F(x)F(x)와 해당 출력값과 최소 Gradient로 1은 갖도록 하기 위해 입력값 xx를 더하는 구조를 가지고 있다. 이 입력값에 F(x)F(x)를 더해주기 위해 생겨난 추가적인 길을 지름길(Shortcut)을 추가하고, 최종 Residual Block 결과값 H(x)=F(x)+xH(x)=F(x)+x 형태로 Residual Block을 형성한다.

Convolution Block

ResNet의 Convolution Block은 Input값이
Convolution -> BatchNormalization -> Activation -> Convolution -> BatchNormalization -> Activation -> Convolution -> BatchNormalization -> Add -> Activation 순으로 진행될 때 이미지의 크기나, 필터의 개수가 일치하지 않아 발생하는 문제를 해결하기 위해 등장한 블록으로, 현재의 shortcut을 하게 되면 필터를 통과하지 않은 input Feature map의 channel은 64이지만, 필터를 통과한 이후는 256의 channel을 가지는 경우가 있다.

해당 사진은 ResNet 50 Layer의 Convolution Block을 가져온 사진이다, 18Layer모델과 달리 세 가지 블록을 통과하지만, 여기서 주목할 것은 shortcut 즉 다른 채널을 갖는 input값과 output값을 더하기 위해, Convolution을 진행하고, BatchNomalization을 시행하는 것까지 수행한 뒤에 합하는 형태를 가지고 있다.

구현

1. 라이브러리 선언

from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input,GlobalAveragePooling2D, Conv2D, BatchNormalization, Activation, Add, MaxPooling2D, AveragePooling2D, Flatten, Dense, ZeroPadding2D
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import os
import cv2
import time
import math
from tensorflow.keras.optimizers import Adam,SGD
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential
from sklearn.model_selection import train_test_split
import tensorflow as tf
import tensorflow.keras.backend as K
from tensorflow.keras import layers
import random
from keras.preprocessing.image import ImageDataGenerator

사용 툴은 Tensorflow,Keras이다.
그 이외에, tensorflow는 numpy와 연계되는 점에서 라이브러리를 호출하고, 그래프를 그리기 위한 matplotlib, 이미지 연산을 위한 cv2 등을 호출한다.

2. 시드 고정

os.environ['PYTHONHASHSEED']='1'
os.environ['TF_DETERMINISTIC_OPS']='1'
np.random.seed(5148)
random.seed(5148)
tf.random.set_seed(5148)

환경은 Google Colab에서 시행하기 때문에, numpy나 random, tensorflow의 랜덤 변수 이외에도 environ환경에 변수를 지정해 줄 필요가 있다.

3. GPU사용

#%% GPU 할당
# GPU를 사용할 수 있는지 확인
gpus = tf.config.list_physical_devices('GPU')
if gpus:
  try:
    tf.config.set_logical_device_configuration(
        gpus[0],
        [tf.config.LogicalDeviceConfiguration(memory_limit=8*1024)])
  except RuntimeError as e:
    # 프로그램 시작시에 가상 장치가 설정되어야만 합니다
    print(e)

학습에 GPU를 사용하기 위해 장치의 메모리를 확인하고, 메모리의 한계를 지정하고 학습을 시작한다.

4. 구글 Colab환경 드라이브 Mount

# 구글 Colab에서 Drive 마운트 (파일을 가져오기 위함)
from google.colab import drive    # google drive mount
drive.mount('/content/drive')

드라이브를 마운트시켜 Notebook환경에서 Drive 파일에 접속할 수 있도록 함.

5. ResNet 구조 설계

해당 표를 참고하여 ResNet 50 Layer모델을 설계해 보자

5-1. Conv1

def conv1_layer(x):
  x=ZeroPadding2D(padding=(3, 3))(x)
  x=Conv2D(64,(7,7),strides=(2,2))(x)
  x=BatchNormalization()(x)
  x=Activation('relu')(x)
  x=ZeroPadding2D(padding=(1,1))(x)
  return x

해당 코드는 18-Layer모델과는 다르게 작성이 되어있는데, 해당 방식은 Convolution을 진행할 때, padding="same" 선언 없이, Zero padding을 수동으로 추가하여, Convolution을 진행한다.
18Layer모델과 동일하게, Convolution, BatchNormalization, Activation순으로 진행한다.

5-2. Conv2

def conv2_layer(x):
    x=MaxPooling2D((3, 3), 2)(x)
    shortcut=x # input 값을 shortcut에 저장
    for i in range(3):
        if (i == 0):
            x=Conv2D(64, (1, 1), strides=(1, 1), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(64, (3, 3), strides=(1, 1), padding='same')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(256, (1, 1), strides=(1, 1), padding='valid')(x)
            shortcut=Conv2D(256, (1, 1), strides=(1, 1), padding='valid')(shortcut)
            x=BatchNormalization()(x)
            shortcut=BatchNormalization()(shortcut)
            x=Add()([x, shortcut])
            x=Activation('relu')(x)
            shortcut=x
        else:
            x=Conv2D(64, (1, 1), strides=(1, 1), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(64, (3, 3), strides=(1, 1), padding='same')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(256, (1, 1), strides=(1, 1), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Add()([x, shortcut])
            x=Activation('relu')(x)
            shortcut=x
    return x

코드를 보게 되면, 입력 값을 shortcut으로 받고, 이후 Conv2D(64,(1,1),strides=1) -> BatchNormalization -> Activation -> Conv2D(64,(3,3),strides=1) -> BatchNormalization -> Activation -> Conv2D(256,(1,1),strides=1) -> BatchNormalization -> Add -> Activation 을 3번 반복하는 모습을 볼 수 있다.
이 때, shortcut은 첫 시행때만 Convolution Block형식을 적용하고, 이후에는 이미 256에 해당하는 shortcut을 얻었기 때문에, 이를 바로 더해주는 Identity Block방식으로 사용한다.

5-3. Conv3

def conv3_layer(x):
    shortcut = x
    for i in range(4):
        if(i == 0):
            x = Conv2D(128, (1, 1), strides=(2, 2), padding='valid')(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)
            x = Conv2D(128, (3, 3), strides=(1, 1), padding='same')(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)
            x = Conv2D(512, (1, 1), strides=(1, 1), padding='valid')(x)
            shortcut = Conv2D(512, (1, 1), strides=(2, 2), padding='valid')(shortcut)
            x = BatchNormalization()(x)
            shortcut = BatchNormalization()(shortcut)
            x = Add()([x, shortcut])
            x = Activation('relu')(x)
            shortcut = x
        else:
            x = Conv2D(128, (1, 1), strides=(1, 1), padding='valid')(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)
            x = Conv2D(128, (3, 3), strides=(1, 1), padding='same')(x)
            x = BatchNormalization()(x)
            x = Activation('relu')(x)
            x = Conv2D(512, (1, 1), strides=(1, 1), padding='valid')(x)
            x = BatchNormalization()(x)
            x = Add()([x, shortcut])
            x = Activation('relu')(x)
            shortcut = x
    return x

이후 방식은 거의 동일하게, 시행횟수와 필터만을 바꾸어서 설계하였다.

5-4. Conv4

def conv4_layer(x):
    shortcut=x
    for i in range(6):
        if(i == 0):
            x=Conv2D(256, (1, 1), strides=(2, 2), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(256, (3, 3), strides=(1, 1), padding='same')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(1024, (1, 1), strides=(1, 1), padding='valid')(x)
            shortcut=Conv2D(1024, (1, 1), strides=(2, 2), padding='valid')(shortcut)
            x=BatchNormalization()(x)
            shortcut=BatchNormalization()(shortcut)
            x=Add()([x, shortcut])
            x=Activation('relu')(x)
            shortcut=x
        else:
            x=Conv2D(256, (1, 1), strides=(1, 1), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(256, (3, 3), strides=(1, 1), padding='same')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(1024, (1, 1), strides=(1, 1), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Add()([x, shortcut])
            x=Activation('relu')(x)
            shortcut=x
    return x

5-5. Conv5

def conv5_layer(x):
    shortcut = x
    for i in range(3):
        if(i == 0):
            x=Conv2D(512, (1, 1), strides=(2, 2), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(512, (3, 3), strides=(1, 1), padding='same')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(2048, (1, 1), strides=(1, 1), padding='valid')(x)
            shortcut=Conv2D(2048, (1, 1), strides=(2, 2), padding='valid')(shortcut)
            x=BatchNormalization()(x)
            shortcut=BatchNormalization()(shortcut)
            x=Add()([x, shortcut])
            x=Activation('relu')(x)
            shortcut=x
        else:
            x=Conv2D(512, (1, 1), strides=(1, 1), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(512, (3, 3), strides=(1, 1), padding='same')(x)
            x=BatchNormalization()(x)
            x=Activation('relu')(x)
            x=Conv2D(2048, (1, 1), strides=(1, 1), padding='valid')(x)
            x=BatchNormalization()(x)
            x=Add()([x, shortcut])
            x=Activation('relu')(x)
            shortcut=x
    return x

5-6 ResNet-50Layer

def resnet50(input_shape,num_classes):
  inputs=Input(shape=input_shape)
  x = conv1_layer(inputs)
  x = conv2_layer(x)
  x = conv3_layer(x)
  x = conv4_layer(x)
  x = conv5_layer(x)
  x = GlobalAveragePooling2D()(x)
  x = Dense(num_classes, activation='softmax')(x)
  model=Model(inputs=inputs,outputs=x)
  return model

해당 레이어들을 모두 통과시킨 후 AveragePooling, softmax를 통한 Fully Connect를 진행하면 모델이 나오게 된다.
이후 모든 과정은 ResNet-18Layer모델과 동일하게 진행되므로 생략

결과

위와 같은 특성곡선이 나타나게 되었고, 거의 50 에폭수준부터 98~99%의 정확도에 수렴하는 현상을 보이고 있다.
하지만, 중간 중간에 정확도가 내려가는 현상을 보이고 있다.
Test Data Accuracy는 각막궤양의 경우는 90.08%, 각막 부골편의 경우는 95.67%까지 나타나 해당 모델을 사용하였다.

개선할 점

ResNet-18Layer모델을 사용하여 작성을 하였을 때보다 다소 정확도가 올라간 모습을 볼 수 있다.
너무 깊은 Layer층을 두는 것이 오히려 성능이 하락한다는 ResNet의 연구팀의 이론 및 시각적으로 확인할 수 있을만한 차이에는 깊은 레이어는 필요하지 않다는 주장 등 여러 이야기를 종합하여, ResNet 18Layer에서 50Layer모델로 증진시켜 보았다.
이를 통해 확인할 수 있는 부분은 50Layer모델이 18Layer모델에 비해 조금 더 해당 이미지 처리에 적합한 모델임을 알고, 이와 비슷한 혹은 조금 더 깊거나 효율적인 Network를 찾아가야 할 것이다.

Git:
https://github.com/Yeon1A/KWHackathon/blob/main/ResNet50_disease_a.ipynb

profile
I'm Free!

0개의 댓글