Eggs Sunny Side Up
본문 바로가기
Computer Engineering/딥러닝

mlp, cnn_개, 고양이_이미지데이터 _분류

by guswn100059 2023. 7. 6.
# ex06_mlp, cnn_개, 고양이_이미지데이터 _분류

from google.colab import drive
drive.mount('/content/drive')

# 경로 이동하기
# /content/drive/MyDrive/Colab Notebooks/DeepLearning_빅데/data/cats_and_dogs_filtered.zip
%cd /content/drive/MyDrive/Colab Notebooks/DeepLearning_빅데

%pwd

### 이미지 데이터 전처리
 - 사진을 데이터화 시키는 작업
 - 이미지 -> 배열화 시키기
 - 압축된 형식의 배열로 저장 : npz
 - 장점 : 대용량의 데이터를 압축형태로 정리해두면 클라우드 환경에 쉽게 업로드, 다운로드 할 수 있음 (관리가 편리해짐)

import pandas as pd
import os
import numpy as np
import matplotlib.pyplot as plt

# 이미지를 불러오는 라이브러리
from PIL import Image
from sklearn.model_selection import train_test_split
from zipfile import ZipFile # 압축관련 파일 처리 모듈

# zip파일 압축해제하여 이미지 불러오기
zip_file = './data/cats_and_dogs_filtered.zip'
# 파일을 열어서 압축을 해제
with ZipFile(zip_file, 'r') as z :
  # 압축 파일 내용 먼저 확인
  # z.printdir()
  # 압축 파일 해제하기, path 저장할 경로 설정
  z.extractall(path='./data/')

# 이미지 접근하려면 경로 필요
# './data/cats_and_dogs_filter/train/cats/cat.1.jpg
train_cats_dir = './data/cats_and_dogs_filtered/train/cats'
train_dogs_dir = './data/cats_and_dogs_filtered/train/dogs'
test_cats_dir = './data/cats_and_dogs_filtered/test/cats'
test_dogs_dir = './data/cats_and_dogs_filtered/test/dogs'

# os lib 활용하여 폴더 안에 있는 데이터 이름을 리스트로 저장
# os.listdir() : 해당 경로에 있는 파일명들을 리스트에 순서대로 저장하는 함수
train_cats_fnames = os.listdir(train_cats_dir)
train_dogs_fnames = os.listdir(train_dogs_dir)
test_cats_fnames = os.listdir(test_cats_dir)
test_dogs_fnames = os.listdir(test_dogs_dir)

print(train_cats_fnames[:2])
print(train_dogs_fnames[:2])
print(test_cats_fnames[:2])
print(test_dogs_fnames[:2])

print(len(train_cats_fnames)) # train 고양이 1000개
print(len(train_dogs_fnames)) # train 강아지 1000개
print(len(test_cats_fnames))  # test 고양이 500개
print(len(test_dogs_fnames))  # test 강아지 500개
# train 총 2000개, test 총 1000개
# 답 train 2000개(1000+1000), test 1000개(500+500)

# 경로 연결하기
# train_dogs_dir, train_dogs_fnames
# os.path.join(경로, 경로)
tmp_path = os.path.join(train_dogs_dir, train_dogs_fnames[0])
tmp_path

# 이미지 처리
# 불러와서 사이즈 조정 및 배열 변환
# 행 224, 열 224 사이즈로 조정하기
img_size = (224, 224)
img = Image.open(tmp_path).resize(img_size)
img_arr = np.array(img)
print(img_arr.shape) # (행, 열, 차원(채널수))
print(img_arr.ndim) # 컬러이미지, 채널 3 (r, g, b)

# 사진을 불러와서 배열로 변경하는 함수
# 모든 폴더의 모든 사진은 224 * 224
def load_images(folder_path, file_name, img_size=(224, 224)) :
  # 한 폴더 안에 있는 이미지를 묶어줄 변수
  image = []

  for i in file_name :
    # 폴더 경로 + 파일명 합치기
    path = os.path.join(folder_path, i)
    # 파일 오픈, 크기 변경
    img = Image.open(path).resize(img_size)
    # 배열로 변경, 이미지 arr -> img 리스트에 저장(list 뒤로 추가)
    # img_arr = np.array(img)
    image.append(np.array(img))
  return np.array(image)

# 함수 사용하기 -> 호출 4번
X_train_cats = load_images(train_cats_dir, train_cats_fnames)
X_train_dogs = load_images(train_dogs_dir, train_dogs_fnames)
X_test_cats = load_images(test_cats_dir, test_cats_fnames)
X_test_dogs = load_images(test_dogs_dir, test_dogs_fnames)

# 이미지 데이터가 반환
# 크기 확인
X_train_cats.shape, X_train_dogs.shape, X_test_cats.shape, X_test_dogs.shape

# train 끼리 병합
# test 끼리 병합
# 고양이, 개 순서대로 병합
X_train = np.concatenate((X_train_cats, X_train_dogs)) # 1000 + 1000 = 2000
X_test = np.concatenate((X_test_cats, X_test_dogs)) # 500 + 500 = 1000
print(X_train.shape, X_test.shape)
# (이미지 개수, 행, 열, 채널수)

# y_train, y_test 생성
# 고양이 : 0, 개 : 1
# 정답 데이터 만들기
# y_train = 고양이*1000 + 개*1000
# y_test = 고양이*500 + 개*500
y_train = [0]*1000 + [1]*1000
y_test = [0]*500 + [1]*500
# 크기확인
print(len(y_train), len(y_test))

# npz 넘파이 배열 압축 형식으로 데이터 저장
np.savez_compressed('./data/cats_and_dogs.npz',  # 저장 경로 및 파일명
                    X_train = X_train,  # 훈련 문제
                    X_test = X_test,    # 테스트 문제
                    y_train = y_train,  # 훈련 답
                    y_test = y_test)    # 테스트 답

### npz 파일 로딩

data = np.load('./data/cats_and_dogs.npz')
len(data)

# X_train, X_test, y_train, y_test 접근해서 각 변수에 저장
X_train = data['X_train']
X_test = data['X_test']
y_train = data['y_train']
y_test = data['y_test']
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)

### MLP 모델 생성
 - 1. 신경망 설계 (뼈대, 층 쌓기)
 - 2. 학습 / 평가 방법 설정 (compile)
 - 3. 학습 (fit) 및  시각화
 - 4. 예측, 평가

from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.callbacks import EarlyStopping

# mlp모델 생성
mlp_model = Sequential()

# 입력층, 3 -> 1차원
mlp_model.add(Flatten(input_shape=(224, 224, 3)))

# 중간층
# 256, 128, 64
mlp_model.add(Dense(256, activation='relu'))
mlp_model.add(Dense(128, activation='relu'))
mlp_model.add(Dense(64, activation='relu'))

# 출력층
# 회귀, 이진, 다중(다지)분류
# 고양이, 개 분류 -> 이진분류
# 회귀 units = 1, 활성화 activation = 'linear' (항등함수, 생략)
# 이진분류 units = 1, activation = 'sigmoid'
# 다중분류 units = 클래스의 개수, activation = 'softmax'
mlp_model.add(Dense(1, activation='sigmoid'))

mlp_model.summary()

# 2. compile
# 이진분류
# loss, optimizer, metrics
mlp_model.compile(loss='binary_crossentropy',
                  optimizer='adam',
                  metrics=['accuracy'])

# 3. 조기 학습중단, fit
f_early = EarlyStopping(monitor = 'val_accuracy',
                        # 성능 개선되지 않는 것을 몇 번 정도 기다릴 것인지
                        patience = 10)
# 검증 데이터셋 val 0.3, 반복횟수 100, 배치사이즈 64, 조기학습중단 연결해보기
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.3, random_state=2)
mlp_h = mlp_model.fit(X_train, y_train, validation_data = (X_val, y_val),
                      epochs=100, batch_size=64, callbacks=[f_early])

mlp_h.history['accuracy']
mlp_h.history['val_accuracy']

# acc, val_acc 시각화
plt.figure(figsize=(10, 3))
# plt.plot(mlp_h.history['accuracy'], label='acc) 도 가능
plt.plot(range(1, 26), mlp_h.history['accuracy'], label='acc')
plt.plot(range(1, 26), mlp_h.history['val_accuracy'], label='val_acc')
plt.legend()
plt.show()

# 학습이 제대로 되지 않음
# 성능이 별로 좋지 않음
# 일반화 모델은 아님
# 어떻게하면 개, 고양이 이미지를 잘 학습하고 성능이 좋은 모델을 만들 수 있을까?
# mlp -> cnn 데이터 학습하는데 보다 안정되지 않을까?

### CNN 모델

from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPooling2D
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping

# cnn 신경망 구조를 직접 쌓기!
# 뼈대 구축
cnn_model = Sequential()

# 합성곱층(convolution) - 특성 추출부
# 필터 크기, 필터 개수, 스트라이드, 패딩, 활성화, 입력데이터크기
# 맥스풀링 - 지역 내에서 최대값 추출하기
cnn_model.add(Conv2D(filters=32, kernel_size=(3, 3), padding='same',
                     activation='relu', input_shape=(224, 224, 3)))
cnn_model.add(MaxPooling2D(pool_size=2)) # 주요 특징값 추출

cnn_model.add(Conv2D(filters=64, kernel_size=(3, 3), padding='same', activation='relu'))
cnn_model.add(MaxPooling2D(pool_size=2)) # 주요 특징값 추출

cnn_model.add(Conv2D(filters=128, kernel_size=(3, 3), padding='same', activation='relu'))
cnn_model.add(MaxPooling2D(pool_size=2)) # 주요 특징값 추출

# 전결합층(mlp) - 분류부(분류하는 층, 개/고양이 분류)
cnn_model.add(Flatten())
cnn_model.add(Dense(256, activation='relu'))
cnn_model.add(Dense(1, activation='sigmoid'))

# 모델 요약
cnn_model.summary()

# cnn 학습 / 평가 방법 설정
cnn_model.compile(loss='binary_crossentropy',
                  optimizer=Adam(learning_rate=0.001),
                  metrics = ['accuracy']
                  )

# 초기학습 중단 연결해서 fit
cnn_h = cnn_model.fit(X_train, y_train,
                      validation_split = 0.3,
                      epochs = 100,
                      batch_size=64,
                      callbacks=[f_early])

# acc, val_acc 시각화
plt.figure(figsize=(10, 3))
plt.subplot(1, 2, 1) # 1행, 2칸(열), 1칸(열)

# acc, val_acc 시각화
# plt.plot(mlp_h.history['accuracy'], label='acc) 도 가능
plt.plot(range(1, 26), mlp_h.history['accuracy'], label='acc')
plt.plot(range(1, 26), mlp_h.history['val_accuracy'], label='val_acc')
plt.legend()

plt.subplot(1, 2, 2)
# plt.plot(mlp_h.history['accuracy'], label='acc) 도 가능
plt.plot(range(1, 13), cnn_h.history['accuracy'], label='acc')
plt.plot(range(1, 13), cnn_h.history['val_accuracy'], label='val_acc')
plt.legend()
plt.show()

# mlp 보다는 빠르게 이미지에 대한 학습이 되고 있음
# cnn모델은 train 거의 100% 맞추지만, val은 거의 못 맞추고 있음 -> 과대적합일 확률이 높음
# cnn모델을 잘 쌓아야하는 경우가 있음
# 현재 개, 고양이 데이터가 굉장히 복잡한 문제일 수 있음
# 학습되는 이미지의 양이 부족할 수도 있음, 더 많은 경우의 수를 학습시켜야할 수도 있음

### 과대적합 방지
 - Dropout : 일정한 비율만큼 랜덤으로 중간층의 뉴런을 비활성화, 과도하게 학습되는 현상을 방지할 수 있음
 - Data augumentation(데이터 증식)
  - 데이터 증식을 위해 keras에서 제공하는 이미지 제너레이터를 사용
  - 학습하는 시간은 오래걸리지만 기존보다 과대적합이 나름대로 해소됨
  - 조기학습 중단을 하기보다는 최대한 반복횟수를 늘려서 학습 진

from tensorflow.keras.preprocessing.image import ImageDataGenerator

# 데이터 확장(증식)
train_gen = ImageDataGenerator(rescale = 1./255,    # 픽셀 0 ~ 255 -> 분산을 1로 (범위를 줄이자)
                               shear_range = 0.15,  # 시계 반대방향으로 회전
                               zoom_range = 0.2,  # 확대, 축소시 사용
                               horizontal_flip = True # 수평 방향으로 뒤집기
                               )
# test 설정
# 이미지 증식 test에도 필요할까 ?
test_gen = ImageDataGenerator(rescale = 1./255)

# 데이터 경로 설정
train_dir = './data/cats_and_dogs_filtered/train'
test_dir = './data/cats_and_dogs_filtered/test'

# 설정값 모아서 정의
train_generator = train_gen.flow_from_directory(train_dir,  # 폴더 경로
                                                target_size = (224, 224), # 변환한 이미지의 크기
                                                batch_size = 10, # 한 번에 변환할 이미지 개수
                                                class_mode = 'binary'
                                                # 라벨 번호는 0번부터 시작, 폴더는 알파벳 순서로 읽음
                                                )

test_generator = train_gen.flow_from_directory(test_dir,  # 폴더 경로
                                                target_size = (224, 224), # 변환한 이미지의 크기
                                                batch_size = 10, # 한 번에 변환할 이미지 개수
                                                class_mode = 'binary'
                                                # 라벨 번호는 0번부터 시작, 폴더는 알파벳 순서로 읽음
                                                )

# 라벨링 결과 확인
print(train_generator.class_indices)
print(test_generator.class_indices)
# 고양이 0, 개 1

# cnn 신경망 구조를 직접 쌓기!
# 뼈대 구축
cnn_model2 = Sequential()

# 합성곱층(convolution) - 특성 추출부
# 필터 크기, 필터 개수, 스트라이드, 패딩, 활성화, 입력데이터크기
# 맥스풀링 - 지역 내에서 최대값 추출하기
cnn_model2.add(Conv2D(filters=32, kernel_size=(3, 3), padding='same',
                     activation='relu', input_shape=(224, 224, 3)))
cnn_model2.add(MaxPooling2D(pool_size=2)) # 주요 특징값 추출

cnn_model2.add(Conv2D(filters=64, kernel_size=(3, 3), padding='same', activation='relu'))
cnn_model2.add(MaxPooling2D(pool_size=2)) # 주요 특징값 추출

cnn_model2.add(Conv2D(filters=128, kernel_size=(3, 3), padding='same', activation='relu'))
cnn_model2.add(MaxPooling2D(pool_size=2)) # 주요 특징값 추출

# 전결합층(mlp) - 분류부(분류하는 층, 개/고양이 분류)
cnn_model2.add(Flatten())
cnn_model2.add(Dense(256, activation='relu'))
cnn_model2.add(Dense(1, activation='sigmoid'))

# 모델 요약
cnn_model2.summary()

# compile
cnn_model2.compile(loss='binary_crossentropy',
                   optimizer = Adam(learning_rate=0.001),
                   metrics=['accuracy'])

# fit - 증식 연결
# 이미지 증식할 때는 조기학습중단을 사용하지 X
# 이미지 증식할 경우 학습이 개선되면서 성능의 변동이 많을 수 있음
# 학습횟수를 많이 설정할 필요가 있음
cnn_h2 = cnn_model2.fit_generator(generator = train_generator,
                        epochs = 50,
                        validation_data = test_generator)

# acc, val_acc 시각화
# cnn_model과 성능 비교해보기
plt.figure(figsize=(10, 3))
# plt.plot(mlp_h.history['accuracy'], label='acc) 도 가능
plt.plot(cnn_h2.history['accuracy'], label='acc')
plt.plot(cnn_h2.history['val_accuracy'], label='val_acc')
plt.legend()
plt.show()
# mlp와 일반 cnn보다 증식된 데이터로 학습 cnn이 성능이 제일 높게 나옴
# 그래도 여전히 train에 대한 과대적합이 이루어지고 있는 현상이 보임
# val, test 데이터를 잘 맞출 수 있게 하는 방법은 없을까?

### 전이학습
 - 사전에 학습된 모델을 이용하여 개, 고양이 분류를 진행해보자
 - 미세조정방식을 활용해보자
   - vgg16모델의 마지막 컨볼루션층과 mlp층을 미세하게 조정하여 활용

print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)

# 사전학습 모델 불러오기
from tensorflow.keras.applications import VGG16

vgg16_model = VGG16(include_top = False,
                    weights = 'imagenet',
                    input_shape = (224, 224, 3)
                    )
# include_top = False : 불러온 vgg16 모델의 mlp층을 사용하지 않겠다 -> 특징추출부만 사용
# weights : ImageNet 경진대회 (이미지 인식/분류)에서 학습된 가중치(w)를 그대로 가지고 옴
# 분류 가능한 클래스의 개수는 1000개 (100만장의 이미지 데이터셋 학습함)

# 요약
vgg16_model.summary()

# 미세 조정 방식 활용
# 층 이름 확인
for i in vgg16_model.layers :
  print(i.name) # 층 이름만 출력 레이어.name

# 신경망 설계
frs_model = Sequential() # 뼈대 구축

# ----------------------------
# 미세 조정 방식 적용
# 마지막 컨볼루션 층만 학습 가능하도록 설정
# block5_conv3 학습 가능하게 설정, 나머지층은 동결(고정)
for layer in vgg16_model.layers :
  if layer.name == 'block5_conv3' :
    layer.trainable = True    # 학습 가능하게
  else :
    layer.trainable = False   # 학습 불가능하게 (동결, 고정)

# ----------------------------
# vgg16 특성 추출부 연결
frs_model.add(vgg16_model)
# 전결합층 연결
# mlp -> 1차원
frs_model.add(Flatten())
frs_model.add(Dense(128, activation='relu')) # 중간층
frs_model.add(Dense(1, activation='sigmoid')) # 출력층
frs_model.summary()

# 2. 학습/평가 방법 설정
frs_model.compile(loss='binary_crossentropy',
                  optimizer = Adam(learning_rate=0.0001),
                  metrics=['accuracy'])

# 3. 학습
# 반복횟수 10
# fit, 훈련 문제, 답
# 0.3 검증데이터 분리
frs_h = frs_model.fit(X_train, y_train, validation_split = 0.3,
                      epochs = 10)
# batch_size = 32 (기본값)

# 시각화
# acc, val_acc
plt.figure(figsize=(10, 3))

plt.plot(frs_h.history['accuracy'], label='acc')
plt.plot(frs_h.history['val_accuracy'], label='val_acc')
plt.legend()
plt.show()

# cnn + 이미지 증식했을 때보다 전이 학습 모델이 성능 개선이 많이 됨

# test 평가
frs_model.evaluate(X_test, y_test)[1]

'Computer Engineering > 딥러닝' 카테고리의 다른 글

yolov8_chinchilla_detect  (0) 2023.07.07
yolov6_chinchilla_detect  (0) 2023.07.07
OpenCV04_픽셀 및 채널  (0) 2023.07.06
OpenCV03_동영상로드  (0) 2023.07.06
OpenCV02_이진화(흑백 이미지)  (0) 2023.07.03

댓글