Hyebin‘s blog
Published 2021. 12. 17. 01:41
[CV] 전이 학습 Other/Computer vision

전이 학습?

데이터가 적은 경우에 CNN을 가지고 학습하기 어렵다. 특히 영상 크기가 크게 되면 학습 해야할 파라미터가 많은데 충분히 데이터가 공급되어 있지 않으면 학습이 정상정으로 진행되지 않는다.

따라서 이를 해결하기 위해 쉽게 설명하면 A라는 문제를 푼 경험이 있다면 A와 유사한 문제를 쉽게 풀 수 있지 않을까? 하는 개념이다. 이러한 방식을 사용하여 임의의 객체를 분류하기 위해서 ImageNet을 이용하여 특징 추출을 사용하고 추출된 특징을 기반으로 분류기만 학습을 시킴. 이런 방법이 전의학습이다.

 

영상인식은 특징 추출과 분류하는 부분으로 나누게 되는데 특징 추출은 굉장히 시간이 오래걸리는 분야로, ImageNet으로 썻던 학습 파라미터 CNN을 사용하고 분류기만 재 학습을 시키자!

우리가 알아 볼 것!

  • 데이터가 적은 경우 어떻게 데이터를 불릴 수 있을까?
  • 안정적으로 깊은 네트워크 설계방법은?
  • 특징 추출을 사용하고 분류기만 새로 개발!

전이학습

from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.layers import Flatten, GlobalAveragePooling2D
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from  keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array
import keras
import tensorflow as tf
import numpy as np
from numpy  import expand_dims
from matplotlib import pyplot as plt
import cv2

도형인식

#네모 세모 동그라미 분류
np.random.seed(3)

datagen = ImageDataGenerator(rescale=1./255)
#ImageDataGenerator는 0~1사이로 정규화

train_generator = datagen.flow_from_directory(
        'dataset/hard_handwriting_shape/train',
        target_size=(24, 24),#입력 영상 리사이즈
        batch_size=3, #데이터로 부터 이미지를 3개씩 읽어옴
        class_mode='categorical') #원핫인코딩

ImageDataGenerator는 input 영상 rescale, 출력된 이미지는 0~1로 정규화된 값을 가짐

directory로 부터 파일을 읽어서 ImageDataGenerator를 적용

일일히 레이블을 잡을 필요 없이 flow_from_directory를 이용하여 경로 하위에 있는 폴더를 읽어서 클래스를 구분지어 만들어 주면 함수가 레이블을 자동으로 생성해줌 3개의 클래스(폴더)에서 총 45개의 이미지를 찾아서 읽음

 

# 모델 구성하기
model = Sequential()
#간단한 cnn구성
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=(24,24,3)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))#영상크기 줄이고
model.add(Flatten()) #1차원으로
model.add(Dense(128, activation='relu'))#은닉층
model.add(Dense(3, activation='softmax'))#출력층 분류할것 3개 클래스,softmax

# 모델 엮기
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()

클래스 모드가 _mode='categorical' 되어 있으면 categorical로 자동으로 레이블이 원핫인코딩 되기 때문에 사용해도됨

 

# 모델 학습시키기
model.fit_generator(train_generator,  epochs=5)

#train_generator를 넣어주면 실제 데이터와 레이블 데이터를 다 생성시켜줌

네트워크 구조가 간단해서 굉장히 빠르다

 

print("-- Evaluate --")
test_generator = datagen.flow_from_directory(
        'dataset/hard_handwriting_shape/test_easy', #테스트 이미지로 바꿔줌
        target_size=(24, 24),  batch_size=3, class_mode='categorical')
scores = model.evaluate_generator(test_generator)

print("%s: %.2f%%" %(model.metrics_names[1], scores[1]*100))
#0은 로스와 **1은 인식률**, 인식률 결과 나옴

evaluate_generator는 인식률 계산 가능

 

print("-- Evaluate --")
test_generator = datagen.flow_from_directory(
        'dataset/hard_handwriting_shape/test',
        target_size=(24, 24),    batch_size=3,   class_mode='categorical')
scores = model.evaluate_generator(test_generator)

print("%s: %.2f%%" %(model.metrics_names[1], scores[1]*100))

성능이 매우 낮게 나옴.

이유는 ? test폴더는 작은 사이즈와 치우쳐진, 찌그러진 경우가 있기 때문에 성능이 떨어짐

→ 이와 같은 상황에서 어떻게 학습 데이터를 공급해주는 학습 체계를 갖출까?

 

 

2. Data Augmentation = 데이터 증강

dataset 이 너무 적은 경우 overfitting(추정해야 하는 파라미터가 데이터의 갯수보다 너무 많을 경우)문제가 발생함 keras에서는 data augmentation(데이터를 강제로 늘려서)을 통해 해결

  • Translations
  • Rotations
  • Changes in scale
  • Shearing
  • Horizontal (and in some cases, vertical) flips

데이터를 기반으로 주변에 일부러 노이즈를 주어서 데이터를 생성하면, 기존의 분포에 크게 벗어나지 않게 데이터를 포함해서 overfiitting문제를 해결해나감. ( 회전을 시키거나 스케일을 키우는 등 )

augmentation 방법은 두가지

 

 

2가지 방법

  • Type #1: Dataset generation and expanding an existing dataset (less common)
  • 오리지날 이미지 에서 augmentation, 그리고 생성된 가상의 데이터를 저장하고 모두 모아서 학습하는 방식 →이상적인 방식
  • Type #2: In-place/on-the-fly data augmentation (most common) - keras에서 구현→ 조금 씩 가져와서 generated된 이미지를 가져와서 부분별로 학습하는 방식케라스에서 학습을 수행할 때, 전체를 가지고 이미지를 학습하는 것이 아닌 배치 기반으로 이미지를 학습 , 생성된것을 학습 데이터로 공급. 원본은 사용하지 않음
    1. 데이터가 너무 커서 한번에 메모리에 들어가지 못함
    2. 경사 하강법으로 학습 시 평균 기울기 만큼 이동하는데 샘플이 많으면 샘플마다 기울기와 평균을 구할 시조금씩 밖에 이동이 안돼 학습 속도가 느리기 때문에 배치수 사용
    이때 두번째 방법에서 주의할 점은 원본 이미지는 반드시 제외를 하고 학습하는것을 초점을 맞춤!
  • 이유는
  • ImageDataGenerator는 가상의 데이터를 생성하는 역할
  • 오리지날 이미지가 ImageDataGenerator에 제공, 배치수 만큼 augmentation, 생성된것을 학습 데이터로 공급원본은 사용하지 않음
img = load_img('cat.jpg')  # 이미지 객체
data = img_to_array(img)    # numpy
print(data.shape) #3차원
sample = expand_dims(data,0) #4차원으로 만들어줌 
print(sample.shape) #4차원
#open cv에서 읽고 차원 확장
data = cv2.imread('cat.jpg')
data = cv2.cvtColor(data, cv2.COLOR_BGR2RGB)
sample = expand_dims(data,0)
print(sample.shape) #4차원
generator = ImageDataGenerator(width_shift_range = 0.2) 
#어떠한 이미지 영상처리를 하겠다 ! 선언만 함

obj = generator.flow(sample, batch_size=1)
#flow 함수를 가지고 적용시킴

 

수평, 수직 이동

generator = ImageDataGenerator(width_shift_range = 0.2) 
#어떠한 이미지 영상처리를 하겠다 ! 선언만 함

obj = generator.flow(sample, batch_size=1)
#flow 함수를 가지고 적용시킴
#batch_size는 내가 생성을 한번 할때마다 하나의 이미지가 생성이 된다.
image = obj.next()
#실제로 배치 수 만큼의 이미지가 만들어짐 (넘파이 타입) 실제 생성!
#실행 할때마다 랜덤하게 양을 결정함

print(image.shape)  # 
print(image[0].shape)
#plt.imshow(image[0].astype('uint8') )  #  plt로 할때는 0~1사이로  혹은 타입을 uint8로 
plt.imshow(image[0]/255)  #  plt로 할때는 0~1사이로  혹은 타입을 uint8로

기본적으로 image 타입이 unit8이 아니고, next()함수로 읽으면 float타입이 된다. plt.imshow함수는 unit8타입이나 float타입에 따라 결과를 다르게 해석하는데 float타입은 0~1사이로 넣어준다는 가정, unit8타입일 경우 0~255사이로 가정

따라서 정상적으로 이미지를 나오게 하기 위해서는 0~255로 나오게, unit8로 바꾸거나 기본이 실수로 나오기 때문에

255로 나눈다.

 

image = obj.next()
plt.imshow(image[0].astype('uint8') )  #  plt로 할때는 0~1사이로  혹은 타입을 uint8로
generator = ImageDataGenerator(    
    width_shift_range = 0.2, rescale=1./255)   
obj = generator.flow(sample, batch_size=1)

rescale 해주면 모든 데이터를 255로 나누는 정규화를 해준다

이때는 imshow할때 type변한 필요없다.

 

image = obj.next()
plt.imshow(image[0] )

 

→ 따라서 ImageDataGenerator를 사용하는 경우에는 보통 rescale=1./255 값을 주게된다.

 

# 고양이 9개를 생성 3*3
# 부족한 부분을 마지막 픽셀을 복사함
fig = plt.figure(figsize=(30,30))
for i in range(9) :
    plt.subplot(3, 3, i+1)
    image = obj.next()
    plt.imshow(image[0])

 

수평 , 수직 뒤집기 horizontal_flip, vertical_flip

generator = ImageDataGenerator( 
    horizontal_flip = True, vertical_flip = True, rescale=1./255)
obj = generator.flow(sample, batch_size=1)   

fig = plt.figure(figsize=(30,30))
for i in range(9) :
    plt.subplot(3,3,i+1)
    image = obj.next()
    plt.imshow(image[0])

 

회전 rotation

generator = ImageDataGenerator( 
    rotation_range=90, rescale=1./255)
obj = generator.flow(sample, batch_size=1)   

fig = plt.figure(figsize=(30,30))
for i in range(9) :
    plt.subplot(3,3,i+1)
    image = obj.next()
    plt.imshow(image[0])

 

밝기 brightness

generator = ImageDataGenerator(brightness_range=[0.2, 1.2],rescale=1./255)
obj = generator.flow(sample, batch_size=1)   

fig = plt.figure(figsize=(30,30))
for i in range(9) :
    plt.subplot(3,3,i+1)
    image = obj.next()
    plt.imshow(image[0])

 

확대 zoom

generator = ImageDataGenerator( 
    zoom_range=[0.5, 1.5],   rescale=1./255)
obj = generator.flow(sample, batch_size=1)   

fig = plt.figure(figsize=(30,30))
for i in range(9) :
    plt.subplot(3,3,i+1)
    image = obj.next()
    plt.imshow(image[0])

 

모두 적용하기

generator = ImageDataGenerator( 
    width_shift_range = 0.2,
    zoom_range=[0.5, 1.0],   
    horizontal_flip = True, vertical_flip = True,
    rotation_range=90,
    rescale=1./255)
obj = generator.flow(sample, batch_size=1)   

fig = plt.figure(figsize=(30,30))
for i in range(9) :
    plt.subplot(3,3,i+1)
    image = obj.next() #호출 할 때마다 데이터가 생성이 됨
    plt.imshow(image[0])

 

이미지 데이터와 레이블 생성

obj = generator.flow_from_directory(
    'dataset/hard_handwriting_shape/train',
    target_size = (150, 150),   # 읽어드릴 이미지 크기
    batch_size = 9,#9개씩
    class_mode = 'binary' )    # binary(label)  |  categorical (one-hot)

iterations = 5

#데이터 생성 부분
for i, (img, label) in enumerate(obj): 
    n_img = len(label)  #9
    print(label) #9개의 레이블
    print(img.shape) #9,150,150,3
    if i is iterations - 1: #루프를 5번
        break

폴더로 부터 이미지를 생성하고, 이미지 크기 적용하고, 한번에 데이터 생성 갯수를 정하고, class_mode을 categorical 로 하면 데이터를 생성했을 때 y값이 원핫 인코딩 된 형태로 리턴이 되고 binary는 레이블 형태 (10개의 클래스면 0~9까지 이렇게)로 리턴이 된다.

조건을 어느 폴더로 어떤 방식으로 생성 할지 명시 하고 ,

enumerate는 원래 오브젝트 에 인덱스를 같이 만들어줌

 

 

augmentation 이용한 도형인식

train_datagen = ImageDataGenerator(rescale=1./255, #0~1로 정규화 
                                   rotation_range=10, #회전
                                   width_shift_range=0.2, #수평 이동
                                   height_shift_range=0.2, #수직 이동
                                   shear_range=0.7,#휘어짐
                                   zoom_range=[0.9, 2.2], #확대
                                   horizontal_flip=True, #뒤집어짐
                                   vertical_flip=True,
                                   fill_mode='nearest') #빈 공간을 가장 근접의 색상으로 채우기

train_generator = train_datagen.flow_from_directory(
        'dataset/hard_handwriting_shape/train', #학습 데이터
        target_size=(24, 24),
        batch_size=3, #한번에 생성할 이미지 크기
        class_mode='categorical') #원핫 인코딩

#학습 데이터는 augmentation을 수행 시켜야 하지만 테스트 데이터는 그럴 필요가 없다. 
#rescale 은 지원하면 된다!
test_datagen = ImageDataGenerator(rescale=1./255) #0~1로 구성되어야 한다.

test_generator = test_datagen.flow_from_directory(
        'dataset/hard_handwriting_shape/test',
        target_size=(24, 24),    
        batch_size=3,
        class_mode='categorical')

test 데이터를 가지고 다시 한번 수행을 하면

학습에 필요한 45장과 세개의 클래스, 테스트에 필요한 15장과 세개의 클래스가 입력으로 들어가 있다.

 

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=(24,24,3)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dense(3, activation='softmax'))

# 모델 엮기
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

네트워크는 동일한 방식으로 구성

 

# 모델 학습시키기
model.fit_generator(
        train_generator,        
        epochs=50)

# 모델 평가하기
print("-- Evaluate --")

scores = model.evaluate_generator(
            test_generator, 
            steps = 5)

print("%s: %.2f%%" %(model.metrics_names[1], scores[1]*100))

최종 성능이 80%가 나옴.

같은 학습 데이터와 네트워크 구조를 사용했음에도 매우 많이 향상 된 것을 확인 할 수 있다.

기하학적 변형을 같이 포함시켜 학습을 함으로써 인식기의 성능이 매우 높아졌다.

 

 

개고양이 분류하기

  • 영상이 크기 때문에 shallow CNN으로 학습이 어려움(학습되는 파라미터가 많아 학습 데이터가 충분히 제공되지 않으면 오버피팅 문제가 발생하거나 성능이 떨어짐)
  • 학습 데이터로 1,000장의 고양이 사진과 1,000장의 강아지 사진
batch_size = 16

# 학습 이미지에 적용한 augmentation 인자를 지정해줍니다.
train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255) #테스트는 스케일만!

# 이미지를 배치 단위로 불러와 줄 generator입니다.
train_generator = train_datagen.flow_from_directory(
        'dataset/smallcatdog/train',  # this is the target directory
        target_size=(224, 224),  # 모든 이미지의 크기가 150x150로 조정됩니다.
        batch_size=batch_size,
        class_mode='binary')  # binary_crossentropy 손실 함수를 사용하므로 binary 형태로 라벨을 불러와야 합니다.

test_generator = test_datagen.flow_from_directory(
        'dataset/smallcatdog/test',
        target_size=(224, 224),
        batch_size=batch_size,
        class_mode='binary')

2000장의 학습이미지와 2개의 클래스

800장의 테스트 이미지와 2개의 클래스

 

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=(224, 224,3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())  # this converts our 3D feature maps to 1D feature vectors
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5)) #overfiting을 막기 위해, 50% weight를 다시 0으로 랜덤하게 만든다.
model.add(Dense(1)) #개냐 고양이냐 이진분류 문제
model.add(Activation('sigmoid'))

model.compile(loss='binary_crossentropy', #이진분류 문제
              optimizer='rmsprop',
              metrics=['accuracy'])
model.summary()

영상의 크기가 크기 때문에 충분한 MaxPooling2D단계를 거쳐야 된다.

 

model.fit_generator(train_generator, epochs=80)
scores = model.evaluate_generator(test_generator)
print("%s: %.2f%%" %(model.metrics_names[1], scores[1]*100))

'Other > Computer vision' 카테고리의 다른 글

[CV] ImageNet을 이용한 인식  (0) 2021.12.17
[CV] CNN 활용한 영상 인식  (0) 2021.12.17
[CV] 사람의 시각을 닮은 신경망 CNN  (0) 2021.12.17
[CV] 객체 검출, YOLO  (0) 2021.12.06
[CV] 전이학습 Freeze, Fine:Tunning  (0) 2021.12.06
profile

Hyebin‘s blog

@hyebin Lee

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그