Find The Capybaras!

Theresia Londong

2023-04-21

Photo by Photo by <a href="https://unsplash.com/@sushioutlaw?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Brian McGowan</a> on <a href="https://unsplash.com/collections/sBFA3nrKsRo/capybara-bro?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>

Photo by Photo by Brian McGowan on Unsplash

Intro

Dalam project LBB kali ini, kita akan mencoba untuk mengaplikasikan prinsip dasar Neural Network - Deep Learning pada kasus Image Classification. Tantangan yang akan coba kita selesaikan kali ini adalah menemukan Capybara dari 2 kelompok gambar yang berisikan capybara dan beaver. Keduanya berasal dari ordo rodent sehingga akan banyak kemiripan fitur antara keduanya, seberapa jauh kemampuan model yang akan kita dalam membedakan keduanya? Kita akan menggunakan semua matriks pada confusionMatrix() seperti nilai Accuracy, Sensitivity, Specificity dan Precision dalam evaluasi performa model kita. Selama model kita dapat melewati batas 75%, maka model tersebut akan dianggap baik. Kita akan menentukan toleransi dalam selisih nilai matriks antara data train dan data test maksimal sebesar 15%.

Mari kita mulai dengan mengunduh gambar dari list url hasil web scrapping dan menyimpannya dalam lokal direktori kita.

Persiapan

Unduh Paket

Kita akan membutuhkan beberapa paket untuk mengunduh dan menyimpan objek gambar hasil webscrapping pada lokal direktori, paket untuk manipulasi dan pembersihan data, manipulasi gambar, deep learning dengan keras serta evaluasi model hasil deep learning.

# membaca & menyimpan objek gambar 

library(jpeg)  
library(here) 

# manipulasi dan pembersihan data

library(tidyr) 
library(dplyr)
library(purrr)
library(stringr)

# Manipulasi data gambar
library(imager)

# Deep learning
library(keras)

# Evaluasi Model
library(caret)

#memilih conda env.
use_condaenv('r-tensorflow')

#nomor scientific
options(scipen = 999)

Unduh dan Simpan Gambar

Sebagai persiapan kita akan mengunduh data gambar yang akan kita simpan dalam folder capybara/ dan beaver/ yang akan kita gunakan nanti sebagai data training dan data test melalui proses cross validasi dengan proporsi sebesar 80:20.

#simpan list url hasil dari webscrapping dalam objek dataframe

# dataframe dengan object beaver
beav <- read.csv('beavers.csv')
n_bv <- nrow(beav)

# dataframe dgn objek capybara
capy <- read.csv('capybaras.csv')
n_cpy <- nrow(capy)

Jalankan fungsi di bawah ini untuk mengunduh setiap objek gambar dari url yang telah disimpan dalam dataframe sebelumnya.

#mengunduh gambar dari list url hasil scrapping dan menyimpan dalam local directory 

for (i in 1:n_bv) { #isi dengan jumlah kolom di setiap file berisikan list url
  myurl <- paste(beav[i,1], sep = "") #you need the exact column number so change 1 to that value and change df to your df's name
  z <- tempfile() #same as above
  try(download.file(myurl,z,mode="wb"), silent = TRUE)
  pic <- try(readJPEG(z), silent = TRUE)
  try(writeJPEG(pic, here("beaver/", paste("image", i, ".jpg", sep = ""))), silent = TRUE)
  try(file.remove(z), silent = TRUE)
}

EDA

Membuat path untuk mengakses data gambar pada folder data_input/ untuk masing-masing kelompok gambar.

folder_list <- list.files("data_input/")

folder_list
## [1] "beaver"   "capybara"
folder_path <- paste0('data_input/', folder_list, '/')

folder_path
## [1] "data_input/beaver/"   "data_input/capybara/"
# Mendapatkan nama file
file_name <- map(folder_path, 
                 function(x) paste0(x, list.files(x))
                 ) %>% 
  unlist()

# 6 file name pertama dan terakhir
head(file_name)
## [1] "data_input/beaver/image1.jpg"   "data_input/beaver/image10.jpg" 
## [3] "data_input/beaver/image100.jpg" "data_input/beaver/image102.jpg"
## [5] "data_input/beaver/image103.jpg" "data_input/beaver/image104.jpg"
tail(file_name)
## [1] "data_input/capybara/image92.jpg" "data_input/capybara/image94.jpg"
## [3] "data_input/capybara/image95.jpg" "data_input/capybara/image96.jpg"
## [5] "data_input/capybara/image97.jpg" "data_input/capybara/image98.jpg"

Ada berapa banyak data gambar dalam dataset kita?

#Mengetahui berapa banyak data untuk seluruh kategori  

length(file_name)
## [1] 461

Selanjutnya kita akan coba menampilkan sample data gambar kita sebagai berikut,

#Memilih gambar secara random
set.seed(88)
sample_image <- sample(file_name, 6)

# Load gambar ke R
img <- map(sample_image, load.image)

# Plot gambar
par(mfrow = c(2, 3)) #  tampilkan dlm 2 x 3 grid gambar
map(img, plot)

## [[1]]
## Image. Width: 2304 pix Height: 2304 pix Depth: 1 Colour channels: 3 
## 
## [[2]]
## Image. Width: 620 pix Height: 350 pix Depth: 1 Colour channels: 3 
## 
## [[3]]
## Image. Width: 1280 pix Height: 720 pix Depth: 1 Colour channels: 3 
## 
## [[4]]
## Image. Width: 1600 pix Height: 1200 pix Depth: 1 Colour channels: 3 
## 
## [[5]]
## Image. Width: 1300 pix Height: 866 pix Depth: 1 Colour channels: 3 
## 
## [[6]]
## Image. Width: 500 pix Height: 300 pix Depth: 1 Colour channels: 3

Dimensi Gambar

Dalam kasus image classification menggunakan neural-network/deep-learning, adalah penting untuk mengerti variasi ukuran / dimensi gambar untuk kemudian dapat diseragamkan. Karena dalam neural network, ukuran input akan mempengaruhi arsitektur model dan jumlah node pada input layer model deep-learning kita nanti akan sejumlah dimensi gambar setelah diseragamkan.

# Deskripsi Gambar Penuh
img <- load.image(file_name[1])
img
## Image. Width: 718 pix Height: 721 pix Depth: 1 Colour channels: 3
dim(img)
## [1] 718 721   1   3
# ssimpan dimensi gambar dalam data frame df_img
get_dim <- function(x){
  img <- load.image(x) 
  
  df_img <- data.frame(height = height(img),
                       width = width(img),
                       filename = x
                       )
  
  return(df_img)
}

get_dim(file_name[1])
##   height width                     filename
## 1    721   718 data_input/beaver/image1.jpg
# ambil sampel random pada file_name utk mendapat 100 gambar

set.seed(88)
sample_file <- sample(file_name, 100)

# jalankan fungsi get_dim()  utk tiap gambar
file_dim <- map_df(sample_file, get_dim)

head(file_dim, 10)
##    height width                         filename
## 1    2304  2304 data_input/capybara/image127.jpg
## 2     350   620     data_input/beaver/image2.jpg
## 3     720  1280  data_input/capybara/image73.jpg
## 4    1200  1600    data_input/beaver/image55.jpg
## 5     866  1300 data_input/capybara/image299.jpg
## 6     300   500   data_input/beaver/image203.jpg
## 7     460   860    data_input/beaver/image31.jpg
## 8     720   720 data_input/capybara/image214.jpg
## 9     720  1280   data_input/beaver/image116.jpg
## 10    180   300  data_input/capybara/image91.jpg

Untuk mengetahui statistik dari data gambar yang akan digunakan, kita manfaatkan fungsi summary().

# mengetahui statistik dari data sample gambar

summary(file_dim)
##      height           width          filename        
##  Min.   : 147.0   Min.   : 220.0   Length:100        
##  1st Qu.: 378.8   1st Qu.: 580.0   Class :character  
##  Median : 546.0   Median : 793.5   Mode  :character  
##  Mean   : 674.6   Mean   : 990.1                     
##  3rd Qu.: 724.2   3rd Qu.:1125.0                     
##  Max.   :3456.0   Max.   :5108.0

Dari informasi summary() 100 sample gambar, diketahui bahwa ukuran tinggi (height) gambar bervariasi dari 147 hingga 3456 pixel, sedangkan ukuran lebar (width) gambar bervariasi antara 220 sampai 5108 pixels.

Ketidakseragaman ukuran gambar sebagai input model nantinya harus ditangani dengan metode augmentasi data, atau pengurangan jumlah informasi data sampai dengan jumlah tertentu tanpa menghilangkan informasi yang signifikan di dalamnya.

Pre-Processing Data

Seperti disebutkan sebelumnya, bahwa mengetahui dimensi data gambar sangat dibutuhkan dalam pembuatan model karena nantinya akan dibutuhkan keseragaman ukuran input data yang dapat dicapai melalui proses modifikasi ukuran data lewat Data Augmentation dengan memanfaatkan fungsi image_data_generator(). Dalam hal ini kita akan mencoba untuk menyeragamkan data dimensi data menjadi 90 x 90 pixels dan membagi ke dalam batches dgn ukuran 32 data point per batch.

# tinggi x lebar data gambar yg diinginkan
target_size <- c(90, 90)

# ukuran batch utk training model
batch_size <- 32
#image generator

train_data_gen <- image_data_generator(rescale = 1/255, # Scale nilai pixel menjadi rentang 0-1
                                       horizontal_flip = T, # Balik gambar horisontal
                                       vertical_flip = T, # Balik gambar vertikal 
                                       rotation_range = 45, # Rotasi gambar dari 0 - 45 derajat
                                       zoom_range = 0.25, # perbesar perkecil gambar sesuai kisaran 
                                       validation_split = 0.2 # cross val. - 20% data dijadikan data validasi
                                       )

Pada proses diatas, kita melakukan beberapa variasi gambar yaitu rotasi/tilting, flip, zoom, agar data train dapat menegnali variasi dari data2 tersebut dan akibatnya model menjadi lebih robust.

Selanjutnya adalah proses inputasi pada image generator untuk menghasilkan data tarining dan validasi (cross validation). Disini kita akan menggunakan data dari folder /train untuk menghasilkan dataset training dan validasi, dengan proporsi 80:20. Nantinya model akan kita latih dengan data set tersebut dan coba kita evaluasi dengan dataset validasi yang berasal dari induk data yang sama.

Selanjutnya kita akan masukkan data ke dalam image_generator sekaligus dilakukan cross-validation antara data train dan data validation/test:

#memasukkan data gambar ke dalam image generator

# Training Dataset
train_image_array_gen <- flow_images_from_directory(directory = "data_input/", # Folder  data
                                                    target_size = target_size, # target dimensi gambar (64 x 64)  
                                                    color_mode = "rgb", # warna RGB 
                                                    batch_size = batch_size, 
                                                    seed = 88,  # set random seed
                                                    subset = "training", # utk training data
                                                    generator = train_data_gen
                                                    )

# Validation Dataset
val_image_array_gen <- flow_images_from_directory(directory = "data_input/",
                                                  target_size = target_size, 
                                                  color_mode = "rgb", 
                                                  batch_size = batch_size ,
                                                  seed = 88,
                                                  subset = "validation", # utk validation data
                                                  generator = train_data_gen
                                                  )

Untuk memastikan proporsi antar kelas dalam data train dan data validasi, kita jalankan fungsi berikut:

# jumlah training samples
train_samples <- train_image_array_gen$n

# jumlah validation samples
valid_samples <- val_image_array_gen$n

# jumlah kelas target / kategori
output_n <- n_distinct(train_image_array_gen$classes)

# proporsi kelas
table("\nFrequency" = factor(train_image_array_gen$classes)
      ) %>% 
  prop.table()
## 
## Frequency
##         0         1 
## 0.5594595 0.4405405

Dapat dilihat bahwa kedua kelas sudah proporsional atau seimbang dengan rasio 56 : 44. Proporsi kelas harus proporsional agar model yang kita hasilkan nantinya tidak bias terhadap salah satu kelas dan tidak robust terhadap data baru.

Label diasosikan dengan kelas sesuai urutan abjad, 0 : beaver 1 : capybara

CNN (Convolutional Neural Network)

Arsitektur Model Dasar

Untuk kasus klasifikasi image kita akan gunakan model CNN (Convolutional Neural Network) dimana secara dasar, arsitektur CNN terdiri atas layer yang sama dengan Neural Network pada umunya kecuali adanya layer_conv_2d() dan layer_max_pooling_2d() serta dilakukan flattening untuk flattening layer dari 2D array ke 1D array mendekati layer output.

Kita akan simpan model dasar kita dengan nama model_base yang akan dibuat dengan menggunakan arsitektur CNN sbb :

# Set Random Weight Mula2
tensorflow::tf$random$set_seed(88)

model_base <- keras_model_sequential(name = "base_model") %>% 
  
  # Convolution Layer
  layer_conv_2d(filters = 16, #jumlah filter
                kernel_size = c(3,3), # ukuran filter konvolusi
                padding = "same",
                activation = "relu",
                input_shape = c(target_size, 3) 
                ) %>% 

  # Max Pooling Layer
  layer_max_pooling_2d(pool_size = c(2,2)) %>% 
  
  # Flattening Layer
  layer_flatten() %>% 
  
  # Dense Layer
  layer_dense(units = 16,
              activation = "relu") %>% 
  
  # Output Layer
  layer_dense(units = output_n,
              activation = "softmax",
              name = "Output")
  
model_base
## Model: "base_model"
## ________________________________________________________________________________
## Layer (type)                        Output Shape                    Param #     
## ================================================================================
## conv2d (Conv2D)                     (None, 90, 90, 16)              448         
## ________________________________________________________________________________
## max_pooling2d (MaxPooling2D)        (None, 45, 45, 16)              0           
## ________________________________________________________________________________
## flatten (Flatten)                   (None, 32400)                   0           
## ________________________________________________________________________________
## dense (Dense)                       (None, 16)                      518416      
## ________________________________________________________________________________
## Output (Dense)                      (None, 2)                       34          
## ================================================================================
## Total params: 518,898
## Trainable params: 518,898
## Non-trainable params: 0
## ________________________________________________________________________________

Selanjutnya kita akan fitting model dasar kita dengan data train yang sudah disiapkan sebelumnya.

Model Fitting

Mula-mula, gunakan 30 epochs & 0.01 learning rate untuk proses fitting pada model_base dengan optimizer jenis ADAM dan loss function categorical_crossentropy.

model_base %>% compile(
    loss = "categorical_crossentropy",
    optimizer = optimizer_adam(lr = 0.01),
    metrics = "accuracy"
  )

# Fit data ke model
history <- model_base %>% 
  fit_generator(
  # training data
  train_image_array_gen,

  # training epochs
  steps_per_epoch = as.integer(train_samples / batch_size), 
  epochs = 30, 
  
  # validation data
  validation_data = val_image_array_gen,
  validation_steps = as.integer(valid_samples / batch_size)
)

Evaluasi Model

Evaluasi model dengan dataset validasi,

val_data <- data.frame(file_name = paste0("data_input/", val_image_array_gen$filenames)) %>% 
  mutate(class = str_extract(file_name, "beaver|capybara"))

head(val_data, 10)
##                          file_name  class
## 1    data_input/beaver\\image1.jpg beaver
## 2   data_input/beaver\\image10.jpg beaver
## 3  data_input/beaver\\image100.jpg beaver
## 4  data_input/beaver\\image102.jpg beaver
## 5  data_input/beaver\\image103.jpg beaver
## 6  data_input/beaver\\image104.jpg beaver
## 7  data_input/beaver\\image105.jpg beaver
## 8  data_input/beaver\\image106.jpg beaver
## 9   data_input/beaver\\image11.jpg beaver
## 10 data_input/beaver\\image110.jpg beaver

Memeriksa proporsi kelas di dataset validasi dan memastikan masing-masing kelas terwakilkan secara proporsional / seimbang.

prop.table(table(val_data$class))
## 
##    beaver  capybara 
## 0.5604396 0.4395604
# Membuat fungsi utk konversi data gambar ke array
image_prep <- function(x) {
  arrays <- lapply(x, function(path) {
    img <- image_load(path, target_size = target_size, 
                      grayscale = F # Set FALSE apabila gambar RGB
                      )
    
    x <- image_to_array(img)
    x <- array_reshape(x, c(1, dim(x)))
    x <- x/255 # merubah skala pixel gambar menjadi rentang 0-1
  })
  do.call(abind::abind, c(arrays, list(along = 1)))
}
test_x <- image_prep(val_data$file_name)

# Cek dimensi data test_x
dim(test_x)
## [1] 91 90 90  3

Dimensi object test_x sudah sesuai dengan jumlah data validasi yang kita cek di awal yaitu sebanyak 91 data point dengan ukuran 64 x 64 dan 3 layer warna (RGB). Selanjutnya data test_x akan digunakan untuk memprediksi hasil pemodelan dengan model_base

#prediksi menggunakan  data test_x
pred_test <- predict(model_base, test_x) %>% #menghasilkan peluang
               k_argmax() %>% #mengambil yg peluang paling besar 
               as.array() %>% 
               as.factor()

head(pred_test, 10)
##  [1] 0 1 1 1 1 1 1 0 1 1
## Levels: 0 1
# Konversi label numerik ke label huruf

decode <- function(x){
  case_when(x == 0 ~ "beaver",
            x == 1 ~ "capybara")
}

pred_test <- sapply(pred_test, decode) 

head(pred_test, 10)
##  [1] "beaver"   "capybara" "capybara" "capybara" "capybara" "capybara"
##  [7] "capybara" "beaver"   "capybara" "capybara"

Kemudian kita lakukan evaluasi dengan menggunakan confusionMatrix()

confusionMatrix(as.factor(pred_test), 
                as.factor(val_data$class)
                )

Hasil dari COnfusionMatrix menunjukkan bahwa performa model_base membutuhkan perbaikan yang signifikan karena nilai akurasi yang rendah (~ 50%), Sensitivity dan Specificity yang sangat timpang menunjukkan kemampuan belajar hanya kuat pada salah satu kelas.

Tuning Model

Setelah melihat performa model_base yang masih jauh dari target, maka kita akan melakukan sedikit penyesuaian pada model CNN kita seperti,

Pada model_tuned akan diimplementasikan sebanyak 5 layer konvolusi dengan arsitektur sbb:

  • #1 Convolutional layer untuk ekstraksi fitur dari gambar 2D dengan fungsi aktifasi relu
  • #2 Convolutional layer untuk ekstraksi fitur dari gambar 2D dengan fungsi aktifasi relu
  • Max pooling layer
  • #3 Convolutional layer untuk ekstraksi fitur dari gambar 2D dengan fungsi aktifasi relu
  • Max pooling layer
  • #4 Convolutional layer untuk ekstraksi fitur dari gambar 2D dengan fungsi aktifasi relu
  • Max pooling layer
  • #5 Convolutional layer untuk ekstraksi fitur dari gambar 2D dengan fungsi aktifasi relu
  • Max pooling layer
  • #6 Convolutional layer untuk ekstraksi fitur dari gambar 2D dengan fungsi aktifasi relu
  • Max pooling layer
  • layer_flatten() untuk flattening layer dari 2D array ke 1D array
  • Dense layer utk menangkap lebih banyak informasi dengan fungsi aktivasi relu
  • Dense layer utk output layer dengan fungsi aktivasi softmax
  • Ukuran kernel 5x5 hanya digunakan pada layer konvolusi #1 & #2, sedangkan layer lainnya akan menggunakan ukuran kernel seragam 3x3
  • Ukuran layer_max_pooling juga diseragamkan 2x2 untuk menghindari generalisasi nilai maksimum apabila dibuat terlalu besar
tensorflow::tf$random$set_seed(88)

model_tuned <- keras_model_sequential(name = "Tuned_00") %>% 
  
  # First convolutional layer
  layer_conv_2d(filters = 32,
                kernel_size = c(5,5), # 5 x 5 filters
                padding = "same",
                activation = "relu",
                input_shape = c(target_size, 3)
                ) %>% 
  
  # second conv layer
  layer_conv_2d(filters = 32,
                kernel_size = c(5,5), # 5 x 5 filters
                padding = "same",
                activation = "relu"
                ) %>%
  
  # Max pooling layer
  layer_max_pooling_2d(pool_size = c(2,2)) %>% 
  
  # third convolutional layer
  layer_conv_2d(filters = 64,
                kernel_size = c(3,3),
                padding = "same",
                activation = "relu"
                ) %>% 

  # Max pooling layer
  layer_max_pooling_2d(pool_size = c(2,2)) %>% 
  
  # forth convolutional layer
  layer_conv_2d(filters = 128,
                kernel_size = c(3,3),
                padding = "same",
                activation = "relu"
                ) %>% 
  
  # Max pooling layer
  layer_max_pooling_2d(pool_size = c(2,2)) %>% 

  # fifth convolutional layer
  layer_conv_2d(filters = 256,
                kernel_size = c(3,3),
                padding = "same",
                activation = "relu"
                ) %>% 
  
  # Max pooling layer
  layer_max_pooling_2d(pool_size = c(2,2)) %>% 
  
  # sixth convolutional layer
  layer_conv_2d(filters = 256,
             kernel_size = c(3,3), 
               padding = "same",
               activation = "relu"
               ) %>% 
  
  # Max pooling layer
  layer_max_pooling_2d(pool_size = c(2,2)) %>% 
  
  # Flattening layer
  layer_flatten() %>% 
  
  # Dense layer
  layer_dense(units = 90,
              activation = "relu") %>% 
  
  # Output layer
  layer_dense(name = "Output",
              units = 2, #sesuaikan dengan kelas output 
              activation = "softmax")

model_tuned
## Model: "Tuned_00"
## ________________________________________________________________________________
## Layer (type)                        Output Shape                    Param #     
## ================================================================================
## conv2d_6 (Conv2D)                   (None, 90, 90, 32)              2432        
## ________________________________________________________________________________
## conv2d_5 (Conv2D)                   (None, 90, 90, 32)              25632       
## ________________________________________________________________________________
## max_pooling2d_5 (MaxPooling2D)      (None, 45, 45, 32)              0           
## ________________________________________________________________________________
## conv2d_4 (Conv2D)                   (None, 45, 45, 64)              18496       
## ________________________________________________________________________________
## max_pooling2d_4 (MaxPooling2D)      (None, 22, 22, 64)              0           
## ________________________________________________________________________________
## conv2d_3 (Conv2D)                   (None, 22, 22, 128)             73856       
## ________________________________________________________________________________
## max_pooling2d_3 (MaxPooling2D)      (None, 11, 11, 128)             0           
## ________________________________________________________________________________
## conv2d_2 (Conv2D)                   (None, 11, 11, 256)             295168      
## ________________________________________________________________________________
## max_pooling2d_2 (MaxPooling2D)      (None, 5, 5, 256)               0           
## ________________________________________________________________________________
## conv2d_1 (Conv2D)                   (None, 5, 5, 256)               590080      
## ________________________________________________________________________________
## max_pooling2d_1 (MaxPooling2D)      (None, 2, 2, 256)               0           
## ________________________________________________________________________________
## flatten_1 (Flatten)                 (None, 1024)                    0           
## ________________________________________________________________________________
## dense_1 (Dense)                     (None, 90)                      92250       
## ________________________________________________________________________________
## Output (Dense)                      (None, 2)                       182         
## ================================================================================
## Total params: 1,098,096
## Trainable params: 1,098,096
## Non-trainable params: 0
## ________________________________________________________________________________

Model Fitting

Pada model_tuned digunakan epoch 50 dan learning rate of 0.001 dengan loss function sama seperti model_base

# compile model
model_tuned %>% 
  compile(
    loss = "categorical_crossentropy",
    optimizer = optimizer_adam(lr = 0.001),
    metrics = "accuracy"
  )

# fit model
history <- model_tuned %>% 
  fit_generator( 
  # training data
  train_image_array_gen,
  
  # epochs
  steps_per_epoch = as.integer(train_samples / batch_size), 
  epochs = 50, 
  
  # validation data
  validation_data = val_image_array_gen,
  validation_steps = as.integer(valid_samples / batch_size),
  
  # tampilkan progres tanpa menampilkan grafik
  verbose = 1,
  view_metrics = 0
)

Dengan fungsi plot(), kita akan mendapatkan hasil di bawah :

Evaluasi Model

Memanggil validasi data yang sudah kita siapkan sebelumnya :

val_data <- data.frame(file_name = paste0("data_input/", val_image_array_gen$filenames)) %>% 
  mutate(class = str_extract(file_name, "beaver|capybara"))

head(val_data, 10)
##                          file_name  class
## 1    data_input/beaver\\image1.jpg beaver
## 2   data_input/beaver\\image10.jpg beaver
## 3  data_input/beaver\\image100.jpg beaver
## 4  data_input/beaver\\image102.jpg beaver
## 5  data_input/beaver\\image103.jpg beaver
## 6  data_input/beaver\\image104.jpg beaver
## 7  data_input/beaver\\image105.jpg beaver
## 8  data_input/beaver\\image106.jpg beaver
## 9   data_input/beaver\\image11.jpg beaver
## 10 data_input/beaver\\image110.jpg beaver
# Fungsi konversi data gambar ke array
image_prep <- function(x) {
  arrays <- lapply(x, function(path) {
    img <- image_load(path, target_size = target_size, 
                      grayscale = F # Set FALSE jika gambar format RGB
                      )
    
    x <- image_to_array(img)
    x <- array_reshape(x, c(1, dim(x)))
    x <- x/255 # rescale image pixel
  })
  do.call(abind::abind, c(arrays, list(along = 1)))
}
test_x <- image_prep(val_data$file_name)

# Check dimension of testing data set
dim(test_x)
## [1] 91 90 90  3

Melakukan prediksi dengan data test_x yang berasal dari dataset validasi

pred_test <- predict(model_tuned, test_x) %>% #menghasilkan peluang
               k_argmax() %>% #mengambil yg peluang paling besar 
               as.array() %>% 
               as.factor()

head(pred_test, 10)
##  [1] 0 0 0 0 0 0 0 0 0 0
## Levels: 0

Merubah kelas numerik 0,1 menjadi label beaver & capybara.

# Konversi ke label
decode <- function(x){
  case_when(x == 0 ~ "beaver",
            x == 1 ~ "capybara")
}

pred_test <- sapply(pred_test, decode) 

head(pred_test, 10)
##  [1] "beaver" "beaver" "beaver" "beaver" "beaver" "beaver" "beaver" "beaver"
##  [9] "beaver" "beaver"

Evaluasi dengan confusionMatrix dan mendapatikan hasil berikut

confusionMatrix(as.factor(pred_test), 
                as.factor(val_data$class)
                )

Proses prediksi dengan data validasi untuk model_tuned menghasilkan,

  • Nilai matriks Accuracy <70 %
  • Belum memenuhi kriteria > 75% semua kelas khususnya pada matriks Specificity dan Pos Pred Value/Precision

Kesimpulan

  • Dari beberapa hasil percobaan tuning model yang dilakukan dengan modifikasi arsitektur CNN, penambahan layer, penambahan ukuran gambar inputan dari 64x64 pixel sampai 90x90 pixels, variasi ukuran matriks konvolusi, hingga variasi epoch dan learning rate, didapatkan hasil bahwa akurasi maksimal dari model masih berada id kisaran ~70%, nilai matriks Specificity & Precision selalu <75%, atau dengan kata lain model cenderung membaca gambar sebagai beaver daripada capybara.

  • Solusi yang bisa dicoba untuk pengembangan selanjutnya adalah penambahan training data untuk kedua kelas. Kemiripan fitur pada beaver dan capybara membutuhkan bahan belajar yang lebih banyak untuk model kita. Menaikkan data training dari 50 - 100% mungkin bisa menjadi solusi untuk keterbatasan model saat ini.