티스토리 뷰

반응형

0. 들어가면서(VGG16 모델)

  • backbone으로 가장 많이 사용된다.
  • 2014년에 ILSVRC에서 2등 한 모델이다. 1등은 구글에서 했지만, 더 많이 사용한다.
  • 특이한 것이 없는 일반적인 CNN이다.
  • CNN과 동일하게 convolution layer와 max pooling later가 있다.
  • 21개의 layer를 갖고 weight 는 1억 4천 개
  • pooling 레이어를 제외하고 모두 16개의 레이어가 있다.

1. 코드

1-1. 데이터 받아 저장하기

기본적으로 colab을 사용한다.

!rm -rf imagenet
!mkdir imagenet

# 버섯
!wget -O imagenet/mushroom1.jpg http://farm4.static.flickr.com/3023/2822584107_186167ff68.jpg
!wget -O imagenet/mushroom2.jpg http://farm3.static.flickr.com/2416/1593642808_efcef6c9c2.jpg
!wget -O imagenet/mushroom3.jpg http://farm4.static.flickr.com/3003/2536991564_5f9b2f5b53.jpg
  
# 강아지
!wget -O imagenet/dog1.jpg http://farm1.static.flickr.com/58/160964915_d708f48d0d.jpg
!wget -O imagenet/dog2.jpg http://farm1.static.flickr.com/51/144906086_049df05364.jpg
!wget -O imagenet/dog3.jpg http://farm3.static.flickr.com/2133/2236535445_ca650757f2.jpg
  
# 고양이  
!wget -O imagenet/cat1.jpg http://farm1.static.flickr.com/131/393656824_bd89c512d0.jpg
!wget -O imagenet/cat2.jpg http://farm1.static.flickr.com/213/505539125_d7193beb76.jpg
!wget -O imagenet/cat3.jpg http://farm1.static.flickr.com/24/63785988_c16c10b4e5.jpg
  
  
!ls -al imagenet  

위의 코드는 파이썬이 아니라 리눅스 코드다. 리눅스의 셀 명령어로 나타내기 위해 앞에! 를 붙인 거다.

  • ! rm -rf imagenet : 파일을 지워
  • ! mkdir imagenet : 파일을 만들어
  • ! wget -O imagenet/mushroom1.jpg URL   :   wget은 파일을 인터넷인 http로 가져와서 결과물을 imagenet이라는 파일 안에 mushroom1.jpg로 저장하라는 뜻이다.
  • ! ls -al imagenet  : 파일의 리스트를 보여달라는 거다. 다저장이 됐으면 9개의 파일이 보여야 한다.

 

1-2. 모델 불러오기

from keras.applications import vgg16

# VGG16 모델 불러오기
model = vgg16.VGG16()

# 모델의 모양을 보여준다.
model.summary()

# 테스트 할 이미지 파일들
files = [
    'imagenet/mushroom1.jpg',
     'imagenet/mushroom2.jpg',
     'imagenet/mushroom3.jpg',
     'imagenet/dog1.jpg',
     'imagenet/dog2.jpg',
     'imagenet/dog3.jpg',
     'imagenet/cat1.jpg',
     'imagenet/cat2.jpg',
     'imagenet/cat3.jpg',
    ]

# 분류 실행
for file in files :
  predict_vgg16(model, file)  
  • from keras.applications import vgg16
    • keras의 applications에서 vgg16를 사용하겠다는 것이다. colab은 기본적으로 tensor flow와 keras가 설치되어 있다.
  • vgg16.VGG16()
    • vgg16에서 VGG16을 호출해서 모델을 바로 생성했다. 이걸 실행하는 순간 아래처럼 모델 파일이 다운로드하여진다.

  • model.summary
    • 이 부분은 실행과 전혀 관계가 없지만 모델의 모양을 보는 것이다. 아래 그림을 참고해보자.

 

layer의 input_1을 보면 (None, 224, 224, 3)이라고 볼 수 있다. 실제 데이터의 모양(shape)은 224*224*3이라는 것을 알 수있다. 추후 이미지를 모델에 넣을 때 224, 224, 3으로 reshape 해서 넣어야 한다. 아래 코드에서 나온다.

 

None라는 것은 보통 배치라고 한다. cpu와 gpu사이에서 주고받는 게 많으면 병목이 일어난다. 쉽게 말해서 데이터를 몇 개를 동시에 보내냐라는 것을 적은 배치의 개수이다. 사실 VGG를 이해하는데 크게 중요하지 않다.

 

convolution layer와 max pooling later를 반복하는 것을 볼 수 있다.

 

 

 

 

 

 

 

 

 

 

쭉 내려가다 flatten 시킨 것을 알 수 있다. 이 부분은 한 줄로 세운 부분이다. 그리고 Dense이 후에 제일 마지막에 predictions를 보면 1000개인 것을 알 수있다. output의 개수가 class의 개수이다. 쉽게 말하면, 분류하는 종류의 개수가 1000개라는 말이다.

 

 

 

 

  • for문은 각각의 파일을 하나씩 모델에 넣는 거다.
  • predict_vgg16(model, file) 이 부분은 다음 나오는 predict_vgg16이라는 함수에 model과 file을 넘겨서 분류해달라고 선언하는 거다. 사실 다음 나오는 코드를 먼저 실행하고 위의 코드를 실행해야 하지만, 설명의 편의를 위해 위의 코드를 먼저 적었다.

1-3. 모델 실행하기

from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array
from keras.applications import vgg16
from IPython.display import display # 이미지 출력 함수
import numpy as np



def predict_vgg16(model, filename) :

  # 이미지 파일을 읽고 화면에 표시
  image = load_img(filename)
  # image = PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=688x550
  display(image)

  
  # 모델 사이즈로 이미지 파일을 읽기
  image = load_img(filename, target_size=(224, 224))
  # image = PIL.Image.Image image mode=RGB size=224x224

  
  # 이미지 데이터를 numpy로 변환
  image = img_to_array(image)
  # [
  #   [[211. 184. 163.]
  #   [225. 193. 170.]
  #   ...
  #   [237. 202. 180.]]
  #   ...
  # ]  
  #
  # image.shape = (224, 224, 3)

  # vgg16.preprocess_input()을 호출하기 위해 차원을 조정
  # 보통 모델을 여러 이미지를 한번에 호출. 
  # 맨 앞의 1 : 이미지 갯수가 1개라는 것.
  # 두번째 224 : 가로
  # 세번째 224 : 세로
  # 네번째 3 : R, G, B 3개
  image = image.reshape((1, 224, 224, 3))

  # VGG16 모델 호출을 위해 데이터 전처리.
  # -255 ~ 255 사이 값으로 정규화한다.
  # 그리고 RGB를 BGR순으로 바꾼다.
  image = vgg16.preprocess_input(image)
  
  
  # 이미지를 모델에 적용
  yhat = model.predict(image)
  # yhat = [[2.03485320e-06 4.21382174e-06 1.45730738e-07 1.04057870e-06
  #          6.61934010e-08 2.63145339e-04 4.49358195e-05 2.03222541e-08
  #          ... ]] # 1000개 클래스에 대한 결과값.
  #
    
    
  # 모델 적용된 결과를 파싱
  label = vgg16.decode_predictions(yhat)
  # label = [[('n02655020', 'puffer', 0.9612253), ... ]]

  # 가장 확률이 높은 결과를 획득
  label = label[0][0]
  # label = ('n02655020', 'puffer', 0.9612253)

  # 라벨과 라벨을 예측한 확률을 출력
  print('%s (%.2f%%)' % (label[1], label[2]*100))    

    

    

 

함수 안의 처음 두줄은 사실 관련이 없다. 단지 내가 분류하는 이미지가 무엇인지 먼저 이미지를 띄워주는 것이다. load_img는 이미지를 로드하는 것이고 display는 보여주는 것이다.

  • image = load_img(filename, target_size=(224, 224))
    • filename을 로드해 준다. 
    • 보통을 파일을 로드해서 resize를 할 수 있다. 그러나 여기서는 처음 로드할 때부터 size를 바꿔서 로드한 결과를 바로 image에 담는 것이다.
  • image = img_to_array(image)
    • image는 객체로 다양한 정보를 가지고 있다. 그런데 우리가 분석을 위해서는 단지 숫자로 이루어진 array 만 있으면 되기 때문에 객체를 array로 변환해주는 함수다. 
    • load_img와 img_to_array 함수는 keras.preprocessing.image안에 있다.
    • 여기서 print를 해서 image에 들어간 것을 보면 3차원으로 들어간 것을 확인할 수 있다. [[[왼쪽 가장 위 픽셀][가장 위 왼쪽에서 두 번째 픽셀]...][[위에서 두번쨰 왼쪽에서 첫 번째 픽셀]...]...]]]
    • image.shape를 출력해보면 (224, 224, 3)이라고 출력될 것이다. 딥러딩에서 가장 신경 써야 할 부분 중 하나는 입력에 들어가야 할 데이터 size와 실제 자신의 데이터 사이즈가 다르면 Error 뜬다.

+ 하나씩 확인을 하기 위해서는 predict_vgg16 함수에 print(image)와 print(image.shape)를 추가시킨 후에 predict_vgg16(model, 'imagenet/파일 이름. jpg')를 실행시켜 확인을 해보면 된다.

 

 

대략 아래와 같이 나온다.

<PIL.Image.Image image mode=RGB size=224x224 at 0x7F04A80A5198>
[[[255. 255. 255.]   <=== 가장 왼쪽 윗부분은 rgb가 255, 255, 255인 것을 보니 흰색인걸 알수있다.
  [255. 255. 255.]
  [255. 255. 255.]
  ...
  [254. 254. 254.]
  [255. 255. 255.]
  [255. 255. 255.]]

 [[255. 255. 255.]   <=== 위에서 두번째 가장 왼쪽 픽셀이겠군
  [255. 255. 255.]
  [255. 255. 255.]
  ...
  [255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]]

 [[255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]
  ...
  [255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]]

 ...

 [[255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]
  ...
  [255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]]

 [[255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]
  ...
  [255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]]

 [[255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]
  ...
  [255. 255. 255.]
  [255. 255. 255.]
  [255. 255. 255.]]]
(224, 224, 3)             <======== shape이다

 

  

  •   image = image.reshape((12242243))
    • 이 부분에서는 image가 현재 (224, 224, 3)인 부분을 실제 입력값에 맞게 모델에서 None부분을 만들어 주기 위해 reshape 하는 부분이라고 생각하면 된다.(vgg16.preprocess_input()을 호출하기 위해 차원을 조정)
    • 맨 앞의 1은 배치 부분으로 이미지 개수가 1개라는 뜻이다.
    • 두 번째 224는 가로를 의미한다
    • 세 번째 224는 세로를 의미한다
    • 네 번째 3은 R, G, B 3개를 의미한다.

 

  •   image = vgg16.preprocess_input(image)
    • 쉽게 말해 모델 호출을 위한 데이터 전 처리하고 생각하면 된다.
    • -255에서 255 사이 값으로 정규화를 하고 RGB를 BGR 순서로 바꾼다.
    • 여기까지 하면 이미지 로딩 후 모양을 맞춰졌고 preprocess가 끝난 것이 image에 들어간다.(모델에 들어갈 준비 끝)

 

  •   yhat = model.predict(image)
    • 이미지를 모델에 적용한 것이다. 
    • 위에서 적용한 vgg16 모델에 predict를 image 데이터로 하라는 말이다.
    • 그 결과가 yhat이고 yhat을 찍어보면 아래와 같은 결과가 나온다.

         # yhat = [[2.03485320 e-06 4.21382174 e-06 1.45730738 e-07 1.04057870 e-06

         #          6.61934010 e-08 2.63145339 e-04 4.49358195 e-05 2.03222541 e-08

         #... ]] # 1000개 클래스에 대한 결괏값으로 대부분 값이 매우 작고 2~3개 정도만 숫자가 좀 높다

 

  •   label = vgg16.decode_predictions(yhat)
    • 적용된 결과를 파싱 한다. 즉 디코딩한다
    • 아래의 n02655020은 이미지 넷에서 정의한 클래스의 코드(class ID)가 된다.
    • ('n02655020', 'puffer', 0.9612253)의 뜻은, n02655020은 어딘가에 puffer라고 정의돼 있고 확률은 0.9612253이다. 그리고 확률이 높은 순서로 sorting이 되어있다.

          # label = [[('n02655020', 'puffer', 0.9612253),... ]]

 

  •   label = label [0][0] 
    • 확률이 가장 높은 것을 획득하려면, 첫 번째를 고르면 된다.(sorting 되어있기 때문이다.)

          # label = ('n02655020', 'puffer', 0.9612253)

 

  •   print('% s % (label [1], label [2]*100))   
    • 출력하고자 하는 것이 라벨과 라벨에 대한 확률이므로 인덱스 1과 인덱스 2에 100을 곱해서 출력해준다.

 

여기까지가 분류이다.

 

 

2. 커스텀 데이터(내가 만든 데이터)로 학습하자

이미지 데이터를 tar로 바꾸자. tar로 바꾸는 방법은 아래의 방식을 참고하자.아래의 방법은 아나콘다에서 파일 위치로 들어가서 바꾸는 방법이고

# 일반 폴더나 파일을 tar 타입으로 압축할 경우
> tar -cvf 생성될파일명 압축할대상


# tar 타입을 압축 해제할 경우
> tar -xvf 압축을해제할파일명

colab에서 바로 압축을 풀려면 아래와 같은 코드를 치면 된다.! ls -al은 파일 내용을 보겠다는 말이다.

# 기존 폴더 있으면 삭제
!rm -rf dental_image

# 압축 파일을 풀기
!tar xvfz dental_image.tar.gz

!ls -al

 

tar파일을 만들었으면 colab에 아래와 같이 친 후에 파일을 넣자. 시간은 데이터 양에 따라 좀 걸릴 수 있다.

from google.colab import files

# 파일이름 dental_image.tar.gz
uploaded = files.upload()

!ls -al 

 

2.0 데이터 준비하기

일단 예시로 tar로 압축한 파일의 폴터 구조는

dental_image - test   - cured(치료됨)    - 실제 관련 사진이 들어가 있다

                  |         |- decayed(충치)   - 사진들

                  |         - healthy(건강)    - 사진들

                  - train - cured(치료됨)    - 사진들

                           |- decayed(충치)    - 사진들

                           - healthy(건강)     -  사진들

이와 같다고 해보자. 이제 우리는 아래와 같이 keras에 있는 ImageDataGenerator를 사용할 것이다.

from keras.preprocessing.image import ImageDataGenerator

 사용하는 이유는 뭘까? 사실 CNN에서 학습을 할 때, 고양이를 학습시키면 고양이라는 label을 image와 같이 줘야 하고, 개를 학습할 때도 image와 함께 label을 같이 줘야 한다. 이때, 별도의 파일을 안 만들고, 위처럼 폴더의 이름 자체를 label 하는 방법을 이제부터 할 예정이다. 즉 train안에 폴더 3개를 보고 각각 분류가 됐다는 걸 알 수 있다. label의 정보를 주는 방법 중 하나라고 생각하면 될 것이다. 이 폴더를 학습을 시키면 labeling 없이 학습이 된다.

다시 정리하면, 이러한 방식으로 진행하기 위해선 기본적으로 폴더 형식을 맞춰야 한다. root 폴더의 이름은 아무것이나 상관없고, 그 안의 폴더는 test와 train으로 2개가 꼭 있어야 한다. test와 train 폴더 안에는 class의 숫자만큼 폴더가 있고, 그 폴더 안에는 관련된 image가 있으면 된다. 이제 이걸 하기 위한 코드를 보자.

 

2.1 커스텀 데이터 분류하기

일단 우리는 vgg16을 다운로드하여서 하고 있다. vgg16은 1000개의 class에 대해 학습이 돼있다. 근데 지금 학습하려고 하는 부분은 1000개 안에 있는 부분이 아닌 전혀 다른 class다. 따라서 실행시켜도 당연히 제대로 안된다. 실행되나 안 되나 위에서 압축 푼 파일 중 아무것이나 실험을 해보자. 관련 예시 코드는 아래와 같다.

from keras.applications import vgg16

# VGG16 모델을 불러오기
model = vgg16.VGG16()

# 모델의 모양을 보여준다.
model.summary()
  

files = [
    'dental_image/test/healthy/1.jpg',
    'dental_image/test/decayed/101.jpg',
    'dental_image/test/cured/301.jpg'
        ]

  
for file in files :
  predict_vgg16(model, file)   

 내가 넣은 데이터 이미지는 치아인데, 실행해보니, 만두 해파리 소라 같은 이런 것들로 검색되어 나온다. 당연하게 우리가 하고 싶은 데이터는 class에 없으니 안 맞는다. 따라서 우리 데이터로 다시 학습시켜보자! 지금부터 하는 것들은 우리의 이미지 데이터를 실제로 훈련을 시킬 것이다. 그런데 데이터가 매우 작다. 이런 경우 보통 데이터 증강이라는 것을 사용한다.(동일한 이미지의 위치를 바꾸거나 대칭시켜 학습시키는 법)

 

커스텀으로 새로 학습 시 생각해야 할 점을 알기 위해서 기존의 VGG 모습부터 알아야 한다. VGG16은 입력 데이터는 244(가로)* 244(세로)* 3(RGB)이다. 이 부분은 우리가 쓸 데이터를 reshape 하면 되기 때문에 큰 문제가 되지 않지만, 출력 값의 노드 개수가 우리가 정한 거너 3개지만, VGG는 1000개의 class를 가 있기 때문에 이 부분을 바꿔줘야 한다.

 

데이터 전처리

VGG16의 입력은 224x224의 크기로 고정이 되어 있기 떄문에 이미지 파일을 로딩 후에 이러한 사이즈로 크기 조절이 필요하다.

 

Labeling Data

Keras의 ImageDataGenerator를 사용하면 폴더 이름을 레이블링으로 사용한다. 위에서 말한 것 처럼 폴더가 class가 된다.

 

데이터 증강

Keras의 ImageDataGenerator에서 제공하는 기능이다. 데이터 부족을 극복하기 위해 위치 이동, 회전, 좌우반전등의 변화를 준다. 흑백이나 축소확대를 이용하기도 한다. 

 

2.2 데이터 불러오기

이제 코드를 보자.

from keras.preprocessing.image import ImageDataGenerator

train_dir = 'dental_image/train'
validation_dir = 'dental_image/test'
batch_size = 32
image_size = 224

# 학습에 사용될 이미지 데이터 생성기
train_datagen = ImageDataGenerator(
      rotation_range=180, # 회전 쵀대 20도
      width_shift_range=0.2, # 좌우 이동
      height_shift_range=0.2, # 상하 이동
      horizontal_flip=True, # 좌우 반전
      vertical_flip=True, # 상하 반전
      )
 
# 검증에 사용될 이미지 데이터 생성기
validation_datagen = ImageDataGenerator()
 

# 학습에 사용될 데이터 생성기  
train_generator = train_datagen.flow_from_directory(
        train_dir,
        target_size=(image_size, image_size),
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=True)

# 검증에 사용될 데이터 생성기
validation_generator = validation_datagen.flow_from_directory(
        validation_dir,
        target_size=(image_size, image_size),
        batch_size=batch_size,
        class_mode='categorical',
        shuffle=False)

class_num=len(train_generator.class_indices)

custom_labels = list(validation_generator.class_indices.keys())

from keras.preprocessing.image import ImageDataGenerator

> imagedatagenerator를 불오온것이다.

 

train_dir = 'dental_image/train' # train 데이터 위치 지정

validation_dir = 'dental_image/test' # test 데이터 위치 지정

batch_size = 32

image_size = 224

 

# 학습(train)에 사용될 이미지 데이터를 증강하는 부분

train_datagen = ImageDataGenerator(     # ImageDataGenerator를 이용해서 train_datagen을 만듬

      rotation_range=180# 회전 최대 1800도

      width_shift_range=0.2# 좌우로 얼마나 이동할 것인가

      height_shift_range=0.2# 상하로 얼마나 이동할 것인가

      horizontal_flip=True# 좌우 반전

      vertical_flip=True# 상하 반전

      )

 

# 검증에 사용될 이미지 데이터 생성기

# 학습하면서 주기적으로 테스트 해서 얼마나 되는가 확인을 해주는 부분이다. 그리고 당연히 바로위 train증강 처럼 테스트 이미지는 증강을 할 필요가 없다.

validation_datagen = ImageDataGenerator()

 

 

# 학습에 사용될 데이터 생성기  

train_generator = train_datagen.flow_from_directory(  # 디렉토리에서 가져온 데이터를 flow 시키는 것이다.

        train_dir,          # 위에서 설정한 결로

        target_size=(image_size, image_size),     # 224x224를 나타낸 것이다.

        batch_size=batch_size,                       # 배치 size는 한번에 gpu를 몇개 보내는가

        class_mode='categorical',                    # class를 어떻게 읽냐 설정. categorical이라고 명시를 해줘야 위에서 설정한 것처럼 파일 디렉토리로 class가 나눠진다.

        shuffle=True)                                   # 말그데로 섞는다는 말이고 순서를 무작위로 적용한다는 뜻이다.

 

# 검증에 사용될 데이터 생성기(바로 위와 동일하다.)

validation_generator = validation_datagen.flow_from_directory(

        validation_dir,

        target_size=(image_size, image_size),

        batch_size=batch_size,

        class_mode='categorical',

        shuffle=False)

 

class_num=len(train_generator.class_indices)  # 클래스 개수가 인덱스 0, 1, 2로 세개가 나와야한다.

 

custom_labels = list(validation_generator.class_indices.keys())  # custom_labels는 폴더의 이름이다. 여기서는 cured(치료됨), decayed(충치), healthy(건강)가 나올 것이다.

 

 

2.3 모델을 새롭게 정의

from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Sequential
from keras.layers import Dropout, Flatten, Dense
from keras.models import Model
from keras import models
from keras import layers
from keras import optimizers
import keras.backend as K

K.clear_session() # 새로운 세션으로 시작

from keras.applications import VGG16
# 모델 불러오기
conv_layers = VGG16(weights='imagenet', include_top=False, input_shape=(image_size, image_size, 3))
conv_layers.summary()

# Convolution Layer를 학습되지 않도록 고정 
for layer in conv_layers.layers:
    layer.trainable = False


# 새로운 모델 생성하기
model = models.Sequential()

# VGG16모델의 Convolution Layer를 추가
model.add(conv_layers)
 
# 모델의 Fully Connected 부분을 재구성
model.add(layers.Flatten())
model.add(layers.Dense(1024, activation='relu'))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(class_num, activation='softmax'))

# 모델
model.summary()



vgg16_model_path = 'new_trained_from_vgg16.h5'

model.save(vgg16_model_path)

from keras.preprocessing.image import ImageDataGenerator

from keras import optimizers

from keras.models import Sequential

from keras.layers import Dropout, Flatten, Dense

from keras.models import Model

from keras import models

from keras import layers

from keras import optimizers

import keras.backend as K

 

K.clear_session() # 새로운 세션으로 시작, 위의 코드에서 실행을 한번 했으므로 메모리를 잡아먹고 있다. 그래서 새로운 작업을 할때, 이런식으로 초기화를 시켜준다고 생각하면된다.

 

from keras.applications import VGG16

# 모델 불러오기

conv_layers = VGG16(weights='imagenet', include_top=False, input_shape=(image_size, image_size, 3))

# 이전에 모델 불러오는거 보다 복잡하다. imagenet에 학습된 것으로weights를 가져온다. include_top은 컨볼루션과 풀링 layer 이후에 dense하는 부분을 top이라고 하는데 모델 로딩시 top부분에서 1000개의 class가 있는 부분을 가져오지 않는다는 말이다. shape에서 3은 이미지넷부분이다. 정리하면, 우리가 안 건드리는 앞부분만 가져오는거다.

 

conv_layers.summary() # 위 부분을 로드해서 요약을 한번 보자. 이부분 보면 뒷단 1000개부분이 빠져있다. 아래그림을 참고 하면 대략 저렇게 끝난다.

남은 코드도 전이학습에서 연결된다.

 

 

2.4 전이학습

VGG16은 처음엔 몇십만개의 데이터를 학습하고, Dense 이전에는 4000개로 줄어든다. 그런데 그 4천개에 몇십만개의 정보가 포함되어 있다. 즉, 훌륭한 특징 추출기다. 그렇기 때문에 모델을 처음부터 학습하지 않고 Dense 이후의 부분만 학습시키자는 전략이다. 이것을 trasfer Learning(전의학습) 이라고 한다.

 

 

# Convolution Layer를 학습되지 않도록 고정 

for layer in conv_layers.layers:

    layer.trainable = False        # conv_layers로 앞부분을 가져온 것을 trainable 안시키는 설정이다.



# 새로운 모델 생성하기

model = models.Sequential()

 

# VGG16모델의 Convolution Layer를 추가

model.add(conv_layers)          # 설정이 완료된 layer를 모델에 먼저 넣어준다.

 

# 모델의 Fully Connected 부분을 재구성(레이어를 추가한거다)

model.add(layers.Flatten())                                   # Flatten 시킨다.

model.add(layers.Dense(1024, activation='relu'))        # Dense넷을 넣어준다 

model.add(layers.Dropout(0.5))

model.add(layers.Dense(class_num, activation='softmax'))

 

# 모델

model.summary()         # 위에서한 summary는 conv_layer에 대한거고 여기서는 전체model에 대한 summary다.

 

summary된 결과를 보면 원래 있던게 없어지고 위에서 add한 새로운 것들이 추가 된걸 알 수있다.
결과값을 보면 Non-trainable params는 컨볼루션 레이어에서 업데이트된 파라미터가 14714688개라는 말이다. 즉, 위에있는 그림의 토탈 파라메터 값이라고 보면된다. 그리고 Trainable params인 25694211개는 왼쪽 그림의 Dense부분을 합한 한 값이라 생각하면 된다

 

 

 

요약하면, 컨볼루션부분은 False 줘서 그데로 쓰고, Dense부분은 2개의 layer를 2개 쓰는데, 마지막 layer의 노드수는 3개이다.(class가 3개이기 떄문이다.) 분류에서 입력은 이미지 크기고 출력은 class의 개수이다.

 

 

 

vgg16_model_path = 'new_trained_from_vgg16.h5'

 

model.save(vgg16_model_path)           # 모델 저장함. 모델에는 모델의 weights가 들어가 있을 뿐만 아니라 모델의 구조도 들어가있다. 즉, 여기서 한번 저장을 하고 다음 단계에서 다시 불러와서 학습을 한다.

 

이제 모델 학습하는 코드봐야겠네

 

2.5 모델 학습

from keras.models import load_model

# 모델 로딩
model = load_model(vgg16_model_path)

# 모델 컴파일
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

# 모델 학습
history = model.fit_generator(
      train_generator,
      steps_per_epoch=train_generator.samples/train_generator.batch_size ,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=validation_generator.samples/validation_generator.batch_size,
      verbose=1)
 
# 모델 저장
model.save(vgg16_model_path)

from keras.models import load_model

 

# 모델 로딩

model = load_model(vgg16_model_path) # 위에서 저장한 모델을 가지고 온다.

 

# 모델을 컴파일하는 부분이다(학습하기 위하 변수를 설정)

model.compile(loss='categorical_crossentropy',   # loss function을 무엇으로 할 것인가. 즉,  cost function정한다.

              optimizer=optimizers.RMSprop(lr=1e-4), # optimizer을 무엇으로 할 것인가. 그리에이트 디센트정하는 부분이 RMSprop이고 lr은 learning rate를 의미한다. learning rate를 아래에서 수정할 것이다.

              metrics=['acc'])   # 이부분은 acc를 기준으로 한다는 말이다. accuracy

 

# 모델 학습

history = model.fit_generator(

      train_generator,  # 이걸 통해 데이터를 받는 다.

      steps_per_epoch=train_generator.samples/train_generator.batch_size ,  # 1 epoch는 전체 데이터를 다 학습해야 1epoch이다. 그런데 배치로 GPU에 보내는 싸이즈가 2이면 1 epoch를 하기 위한 수행 횟수는 반으로 줄어든다. 즉, 전체 데이터 수에서 배치 값을 나눈 step_per_epoch는 1epoch를 하는데 수행해야할 횟수를 나타낸다.

      epochs=100,                     # 몇번 1epoch를 반복하냐를 나타낸다.

      validation_data=validation_generator,

      validation_steps=validation_generator.samples/validation_generator.batch_size,

      verbose=1)

 

# 모델 저장

model.save(vgg16_model_path)  # 우린 학습된 모델의 결과만 사용하기 떄문에 저장이 필요하다. 나중에 이 파일을 읽어서 사용한다. 이거 저장 안하면 훈련한 데이터 사라진다.....

 

 

아래 그림은 모델이 학습되고 있는 과정에 대한 결과값이다.

 

이런식으로 Epoch가 하나씩 뜬다. loss는 학습을 진행할 수록 낮아져야 하는 값이다. acc는 정확도를 나타내므로 학습을 하면서 높아져야된다. val_loss는 test 밑에 있던 것들이다. 학습에는 train에 있는 데이터만 사용한다. 그리고 학습이 되어가면서 학습에 사용되지 않는 test 데이터에 적용해 봤을 때, 잘되는가를 확인하는 값이 val_loss와 val_acc이다. val_acc의 첫번쨰 값을 보면 0.3333이라고 나타나는데 이건 바로 class가 3개기 때문에 3분의 1확률이라고 보면 된다. 

즉, 왼쪽이 train 관련 값이고 오른쪽이 validation한 값이다. 당연히 왼쪽이 오른쪽 보다 높다. 왜냐하면, 나는 train data로 학습을 했다. 그런데 학습한 데이터가 당연히 새로운 데이터보다 확률이 높은건 당연하다.

 

 

2.6 모델 정확도를 그래프로 보기

import matplotlib.pyplot as plt

acc = history.history['acc']
loss = history.history['loss']
 
epochs = range(len(acc))
 
plt.plot(epochs, acc, 'b', label='accuracy')
plt.plot(epochs, loss, 'r', label='loss')
plt.title('accuracy and loss')
plt.legend()
  
plt.show()

출력된 그래프를 해석해 보자. 빨강은 cost이다. 파랑은 정확도기 때문에 최대값이 1이다. loss를 보면 출렁거린다. 출렁임은 learning rate가 너무 크거나 데이터가 너무 적어서 그런 것이다.(보통 데이터가 천개나 만개는 되야한다.) 그런데 그래프보면 loss가 떨어지긴 하니 학습이 되고 있긴하지만, 데이터가 작긴하다.

 

 

 

 

 

 

폭이 너무 큰거 같으면 optimizer=optimizers.RMSprop(lr=1e-4), 부분에서 learning rate를 더 낮추면 된다. 10의 -4승이므로 보통 나누기 10씩 해서 optimizer=optimizers.RMSprop(lr=1e-5),를 적용하고, optimizer=optimizers.RMSprop(lr=1e-6), 을 적용하면서 러닝을 한다. 그리고 모델 학습을 초기화 없이 바로 진행한 후에 위의 그래프를 다시 실행해보면, 이전에 학습이 된부분인 loss값인 4인 부분(위의 그래프에서 오른쪽 끝의 y값)에서 학습을 시작할 것이고, 흔들리는 폭은 작아져서 덜 출렁 거릴 것이다.

 

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함