1. 비행기, 자동차, 배 이미지 분류 1

비행기 vs 자동차 - 강아지 vs 고양이 이미지 분류를 넘어 자동차, 배, 비행기 3종 이미지를 분류해보자. 이를 위해서 CNN(Convolution Neural Network)을 사용하는데 CNN 아키텍쳐는 입력 데이터가 이미지라는 가정을하여 이미지 데이터가 갖는 특성들을 인코딩에 반영한다.

2. 이미지 데이터 가져오기

비행기 vs 자동차 - 강아지 vs 고양이와 동일한 방법으로 이번에는 배를 추가한다.

  • phantomJS를 설치한다.
  • imageScrape.js 파일으로 R로 가져와서 구글 이미지 검색어를 바꾸어서 phantomJS를 실행시킨다.
  • 다운로드 받은 1.html 파일을 파싱하여 이미지 url을 추출한다.
  • downloadImages() 함수를 참조하여 이미지를 다운로드한다.
# 0. 환경설정 -----
library(tidyverse)
library(purrr)
library(rvest) 
library(keras) # install_keras()
library(EBImage)
library(reticulate)

# 1. 검색어 불러오기 -----

scrapeJSSite <- function(searchTerm){
    url <- paste0("https://www.google.de/search?q=",searchTerm, "&source=lnms&tbm=isch&sa=X")
    
    lines <- readLines("code/airplane_car/imageScrape.js")
    lines[1] <- paste0("var url ='", url ,"';")
    writeLines(lines, "code/airplane_car/imageScrape.js")
    
    ## 다운로드 웹사이트 실행
    system("phantomjs code/airplane_car/imageScrape.js")
    
    pg <- read_html("1.html")
    files <- pg %>% html_nodes("img") %>% html_attr("src")
    df <- data.frame(images=files, search=searchTerm)
    return(df)
}

# 2. 검색 이미지 다운로드 함수 -----

downloadImages <- function(files, type, outPath="data/images"){
    for(i in 1:length(files)){
        download.file(files[i], destfile = paste0(outPath, "/", type, "_", i, ".jpg"), mode = 'wb')
    }
}

# 3. 이미지 다운로드 실행 -----
## 3.1. 비행기 이미지 다운로드 
airplane_df <- scrapeJSSite(searchTerm = "airplane") %>% 
    filter(!str_detect(images, "tia.png"))

downloadImages(as.character(airplane_df$images), "airplane")

## 3.2. 자동차 이미지 다운로드 
car_df <- scrapeJSSite(searchTerm = "car") %>% 
    filter(!str_detect(images, "tia.png"))

downloadImages(as.character(car_df$images), "car")

## 3.3. 배 이미지 다운로드 
ship_df <- scrapeJSSite(searchTerm = "ship jpg") %>% 
    filter(!str_detect(images, "tia.png"))

downloadImages(as.character(ship_df$images), "ship")

images_df <- bind_rows(airplane_df, car_df) %>% 
    bind_rows(ship_df)

DT::datatable(images_df)

3. 이미지 데이터 살펴보기

다운로드받은 이미지를 쭉 살펴보고 나서 비행기, 자동차, 배 이미지 분류 딥러닝 모형구축을 서둘러 보자. 앞서 다운로드 받은 이미지 파일이 비행기 20개, 자동차 20개, 배 20개 data/images/ 디렉토리에 저장되어 있다. EBImage 팩키지 readImage() 함수로 이미지 데이터를 모두 불러와서 하나의 리스트에 담아 놓는다. 그리고 나서 Base 그래프 시각화 함수, plot()을 사용해서 한장의 그래프로 담아본다.

# 1. 이미지 불러오기 -----

img_lst <- list.files("data/images/")
img_lst <- map(img_lst, function(x) paste0("data/images/", x))

imgs_lst <- map(img_lst, readImage)

## 1.1. 이미지 살펴보기 -----

par(mfrow = c(10,6))
for (i in 1:60) plot(imgs_lst[[i]])

이미지 크기가 균일하지 않기 때문에 EBImage 팩키지 resize() 함수를 통해 폭(width, w)과 높이(height, h)가 100인 크기로 통일시킨 후에 combine() 함수와 tile() 함수를 통해 하나의 그래프로 만들어 살펴본다.

# 2. 이미지 시각화 -----
imgs_resize_lst <- map(imgs_lst, resize, w =100, h=100)

imgs_resize_combined <- combine(imgs_resize_lst)

tile(imgs_resize_combined, 8) %>% 
    display()

4. 비행기, 자동차, 배 이미지 분류

4.1. 자료구조 맞추기

CNN 자료구조를 맞추기 위해서 aperm() 함수를 통해 입력받은 데이터 자료구조를 맞춰 넣는다.

# 2. 이미지 데이터 케라스 모형용으로 변환 -----
## 2.1. 훈련데이터 모형설계행렬(X) 

train_x <- combine(imgs_resize_lst)
train_x <- aperm(train_x, c(4, 1, 2, 3))

## 2.2. 훈련데이터 예측(Y)
train_y <- c(rep(0,20), rep(1,20), rep(2,20))
train_label <- to_categorical(train_y)

4.2. 딥러닝 모형 적합

keras_model_sequential() 모형을 생성시키면서 모형구조를 설정한다. input_shape는 앞서 정의한 비행기, 자동차, 배 이미지 크기가 폭(100)과 높이(100) 그리고 RGB채널로 3에 맞춰넣는다. 그리고 나서 layer_conv_2d(), layer_max_pooling_2d(), layer_dropout()을 조합하여 계층을 적절히 쌓는다. layer_flatten()으로 출력계층으로 넘길 준비를 하고 나서 layer_dense(), 마지막으로 layer_dense()activation = 'softmax'을 설정하여 출력값을 사전 정의한다.

그리고 나서 모형 컴파일에서 손실함수(loss)와 최적화 방식(optimizer) 그리고 극대화하고자 하는 측도(metric)를 지정한다. 마지막으로 epochs, batch_size, validation_split를 설정하면 fit() 함수를 통해 딥러닝 학습이 시작된다.

결과값은 plot() 함수를 통해 정적 그래프로 확인할 수 있다.

# 3. 딥러닝 모형 -----
## 3.1. 모형 설계
car_airplane_ship_model <- keras_model_sequential()

car_airplane_ship_model %>%
    layer_conv_2d(filters = 32, 
                  kernel_size = c(3,3),
                  activation = 'relu',
                  input_shape = c(100, 100, 3)) %>%
    layer_conv_2d(filters = 32,
                  kernel_size = c(3,3),
                  activation = 'relu') %>%
    layer_max_pooling_2d(pool_size = c(2,2)) %>%
    layer_dropout(rate = 0.25) %>%
    layer_conv_2d(filters = 64,
                  kernel_size = c(3,3),
                  activation = 'relu') %>%
    layer_conv_2d(filters = 64,
                  kernel_size = c(3,3),
                  activation = 'relu') %>%
    layer_max_pooling_2d(pool_size = c(2,2)) %>%
    layer_dropout(rate = 0.25) %>%
    layer_conv_2d(filters = 64,
                  kernel_size = c(3,3),
                  activation = 'relu') %>%
    layer_conv_2d(filters = 64,
                  kernel_size = c(3,3),
                  activation = 'relu') %>%
    layer_max_pooling_2d(pool_size = c(2,2)) %>%
    layer_dropout(rate = 0.25) %>%
    layer_flatten() %>%
    layer_dense(units = 256, activation = 'relu') %>%
    layer_dropout(rate=0.25) %>%
    layer_dense(units = 3, activation = 'softmax') %>%
    
summary(car_airplane_ship_model)
___________________________________________________________________________
Layer (type)                     Output Shape                  Param #     
===========================================================================
conv2d_1 (Conv2D)                (None, 98, 98, 32)            896         
___________________________________________________________________________
conv2d_2 (Conv2D)                (None, 96, 96, 32)            9248        
___________________________________________________________________________
max_pooling2d_1 (MaxPooling2D)   (None, 48, 48, 32)            0           
___________________________________________________________________________
dropout_1 (Dropout)              (None, 48, 48, 32)            0           
___________________________________________________________________________
conv2d_3 (Conv2D)                (None, 46, 46, 64)            18496       
___________________________________________________________________________
conv2d_4 (Conv2D)                (None, 44, 44, 64)            36928       
___________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)   (None, 22, 22, 64)            0           
___________________________________________________________________________
dropout_2 (Dropout)              (None, 22, 22, 64)            0           
___________________________________________________________________________
conv2d_5 (Conv2D)                (None, 20, 20, 64)            36928       
___________________________________________________________________________
conv2d_6 (Conv2D)                (None, 18, 18, 64)            36928       
___________________________________________________________________________
max_pooling2d_3 (MaxPooling2D)   (None, 9, 9, 64)              0           
___________________________________________________________________________
dropout_3 (Dropout)              (None, 9, 9, 64)              0           
___________________________________________________________________________
flatten_1 (Flatten)              (None, 5184)                  0           
___________________________________________________________________________
dense_1 (Dense)                  (None, 256)                   1327360     
___________________________________________________________________________
dropout_4 (Dropout)              (None, 256)                   0           
___________________________________________________________________________
dense_2 (Dense)                  (None, 3)                     771         
===========================================================================
Total params: 1,467,555
Trainable params: 1,467,555
Non-trainable params: 0
___________________________________________________________________________
## 3.2. 모형 컴파일
car_airplane_ship_model %>%
    compile(loss = 'categorical_crossentropy',
            optimizer = optimizer_sgd(lr = 0.01,
                                      decay = 1e-6,
                                      momentum = 0.9,
                                      nesterov = T),
            metrics = c('accuracy'))

## 3.3. 모형 적합
car_airplane_ship_history <- car_airplane_ship_model %>%
    fit(train_x,
        train_label,
        epochs = 60,
        batch_size = 40,
        validation_split = 0.1)

plot(car_airplane_ship_history)

5.3. 딥러닝 모형 평가

evaluate()함수를 통해 손실함수를 통한 최종 결과값과 정확도(accuracy)를 살펴보고 딥러닝 모형향상을 위한 데이터프레임으로 정리한다.

## 3.4. 모형 평가 및 예측
car_airplane_ship_model %>% evaluate(train_x, train_label)
$loss
[1] 0.4996903

$acc
[1] 0.9
car_airplane_ship_pred <- car_airplane_ship_model %>% predict_classes(train_x)
car_airplane_ship_prob <- car_airplane_ship_model %>% predict_proba(train_x) %>% as_tibble() %>% 
    rename(airplane = V1, car = V2, ship = V3)

car_airplane_ship_df <- car_airplane_ship_prob %>% 
    mutate(img_no = row_number()) %>% 
    mutate(pred = car_airplane_ship_pred,
           actual = train_y) %>% 
    mutate(error = ifelse(pred == actual, 'good', 'bad')) %>% 
    arrange(error)

DT::datatable(car_airplane_ship_df)

5.4. 딥러닝 모형 예측모형 오류

딥러닝 예측모형에서 분류에 실패한 이미지를 뽑아 내서 시각적으로 살펴보고 개선방향에 대해 고민해본다.

## 3.5. 딥러닝 결과 보고서 작성
error_img_v <- car_airplane_ship_df %>% filter(error == "bad") %>% 
    pull(img_no)

error_img_lst <- list()

for(i in seq_along(error_img_v)) {
    error_img_lst[[i]] <- imgs_resize_lst[[error_img_v[i]]]
}

error_imgs <- combine(error_img_lst)
tile(error_imgs, length(error_img_lst)) %>%
    display()