22.01.19 CNN(Convolutional Neural Network)

2022. 1. 22. 14:03작업/ComputerVision

퍼셉트론 : Fully Connected Layer

따라서 일반 딥러닝이 아닌 이미지 처리에 특화된 딥러닝 모델이 등장

Convolution 연산

커널=필터 인데 우리는 의미상 커널이라고 표현해본다

2차원 이미지 데이터를 행렬로 표시하고, 커널(필터)도 2차원 행렬로 표현해서 연산. 이미지데이터를 변형 없이 그대로 사용할 수 있다.

 

Convolution 연산과정

이미지에 커널을 겹침. 겹치는 요소를 곱해서 결과에 넣는다. 커널은 이미지 영역 내에만 들어가야 한다.!

결과 3X3 은 겹쳐서 나올 수 있는 경우의 수에 따라 다름

컬러 이미지의 Convolution 연산

input채널의 갯수와 커널의 채널 갯수는 동일하게 해줘야 한다. (Filters always extend the full depth of the input volumne)

 

RGB처럼 채널이 여러개 있으면 커널도 여러개 있어야하고 Feature Map(컨볼루션 결과)도 여러개 나온다

채널(input) + 커널(필터) -> 피쳐맵(Feature Map)

 

 

CNN의 구성요소

# 1. Convolutional Layer

하나의 convolutional layer에는 여러 커널이 있을 수 있따. 저 그림에서도 보면 6개를 쌓아서 6채널 만들고, 그 다음 10개를 쌓아서 10채널 만듦

 

Featuer map  = convolution 결과 = 이미지의 특징을 담고 있다

CNN에서는 저 Feature 맵은 커널을 학습하여 나온 결과이다(저런 색깔, 세로선 등)

커널에 따라 추출하는 Feature를 다르게 학습한다

레이어에 따라 Feature의 양상이 다르다(Low level -> high level)

 

Hyper parameter 사람이 정해줘야 하는 변수

1. Stride 보폭 = 커널이 이미지 내에서 이동하는 칸수

원래는 한칸씩 이동하는데 stride가 2칸이어서 커널이 2칸씩 이동한다 

2. Padding 이미지의 테두리를 감싼다 (padding=1 1칸씩) 3x3 이미지 -> 5x5 

feature map을 3x3으로 할 수 있다.

padding에는 보통 0을 넣어놓는다

 

Convolutional Layer를 왜 쓰는가

FC Layer는 1차원배열01001101011 (우리가 가진 이미지는 2x2) -> 선 하나마다 하나의 가중치

CNN은 커널 사이즈에 의해만 가중치가 결정됨(가중치 개수가 줄어든다-> 과적합 방지에 유리)

곱셈과 덧셈으로만 이루어짐 -> 선형 연산 a1x1+a2x2...=Ax

 

# 2. Pooling layer

방법1 max pooling 각 영역에서 최대값을 뽑아내서 새로운 Feature map 구성

일반적으로 Max Pooling을 많이 쓴다(영향력이 큰 Feature)

 

분류기(Classfier)

CNN은 2차원으로 했는데 왜 다시 1차원(FC)으로 하느냐.. 개,고양이,자동차, 사람.. 구분함

 

대표적인 CNN 모델

Lenet 1990 - 우편번호 인식을 위한 모델

SVM - 고성능컴퓨터

AlexNet 2012 - 유명한 데이터셋 imageNet에서 가장 좋은 성능을 보임(이떄부터 딥러닝으로 시작)

VGGNet 2014

ResNet 2015

Layer가 무작정 많으면 문제가 있을 수도 있다.

해결책 : Residual Connection 우회로 주기 -> 항상 기울기가 1이 넘는다.

(pooling layer는 가중치에 영향X, 두 weight layer가 존재하는데

---> 이 모든 모델들은 일단은 분류모델이다

분류작업이 아닌 경우는 YOLO, R-CNN, (객체 인식)/U-Net(이미지 segmentation) 등의 예시가 있다

분류작업이 아니라면 모델의 출력값, 손실함수, 데이터셋구성 이 완전히 다르게 이루어진다.

 

tensorflow로 conv2d 사용하기

import tensorflow as tf

from tensorflow import keras

 

tf.ones((2,2,1,1)) 2x2x1 필터가 1개

tf.ones((1,3,3,1)) 3x3x1 이미지가 1개

 

output = tf.nn.conv2d(inp, filter, strides,padding = 'VALID') # padding을 'VALID'으로 설정 = 패딩을 하지 않음
print(output)
# [[  [[4.] [4.]]
#     [[4.] [4.]]  ]], shape=(1, 2, 2, 1), dtype=float32)

keras 사용

y = tf.keras.layers.Conv2D( filters = 1, # 필터의 갯수 
                            kernel_size = [2, 2], # "kernel_size = 2" 와 같은 의미 (높이, 너비)
                            strides = (1, 1), 
                            padding = 'same', # keras.layers.Conv2D 의 padding은 소문자 'same', 'valid'
                            activation = 'relu', 
                            input_shape = input_shape[1:]) (x) # 입력 : x
print(y)
# [[ [[0.36910588] [0.36910588] [0.54728895]]
#    [[0.36910588] [0.36910588] [0.54728895]]
#    [[0.8551657 ] [0.8551657 ] [0.6025906 ]] ]], shape=(1, 3, 3, 1), dtype=float32)

실습1

padding, stride와 layer size

import tensorflow as tf
from tensorflow.keras import layers, Sequential

# TODO: [지시사항 1번] 지시사항 대로 Conv2D 하나로 이루어진 모델을 완성하세요 
def build_model1(input_shape):
    model = layers.Conv2D(1, kernel_size=(3,3), strides=(1,1), padding='same',
    # 커널개수, 커널크기, 스트라이드(오른쪽왼쪽), 패딩값 그대로나오게
                          activation="relu", 
                          input_shape=input_shape[1:])
    
    return model

# TODO: [지시사항 2번] 지시사항 대로 Conv2D 두개로 이루어진 모델을 완성하세요 
def build_model2(input_shape):
    model = Sequential([layers.Conv2D(4,kernel_size=(3,3), strides=(1,1), padding='same', input_shape=input_shape[1:]),
                        layers.Conv2D(4,kernel_size=(3,3), strides=(1,1), padding='same')]) # Sequential ([리스트])에 담으면 딥러닝모델로 만들어준다.
    
    return model

# TODO: [지시사항 3번] 지시사항 대로 Conv2D 세개로 이루어진 모델을 완성하세요 
def build_model3(input_shape):
    model = Sequential()
    
    model.add(layers.Conv2D(2,kernel_size=(3,3), strides=(1,1), padding='same', input_shape=input_shape[1:]))
    model.add(layers.Conv2D(4,kernel_size=(3,3), strides=(1,1), padding='same'))
    model.add(layers.Conv2D(8, kernel_size=(3,3), strides=(1,1)))
    
    return model

def main():
    input_shape = (1, 5, 5, 1)
    
    model1 = build_model1(input_shape)
    model2 = build_model2(input_shape)
    model3 = build_model3(input_shape)

    x = tf.ones(input_shape)
    print("model1을 통과한 결과:", model1(x).shape)
    print("model2을 통과한 결과:", model2(x).shape)
    print("model3을 통과한 결과:", model3(x).shape)

if __name__=="__main__":
    main()

실습2 

MLP로 이미지데이터 학습하기

CIFAR-10 데이터셋 사용

from elice_utils import EliceUtils

elice_utils = EliceUtils()

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

import tensorflow as tf
from tensorflow.keras import layers, Sequential, Input
from tensorflow.keras.optimizers import Adam

import numpy as np
import matplotlib.pyplot as plt

SEED = 2021

def load_cifar10_dataset():
    train_X = np.load("./dataset/cifar10_train_X.npy")
    train_y = np.load("./dataset/cifar10_train_y.npy")
    test_X = np.load("./dataset/cifar10_test_X.npy")
    test_y = np.load("./dataset/cifar10_test_y.npy")
    
    train_X, test_X = train_X / 255.0, test_X / 255.0 #이미지픽셀0~255, 각 픽셀을 0~1사이로 정규화
    
    return train_X, train_y, test_X, test_y

def build_mlp_model(img_shape, num_classes=10):
    model = Sequential()

    model.add(Input(shape=img_shape))
    
    # TODO: [지시사항 1번] 모델을 완성하세요.
    model.add(layers.Flatten())
    model.add(layers.Dense(4096, activation='relu'))
    model.add(layers.Dense(1024, activation='relu'))
    model.add(layers.Dense(256, activation='relu'))
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(num_classes, activation='softmax'))  #분류기니까 분류class갯수로 하고
    # 활성화 함수는 확률계산인 softmax로 해주기

    return model
    
def plot_history(hist):
    train_loss = hist.history["loss"]
    train_acc = hist.history["accuracy"]
    valid_loss = hist.history["val_loss"]
    valid_acc = hist.history["val_accuracy"]
    
    fig = plt.figure(figsize=(8, 6))
    plt.plot(train_loss)
    plt.plot(valid_loss)
    plt.title('Loss')
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.legend(['Train', 'Valid'], loc='upper right')
    plt.savefig("loss.png")
    elice_utils.send_image("loss.png")
    
    fig = plt.figure(figsize=(8, 6))
    plt.plot(train_acc)
    plt.plot(valid_acc)
    plt.title('Accuracy')
    plt.xlabel('epoch')
    plt.ylabel('accuracy')
    plt.legend(['Train', 'Valid'], loc='upper left')
    plt.savefig("accuracy.png")
    elice_utils.send_image("accuracy.png")

def main(model=None, epochs=10):
    tf.random.set_seed(SEED)
    np.random.seed(SEED)
    
    train_X, train_y, test_X, test_y = load_cifar10_dataset()
    img_shape = train_X[0].shape
    
    # TODO: [지시사항 2번] Adam optimizer를 설정하세요.
    optimizer = Adam(learning_rate=0.001)

    mlp_model = model
    if model is None:
        mlp_model = build_mlp_model(img_shape)
    
    # TODO: [지시사항 3번] 모델의 optimizer, 손실 함수, 평가 지표를 설정하세요.
    mlp_model.compile(optimizer=optimizer, loss='sparse_categorical_crossentropy', metrics=['accuracy'])
    
    # TODO: [지시사항 4번] 모델 학습을 위한 hyperparameter를 설정하세요.
    hist = mlp_model.fit(train_X, train_y, epochs=epochs, batch_size=64, validation_split=0.2, shuffle=True, verbose=2)
    # validation은 training 데이터 중 20퍼센트를 검증에서 사용하겠다
    # shuffle은 순서를 섞어줘서 trainig하게 된다.
    
    plot_history(hist)
    test_loss, test_acc = mlp_model.evaluate(test_X, test_y)
    print("Test Loss: {:.5f}, Test Accuracy: {:.3f}%".format(test_loss, test_acc * 100))
    
    return optimizer, hist

if __name__ == "__main__":
    main()

실습3

MLP vs CNN 모델 비교

tensorflow에서는 .compile, .fit 등 제공하지만 .summary하면 내 모델잉 ㅓ떻게 바뀌는지

특히 Trainable params 를 알려줘서 실제 모델 학습에 사용되는 파라미터 개수를 알 수 있다.

from elice_utils import EliceUtils

elice_utils = EliceUtils()

import tensorflow as tf
from tensorflow.keras import layers, Sequential, Input
from tensorflow.keras.optimizers import Adam

import numpy as np
import matplotlib.pyplot as plt

SEED = 2021

def load_cifar10_dataset():
    train_X = np.load("./dataset/cifar10_train_X.npy")
    train_y = np.load("./dataset/cifar10_train_y.npy")
    test_X = np.load("./dataset/cifar10_test_X.npy")
    test_y = np.load("./dataset/cifar10_test_y.npy")
    
    train_X, test_X = train_X / 255.0, test_X / 255.0
    
    return train_X, train_y, test_X, test_y
    
def build_mlp_model(img_shape, num_classes=10):
    model = Sequential()

    model.add(Input(shape=img_shape))
    
    # TODO: [지시사항 1번] MLP 모델을 완성하세요.
    model.add(layers.Flatten())
    model.add(layers.Dense(4096, activation='relu'))
    model.add(layers.Dense(1024, activation='relu'))
    model.add(layers.Dense(256, activation='relu'))
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(num_classes, activation='softmax'))

    return model

def build_cnn_model(img_shape, num_classes=10):
    model = Sequential()

    # TODO: [지시사항 2번] CNN 모델을 완성하세요.
    model.add(layers.Conv2D(16, kernel_size=(3,3), padding='same', input_shape=(img_shape), activation='relu')) # strides default는 1,1
    model.add(layers.Conv2D(32, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.MaxPool2D(pool_size=(2,2), strides=(2,2)))
    # model.add(layers.MaxPool2D(2))와도 같은 의미
    # 이미지 사이즈가 2배로 줄도록 설정하세요.
    model.add(layers.Conv2D(64, kernel_size=(3,3), padding='same', strides=(2,2), activation='relu'))
    model.add(layers.Conv2D(64,kernel_size=(3,3), padding='same', strides=(2,2), activation='relu'))
    model.add(layers.MaxPool2D(2))
    model.add(layers.Flatten()) # FC사용하겠다 분류기시작
    model.add(layers.Dense(128,activation='relu'))
    model.add(layers.Dense(num_classes, activation='softmax'))
    return model
    
def plot_history(hist):
    train_loss = hist.history["loss"]
    train_acc = hist.history["accuracy"]
    valid_loss = hist.history["val_loss"]
    valid_acc = hist.history["val_accuracy"]
    
    fig = plt.figure(figsize=(8, 6))
    plt.plot(train_loss)
    plt.plot(valid_loss)
    plt.title('Loss')
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.legend(['Train', 'Valid'], loc='upper right')
    plt.savefig("loss.png")
    elice_utils.send_image("loss.png")
    
    fig = plt.figure(figsize=(8, 6))
    plt.plot(train_acc)
    plt.plot(valid_acc)
    plt.title('Accuracy')
    plt.xlabel('epoch')
    plt.ylabel('accuracy')
    plt.legend(['Train', 'Valid'], loc='upper left')
    plt.savefig("accuracy.png")
    elice_utils.send_image("accuracy.png")
    
def run_model(model, train_X, train_y, test_X, test_y, epochs=10):
    # TODO: [지시사항 3번] Adam optimizer를 설정하세요.
    optimizer = Adam(learning_rate=0.001)
    
    model.summary()
    # TODO: [지시사항 4번] 모델의 optimizer, 손실 함수, 평가 지표를 설정하세요.
    model.compile(optimizer=optimizer, loss='sparse_categorical_crossentropy', metrics=['accuracy'])
    
    # TODO: [지시사항 5번] 모델 학습을 위한 hyperparameter를 설정하세요.
    hist = model.fit(train_X, train_y, epochs=epochs, batch_size=64, validation_split=0.2, shuffle=True, verbose=2)
    
    plot_history(hist)
    test_loss, test_acc = model.evaluate(test_X, test_y)
    print("Test Loss: {:.5f}, Test Accuracy: {:.3f}%".format(test_loss, test_acc * 100))
    
    return optimizer, hist

def main():
    tf.random.set_seed(SEED)
    np.random.seed(SEED)
    
    train_X, train_y, test_X, test_y = load_cifar10_dataset()
    img_shape = train_X[0].shape

    mlp_model = build_mlp_model(img_shape)
    cnn_model = build_cnn_model(img_shape)
    
    print("=" * 30, "MLP 모델", "=" * 30)
    run_model(mlp_model, train_X, train_y, test_X, test_y)
    
    print()
    print("=" * 30, "CNN 모델", "=" * 30)
    run_model(cnn_model, train_X, train_y, test_X, test_y)

if __name__ == "__main__":
    main()

실습4 VGG16 모델 구현 ( 모든 커널 크기를 3x3으로 고정하여 CNN모델의 Layer개수를 늘리기 시작한 모델)

VGGNet은 기존 AlexNet의 Layer 개수보다 두배 이상 늘어난 16개와 19개의 두가지 모델이 있습니다. 이번 실습에서는 이 중에서 16개로 이루어진 VGGNet, 즉 VGG16 모델을 구현하도록 하겠습니다.

 

# layer 갯수 : parameter가 존재하는 층만 세주면 된다 그래서 Flatten이나 Maxpool2D는 포함X

Conv2D 13개, Dense 3개  총 16개 VGG16

import tensorflow as tf
from tensorflow.keras import Sequential, layers

def build_vgg16():
    # Sequential 모델 선언
    model = Sequential()
    
    # TODO: [지시시항 1번] 첫번째 Block을 완성하세요.
    model.add(layers.Conv2D(64,kernel_size=(3,3), padding='same',activation='relu', input_shape=(224, 224, 3)))
    model.add(layers.Conv2D(64, kernel_size=(3,3), padding='same',activation='relu'))
    model.add(layers.MaxPooling2D(2)) #이미지2배로줄임
    
    # TODO: [지시시항 2번] 두번째 Block을 완성하세요.
    model.add(layers.Conv2D(128,kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.Conv2D(128, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.MaxPooling2D(2))
    
    # TODO: [지시시항 3번] 세번째 Block을 완성하세요.
    model.add(layers.Conv2D(256, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.Conv2D(256, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.Conv2D(256, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.MaxPooling2D(2))
    
    # TODO: [지시시항 4번] 네번째 Block을 완성하세요.
    model.add(layers.Conv2D(512, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.Conv2D(512, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.Conv2D(512, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.MaxPooling2D(2))
    
    # TODO: [지시시항 5번] 다섯번째 Block을 완성하세요.
    model.add(layers.Conv2D(512, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.Conv2D(512, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.Conv2D(512, kernel_size=(3,3), padding='same', activation='relu'))
    model.add(layers.MaxPooling2D(2))
    
    # Fully Connected Layer
    model.add(layers.Flatten())
    model.add(layers.Dense(4096, activation="relu"))
    model.add(layers.Dense(4096, activation="relu"))
    model.add(layers.Dense(1000, activation="softmax"))
    
    return model

def main():
    model = build_vgg16()
    model.summary()
    
if __name__ == "__main__":
    main()

실습5 ResNet 구현

Residual Connection(우회로 ) 생기는 Net,

레이어 개수가 매우 많은 경우에 발생할 수 있는 기울기 소실(Vanishing Gradient) 문제를 해결하고자 등장

import tensorflow as tf
from tensorflow.keras import layers, Model, Sequential

class ResidualBlock(Model):
    def __init__(self, num_kernels, kernel_size):
        super(ResidualBlock, self).__init__()

        # TODO: [지시사항 1번] 2개의 Conv2D Layer를 지시사항에 따라 추가하세요.
        self.conv1 = layers.Conv2D(num_kernels, kernel_size=kernel_size, padding='same', activation='relu')
        self.conv2 = layers.Conv2D(num_kernels, kernel_size=kernel_size, padding='same', activation='relu')
        
        self.relu = layers.Activation("relu")
        
        # TODO: [지시사항 1번] Add Layer를 추가하세요.
        # 텐서 두개 Add([x,y])
        self.add = layers.Add()

    def call(self, input_tensor):
        x = self.conv1(input_tensor)
        x = self.conv2(x)

        x = self.add([x, input_tensor]) # 두 값을 더하는 과정 F(x) + x
        x = self.relu(x)
        
        return x
        
def build_resnet(input_shape, num_classes):
    model = Sequential()
    
    model.add(layers.Conv2D(64, kernel_size=(3, 3), padding="same", activation="relu", input_shape=input_shape))
    model.add(layers.MaxPool2D(2))
    
    model.add(ResidualBlock(64, (3, 3)))
    model.add(ResidualBlock(64, (3, 3)))
    model.add(ResidualBlock(64, (3, 3)))
    
    model.add(layers.GlobalAveragePooling2D())
    model.add(layers.Dense(num_classes, activation="softmax"))
    
    return model
    
def main():
    input_shape = (32, 32, 3)
    num_classes = 10

    model = build_resnet(input_shape, num_classes)
    model.summary()

if __name__=="__main__":
    main()