Deep learning adalah subbidang kecerdasan buatan (AI) dan pembelajaran mesin yang melibatkan konstruksi dan pelatihan jaringan saraf tiruan untuk belajar dan merepresentasikan data secara hierarkis. Jaringan saraf tiruan ini terinspirasi dari struktur dan fungsi otak manusia, dengan lapisan-lapisan artificial neuron yang saling terhubung untuk memproses dan mengubah data masukan guna menghasilkan keluaran yang diinginkan. Deep learning merupakan pendekatan yang sangat efektif untuk mengatasi data tidak terstruktur seperti teks, suara, video, dan gambar. Pada sesi ini, kita akan mencoba membangun model deep learning dengan arsitektur convolutional neural network (CNN) untuk mengklasifikasikan gambar bunga berdasarkan jenisnya.
Dataset gambar yang digunakan dalam eksperimen ini diambil dari Kaggle (tautan : https://www.kaggle.com/datasets/alxmamaev/flowers-recognition). Terdapat 5 jenis bunga yang digunakan dalam eksperimen ini, yakni bunga jenis daisy, dandelion, rose, sunflower, dan tulip.
Before we do the analysis, we need to install and load the required library.
library(tidyverse) # data wrangling
library(imager) # image manipulation
library(keras) # deep learning
library(caret) # model eval# Cek apakah keras sudah berjalan dengan baik
# model <- keras_model_sequential()Dataset gambar yang disediakan dalam eksperimen ini diorganisir ke dalam folder terpisah berdasarkan label/kelas dari gambar-gambar tersebut. Oleh karena itu, kita perlu melakukan persiapan awal untuk memfasilitasi proses eksplorasi data.
folder_list <- list.files("data/")
folder_list#> [1] "daisy" "dandelion" "rose" "sunflower" "tulip"
Untuk mengakses konten di dalam setiap folder, kita menggabungkan nama folder dengan path atau direktori dari folder “train”
folder_path <- paste0("data/", folder_list, "/")
folder_path#> [1] "data/daisy/" "data/dandelion/" "data/rose/" "data/sunflower/"
#> [5] "data/tulip/"
Pada proses selanjutnya, kita akan menggunakan fungsi map() untuk mengiterasi dan mengumpulkan nama-nama file untuk setiap folder (daisy, sunflower, dsb). Fungsi map() akan mengembalikan sebuah daftar (list) dari nama-nama file, dan jika kita ingin menggabungkan nama-nama file dari ketiga folder yang berbeda, kita dapat dengan mudah melakukannya dengan menggunakan fungsi unlist().
# Get file name
file_name <- map(folder_path,
function(x) paste0(x, list.files(x))
) %>%
unlist()
# first 6 file name
head(file_name)#> [1] "data/daisy/100080576_f52e8ee070_n.jpg"
#> [2] "data/daisy/10140303196_b88d3d6cec.jpg"
#> [3] "data/daisy/10172379554_b296050f82_n.jpg"
#> [4] "data/daisy/10172567486_2748826a8b.jpg"
#> [5] "data/daisy/10172636503_21bededa75_n.jpg"
#> [6] "data/daisy/102841525_bd6628ae3c.jpg"
Kita juga dapat memeriksa 6 gambar terakhir dan melihat berapa banyak gambar yang kita miliki.
tail(file_name)#> [1] "data/tulip/9446982168_06c4d71da3_n.jpg"
#> [2] "data/tulip/9831362123_5aac525a99_n.jpg"
#> [3] "data/tulip/9870557734_88eb3b9e3b_n.jpg"
#> [4] "data/tulip/9947374414_fdf1d0861c_n.jpg"
#> [5] "data/tulip/9947385346_3a8cacea02_n.jpg"
#> [6] "data/tulip/9976515506_d496c5e72c.jpg"
length(file_name)#> [1] 4317
Untuk memeriksa isi file, kita dapat menggunakan fungsi load.image() dari paket imager. Sebagai contoh, mari tampilkan secara acak 6 gambar dari dataset.
# Randomly select image
set.seed(99)
sample_image <- sample(file_name, 6)
# Load image into R
img <- map(sample_image, load.image)
# Plot image
par(mfrow = c(2, 3)) # Create 2 x 3 image grid
map(img, plot)#> [[1]]
#> Image. Width: 180 pix Height: 240 pix Depth: 1 Colour channels: 3
#>
#> [[2]]
#> Image. Width: 240 pix Height: 159 pix Depth: 1 Colour channels: 3
#>
#> [[3]]
#> Image. Width: 320 pix Height: 240 pix Depth: 1 Colour channels: 3
#>
#> [[4]]
#> Image. Width: 500 pix Height: 333 pix Depth: 1 Colour channels: 3
#>
#> [[5]]
#> Image. Width: 500 pix Height: 332 pix Depth: 1 Colour channels: 3
#>
#> [[6]]
#> Image. Width: 320 pix Height: 240 pix Depth: 1 Colour channels: 3
Pada tahap ini, kita akan melakukan eksplorasi data gambar yang kita miliki. Kita akan memeriksa dimensi dari gambar-gambar tersebut dan meninjau distribusi data target. Tujuan dari eksplorasi ini adalah untuk mendapatkan wawasan tentang dataset dan memahami karakteristiknya dengan lebih baik.
Pertama, kita akan memeriksa dimensi dari gambar-gambar tersebut untuk memastikan bahwa mereka memiliki ukuran yang konsisten. Langkah ini sangat penting karena model deep learning umumnya memerlukan data masukan dengan dimensi yang seragam. Jika gambar-gambar memiliki ukuran yang berbeda-beda, kita mungkin perlu memprosesnya dengan merubah ukuran (resize) atau memotong (crop) agar menjadi seragam. Selanjutnya, kita akan meninjau distribusi data target, yang mewakili kelas atau label dari gambar-gambar tersebut. Memahami distribusi kelas ini penting karena hal ini memungkinkan kita mengidentifikasi ketidakseimbangan potensial dalam dataset. Ketidakseimbangan kelas dapat mempengaruhi kinerja model selama pelatihan, dan mungkin memerlukan langkah tambahan seperti augmentasi data atau menggunakan bobot kelas (class weights) untuk mengatasi masalah ini.
Kita akan melakukan pemeriksaan dimensi pada salah satu data menggunakan fungsi load.image.
# Full Image Description
img <- load.image(file_name[1])
img#> Image. Width: 320 pix Height: 263 pix Depth: 1 Colour channels: 3
Nilai tinggi (height) dan lebar (width) mewakili dimensi gambar dalam piksel, sedangkan nilai kanal warna (color channel) menunjukkan apakah gambar tersebut dalam format grayscale (kanal warna = 1) atau format RGB (kanal warna = 3). Dengan menggunakan fungsi dim(), kita dapat mengambil nilai untuk setiap dimensi, termasuk tinggi, lebar, kedalaman (depth), dan jumlah kanal warna.
dim(img)#> [1] 320 263 1 3
Setelah berhasil memasukkan sebuah gambar dan mendapatkan dimensinya, kita akan membuat sebuah fungsi yang dapat dengan cepat mengekstrak tinggi (height) dan lebar (width) dari gambar tersebut dan mengonversi informasi ini menjadi sebuah dataframe.
get_dim <- function(x){
img <- load.image(x)
df_img <- data.frame(height = height(img),
width = width(img),
file_name = x)
return(df_img)
}
get_dim(file_name[1])# Randomly get all images
set.seed(123)
sample_file <- sample(file_name)
# Run the get_dim() function for each image
file_dim <- map_df(sample_file, get_dim)
head(file_dim, 10)dim(file_dim)#> [1] 4317 3
Sekarang, mari kita periksa ringkasan (summary) dari tinggi (height) dan lebar (width) untuk semua data gambar yang tersedia.
summary(file_dim)#> height width file_name
#> Min. : 80.0 Min. : 134.0 Length:4317
#> 1st Qu.:214.0 1st Qu.: 240.0 Class :character
#> Median :240.0 Median : 320.0 Mode :character
#> Mean :253.1 Mean : 338.4
#> 3rd Qu.:329.0 3rd Qu.: 500.0
#> Max. :442.0 Max. :1024.0
Secara ringkas, dataset ini berisi gambar-gambar dengan tinggi dan lebar yang bervariasi, mulai dari 80.0 hingga 442.0 piksel dalam tinggi dan 134.0 hingga 1024.0 piksel dalam lebar. Rata-rata tingginya sekitar 253.1 piksel, dan lebar dengan rata-rata sekitar 338.4 piksel. Karena dimensi data berbeda, modifikasi akan dilakukan selanjutnya. Pendekatan umum melibatkan pengubahan ukuran gambar agar memiliki ukuran yang seragam.
Jika terdapat data dengan dimensi yang berbeda, modifikasi akan diterapkan selanjutnya. Pendekatan umum yang biasa digunakan adalah meresize (mengubah ukuran) gambar tersebut untuk memastikan ukuran yang seragam.
Pada bagian ini, kita akan menentukan ukuran masukan (input size)
untuk gambar, sehingga semua gambar masukan akan memiliki dimensi yang
serupa. Kita akan menggunakan ukuran 128 x 128 piksel saja
untuk nantinya bisa mempercepat komputasi. Selanjutnya, kita akan
mengatur ukuran batch (batch size) menjadi 128. Dalam deep learning,
batch size mengacu pada jumlah sampel yang akan digunakan dalam setiap
iterasi saat melatih model.
Dengan menggunakan ukuran masukan dan ukuran batch yang telah ditentukan, kita akan mempersiapkan dataset dan melatih model klasifikasi gambar untuk mengenali apakah gambar tersebut adalah pantai, hutan, atau gunung.
# Desired height and width of images
target_size <- c(128, 128)
# Batch size for training the model
batch_size <- 128Selanjutnya kita akan menggunakan metode yang disebut Augmentasi Gambar (Image Augmentation). Augmentasi gambar adalah teknik berharga untuk memperluas ukuran set pelatihan tanpa perlu mendapatkan gambar-gambar baru. Tujuan utamanya adalah untuk menyajikan model tidak hanya pada gambar-gambar asli, tetapi juga pada berbagai modifikasi gambar, seperti flipping (pembalikan), rotasi, zooming (pembesaran), cropping (pemotongan), dan transformasi lainnya. Dengan melakukan hal ini, tujuannya adalah menciptakan model yang lebih kuat dan dapat menggeneralisasi dengan baik terhadap variasi yang berbeda dalam data. Untuk mengimplementasikan augmentasi data, kita dapat menggunakan image data generator dari Keras.
Untuk melakukan augmentasi gambar, kita dapat menyediakan data pada sebuah generator. Di sini, kita akan membuat image generator untuk Keras dengan properti berikut:
Dibawah ini adalah ilustrasi apa yang akan dihasilkan dari proses image aumentation.
# Image Generator
train_data_gen <- image_data_generator(rescale = 1/255, # Rescaling pixel value
horizontal_flip = T, # Flip image horizontally
rotation_range = 20, # Randomly rotate images within the range of -20 to +20 degrees
validation_split = 0.2, # 20% data as validation data
shear_range = 0.2, #Shear the intensity by 0.2
brightness_range = c(0.7, 1.3), # Randomly change brightness by a factor between 0.7 and 1.3
width_shift_range=0.2, # Randomly shift images horizontally by 0.2 times the size of the image
height_shift_range=0.2, # Randomly shift images vertically by 0.2 times the size of the image
fill_mode = "nearest" # Fill empty pixels resulting from transformations with the nearest pixel value
)Pada langkah selanjutnya, kita akan membagi data pelatihan menjadi dua bagian, yaitu data pelatihan model dan data validasi, dengan perbandingan 8:2. Dengan membagi data, kita dapat mengembangkan dan mengevaluasi model machine learning secara lebih efektif, sehingga menghasilkan model yang lebih baik dan lebih dapat diandalkan.
Kita akan menggunakan data pelatihan model untuk melatih atau mengajari model kita untuk mengenali pola dan membuat prediksi, sementara data validasi akan digunakan untuk mengevaluasi kinerja model secara objektif. Setelah itu, kita dapat memasukkan data gambar kita (data pelatihan & data validasi) ke dalam generator menggunakan fungsi flow_images_from_directory(). Dari proses ini, kita akan mendapatkan gambar-gambar yang telah di-augmentasi baik untuk data pelatihan maupun data validasi.
# Training Dataset
train_image_array_gen <- flow_images_from_directory(directory = "data/", # Folder of the data
target_size = target_size, # target of the image dimension
color_mode = "rgb", # use RGB color
batch_size = batch_size ,
seed = 30, # set random seed
subset = "training", # declare that this is for training data
class_mode = "categorical",
generator = train_data_gen
)
# Validation Dataset
val_image_array_gen <- flow_images_from_directory(directory = "data/",
target_size = target_size,
color_mode = "rgb",
batch_size = batch_size ,
seed = 30,
subset = "validation", # declare that this is the validation data
class_mode = "categorical",
generator = train_data_gen
)Di sini, kita akan mengumpulkan beberapa informasi dari generator dan memeriksa proporsi kelas dari data train.
# Number of training samples
train_samples <- train_image_array_gen$n
train_samples#> [1] 3457
# Number of validation samples
valid_samples <- val_image_array_gen$n
valid_samples#> [1] 860
# Number of target classes/categories
output_n <- n_distinct(train_image_array_gen$classes)
output_n#> [1] 5
Berdasarkan informasi di atas, dapat terlihat bahwa kita memiliki 3457 gambar data pelatihan dan 860 gambar data validasi. Pada bagian selanjutnya, kita akan melihat distribusi/rasio dari label kelas pada data gambar yang kita miliki.
Sebelum melakukan pemodelan, penting bagi kita untuk memeriksa proporsi kelas data yang ada. Tujuannya adalah untuk mengidentifikasi ketidakseimbangan kelas yang mungkin ada. Ketidakseimbangan kelas dapat berdampak signifikan pada kinerja dan generalisasi model kita. Jika satu kelas secara signifikan overrepresented (lebih banyak jumlahnya) sementara kelas lain underrepresented (jumlahnya lebih sedikit), model mungkin menjadi condong ke arah kelas mayoritas, menyebabkan prediksi yang buruk untuk kelas minoritas. Selain itu, model mungkin tidak belajar secara efektif untuk mengenali pola dalam kelas minoritas karena kurangnya paparan selama pelatihan.
Dengan memeriksa proporsi kelas, kita dapat mengetahui seberapa seimbang atau tidaknya distribusi kelas dalam dataset. Jika ada ketidakseimbangan, kita dapat mengambil langkah-langkah seperti oversampling (menggandakan sampel kelas minoritas), undersampling (mengurangi sampel kelas mayoritas), atau menggunakan bobot kelas (class weights) saat melatih model untuk mengatasi masalah ini. Dengan melakukan langkah-langkah ini, kita dapat membantu model untuk lebih adil dalam mengenali dan memprediksi data dari semua kelas, sehingga menghasilkan hasil yang lebih akurat dan dapat diandalkan.
# Get the class proportion
table("\nFrequency" = factor(train_image_array_gen$classes)
) %>%
prop.table()#>
#> Frequency
#> 0 1 2 3 4
#> 0.1770321 0.2435638 0.1816604 0.1698004 0.2279433
Kelas yang satu dengan yang lainnya memiliki proporsi yang cukup seimbang dan tidak terpaut jauh. Dengan keseimbangan ini, diharapkan model dapat mengenali pola dari semua kelas dengan merata dan memberikan prediksi yang akurat untuk setiap kelas.
Convolutional Neural Network (CNN) adalah arsitektur deep learning yang khusus dirancang untuk memproses dan menganalisis data visual, seperti gambar dan video. Convolutional Neural Network terdiri dari beberapa lapisan seperti lapisan konvolusi (convolutional layer), lapisan penggabungan (pooling layer), lapisan pelebaran (flattening layer), lapisan terhubung penuh (fully connected layers), dan lapisan keluaran (output layer).
Convolutional Neural Networks (CNN) atau covnets adalah jenis arsitektur jaringan saraf yang terinspirasi oleh cara otak manusia memproses informasi visual. CNN memiliki lapisan-lapisan khusus yang memungkinkannya untuk secara efektif memahami dan menganalisis data visual, seperti gambar.
Saat Anda memiliki sebuah gambar, gambar tersebut dapat diwakili sebagai kubus dengan panjang, lebar, dan tinggi. Dimensi panjang dan lebar mengacu pada dimensi gambar (misalnya, 224 piksel x 224 piksel), sementara tinggi mengacu pada kanal warna gambar. Secara khusus, gambar umumnya memiliki tiga kanal warna utama: merah, hijau, dan biru (RGB). Setiap kanal ini membawa informasi tentang intensitas warna pada setiap piksel dalam gambar. Dengan adanya tiga kanal warna, gambar dapat direpresentasikan sebagai kubus tiga dimensi dengan dimensi panjang, lebar, dan tinggi (3 kanal warna).
CNN bekerja dengan melakukan operasi konvolusi pada kubus ini untuk mengidentifikasi pola dan fitur yang penting dalam gambar. Selama proses pelatihan, parameter yang digunakan untuk operasi konvolusi ini dibagikan (shared) di seluruh lapisan konvolusi, sehingga memungkinkan CNN untuk secara efisien mengenali pola serupa dalam gambar tanpa harus mempelajari parameter baru untuk setiap posisi dalam gambar.
Dengan cara ini, CNN dapat secara efektif mengidentifikasi fitur-fitur penting dalam data visual dan memahami struktur spasial gambar, yang membuatnya menjadi sangat baik dalam tugas-tugas pengenalan pola dan pengolahan gambar.
Sekarang, kita akan mencoba membangun arsitektur Convolutional Neural Network (CNN) untuk memodelkan data gambar yang ada.
Kita akan memulai dengan membangun model sederhana terlebih dahulu dengan lapisan-lapisan berikut:
Kita dapat mulai melatih data ke dalam model. Untuk memulainya, kita
akan menggunakan 15 epoch untuk melatih data. Untuk klasifikasi
multilabel, kita akan menggunakan categorical cross-entropy
sebagai loss function. Selain itu, kita juga akan mencoba
membandingkan Adam optimizer dan Adagrad optimizer dengan learning rate
sebesar 0.01 ditahap pemodelan awal ini.
Fitting Model with Adam Optimizer
# Set Initial Random Weight
tensorflow::tf$random$set_seed(30)
model_adam <- keras_model_sequential(name = "simpl_model_adam") %>%
# Convolution Layer
layer_conv_2d(filters = 16,
kernel_size = c(3,3),
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_adam#> Model: "simpl_model_adam"
#> ________________________________________________________________________________
#> Layer (type) Output Shape Param #
#> ================================================================================
#> conv2d (Conv2D) (None, 128, 128, 16) 448
#> max_pooling2d (MaxPooling2D) (None, 64, 64, 16) 0
#> flatten (Flatten) (None, 65536) 0
#> dense (Dense) (None, 16) 1048592
#> Output (Dense) (None, 5) 85
#> ================================================================================
#> Total params: 1,049,125
#> Trainable params: 1,049,125
#> Non-trainable params: 0
#> ________________________________________________________________________________
Seperti yang kita lihat, kita memulainya dengan memasukkan data
gambar dengan ukuran 128 x 128 piksel ke dalam lapisan konvolusi, yang
memiliki 16 filter untuk mengekstrak fitur dari gambar. Argumen
padding = same digunakan untuk menjaga dimensi fitur tetap
menjadi 128 x 128 piksel setelah diekstraksi. Kemudian, kita melakukan
downsampling atau hanya mengambil nilai maksimum untuk setiap area
pooling 2x2 sehingga data sekarang hanya memiliki ukuran 64 x 64 piksel
dari 16 filter. Setelah itu, dari ukuran 64 x 64 piksel, kita meratakan
(flatten) matriks 2D menjadi matriks 1D dengan 64 x 64 x 16 = 65536
node. Kita dapat mengekstrak informasi lebih lanjut menggunakan lapisan
dense sederhana dan mengalirkan informasi ke lapisan output, yang akan
diubah menggunakan fungsi aktivasi softmax untuk mendapatkan
probabilitas setiap kelas sebagai output.
batch_size#> [1] 128
model_adam <- model_adam %>%
compile(
loss = "categorical_crossentropy",
optimizer = optimizer_adam(lr = 0.01),
metrics = "accuracy"
)
# Fit data into model
history_adam <- model_adam %>%
fit(
# training data
train_image_array_gen,
# training epochs
steps_per_epoch = as.integer(train_samples / batch_size),
epochs = 15,
# validation data
validation_data = val_image_array_gen,
validation_steps = as.integer(valid_samples / batch_size),
verbose = 2
)
plot(history_adam)Fitting model with Adagrad optimizer
# Set Initial Random Weight
tensorflow::tf$random$set_seed(30)
model_adagrad <- keras_model_sequential(name = "simpl_model_adagrad") %>%
# Convolution Layer
layer_conv_2d(filters = 16,
kernel_size = c(3,3),
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_adagrad <- model_adagrad %>%
compile(
loss = "categorical_crossentropy",
optimizer = optimizer_adagrad(lr = 0.01),
metrics = "accuracy"
)
# Fit data into model
history_adagrad <- model_adagrad %>%
fit(
# training data
train_image_array_gen,
# training epochs
steps_per_epoch = as.integer(train_samples / batch_size),
epochs = 15,
# validation data
validation_data = val_image_array_gen,
validation_steps = as.integer(valid_samples / batch_size),
verbose = 2
)
plot(history_adagrad)Let’s compare the performance of the four optimizers that have been executed.
plot(history_adam)plot(history_adagrad)Berdasarkan plot di atas, dapat dilihat bahwa model yang menggunakan optimizer Adagrad mencapai kinerja yang lebih baik dan cenderung lebih fit dengan data kita dibandingkan dengan optimizer adam. Mari kita evaluasi model yang menggunakan optimizer Adagrad ini.
Sekarang, kita akan lebih lanjut mengevaluasi dan mendapatkan matriks kebingungan (confusion matrix) menggunakan data validasi dari generator. Pertama, kita perlu mendapatkan nama file dari gambar yang digunakan sebagai data validasi. Dari nama file tersebut, kita akan mengekstrak label kategorikal sebagai nilai aktual dari variabel target.
Karena kelas target dalam data kita seimbang, kita akan fokus pada matriks akurasi untuk menilai kinerja model. Selain itu, kita juga dapat mempertimbangkan metrik evaluasi lain seperti presisi, sensitivitas (recall), dan spesifisitas untuk mendapatkan pemahaman yang lebih komprehensif tentang efektivitas model.
val_data <- data.frame(file_name = paste0("data/", val_image_array_gen$filenames)) %>%
mutate(class = str_extract(file_name, "daisy|dandelion|rose|sunflower|tulip"))
head(val_data, 10)dim(val_data)#> [1] 860 2
target_size#> [1] 128 128
Langkah selanjutnya adalah mengonversi gambar menjadi array di R. Karena model CNN kita memerlukan gambar dengan dimensi 128 x 128 piksel dan 3 kanal warna (RGB), kita akan menerapkan proses yang sama pada data pengujian. Kita memilih untuk menggunakan array untuk langsung memprediksi gambar asli dari folder, menghindari penggunaan image generator yang dapat memodifikasi gambar dan tidak mewakili gambar sesungguhnya.
# Function to convert image to array
image_prep <- function(x) {
arrays <- lapply(x, function(path) {
img <- image_load(path, target_size = target_size,
grayscale = F # Set FALSE if image is 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 validation data set
dim(test_x)#> [1] 860 128 128 3
Data validasi terdiri dari 860 gambar dengan dimensi 125 x 125 piksel dan 3 kanal warna (RGB). Setelah kita menyiapkan data uji, sekarang kita dapat melanjutkan dengan memprediksi label untuk setiap gambar menggunakan model CNN kita.
pred_adagrad <- predict(object = model_adagrad, x = test_x) %>% k_argmax() %>% as.array() %>% as.factor()pred_adagrad#> [1] 1 4 2 2 2 2 1 2 4 2 2 4 2 2 2 2 2 2 4 2 2 2 1 2 2 2 2 2 2 4 2 4 2 2 4 3 2
#> [38] 2 2 2 2 2 2 2 2 4 2 4 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2 4 1 4 2 4 4 4
#> [75] 4 2 2 2 2 2 4 3 4 2 4 2 2 2 2 2 2 2 2 2 3 4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 4
#> [112] 4 2 4 2 2 4 2 2 2 2 2 3 2 2 2 2 2 2 2 2 2 2 2 1 4 2 2 2 2 2 2 2 4 2 2 4 4
#> [149] 2 2 2 2 4 1 4 2 2 3 2 1 3 2 2 3 4 2 4 2 2 2 4 3 3 2 2 2 2 2 2 2 3 4 1 3 2
#> [186] 2 2 3 2 3 4 3 4 2 2 3 3 2 2 3 2 4 4 3 2 2 2 4 2 4 2 3 2 2 3 2 3 4 2 4 2 2
#> [223] 4 2 3 1 2 3 3 2 2 2 2 2 2 2 4 3 3 4 2 3 2 4 2 2 4 3 2 3 2 4 3 2 2 3 4 2 4
#> [260] 2 2 1 3 4 2 3 4 2 4 4 2 2 4 4 3 2 2 4 2 2 3 3 2 4 2 4 2 2 2 2 2 2 2 2 2 4
#> [297] 2 4 4 2 1 2 3 3 4 2 4 2 1 2 4 2 4 3 4 1 2 4 2 4 2 2 1 2 2 4 4 4 2 4 4 2 2
#> [334] 4 2 2 4 2 4 4 2 4 2 3 2 2 3 3 3 2 2 3 2 3 3 1 1 2 4 3 2 4 2 2 2 2 2 4 2 2
#> [371] 2 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2 4 2 2 2 2 2 2 2 2 2 2 2 2 4 2
#> [408] 2 2 2 2 4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 2 2 2 2 2 4 2 2 2 2 2 2 2 2 2 2 2
#> [445] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
#> [482] 2 2 2 1 2 2 2 2 2 2 4 2 2 2 2 2 2 2 2 4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
#> [519] 1 3 1 2 2 2 2 3 2 2 2 4 3 4 2 2 2 4 2 4 1 2 4 3 3 3 3 2 1 3 4 4 2 3 4 2 3
#> [556] 1 1 4 3 4 4 1 3 1 4 3 3 3 3 3 3 3 3 3 3 3 4 1 4 3 3 1 3 2 3 4 3 2 4 2 4 4
#> [593] 2 3 4 3 4 3 4 3 3 4 2 2 2 2 3 4 2 2 2 3 3 4 4 4 4 4 4 4 2 2 2 4 2 4 3 2 2
#> [630] 3 1 3 3 3 2 3 1 3 3 2 3 4 2 3 2 4 3 3 2 3 3 3 3 3 3 3 3 4 1 3 3 1 3 3 2 2
#> [667] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
#> [704] 2 2 2 2 2 2 4 2 2 2 4 2 2 2 2 3 2 2 2 2 3 2 4 3 2 2 4 4 2 2 4 2 2 2 2 2 2
#> [741] 2 2 2 4 3 4 2 2 4 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 2 2 2 4 2 2
#> [778] 2 2 2 2 4 4 2 2 2 2 2 4 3 4 3 2 2 2 3 2 2 2 2 2 4 4 2 4 2 2 2 2 2 2 2 2 4
#> [815] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 4 2 4 2 2 2 2 2 2 2 2 2 2 2 2 4 2 2 2 2
#> [852] 2 2 2 2 2 2 4 2 2
#> Levels: 1 2 3 4
To get easier interpretation of the prediction, we will convert the encoding into proper class label.
# Convert encoding to label
# daisy|dandelion|rose|sunflower|tulip
decode <- function(x){
case_when(x == 0 ~ "daisy",
x == 1 ~ "dandelion",
x == 2 ~ "rose",
x == 3 ~ "sunflower",
x == 4 ~ "tulip"
)
}
pred_adagrad <- sapply(pred_adagrad, decode)
pred_adagrad[1:50]#> [1] "dandelion" "tulip" "rose" "rose" "rose" "rose"
#> [7] "dandelion" "rose" "tulip" "rose" "rose" "tulip"
#> [13] "rose" "rose" "rose" "rose" "rose" "rose"
#> [19] "tulip" "rose" "rose" "rose" "dandelion" "rose"
#> [25] "rose" "rose" "rose" "rose" "rose" "tulip"
#> [31] "rose" "tulip" "rose" "rose" "tulip" "sunflower"
#> [37] "rose" "rose" "rose" "rose" "rose" "rose"
#> [43] "rose" "rose" "rose" "tulip" "rose" "tulip"
#> [49] "rose" "rose"
Now let’s evaluate using confusion matrix to check the accuracy and other metrics
confusionMatrix(as.factor(pred_adagrad),
as.factor(val_data$class)
)#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction daisy dandelion rose sunflower tulip
#> daisy 0 0 0 0 0
#> dandelion 6 11 2 14 1
#> rose 115 103 146 37 166
#> sunflower 4 43 1 60 7
#> tulip 27 53 7 35 22
#>
#> Overall Statistics
#>
#> Accuracy : 0.2779
#> 95% CI : (0.2482, 0.3092)
#> No Information Rate : 0.2442
#> P-Value [Acc > NIR] : 0.01267
#>
#> Kappa : 0.1084
#>
#> Mcnemar's Test P-Value : < 0.0000000000000002
#>
#> Statistics by Class:
#>
#> Class: daisy Class: dandelion Class: rose Class: sunflower
#> Sensitivity 0.0000 0.05238 0.9359 0.41096
#> Specificity 1.0000 0.96462 0.4020 0.92297
#> Pos Pred Value NaN 0.32353 0.2575 0.52174
#> Neg Pred Value 0.8233 0.75908 0.9659 0.88456
#> Prevalence 0.1767 0.24419 0.1814 0.16977
#> Detection Rate 0.0000 0.01279 0.1698 0.06977
#> Detection Prevalence 0.0000 0.03953 0.6593 0.13372
#> Balanced Accuracy 0.5000 0.50850 0.6689 0.66696
#> Class: tulip
#> Sensitivity 0.11224
#> Specificity 0.81627
#> Pos Pred Value 0.15278
#> Neg Pred Value 0.75698
#> Prevalence 0.22791
#> Detection Rate 0.02558
#> Detection Prevalence 0.16744
#> Balanced Accuracy 0.46425
Berdasarkan hasil di atas, performa model masih perlu ditingkatkan mengingat nilai akurasinya baru mencapai 49.65%. Kita akan tetap mencoba melakukan penyesuaian (tuning) untuk meningkatkan kinerja model dalam memprediksi gambar. Oleh karena itu, kita akan melanjutkan dengan penyesuaian menggunakan optimizer Adagrad dalam proses selanjutnya.
Mari kita perhatikan lebih dekat arsitektur model sebelumnya. Seperti yang kita perhatikan, kita dapat mengekstrak lebih banyak informasi saat data masih berada dalam bentuk array gambar 2D. Lapisan Convolutional Neural Network (CNN) pertama hanya mengekstrak fitur umum dari gambar kita dan kemudian melakukan downsampling menggunakan lapisan max pooling. Bahkan setelah pooling, kita masih memiliki array 64 x 64 yang berisi banyak informasi yang dapat diekstraksi lebih lanjut sebelum meratakan (flattening) data. Untuk menangkap lebih banyak informasi, kita dapat menambahkan lebih banyak lapisan CNN ke dalam model. Dengan menumpuk lapisan CNN tambahan, kita memungkinkan model untuk menangkap detail dan pola yang lebih rumit dari gambar-gambar tersebut. Selain itu, kita juga dapat menempatkan 2 lapisan CNN secara berurutan sebelum melakukan max pooling. Pendekatan ini memungkinkan model untuk mengekstrak fitur yang lebih kompleks dari data, yang dapat meningkatkan kinerja dalam tugas klasifikasi gambar.
model_adagrad#> Model: "simpl_model_adagrad"
#> ________________________________________________________________________________
#> Layer (type) Output Shape Param #
#> ================================================================================
#> conv2d_1 (Conv2D) (None, 128, 128, 16) 448
#> max_pooling2d_1 (MaxPooling2D) (None, 64, 64, 16) 0
#> flatten_1 (Flatten) (None, 65536) 0
#> dense_1 (Dense) (None, 16) 1048592
#> Output (Dense) (None, 5) 85
#> ================================================================================
#> Total params: 1,049,125
#> Trainable params: 1,049,125
#> Non-trainable params: 0
#> ________________________________________________________________________________
The following is our improved model architecture: - 1st Convolutional layer to extract features from 2D image with relu activation function - Max pooling layer - 2nd Convolutional layer to extract features from 2D image with relu activation function - Max pooling layer - Flattening layer from 2D array to 1D array - Dense layer to capture more information - Dropout layer - Dense layer for output layer
# Set Initial Random Weight
tensorflow::tf$random$set_seed(30)
model_tuning1 <- keras_model_sequential(name = "model_tuning1") %>%
# Convolution Layer
layer_conv_2d(filters = 16,
kernel_size = c(3,3),
padding = "same",
activation = "relu",
input_shape = c(target_size, 3)) %>%
layer_max_pooling_2d(pool_size = c(2,2)) %>%
layer_conv_2d(filters = 32,
kernel_size = c(3,3),
padding = "same",
activation = "relu") %>%
layer_max_pooling_2d(pool_size = c(2,2)) %>%
layer_conv_2d(filters = 32,
kernel_size = c(3,3),
padding = "same",
activation = "relu") %>%
layer_max_pooling_2d(pool_size = c(2,2)) %>%
# Flattening Layer
layer_flatten() %>%
# Output Layer
layer_dense(units = output_n,
activation = "softmax",
name = "Output")
model_tuning1 <- model_tuning1 %>%
compile(
loss = "categorical_crossentropy",
optimizer = optimizer_adagrad(lr = 0.01),
metrics = "accuracy"
)
# Fit data into model
history_tuning1 <- model_tuning1 %>%
fit(
# training data
train_image_array_gen,
# training epochs
steps_per_epoch = as.integer(train_samples / batch_size),
epochs = 15,
# validation data
validation_data = val_image_array_gen,
validation_steps = as.integer(valid_samples / batch_size),
verbose = 2
)
plot(history_tuning1)Dengan meningkatkan banyaknya convolution layer, model Jaringan Saraf Konvolusi (CNN) menunjukkan peningkatan kinerja yang signifikan. Bukan hanya itu, model ini juga cukup optimal dan tidak menunjukkan tanda-tanda overfitting atau underfitting. Mari lanjutkan dengan mengevaluasi model menggunakan data validasi seperti yang kita lakukan pada langkah sebelumnya.
pred_tuning1 <- predict(object = model_tuning1, x = test_x) %>% k_argmax() %>% as.array() %>% as.factor()pred_tuning1#> [1] 3 3 2 2 1 0 3 2 4 1 1 1 0 0 1 0 4 4 1 1 0 1 1 0 0 0 1 1 0 1 4 3 0 1 1 1 1
#> [38] 0 1 0 1 0 4 2 2 1 2 1 0 2 0 2 0 0 2 1 2 0 0 0 2 1 4 1 1 0 0 1 0 1 0 1 3 3
#> [75] 3 4 0 2 0 4 1 3 1 0 3 1 0 0 0 0 1 0 4 0 1 1 0 1 0 1 1 0 0 1 1 0 1 0 0 1 1
#> [112] 1 3 1 1 4 1 4 0 1 3 1 1 0 1 1 0 1 0 1 0 1 0 0 3 0 1 0 4 0 1 1 1 1 0 1 4 1
#> [149] 0 1 0 0 1 1 3 1 1 1 0 1 1 0 0 1 4 1 3 1 3 1 1 1 1 1 1 1 1 4 1 1 1 1 1 1 0
#> [186] 1 4 1 1 3 4 3 1 0 1 3 3 1 1 1 1 1 1 3 1 1 1 1 4 1 1 3 1 0 1 1 1 3 1 1 1 1
#> [223] 1 0 3 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 4 1 1 1 1 1 1 3 4 1 1 1 3 0 1 1 1 4 3
#> [260] 4 4 1 1 1 1 1 3 1 1 1 4 0 3 1 3 0 1 1 0 1 3 3 0 1 1 1 1 1 1 1 1 0 0 1 0 1
#> [297] 1 3 1 1 1 1 3 3 1 1 0 2 1 0 1 0 3 3 1 1 0 1 1 1 0 2 3 1 1 1 1 1 1 1 1 1 0
#> [334] 3 0 3 1 0 3 1 0 1 1 3 1 1 3 3 1 4 0 3 4 1 3 1 1 1 1 3 1 1 0 2 4 2 4 3 2 4
#> [371] 4 2 4 2 4 4 2 4 3 4 4 4 4 4 2 2 2 2 4 2 1 2 3 1 2 0 0 4 2 2 2 2 4 4 4 1 2
#> [408] 2 4 1 4 1 0 2 2 4 4 2 2 4 2 2 4 4 2 2 3 0 4 2 0 2 1 2 2 4 1 2 4 2 0 2 4 2
#> [445] 4 2 4 3 2 4 2 4 4 2 4 4 2 4 4 2 4 2 2 2 0 2 2 2 3 2 2 2 2 2 2 4 4 4 4 4 2
#> [482] 2 4 2 1 2 2 2 2 2 4 1 4 4 2 2 4 2 4 1 1 4 2 4 1 1 0 2 4 2 4 4 4 0 4 4 4 1
#> [519] 3 3 3 4 4 4 4 3 4 4 3 3 3 3 3 4 3 3 1 3 3 4 3 3 3 3 3 4 3 3 3 3 2 3 3 4 3
#> [556] 3 3 3 3 3 1 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 0 3 3 3 3 3 4 3 3 3 3 3 0 1 3
#> [593] 3 3 0 3 1 3 3 3 3 3 4 3 4 1 3 3 1 1 4 3 3 3 1 3 3 3 3 3 3 4 4 3 1 3 3 4 4
#> [630] 3 3 3 3 3 4 3 3 3 3 0 3 3 3 3 1 3 3 3 3 1 3 3 3 1 3 3 3 3 3 3 3 3 3 3 2 4
#> [667] 4 4 4 4 4 4 2 4 4 2 4 4 4 4 4 4 4 1 4 4 4 2 4 4 4 4 4 2 2 0 4 4 2 4 1 2 4
#> [704] 1 4 4 2 4 4 1 4 4 4 3 2 4 2 4 3 4 2 4 4 3 4 1 3 1 1 3 1 4 1 3 2 4 4 4 4 4
#> [741] 2 3 4 4 3 3 1 4 3 4 4 2 4 4 2 4 4 2 4 0 2 2 2 2 4 4 4 4 4 1 2 4 4 4 3 4 4
#> [778] 2 4 4 4 3 3 4 4 4 2 4 3 3 4 3 2 4 4 3 4 4 2 4 3 3 3 4 1 4 4 2 4 4 4 4 4 3
#> [815] 4 2 2 4 0 4 4 4 4 4 4 0 2 4 1 4 4 0 1 3 4 4 2 4 3 0 4 2 4 4 4 4 1 4 3 4 4
#> [852] 2 2 4 4 4 2 1 2 4
#> Levels: 0 1 2 3 4
pred_tuning1 <- sapply(pred_tuning1, decode)
pred_tuning1[1:50]#> [1] "sunflower" "sunflower" "rose" "rose" "dandelion" "daisy"
#> [7] "sunflower" "rose" "tulip" "dandelion" "dandelion" "dandelion"
#> [13] "daisy" "daisy" "dandelion" "daisy" "tulip" "tulip"
#> [19] "dandelion" "dandelion" "daisy" "dandelion" "dandelion" "daisy"
#> [25] "daisy" "daisy" "dandelion" "dandelion" "daisy" "dandelion"
#> [31] "tulip" "sunflower" "daisy" "dandelion" "dandelion" "dandelion"
#> [37] "dandelion" "daisy" "dandelion" "daisy" "dandelion" "daisy"
#> [43] "tulip" "rose" "rose" "dandelion" "rose" "dandelion"
#> [49] "daisy" "rose"
confusionMatrix(as.factor(pred_tuning1),
as.factor(val_data$class)
)#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction daisy dandelion rose sunflower tulip
#> daisy 54 25 10 4 6
#> dandelion 61 133 14 13 16
#> rose 12 2 67 1 36
#> sunflower 12 37 6 109 24
#> tulip 13 13 59 19 114
#>
#> Overall Statistics
#>
#> Accuracy : 0.5547
#> 95% CI : (0.5207, 0.5882)
#> No Information Rate : 0.2442
#> P-Value [Acc > NIR] : < 0.00000000000000022
#>
#> Kappa : 0.4381
#>
#> Mcnemar's Test P-Value : 0.00000009695
#>
#> Statistics by Class:
#>
#> Class: daisy Class: dandelion Class: rose Class: sunflower
#> Sensitivity 0.35526 0.6333 0.42949 0.7466
#> Specificity 0.93644 0.8400 0.92756 0.8894
#> Pos Pred Value 0.54545 0.5612 0.56780 0.5798
#> Neg Pred Value 0.87122 0.8764 0.88005 0.9449
#> Prevalence 0.17674 0.2442 0.18140 0.1698
#> Detection Rate 0.06279 0.1547 0.07791 0.1267
#> Detection Prevalence 0.11512 0.2756 0.13721 0.2186
#> Balanced Accuracy 0.64585 0.7367 0.67852 0.8180
#> Class: tulip
#> Sensitivity 0.5816
#> Specificity 0.8434
#> Pos Pred Value 0.5229
#> Neg Pred Value 0.8723
#> Prevalence 0.2279
#> Detection Rate 0.1326
#> Detection Prevalence 0.2535
#> Balanced Accuracy 0.7125
Arsitektur CNN yang lebih kompleks telah terbukti meningkatkan kinerja model dalam mengklasifikasikan gambar. Hal ini terlihat dari peningkatan akurasi dibandingkan dengan model awal. Selain itu, nilai sensitivitas, spesifisitas, dan presisi juga baik setelah proses penyetelan ini.
Kita telah berhasil membangun model klasifikasi gambar dengan
arsitektur CNN, mencapai akurasi yang dapat dianggap cukup baik.
Arsitektur model ini terdiri dari dua lapisan konvolusi dengan aktivasi
ReLU, diikuti oleh lapisan max-pooling. Setelah itu, digunakan lapisan
pemadatan (flattening layer) untuk mengubah keluaran menjadi larik satu
dimensi, dan lapisan dense digunakan untuk mengumpulkan informasi lebih
lanjut sebelum mencapai lapisan keluaran dengan aktivasi softmax untuk
mengklasifikasikan gambar ke dalam beberapa kategori bunga. Pada model
ini, loss function yang digunakan adalah
categorical_crossentropy. Loss function ini umum digunakan
untuk masalah klasifikasi multikelas, di mana variabel target memiliki
lebih dari dua kategori. Fungsi ini mengukur perbedaan antara
probabilitas kelas sebenarnya dan probabilitas kelas yang diprediksi,
mendorong model untuk membuat prediksi yang akurat untuk setiap
kelas.