Berikut akan dibahas mengenai Decision Trees termasuk langkah-langkah bagaimana melakukan fitting, evaluasi, optimasi, dan pemangkasan Decision Trees yang telah dibuat.
Data yang digunakan berupa data simulasi.
# Reading Data
AsiEksdf <- read.csv("ASI_EKSP05.csv")
# Filters the data set down to a few variables
AsiEks <- AsiEksdf %>% dplyr::select(ebf = ASI_Eks, Residence = R105, Quintile = NKAPITA, EducKRT = TAMAT_KRT, HH_family = HH_FAMILY, Kerja_KRT = KERJA_KRT, WealthLevel = MISKIN1, Sex_KRT=SEX_KRT)
# Makes the categorical variables into factors
factor_columns <- c('ebf', 'Residence', 'Quintile','EducKRT','Kerja_KRT',
'WealthLevel')
AsiEks[factor_columns] <- lapply(AsiEks[factor_columns], factor)
levels(AsiEks$ebf) <- c("No", "Yes")
levels(AsiEks$EducKRT) <- c("Tidak/belum pernah+Tidak tamat SD","SD","SMP", "SMA+")
levels(AsiEks$WealthLevel) <- c("Miskin", "Tidak Miskin")
#levels(AsiEks$Sex_KRT) <- c("Laki-laki", "Perempuan")
#levels(AsiEks$HH_family) <- c("1-3","4-5","6+")
levels(AsiEks$Kerja_KRT) <- c("Tidak Bekerja", "Bekerja")
levels(AsiEks$Quintile) <- c("Q1","Q2","Q3","Q4","Q5")
# Remove a few observations which has missing some missing values
AsiEks <- AsiEks %>% na.omit()
datasummary_skim(AsiEks, type = "categorical" )
N | % | ||
---|---|---|---|
ebf | No | 1603 | 28.9 |
Yes | 3948 | 71.1 | |
Residence | 1 | 2107 | 38.0 |
2 | 3444 | 62.0 | |
Quintile | Q1 | 1629 | 29.3 |
Q2 | 1308 | 23.6 | |
Q3 | 1053 | 19.0 | |
Q4 | 895 | 16.1 | |
Q5 | 666 | 12.0 | |
EducKRT | Tidak/belum pernah+Tidak tamat SD | 662 | 11.9 |
SD | 1502 | 27.1 | |
SMP | 1044 | 18.8 | |
SMA+ | 2343 | 42.2 | |
Kerja_KRT | Tidak Bekerja | 274 | 4.9 |
Bekerja | 5277 | 95.1 | |
WealthLevel | Miskin | 895 | 16.1 |
Tidak Miskin | 4656 | 83.9 |
#datasummary_skim(AsiEks, type = "numeric")
Berikut adalah distribusi variabel target Residence:
ggplot(AsiEks) + geom_bar(aes(y = Residence), colour="white", fill ="Dark Green") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5))+
labs(x = "", y = "") +
ggtitle("Distribution of Residence")
Tampak distribusinya terlihat sedikit tidak seimbang, tetapi tidak terlalu signifikan sehingga tidak memerlukan intervensi. Dalam situasi seperti ini, model Machine Learning termasuk Decision Trees, biasanya mampu menangani ketidakseimbangan yang kecil. Namun, jika ketidakseimbangan sangat signifikan, metode penyeimbangan seperti oversampling, undersampling, penyesuaian bobot kelas, perlu dipertimbangkan untuk memastikan prediksi yang adil dan lebih akurat.
Haruskah melakukan standardisasi data sebelum melatih menggunakan decision tree?
Saat menggunakan decision tree, tidak perlu melakukan standardisasi data karena algoritma ini bersifat scale invariant. Cukup dilakukan intuitif saja karena node hanya mempartisi data menjadi beberapa set berdasarkan pemisahan yang memberikan kinerja training terbaik.
Dalam pemodelan dengan Decision Trees, pemilihan variabel dilakukan secara otomatis secara stepwise. Namun, kriteria untuk menentukan pemisahan (split) didasarkan pada beberapa ukuran tertentu. Tujuannya adalah untuk meningkatkan kualitas klasifikasi pada setiap tahap, yaitu dengan mengurangi tingkat impuritas pada setiap node.
Penentuan kriteria yang akan memberikan ukuran “kebaikan” akurasi pemisahan sangat perlu. Pertama, dilakukan pendefinisian \(\widehat{p}_{m \kappa}\) sebagai proporsi dari kelas \(m\) (Urban atau Rural dalam kasus) di node \(\kappa\). Kemudian dilakukan pengklasifikasian setiap observasi di node \(\kappa\) ke kelas mayoritas, yaitu kelas dimana \(\widehat{p}_{m \kappa}\) mencapai nilai maksimum.
Gini Impurity dan Entropy adalah ukuran metrik yang mengukur kualitas suatu pemisahan (split) dalam Decision Trees. Algoritma akan memilih variabel dan titik split pada variabel tersebut berdasarkan metrik-metrik ini (tergantung apa yang memberikan peningkatan terbesar pada kinerja tree).
Dalam rpart
, fitur-fitur pemilihan untuk split dalam
tree secara default didasarkan pada pengukuran Gini
Impurity.
Rumus koefisien Gini pengukuran impurity pada setiap node \(\kappa\) adalah sebagai berikut:
\[ D_{\kappa} = \sum_{m=1}^M \widehat{p}_{m \kappa} (1-\widehat{p}_{m \kappa}) \]
Fungsi \(x*(1-x)\) berbentuk inverted U-shape, yang menunjukkan bahwa untuk sebuah node \(\kappa\), the impurity \(\widehat{p}_{m \kappa} (1-\widehat{p}_{m \kappa})\) adalah maksimum jika kondisi \(\widehat{p}_{m \kappa} =1/2\) yaitu ketika setiap split mengandung 50% Urban dan 50% Rural. Tujuannya adalah untuk menemukan split yang meminimalkan koefisien Gini dan memberikan pemisahan yang paling tidak impure.1
Jika dalam setiap node hanya ada satu kelas yang terwakili, maka impurity akan menjadi 0. Ini berarti bahwa node tersebut sepenuhnya murni, dengan tidak ada keraguan mengenai kelas yang seharusnya diprediksi. Dalam kondisi ini, Decision Trees telah berhasil memisahkan data dengan sangat baik, karena tidak ada ketidakpastian mengenai kelas apa yang akan diprediksi di node tersebut. Ini adalah kondisi ideal dalam Decision Trees dimana setiap node hanya berisi satu kelas.
Optimasi pemodelan lain yang umum digunakan didasarkan pada Information Gain, yang berakar dari teori informasi dan menggunakan Entropy yaitu suatu ukuran ketidakpastian dalam data. Formula untuk menghitung Entropy adalah:
\[ D_{\kappa}^ E = - \sum_{m=1}^M \widehat{p}_{m \kappa} \log \widehat{p}_{m \kappa} \] Entropi mengukur ketidakpastian atau keragaman dalam data; semakin tinggi nilai entropi, semakin tidak pasti distribusi data tersebut.
Sebuah dataset dengan proporsi 50:50 untuk dua kelas akan memiliki entropi maksimum (sama dengan 1), sedangkan dataset yang tidak seimbang dengan pembagian 10:90 akan memiliki entropi yang lebih kecil.2
Disini diiharapkan nilai entropi (atau impurity) setelah pemisahan bernilai lebih rendah daripada nilai entropi sebelumnya, sehingga diperoleh informasi yang baik (ketidakpastian berkurang).
Information Gain mengukur perbedaan antara entropi sebelum dan setelah dilakukan pemisahan pohon (split trees), untuk melihat seberapa besar pengurangan ketidakpastian. Information Gain yang maksimum kemudian dipilih untuk menentukan pemisahan (split) terbaik saat ini.
\[Information\;Gain = Entropy_{\; Before} - Entropy_{\; After}\] Kedua metrik ini (Gini Impurity dan Entropy) digunakan dalam pembagian decision trees dan sering menghasilkan performa yang baik.
Catatan teknis:
Karena Entropy menggunakan log(), penghitungannya lebih kompleks secara komputasional, yang perlu diingat jika akan melakukan penyesuaian banyak pohon keputusan3. Satu permasalahan saat menggunakan Information Gain adalah ukuran ini cenderung lebih menghasilkan atribut dengan banyak nilai unik yang pada akhirnya dapat menyebabkan overfitting.
cp.input <- 0
maxdepth.input <- 30
Original_tree_fit <- rpart(Residence~.,
data= train_data,
control = rpart::rpart.control(cp = cp.input,
maxdepth = maxdepth.input))
# Visualizing a tree
fancyRpartPlot(Original_tree_fit,
caption = paste("Decision tree with no constrains" ),
)
Seperti yang terlihat, Decision Trees dapat menjadi sangat kompleks dan terlalu spesifik dari dataset yang digunakan. Hal ini dapat menyebabkan model menjadi kurang baik dalam melakukan prediksi dan rentan terjadi overfitting.
Pendekatan standard dalam melatih model Decision Trees adalah dengan mengoptimalkan berdasarkan kedalaman maksimum dari tree. Untuk melakukan ini, dideklarasikan method = “rpart2”. Pendekatan ini sangat berguna ketika mulai eksplorasi data, karena belum diketahui seberapa sulit data tersebut untuk dianalisis, atau seberapa kompleks Decision Trees dapat terjadi.
Selanjutnya akan dibuat model Decision Trees menggunakan
rpart
dan rpart2
. Dimulai dengan
rpart2
karena metode ini dapat memberikan gambaran awal
mengenai tingkat kesulitan masalah yang diprediksi.
set.seed(785)
maxdepth_tree_fit <- train(Residence ~.,
data = train_data,
method = "rpart2",
tuneLength = 10,
trControl = K5_CV_seed)
## note: only 9 possible values of the max tree depth from the initial fit.
## Truncating the grid to 9 .
maxdepth_tree_fit
## CART
##
## 4442 samples
## 7 predictor
## 2 classes: '1', '2'
##
## No pre-processing
## Resampling: Cross-Validated (5 fold)
## Summary of sample sizes: 3553, 3554, 3554, 3554, 3553
## Resampling results across tuning parameters:
##
## maxdepth Accuracy Kappa
## 2 0.6490307 0.1244580
## 3 0.6548853 0.1714735
## 4 0.6548853 0.1714735
## 5 0.6548853 0.1714735
## 10 0.6548853 0.1714735
## 16 0.6548853 0.1714735
## 19 0.6548853 0.1714735
## 23 0.6548853 0.1714735
## 29 0.6548853 0.1714735
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was maxdepth = 3.
Dengan kedalaman maksimum yang sangat rendah, sudah dapat dicapai
akurasi yang baik dan nilai Kappa
yang memuaskan. Algoritma
memilih max depth = 3 sebagai kedalaman maksimum optimal, karena setelah
itu, peningkatan kedalaman tidak memberikan peningkatan performa pada
Decision Trees yang dihasilkan.
Berikut divisualisasikan proses penyetelan parameter max depth untuk memeriksa apakah pilihan max depth = 3 memang sesuai.
ggplot(maxdepth_tree_fit) +
ggtitle("Decision Tree - Tuning Maximum Depth") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5))
Dari output di atas, terlihat pilihan 3 adalah keputusan yang masuk akal untuk kedalaman maksimum tree. Bila dilihat lebih jauh, peningkatan performa pada depth berikutnya ternyata cukup kecil namun memberikan tree yang lebih kompleks (akan sulit untuk interpretasi).
Kemudian selanjutnya perlu dilakukan evaluasi performa model pada testing data untuk memastikan bahwa tree ini tidak hanya bagus pada data training, tetapi juga mampu bekerja dengan baik pada data yang belum pernah dilihat sebelumnya (out of sampling).
Berikut merupakan visualisasi decision trees dengan kedalaman optimal (optimal depth):
fancyRpartPlot(maxdepth_tree_fit$finalModel,
caption = "Tree Selected (max depth = 3)")
# rpart.plot(maxdepth_tree_fit$finalModel,
# main = "Tree Selected (max depth = 3)" ,
# type = 4,
# under = TRUE, box.palette = c(Urbancolor, Ruralcolor),
# clip.right.labs = FALSE, branch = .3)
Evaluasi model dalam dilakukan pada proses evaluasi kinerja decision trees pada data di luar sampel (out of sample performance):
maxdepth_tree_pred <- predict(maxdepth_tree_fit, validation_data)
confusionMatrix(maxdepth_tree_pred, validation_data$Residence, positive = "1")
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2
## 1 131 69
## 2 290 619
##
## Accuracy : 0.6763
## 95% CI : (0.6479, 0.7038)
## No Information Rate : 0.6204
## P-Value [Acc > NIR] : 6.055e-05
##
## Kappa : 0.2348
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.3112
## Specificity : 0.8997
## Pos Pred Value : 0.6550
## Neg Pred Value : 0.6810
## Prevalence : 0.3796
## Detection Rate : 0.1181
## Detection Prevalence : 0.1803
## Balanced Accuracy : 0.6054
##
## 'Positive' Class : 1
##
Tampak dari hasil output di atas, nilai akurasi, kappa, dan true positive rate (Sensitivitas) tinggi, serta false positive rate (1 - spesifisitas) rendah, menunjukkan bahwa model berkinerja baik pada data di luar sampel.
Berikut divisualisasikan pentingnya fitur dari decision trees untuk memberikan informasi tentang variabel mana yang berguna dalam memprediksi tempat tinggal di pedesaan atau perkotaan:
theme_models <- theme(plot.title = element_text(hjust = 0.5),
legend.position = "none")
RF_varImp <- data.frame(variables = row.names(varImp(maxdepth_tree_fit)$importance), varImp(maxdepth_tree_fit)$importance)
ggplot(data = RF_varImp, mapping = aes(x=reorder(variables, Overall),
y=Overall)) +
coord_flip() +
geom_bar(stat = "identity", position = "dodge", fill = "Blue") +
theme_minimal()+
theme_models +
labs(x = "", y = "",
title= "Feature Importance",
subtitle = "Decision Tree with optimized max depth")
Tingkat pendidikan dari KRT menjadi faktor penting. Selain itu, quintile juga tampaknya penting untuk digunakan sebagai faktor prediksi.
Complexity parameter \(C_p\) mengatur trade-off antara ukuran pohon dan akurasinya dalam mengklasifikasikan data. Definisi formalnya adalah:
\[ D_{C_p}(T) = D(T) + C_p \cdot |T| \] dimana:
Jumlah node terminal dalam pohon adalah jumlah node yang tidak memiliki cabang lebih lanjut atau tidak membagi data lebih lanjut. Node-terminal adalah titik akhir dalam pohon keputusan yang menunjukkan keputusan akhir atau klasifikasi untuk observasi tertentu.
Complexity parameter digunakan untuk “penalize” terhadap jumlah node terminal \(T\) dalam pencarian pohon dengan impurity minimal.
Menggunakan complexity parameter yang terlalu besar akan menghasilkan pohon yang kecil dan underfit, sementara menggunakan nilai complexity parameter yang kecil akan menghasilkan pohon yang besar, spesifik untuk dataset pelatihan, dan rentan terhadap overfitting.
Untuk memilih hyperparameters model, dibuat grid nilai untuk complexity parameter dan menyesuaikan pohon untuk setiap nilai dalam grid tersebut untuk melihat mana yang memberikan performa terbaik dengan menggunakan 5-fold cross validation.
Setting method = “rpart” ketika melatih menggunakan caret, pohon akan dioptimalkan dengan menyesuaikan hyperparameters, seperti complexity parameter (CP), untuk meningkatkan kinerjanya. Ini biasanya dilakukan menggunakan teknik seperti cross-validation, dimana berbagai nilai untuk hyperparameters diuji untuk menemukan kombinasi yang menghasilkan performa model terbaik 4
Sekarang dilakukan penyesuaian pohon baru berdasarkan complexity parameter. Langkah awalnya dibuat grid nilai yang ingin dicoba nilai complexity parameter, dan disesuaikan decision trees yang divalidasi silang untuk setiap nilai dalam grid tersebut:
set.seed(785)
cp_grid <- expand.grid(cp = seq(from = 0, to = 0.01, by = 0.001))
cp_tree_fit <- train(Residence ~.,
data = train_data,
method = "rpart",
trControl = K5_CV_seed,
tuneGrid = cp_grid)
cp_tree_fit
## CART
##
## 4442 samples
## 7 predictor
## 2 classes: '1', '2'
##
## No pre-processing
## Resampling: Cross-Validated (5 fold)
## Summary of sample sizes: 3553, 3554, 3554, 3554, 3553
## Resampling results across tuning parameters:
##
## cp Accuracy Kappa
## 0.000 0.6400265 0.1699200
## 0.001 0.6465547 0.1766579
## 0.002 0.6533100 0.1825295
## 0.003 0.6537617 0.1848894
## 0.004 0.6587139 0.1924763
## 0.005 0.6584889 0.1916166
## 0.006 0.6584889 0.1916166
## 0.007 0.6578132 0.1892808
## 0.008 0.6578132 0.1892808
## 0.009 0.6578132 0.1888579
## 0.010 0.6548853 0.1714735
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was cp = 0.004.
Dari hasil algoritma, model terbaik diperoleh dengan nilai CP = 0.004 yang ditunjukkan dengan nilai akurasi paling tinggi.
ggplot(cp_tree_fit) +
ggtitle("Decision Tree - Tuning Complexity Parameter Cp") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5))
Dari gambar tersebut, dapat dilihat bahwa dengan menggunakan nilai 0.004, dicapai akurasi yang hampir sempurna pada data training.
Ketika CP bernilai 0, tidak ada batasan terhadap sejauh mana pohon keputusan dapat tumbuh selama kinerjanya terus meningkat, pohon tersebut akan menjadi semakin kompleks.
Seperti sebelumnya, divisualisasikan pohon dengan \(C_p\) yang telah dioptimalkan.
fancyRpartPlot(cp_tree_fit$finalModel, caption = "Tree with optimized Cp")
Tampak decision tree ini sangat kompleks karena tidak ada batasan pada seberapa dalam pohon tersebut dapat berkembang. Jika interpretasi decision tree penting, model seperti ini tidak akan dapat digunakan karena terlalu rumit untuk dipahami.
Dalam kasus seperti ini, pembatasan kedalaman maksimum pohon menjadi hal yang penting, karena pohon yang lebih dangkal tidak hanya lebih mudah diinterpretasikan tetapi juga lebih kecil kemungkinannya untuk overfitting. Selain itu, pohon yang lebih sederhana cenderung memberikan informasi yang lebih jelas tentang hubungan antara variabel independen dan target.
Untuk memeriksa apakah model decision tree ini mengalami overfitting, maka dapat dilakukan cross-validation atau membagi data menjadi data training dan data test. Dengan cara ini, bisa diuji performa model pada data yang tidak pernah dilihat sebelumnya oleh model dan mengevaluasi apakah model tersebut hanya bekerja baik pada data pelatihan atau tidak.
cp_tree_pred <- predict(cp_tree_fit, validation_data)
confusionMatrix(cp_tree_pred, validation_data$Residence, positive = "1")
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2
## 1 130 67
## 2 291 621
##
## Accuracy : 0.6772
## 95% CI : (0.6488, 0.7047)
## No Information Rate : 0.6204
## P-Value [Acc > NIR] : 4.668e-05
##
## Kappa : 0.2357
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.3088
## Specificity : 0.9026
## Pos Pred Value : 0.6599
## Neg Pred Value : 0.6809
## Prevalence : 0.3796
## Detection Rate : 0.1172
## Detection Prevalence : 0.1776
## Balanced Accuracy : 0.6057
##
## 'Positive' Class : 1
##
Dari hasil, model ini dapat menggeneralisasi dengan sangat baik dan tidak menunjukkan tanda-tanda overfitting meskipun sangat kompleks.
Seperti sebelumnya, dilakukan eksplorasi fitur-fitur mana yang penting dalam decision tree:
theme_models <- theme(plot.title = element_text(hjust = 0.5),
legend.position = "none")
RF_varImp <- data.frame(variables = row.names(varImp(cp_tree_fit)$importance), varImp(cp_tree_fit)$importance)
ggplot(data = RF_varImp, mapping = aes(x=reorder(variables, Overall),
y=Overall)) +
coord_flip() +
geom_bar(stat = "identity", position = "dodge", fill = "Red") +
theme_minimal()+
theme_models +
labs(x = "", y = "",
title= "Feature Importance",
subtitle = "Decision Tree with optimized Cp")
Pendidikan dan Kuintil tetap menjadi fitur yang paling penting.
Karena decision tree tidak memiliki cara untuk melakukan regularisasi, bagaimana dapat mencegah overfitting?
Seperti yang telah dilihat sebelumnya ketika memvisualisasikan pohon keputusan (decision tree), pohon tersebut secara cepat menjadi sangat besar. Ada beberapa teknik yang dapat digunakan dengan pohon keputusan untuk mencoba mencegah overfitting. Salah satu caranya adalah dengan menetapkan kriteria penghentian dini untuk mencegah pohon menjadi terlalu besar. Contohnya termasuk menetapkan kedalaman maksimum, jumlah minimum sampel training di setiap node daun, jumlah maksimum node daun, dan ambang batas yang menentukan penurunan minimum impurity di sebuah node untuk memutuskan apakah node tersebut akan membelah lagi atau menjadi node daun.
Pendekatan umum adalah menggunakan kombinasi dari teknik-teknik ini.
Saat memfitting pohon, dapat ditetapkan kedalaman maksimum, dan setelah
pohon tersebut selesai di-fitting, dapat kembali dan memangkas pohon
(pruning) tersebut untuk membuatnya lebih mampu melakukan
generalisasi. Hal ini juga akan membuat pohon lebih mudah untuk
diinterpretasikan. Dengan menggunakan fungsi prune() di
rpart
, dapat dilakukan pemangkasan pohon berdasarkan
parameter kompleksitas yang dipilih. Ini akan menghilangkan node-node
yang dianggap paling tidak penting dari pohon yang sudah di-fitting.
Setelah pohon dilatih tanpa batasan tertentu, dapat dilakukan pemangkasan (pruning) dengan menggunakan parameter kompleksitas untuk menghapus simpul yang kurang penting. Pruning membantu membuat model lebih sederhana, lebih mudah diinterpretasikan, dan cenderung lebih baik dalam generalisasi.
# Prunes the tree
pruned_tree <- prune(cp_tree_fit$finalModel, cp = 0.005)
fancyRpartPlot(pruned_tree, caption = "Pruned tree")
Dengan menggunakan pemangkasan (pruning), dapat secara signifikan mengurangi kompleksitas pohon, sehingga sekarang pohon menjadi lebih mudah untuk diinterpretasikan. Memangkas pohon sering kali menyebabkan penurunan kinerja, tetapi hal ini membuat pohon lebih mudah dipahami. Ini adalah trade-off yang perlu dipertimbangkan saat bekerja dengan data.
Direktorat Statistik Kesejahteraan Rakyat, BPS, saptahas@bps.go.id
Jika di setiap node hanya ada satu kelas yang terwakili, maka \(D_{\kappa}\) = 0.↩︎
Karena logaritma dari pecahan menghasilkan nilai negatif, tanda “-” digunakan dalam formula entropi untuk meniadakan nilai negatif ini.↩︎
seperti pada Random Forest↩︎
lihat rpart vignette↩︎