본문 바로가기
Deep Learning/CNN

딥러닝 다중분류 모델 만들기 [ CNN / VGG16 ]

by leehii 2022. 9. 19.

1. 데이터 크롤링

from selenium import webdriver as wb # 브라우저를 조작하는 도구
from selenium.webdriver.common.keys import Keys # 키 입력을 도와주는 도구(키보드)
from bs4 import BeautifulSoup as bs # 문서를 파싱해서 선택자 활용을 도와주는 도구
from tqdm import tqdm # 반복문 진행 정도를 시각화해주는 도구
from urllib.request import urlretrieve # 이미지 다운로드를 도와주는 도구
import time # 시간제어 도구
import os # 폴더 생성,삭제,이동 등을 도와주는 도구

import chromedriver_autoinstaller
chrome_ver = chromedriver_autoinstaller.get_chrome_version().split('.')[0]
driver_path = f'./{chrome_ver}/chromedriver.exe'
if os.path.exists(driver_path):
    print(f"chrom driver is insatlled: {driver_path}")
else:
    print(f"install the chrome driver(ver: {chrome_ver})")
    chromedriver_autoinstaller.install(True)


driver = wb.Chrome(driver_path
keyword = ['피오라','잭스','카밀']

# 이미지가 저장될 폴더 생성하기
# 해당 폴더가 있는지 확인
for i in range(3) :
    if os.path.isdir(f'./{keyword[i]}') == False :
        os.mkdir(f'./{keyword[i]}') # 폴더 생성
        
for i in range(3) :
    url = f'https://www.google.com/search?q={keyword[i]}&source=lnms&tbm=isch&sa=X&ved=2ahUKEwjs85-GsN7vAhX4xYsBHR6aBg0Q_AUoAXoECAEQAw&biw=1745&bih=852'

    options = wb.ChromeOptions()
    options.add_argument('headless')

    driver = wb.Chrome(options=options) # 브라우져 생성
    driver.get(url) # url 요청
    time.sleep(5) # 페이지 로딩까지 5초 대기

    cnt = 0
    pre_img_src = [] # 이전에 다운로드된 경로

    for j in range(10) :
        img_html = bs(driver.page_source,'html.parser')

        # 이미지 태그 수집
        images = img_html.select('img.rg_i.Q4LuWd')

        # 이미지 태그의 src 속성 값 추출
        img_src = []
        for img in images :
            src = img.get('src')
            if src != None : # img 태그에 src 속성이 없는 경우
                if src not in pre_img_src : # 이전에 다운로드한 경로에 있는지 검사
                    img_src.append(src)
            else : # img 태그에 src 속성이 있는 경우
                src = img.get('data-src')
                if src not in pre_img_src :
                    img_src.append(src)

        # 파일 다운로드
        # img_src를 반복문으로 돌면서 저장, tqdm 사용
        for src in tqdm(img_src) :
            cnt += 1
            try :
                # urlretrieve(src,'./{}/{}.png'.format(keyword,cnt))
                urlretrieve(src, f'./{keyword[i]}/{cnt}.png')
            except :
                print("수집불가")
                continue

        pre_img_src += img_src # 다운로드한 경로를 이전 리스트에 추가    
            
        # 화면 스크롤
        for i in range(6):
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            # driver.find_element_by_css_selector('body').send_keys(Keys.PAGE_DOWN)
            time.sleep(1)

2. 데이터 전처리 (로컬 환경) : 신경망 학습전 이미지 데이터 배열데이터(수치)로 변환

 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from PIL import Image

# 파일 경로
n_dir = '피오라/'
m_dir = '잭스/'
s_dir = '카밀/'

# 파일 이름 불러오기
n_files = os.listdir(n_dir)
m_files = os.listdir(n_dir)
s_files = os.listdir(n_dir)

#파일경로와 이미지 이름 결합
temp_path = os.path.join(n_dir, n_files[0])

#이미지 로딩후 배열데이터로 변환함수 (사진크기 224 * 224)
def load_img(folder_path, file_name, img_size_shape=(224,224) ) :
    img_list = []
    for i in file_name :
        
        # 폴더경로와 파일명 결합
        path = os.path.join(folder_path,i)
        
        # 파일오픈, 크기조정 (resize : 파일 사이즈 변형)
        img = Image.open(path).resize(img_size_shape).convert('RGB')
        
        # numpy 배열로 변경 후 빈 리스트에 추가하기
        img_list.append(np.array(img))
    
    # 리스트도 Numpy 배열로 변경해서 리턴
    return np.array(img_list)

# 데이터 확인
n_train = load_img(n_dir,n_files,img_size_shape=(224,224))
m_train = load_img(m_dir,n_files,img_size_shape=(224,224))
s_train = load_img(s_dir,n_files,img_size_shape=(224,224))

print(n_train.shape) # 출력값 (400, 224, 224, 3) 1 or 3
print(m_train.shape) # 출력값 (400, 224, 224, 3)	
print(s_train.shape) # 출력값 (400, 224, 224, 3)

plt.imshow(s_train[0]) # 이미지 보기

# 문제 / 답 데이터 생성 (np.concatenate() : 배열을 합치는 함수)
X = np.concatenate(
    [n_train, m_train, s_train]
)

y= np.array(
    [0]*400+[1]*400+[2]*400
)

#데이터 분류
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size = 0.2, random_state = 7
)

print(X_train.shape) #출력값 (960, 224, 224, 3)
print(X_test.shape) # 출력값 (240, 224, 224, 3)
print(y_train.shape) # 출력값 (960,)
print(y_test.shape) # 출력값 (240,)

# NPZ 파일로 변환 (Numpy ZIp)

np.savez_compressed(
    'soccer.npz',
    X_train = X_train, 
    X_test = X_test,
    y_train = y_train,
    y_test = y_test) #  좌측 : 변수명 / 우측 : 실제 만든 데이터

 

3. 딥러닝 다중분류 모델만들기

 

    a. 데이터 불러오기

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

# 데이터 불러오기
path = '/~~경로~파일명~.npz' # 위에서 크롤링후 npz파일로 변환한 데이터
data = np.load(path)

X_train = data['X_train']
X_test = data['X_test']
y_train = data['y_train']
y_test = data['y_test']

print(X_train.shape) # 출력값 (960, 224, 224, 3)
print(X_test.shape) # 출력값 (240, 224, 224, 3)
print(y_train.shape) # 출력값 (960,)
print(y_test.shape) # 출력값 (240,)

 

    b-1. MLP로 모델링하기

 

# 답데이터 원핫인코딩

from tensorflow.keras.utils import to_categorical
y_train_oh = to_categorical(y_train) 
y_test_oh = to_categorical(y_test) 

# 모델 구성
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense,Flatten

model = Sequential() #객체

model.add(Flatten(input_shape=(224,224,3))) #입력층
model.add(Dense(units=200, activation = 'relu'))
model.add(Dense(units=100, activation = 'relu'))
model.add(Dense(units=50, activation = 'relu'))
model.add(Dense(units = 3,activation = 'softmax')) #출력층.


# 학습방법 /평가방법 설정 (Categorical_crossentropy에 레이블들의 원핫인코딩까지 지원)
model.compile(
    loss = 'sparse_categorical_crossentropy',     
    optimizer = 'Adam',
    metrics = ['accuracy']
)

# 모델 학습
his = model.fit(X_train, y_train,
                validation_split = 0.2,
                batch_size = 128,
                epochs = 30)
                
# 모델 예측,평가 (pre와 y_test를 각각 출력하여 비교)
pre = model.predict(X_test)
pre
y_test

# 정확도 외 정밀도, 재현율, F1 스코어 확인해보기
from sklearn.metrics import classification_report
print(classification_report(y_test, np.argmax(pre, axis=1)))
# - classification_report(평가용정답, 예측용정답) 
# - np.argmax() : 가장 큰 값의 인덱스를 반환하는 함수
# - axis : 2차원 배열인 pre의 내부 배열들(1차원)만 보겠다는 뜻

    b-2. CNN으로 모델링하기

 

# 모델구성
from tensorflow.keras.layers import Conv2D, MaxPooling2D
cnn_model = Sequential()

# Conv 특징 강조
cnn_model.add(Conv2D(input_shape=(224,224,3),# 1흑백 3컬러
                     filters = 128, # 필터 : 추출하는 특징의 개수
                     kernel_size = (3,3),
                     padding = 'same', # 원본데이터 크기에 맞게 적용해라 / valid : padding 적용하지마라
                     activation = 'relu'))
                     
                     
# pooling 핵심특징만 남기기
cnn_model.add(MaxPooling2D(pool_size =2 ))

# 층쌓기
cnn_model.add(Conv2D(filters = 256, 
                     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))

cnn_model.add(Conv2D(filters = 64, 
                     kernel_size = (3,3),
                     padding = 'same', 
                     activation = 'relu'))
cnn_model.add(MaxPooling2D(pool_size=2))

# 분류기
cnn_model.add(Flatten())
cnn_model.add(Dense(128, activation = 'relu'))
cnn_model.add(Dense(64, activation = 'relu'))
cnn_model.add(Dense(32, activation = 'relu'))
cnn_model.add(Dense(3, activation = 'softmax'))

#학습/평가방법 설정
cnn_model.compile(loss = 'sparse_categorical_crossentropy',
                  optimizer = 'adam',
                  metrics = ['accuracy'])
                  
cnn_model.fit(X_train, y_train,
              validation_split = 0.2,
              epochs = 30,
              batch_size = 128)
              
# 예측/평가
pre = cnn_model.predict(X_test)
print(classification_report(y_test, np.argmax(pre,axis = 1)))

    c. VGG16으로 모델링하기

     vgg16 : CNN모델을 기반으로 사전에 학습된 신경망

 

from tensorflow.keras.applications import VGG16

# include_top : MLP(분류기)를 사용하지 안고 특성추출부만 사용하겠다, 즉 특성추출방식
# weights = 'imagenet' : 이미지넷에서 학습된 가중치를 그대로 가져오겠다
vgg_model = VGG16(include_top = False,
                  weights = 'imagenet',
                  input_shape =(224,224,3)
)

# 객체 선언
cnn_model2 = Sequential()

# VGG16모델 (특성추출부)
cnn_model2.add(vgg_model)

# MLP 층
cnn_model2.add(Flatten())
cnn_model2.add(Dense(128, activation = 'relu'))
cnn_model2.add(Dense(64, activation = 'relu'))
cnn_model2.add(Dense(32, activation = 'relu'))
cnn_model2.add(Dense(3, activation = 'softmax'))

#학습방법 / 평가방법
cnn_model2.compile(loss = 'sparse_categorical_crossentropy',
                   optimizer = 'adam',
                   metrics = ['accuracy'])
                   
# 학습
cnn_model2.fit(X_train, y_train,
               validation_split = 0.2,
               batch_size = 128,
               epochs = 30)
               
               
# 예측 / 평가
pre = cnn_model2.predict(X_test)
print(classification_report(y_test, np.argmax(pre, axis=1)))

 

cnn_model3 = Sequential()

# VGG16모델 (특성추출부)
cnn_model3.add(vgg_model)

# MLP 층
cnn_model3.add(Flatten())
cnn_model3.add(Dense(128, activation = 'relu'))
cnn_model3.add(Dense(64, activation = 'relu'))
cnn_model3.add(Dense(32, activation = 'relu'))
cnn_model3.add(Dense(3, activation = 'softmax'))

cnn_model3.compile(loss = 'sparse_categorical_crossentropy',
                   optimizer = 'adam',
                   metrics = ['accuracy'])

cnn_model3.fit(X_train, y_train,
               validation_split = 0.2,
               batch_size = 128,
               epochs = 30)
               
               
pre = cnn_model3.predict(X_test)
print(classification_report(y_test, np.argmax(pre, axis=1)))

 

cnn_model4 = Sequential()

# VGG16모델(특성추출부)
cnn_model4.add(vgg_model)

# MLP층
cnn_model4.add(Flatten())

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

# 출력층
cnn_model4.add(Dense(3, activation='softmax'))

cnn_model4.summary()

cnn_model4.compile(loss = 'sparse_categorical_crossentropy',
                   optimizer = 'Adam',
                   metrics=['accuracy']
                   )

cnn_model4.fit(X_train,y_train,
               validation_split=0.2,
               epochs=30,
               batch_size=128
               )
               
# 이미지 증식을 위한 ImageDateGenerator 라이브러리 임포트
# 이미지 증식 관련 공식문서 : https://keras.io/ko/preprocessing/image/

from tensorflow.keras.preprocessing.image import ImageDataGenerator

# ImaggeDateGenerator : 이미지 데이터를 증식시켜주기 위한 조건을 설정하는 함수
aug = ImageDataGenerator(rotation_range = 30,       #이미지 회전각도 범위설정
                         width_shift_range = 0.2,   # 20%내외로 수평이동
                         height_shift_range = 0.2,  # 20%내외로 수직이동
                         zoom_range = 0.2,          # 0.8  ~ 1.2배로 축소 / 확대
                         horizontal_flip = True, # 수평방향으로 뒤집기
                         fill_mode = 'nearest'  # 이미지 변형될떄 생기는 공간을 근처 픽셀로 채우기 
                         )
                         
cnn_model4.fit(aug.flow(X_train, y_train,batch_size = 128),
               
               # steps_per_epoch : 한 epoch당 몇번 이미지를 증식시킬지?
               # 한 epoch당 7.5번씩 돌고 끝남
               # 128 * 7.5 = 960
               # 960장의 새로운 이미지를 1 epoch마다 X_train의 갯수만큼 증가시킴
               steps_per_epoch = len(X_train) / 128 ,
               epochs = 30)
               
pre = cnn_model4.predict(X_test)
print(classification_report(y_test, np.argmax(pre,axis=1)))