Kita selaku tim data dari perusahaan pembuat alat pendeteksi asap (Smoke Detection) perlu membuat sistem/model yang dapat mengklasifikasikan apakah terdapat api atau tidak dengan efisien dan efektif.
Kita menyadari sering kali fenomena yang terjadi dilapangan (realitas) sulit ditemuakan polanya, bagaimana membedakan apakah terdapat api atau tidak, terutama ketika informasi yang menjadi pertimbangan (prediktor) sangat banyak. Oleh karena itu, projek Machine Learning ini dibuat untuk mencoba menyelesaikan kesulitan tersebut dengan efektif (karena memiliki ukuran kualitas yang jelas) dan efisien (karena dengan bantuan komputasi bukan manual). Projek ini menggunakan dataset dari Kaggle.
library(tidyverse)
library(caret)
library(keras)
library(rsample)
library(tensorflow)
library(plotly)smoke <- read.csv("smoke_detection_iot.csv")
smokeDeskripsi kolom :
Prediktor : Kolom yang menjadi pertimbangan dalam menentukan Target (input).
X : Index yang menjadi identitas setiap baris.UTC : Waktu UTC Timestamp dalam detik (data konversi dari waktu biasa menjadi UTC timestamp).Temperature_C : Tingkat Suhu dalam satuan celcius.Humidity_percent : Tingkat Kelembaban dalam persen.TVOC_ppb : Total Senyawa Organik Volatil; diukur dalam bagian per miliar dalam ppb.eCO2_ppm : Besaran Konsentrasi setara co2; dihitung dari nilai yang berbeda seperti TVCO dalam ppm.Raw_H2 : Besaran Hidrogen molekuler mentah; tidak dikompensasi (Bias, suhu, dll.).Raw_Ethanol : Besaran gas etanol mentah.Pressure_hPa : Besaran tekanan udara dalam hPa.PM1.0 : Besaran partikel dengan diameter hingga 1 mikron.PM2.5 : Besaran partikel dengan diameter hingga 2.5 mikron.NC0.5 : Number Concentration, Ini berbeda dari PM karena NC memberikan jumlah sebenarnya dari partikel (ukuran <= 0.5 µm) di udara.NC1.0 : Number Concentration, Ini berbeda dari PM karena NC memberikan jumlah sebenarnya dari partikel (ukuran 0.5 µm <= 1 µm) di udara.NC2.5 : Number Concentration, Ini berbeda dari PM karena NC memberikan jumlah sebenarnya dari partikel (ukuran 1 µm < 2.5µm) di udara.CNT : Penghitung sampel.Target : Kolom yang menjadi hasil dari pertimbangan prediktor (output).
Fire_Alarm : Menunjukan apakah ada api atau tidak; 1 = terdapat api, 0 = tidak/anyNA(smoke)#> [1] FALSE
Tidak terdapat nilai hilang sehingga dapat lanjut ke tahap selanjutnya.
anyDuplicated(smoke)#> [1] 0
Tidak terdapat data yang terduplikasi sehingga dapat lanjut ke tahap selanjutnya.
Struktur data sebelum Wrangling Data
glimpse(smoke)#> Rows: 62,630
#> Columns: 16
#> $ X <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,…
#> $ UTC <int> 1654733331, 1654733332, 1654733333, 1654733334, 16547…
#> $ Temperature_C <dbl> 20.000, 20.015, 20.029, 20.044, 20.059, 20.073, 20.08…
#> $ Humidity_percent <dbl> 57.36, 56.67, 55.96, 55.28, 54.69, 54.12, 53.61, 53.2…
#> $ TVOC_ppb <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ eCO2_ppm <int> 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400…
#> $ Raw_H2 <int> 12306, 12345, 12374, 12390, 12403, 12419, 12432, 1243…
#> $ Raw_Ethanol <int> 18520, 18651, 18764, 18849, 18921, 18998, 19058, 1911…
#> $ Pressure_hPa <dbl> 939.735, 939.744, 939.738, 939.736, 939.744, 939.725,…
#> $ PM1.0 <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
#> $ PM2.5 <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
#> $ NC0.5 <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ NC1.0 <dbl> 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.00…
#> $ NC2.5 <dbl> 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.00…
#> $ CNT <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,…
#> $ Fire_Alarm <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
Proses Wrangling Data
rownames(smoke) <- smoke$X
smoke_clean <- smoke %>%
# Membuang kolom X karena sudah menjadi nama kolom
select(-X) %>%
mutate(Fire_Alarm = as.factor(Fire_Alarm))Struktur data sesudah Wrangling Data
glimpse(smoke_clean)#> Rows: 62,630
#> Columns: 15
#> $ UTC <int> 1654733331, 1654733332, 1654733333, 1654733334, 16547…
#> $ Temperature_C <dbl> 20.000, 20.015, 20.029, 20.044, 20.059, 20.073, 20.08…
#> $ Humidity_percent <dbl> 57.36, 56.67, 55.96, 55.28, 54.69, 54.12, 53.61, 53.2…
#> $ TVOC_ppb <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ eCO2_ppm <int> 400, 400, 400, 400, 400, 400, 400, 400, 400, 400, 400…
#> $ Raw_H2 <int> 12306, 12345, 12374, 12390, 12403, 12419, 12432, 1243…
#> $ Raw_Ethanol <int> 18520, 18651, 18764, 18849, 18921, 18998, 19058, 1911…
#> $ Pressure_hPa <dbl> 939.735, 939.744, 939.738, 939.736, 939.744, 939.725,…
#> $ PM1.0 <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
#> $ PM2.5 <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
#> $ NC0.5 <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
#> $ NC1.0 <dbl> 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.00…
#> $ NC2.5 <dbl> 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.00…
#> $ CNT <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,…
#> $ Fire_Alarm <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
summary(smoke_clean)#> UTC Temperature_C Humidity_percent TVOC_ppb
#> Min. :1654712187 Min. :-22.01 Min. :10.74 Min. : 0
#> 1st Qu.:1654743244 1st Qu.: 10.99 1st Qu.:47.53 1st Qu.: 130
#> Median :1654761920 Median : 20.13 Median :50.15 Median : 981
#> Mean :1654792066 Mean : 15.97 Mean :48.54 Mean : 1942
#> 3rd Qu.:1654777577 3rd Qu.: 25.41 3rd Qu.:53.24 3rd Qu.: 1189
#> Max. :1655130051 Max. : 59.93 Max. :75.20 Max. :60000
#> eCO2_ppm Raw_H2 Raw_Ethanol Pressure_hPa
#> Min. : 400 Min. :10668 Min. :15317 Min. :930.9
#> 1st Qu.: 400 1st Qu.:12830 1st Qu.:19435 1st Qu.:938.7
#> Median : 400 Median :12924 Median :19501 Median :938.8
#> Mean : 670 Mean :12942 Mean :19754 Mean :938.6
#> 3rd Qu.: 438 3rd Qu.:13109 3rd Qu.:20078 3rd Qu.:939.4
#> Max. :60000 Max. :13803 Max. :21410 Max. :939.9
#> PM1.0 PM2.5 NC0.5 NC1.0
#> Min. : 0.00 Min. : 0.00 Min. : 0.00 Min. : 0.00
#> 1st Qu.: 1.28 1st Qu.: 1.34 1st Qu.: 8.82 1st Qu.: 1.38
#> Median : 1.81 Median : 1.88 Median : 12.45 Median : 1.94
#> Mean : 100.59 Mean : 184.47 Mean : 491.46 Mean : 203.59
#> 3rd Qu.: 2.09 3rd Qu.: 2.18 3rd Qu.: 14.42 3rd Qu.: 2.25
#> Max. :14333.69 Max. :45432.26 Max. :61482.03 Max. :51914.68
#> NC2.5 CNT Fire_Alarm
#> Min. : 0.000 Min. : 0 0:17873
#> 1st Qu.: 0.033 1st Qu.: 3625 1:44757
#> Median : 0.044 Median : 9336
#> Mean : 80.049 Mean :10511
#> 3rd Qu.: 0.051 3rd Qu.:17165
#> Max. :30026.438 Max. :24993
UTC berada dikisaran 1,655,000,000Temparature_C didominasi (75%-nya) dari rentang -22.01 C hingga 15.97 C dengan tertingggi di angka 59.93 C.Humidity_percent didominasi (75%-nya) dari rentang 10.74% hingga 53.24% dengan tertingggi di angka 75.20%.TVOC_ppb didominasi (75%-nya) dari rentang 0 ppb hingga 1,189 ppb dengan tertingggi di angka 60,000 ppb.eCO2_ppm didominasi (75%-nya) dari rentang 400 ppm hingga 438 ppm dengan tertingggi di angka 60,000 ppm.Raw_H2 didominasi (75%-nya) dari rentang 10,668 hingga 13109 dengan tertingggi di angka 13,803.Raw_Ethanol didominasi (75%-nya) dari rentang 15,317 hingga 20078 dengan tertingggi di angka 21,410.Pressure_hPa didominasi (75%-nya) dari rentang 930.9 hingga 939.4 dengan tertingggi di angka 939.9.PM1.0 didominasi (75%-nya) dari rentang 0.00 hingga 2.09 dengan tertingggi di angka 14,333.69 (Sangat tidak merata).PM2.5 didominasi (75%-nya) dari rentang 0.00 hingga 2.18 dengan tertingggi di angka 45,432.26.NC0.5 didominasi (75%-nya) dari rentang 0.00 hingga 14.42 dengan tertingggi di angka 61,482.03.NC1.0 didominasi (75%-nya) dari rentang 0.00 hingga 2.25 dengan tertingggi di angka 51,914.68.NC2.5 didominasi (75%-nya) dari rentang 0.000 hingga 0.051 dengan tertingggi di angka 30,026.438.CNT didominasi (75%-nya) dari rentang 0 hingga 17,165 dengan tertingggi di angka 24,993.vis_data <- as.data.frame(table(smoke_clean$Fire_Alarm))
plt <-
ggplot(data = vis_data, aes(x = Var1, y = Freq, fill = Var1,
text = glue::glue("Target : {Var1}.
Jumlah : {Freq}"))) +
geom_bar(stat = "identity") +
labs(title = "Perbandingan Target 0 (Tidak Terdapat Api) dan 1 (Terdapat Api)",
y = "Jumlah",
x = "Fire Alarm") +
theme_minimal()
ggplotly(plt, tooltip = "text" )Pada data smoke ini di dominasi
Fire_Alarm1 atau terdapat Api.
prop.table(table(smoke_clean$Fire_Alarm))#>
#> 0 1
#> 0.2853744 0.7146256
Kita menyadari proporsi Target 0 dan 1 pada data kita cenderung tidak seimbang karena Target 0 hanya 28.53% sedangkan Target 1 sebesar 71.46%.
RNGkind(sample.kind = "Rounding")
set.seed(100)
index <- initial_split(data = smoke_clean, #data asli sebelum splitting
prop = 0.74,
strata = Fire_Alarm)
train <- training(index)
test <- testing(index)
train <- upSample(x = train %>% select(-Fire_Alarm),
y = train$Fire_Alarm,
yname = "Fire_Alarm")Oleh karena itu, kita mencoba membagi data train dan test dengan proporsi 74% untuk data train dan 26% untuk data test. Kenapa tidak 80% dan 20%? Agar setelah dilakukan UpSample (Menambah data dengan menduplikasi agar antara jenis target seimbang) pada data train perbandingan data train dan test cenderung sekitar 80% dan 20%.
Kita menyeimbangkan proporsi Target 0 dan 1 hanya pada data train saja karena fokus pada pelatihan model adalah menemukan pola data dalam membedakan Target 0 dengan 1. Sehingga diperlukan bahan belajar (data train) yang seimbang.
prop.table(table(train$Fire_Alarm))#>
#> 0 1
#> 0.5 0.5
Kita tidak menyeimbangkan proporsi Target 0 dan 1 pada data test karena fokus pada pengetesan model adalah menguji apakah model kita sudah dapat menemukan pola yang membedakan Target 0 dengan 1. Sehingga tidak diperlukan soal ujian (data test) yang seimbang.
Kita menyamakan proporsi Target 0 dan 1 pada data test seperti pada data aslinya (smoke_clean). Karena kita mengharapkan model dapat melakukan prediksi yang sesuai dengan keadaan realitas. Alias pada pelatihan model telah berhasil menemukakn pola pembeda Target 0 dan 1.
prop.table(table(smoke_clean$Fire_Alarm))#>
#> 0 1
#> 0.2853744 0.7146256
prop.table(table(test$Fire_Alarm))#>
#> 0 1
#> 0.2853721 0.7146279
Berikut jumlah dan proporasi data train dan test setelah dilakukan UpSample.
prop_train <- nrow(train)/(nrow(train) + nrow(test))*100
prop_test <- nrow(test)/(nrow(train) + nrow(test))*100
data.frame(Data = c("Train", "Test", "Total"),
Jumlah = c(nrow(train), nrow(test), nrow(train)+nrow(test)),
Proporsi_persen = c(prop_train, prop_test, prop_train + prop_test)
)train_scale <- train %>%
mutate(UTC = scale(UTC),
Temperature_C = scale(Temperature_C),
Humidity_percent = scale(Humidity_percent),
TVOC_ppb = scale(TVOC_ppb),
eCO2_ppm = scale(eCO2_ppm),
Raw_H2 = scale(Raw_H2),
Raw_Ethanol = scale(Raw_Ethanol),
Pressure_hPa = scale(Pressure_hPa),
PM1.0 = scale(PM1.0),
PM2.5 = scale(PM2.5),
NC0.5 = scale(NC0.5),
NC1.0 = scale(NC1.0),
NC2.5 = scale(NC2.5),
CNT = scale(CNT)
)
test_scale <- test %>%
mutate(UTC = scale(UTC),
Temperature_C = scale(Temperature_C),
Humidity_percent = scale(Humidity_percent),
TVOC_ppb = scale(TVOC_ppb),
eCO2_ppm = scale(eCO2_ppm),
Raw_H2 = scale(Raw_H2),
Raw_Ethanol = scale(Raw_Ethanol),
Pressure_hPa = scale(Pressure_hPa),
PM1.0 = scale(PM1.0),
PM2.5 = scale(PM2.5),
NC0.5 = scale(NC0.5),
NC1.0 = scale(NC1.0),
NC2.5 = scale(NC2.5),
CNT = scale(CNT)
)# Prediktor
train_x <- train_scale %>% select(-Fire_Alarm) %>% as.matrix()
train_y <- train_scale$Fire_Alarm %>% as.character()
# Target
test_x <- test_scale %>% select(-Fire_Alarm) %>% as.matrix()
test_y <- test_scale$Fire_Alarm %>% as.character()train_x <- array_reshape(train_x, dim = dim(train_x))
test_x <- array_reshape(test_x, dim = dim(test_x))train_y <- to_categorical(train_y, num_classes = 2)
test_y <- to_categorical(test_y, num_classes = 2)# your code here
input_dim <- ncol(train_x) # jumlah neuron yang ada di input layer
input_dim#> [1] 14
num_class <- n_distinct(train$Fire_Alarm) # jumlah neuron yang ada di output layer
num_class#> [1] 2
model1 <- keras_model_sequential(name="model_keras") %>%
layer_dense(units = 64, activation = "relu", input_shape = input_dim, name = "hidden_1") %>%
layer_dense(units = 16, activation = "relu", name = "hidden_2") %>%
layer_dense(units = num_class, activation = "sigmoid", name = "output")
model1#> Model: "model_keras"
#> ________________________________________________________________________________
#> Layer (type) Output Shape Param #
#> ================================================================================
#> hidden_1 (Dense) (None, 64) 960
#> ________________________________________________________________________________
#> hidden_2 (Dense) (None, 16) 1040
#> ________________________________________________________________________________
#> output (Dense) (None, 2) 34
#> ================================================================================
#> Total params: 2,034
#> Trainable params: 2,034
#> Non-trainable params: 0
#> ________________________________________________________________________________
# your code here
model1 %>% compile(loss = loss_binary_crossentropy(),
optimizer = optimizer_sgd(learning_rate = 0.01),
metrics = "accuracy")set.seed(100)
# your code here
history <- model1 %>% fit(x = train_x,
y = train_y,
validation_data = list(test_x, test_y),
batch_size = 200,
epoch=15)
plot(history)# your code here
pred <- predict(model1, test_x) %>% k_argmax() %>% as.array() %>% as.factor()
head(pred)#> [1] 0 0 0 0 0 0
#> Levels: 0 1
confusionMatrix(data = pred, reference = as.factor(test$Fire_Alarm), positive = "1")#> Confusion Matrix and Statistics
#>
#> Reference
#> Prediction 0 1
#> 0 4640 1278
#> 1 7 10359
#>
#> Accuracy : 0.9211
#> 95% CI : (0.9168, 0.9252)
#> No Information Rate : 0.7146
#> P-Value [Acc > NIR] : < 0.00000000000000022
#>
#> Kappa : 0.8212
#>
#> Mcnemar's Test P-Value : < 0.00000000000000022
#>
#> Sensitivity : 0.8902
#> Specificity : 0.9985
#> Pos Pred Value : 0.9993
#> Neg Pred Value : 0.7840
#> Prevalence : 0.7146
#> Detection Rate : 0.6361
#> Detection Prevalence : 0.6366
#> Balanced Accuracy : 0.9443
#>
#> 'Positive' Class : 1
#>
Pada umumnya kita mengevaluasi model menggunakan 5 ukuran:
Sebaiknya pada kasus ini, kita memperhatikan metrik selain Accuracy karena data test asli memiliki ketidakseimbangan Target.
Secara keseluruhan model Deep Learning kita sudah baik karena dapat mengklasifikasikan data dengan baik, terlihat pada 4 metrik (Sensitivity, Specificity, Accuracy, dan Precision) tetapi untuk ukuran Neg Pred Value (Kemampuan model memprediksi kelas negatif (0)) masih sedang yaitu 77,82%.
Kita telah melalui berbagai proses dalam pembuatan Machine Learning yang dapat membedakan Target 0 (Tidak Terdapat Api) dan 1 (Terdapat Api). Pada proses tersebut, kita telah mencoba Deep Learning dalam membangun Machine Learning. Selain itu, kita telah mengevaluasi Machine Learning dan menghasilkan kesimpulan bahwa algoritma Deep Learning memiliki performa baik dan memiliki catatan pada ukuran Negative Predict Value. Pada projek ini saya juga telah mencoba tuning model (mengatur learning rate, batch_size, neuron, epoch dsb) tetapi hasilnya masih seperti menyerupai di atas. Harapannya melalui projek ini dapat menjadi referensi dalam membangun Machine Learning untuk melakukan klasifikasi sehingga dapat menjadi dasar dalam mengambil keputusan secara efektif dan efisien. Sekian terima kasih banyak yang telah melihat projek ini. Saya juga menyukai masukan dan kolaborasi sehingga apabila ada masukan, saran, kritik atau untuk berkolaborasi dapat menghubungi lewat Linkedin.