neural network dengan jenis MLP multli layer perceptron sangat cocok untuk digunakan pada permasalahan non linear. Biasanya kami menggunakan python dengan pytorch atau matlab nn toolbox nya, kali ini kita akan menggunakan R dengan package neuralnet nya.
neural network atau Jaringan saraf adalah model yang dicirikan oleh fungsi aktivasi, yang digunakan oleh unit pemrosesan informasi yang saling berhubungan untuk mengubah input menjadi output.
Sekilas Neural network
Contents
Lapisan pertama dari jaringan saraf menerima input mentah, memprosesnya dan meneruskan informasi yang diproses ke lapisan tersembunyi. Lapisan tersembunyi meneruskan informasi ke lapisan terakhir, yang menghasilkan output. Keuntungan dari neural network adalah sifatnya yang adaptif. Ia belajar dari informasi yang diberikan, yaitu melatih dirinya sendiri dari data, yang memiliki hasil yang diketahui dan mengoptimalkan bobotnya untuk prediksi yang lebih baik dalam situasi dengan hasil yang tidak diketahui. Belajar Algoritma Multi Layer Percepton dan Neural Network Backpropagation
Sebuah perceptron, mis. jaringan saraf lapisan tunggal, adalah bentuk paling dasar dari jaringan saraf. Perceptron menerima input multidimensi dan memprosesnya menggunakan penjumlahan berbobot dan fungsi aktivasi. Ini dilatih menggunakan data berlabel dan algoritma pembelajaran yang mengoptimalkan bobot dalam prosesor penjumlahan. Pembahasan mengenai Algoritma Perceptron
Keterbatasan utama model perceptron adalah ketidakmampuannya untuk menangani non-linearitas. Jaringan saraf multilayer atau disebut dengan MLP mengatasi keterbatasan ini dan membantu memecahkan masalah non-linear. Lapisan input terhubung dengan lapisan tersembunyi, yang pada gilirannya terhubung ke lapisan output. Koneksi diberi bobot dan bobot dioptimalkan menggunakan aturan pembelajaran / fungsi aktifasi.
Package neuralnet
Setelah kalian mengingat kembali pembahasan mengenai dasar neural network bisa kalian pelajari sendiri di blog ini, kita akan coba dengan kasus sederhana yaitu logika boolean AND dan nanti dilanjut dengan data iris dengan nilai input dan output berbeda (tidak rentang 0 sampai 1 tapi). Pastikan kalian install dulu package tersebut install.packages("neuralnet")
Kita coba dengan logika boleean AND
library(neuralnet) df = data.frame(x1=c(1,1,0,0), x2=c(1,0,1,0), y=c(1,0,0,0)) df
Dengan paramater pelatihan sebagai berikut yaitu terdiri dari 2 layer hidden. Untuk hidden 1 menggunakan 3 node; hidden 2 menggunakan 2 node
nn = neuralnet::neuralnet( y ~ x1+x2, data = df, hidden = c(3,2), #jumlah node tiap layer act.fct = "logistic", learningrate = 0.25, stepmax = 10000, #maksimal epoch, threshold = 0.01, #error minimal, lifesign = 'full', #untuk nampilkan log linear.output = FALSE ) nn$net.result plot(nn)
kita run saja, hanya dengan 17 epoch sudah mencapai error yang kita inginkan
hidden: 3, 2 thresh: 0.01 rep: 1/1 steps: 17 error: 0.36525 time: 0.01 secs
kita bisa melihat hasil antara y dan prediksi sebagai berikut
df.test = data.frame(x1 = df$x1, df$x2) prediksi = neuralnet::compute(nn,df.test) hasil = data.frame(df$y,prediksi$net.result) hasil
hasil
df.y prediksi.net.result 1 1 0.96540078 2 0 0.02267503 3 0 0.02113256 4 0 0.01344797
Kita hitung nilai MSE nya
RMSE.NN = (sum((df$y - prediksi$net.result)^2) / nrow(df)) ^ 0.5 RMSE.NN
hasil
0.02418003
Uji data iris
Selanjutnya kita akan pakai data iris yang mempunyai 3 kelas yaitu
- setosa
- versicolor
- virginica
Dengan isi data tersebut sebagai berikut
Sepal.Length Sepal.Width Petal.Length Petal.Width Species 1 5.1 3.5 1.4 0.2 setosa 2 4.9 3.0 1.4 0.2 setosa 3 4.7 3.2 1.3 0.2 setosa 4 4.6 3.1 1.5 0.2 setosa 5 5.0 3.6 1.4 0.2 setosa
ada 4 ciri fitur yaitu Sepal length, Sepal width, petal length, dan petal width dan kolom species yang masih berupa string, oleh sebab itu sekalian saja kita ubah dengan menambahkan kolom Target dengan angka 1 sampai dengan 3
library(dplyr) df = iris df spc = df %>% distinct(Species) spc$id = c(1:nrow(spc)) spc for(i in c(1:nrow(spc))){ print(i) df = df %>% mutate(Target=ifelse(Species==spc$Species[i],spc$id[i],Species)) }
jadinya akan tampil sebagai berikut
Sepal.Length Sepal.Width Petal.Length Petal.Width Species Target 1 5.1 3.5 1.4 0.2 setosa 1 2 4.9 3.0 1.4 0.2 setosa 1 3 4.7 3.2 1.3 0.2 setosa 1 4 4.6 3.1 1.5 0.2 setosa 1 5 5.0 3.6 1.4 0.2 setosa 1
Kita akan mencoba untuk langsung menggunakannya tanpa melakukan normalisasi. Setting paramater untuk NN nya
nn = neuralnet::neuralnet( Target ~ Sepal.Length+Sepal.Width+Petal.Length+Petal.Width, data = df, hidden = c(10,7), #jumlah node tiap layer act.fct = "logistic", learningrate = 0.25, stepmax = 10000, #maksimal epoch, threshold = 0.001, #error minimal, lifesign = 'full', #untuk nampilkan log linear.output = TRUE ) nn$net.result
hemmm… apa yang terjadi?? Yup tidak terjadi konvergen! karena kita salah desain!
hidden: 10, 7 thresh: 0.001 rep: 1/1 steps: 1000 min thresh: 0.159415218879433 2000 min thresh: 0.0541540621703262 3000 min thresh: 0.0541540621703262 4000 min thresh: 0.0541540621703262 5000 min thresh: 0.0541540621703262 6000 min thresh: 0.0541540621703262 7000 min thresh: 0.0541540621703262 8000 min thresh: 0.0522047927959751 9000 min thresh: 0.0504079140553987 stepmax min thresh: 0.0504079140553987 Warning message: Algorithm did not converge in 1 of 1 repetition(s) within the stepmax.
Hal ini terjadi karena menggunakan fungsi aktifasi bertipe logistics yang mempunyai rentang 0 sampai 1 saja. Padahal atribut input dan target mempunyai nilai lebih dari 1. Oleh sebab itu, kita perlu melakukan penyesuaian yaitu
- normalisasi nilai input
- mengubah nilai target kategorikal menjadi one hot encoding
Mengubah input menjadi skala 0 sampai 1
kita cara cepat saja dengan mengubah menjadi skala 0 sampai dengan 1 yaitu dibagi dengan nilai maksimalnya atau bagi dengan 10 saja. Kita buat saja variabel baru dengan nama df2
df2 = df for(i in c(1:4)){ df2[,i] = df2[,i]/10 }
hasilnya input sudah mempunyai sekala 0 sampai 1
Sepal.Length Sepal.Width Petal.Length Petal.Width Species Target 1 0.51 0.35 0.14 0.02 setosa 1 2 0.49 0.30 0.14 0.02 setosa 1 3 0.47 0.32 0.13 0.02 setosa 1 4 0.46 0.31 0.15 0.02 setosa 1 5 0.50 0.36 0.14 0.02 setosa 1
Mengubah kategorikal menjadi one hot encoding
Apa itu one hot encoding? mengubah skala kategorikal menjadi biner, hal ini ditemui pada kasus yang bersifat multi kelas. Banyak cara yang bisa kita gunakan misalkan ada 3 kelas dengan aturan sebagai berikut
- cara pertama, hal mudahnya untuk membedakan kelas 1, 2, dan 3 dengan cara sum()
- kelas 1 : [1,0,0]
- kelas 2: [1,1,0]
- kelas 3: [1,1,1]
- cara kedua, caranya cukup unik untuk membedakan kelas 1, 2, dan 3 yaitu argmax()
- kelas 1: [1,0,0]
- kelas 2: [0,1,0]
- kelas 3: [0,0,1]
Kita pilih cara kedua yang disebut dengan one hot encoding caranya sebagai berikut
ok, sebagai gambarannya nanti kita akan membuat 3 kolom yaitu y1, y2, dan y3 dengan aturan sebagai berikut
df2 = df2 %>% mutate(y1=ifelse(Target==1,1,0)) df2 = df2 %>% mutate(y2=ifelse(Target==2,1,0)) df2 = df2 %>% mutate(y3=ifelse(Target==3,1,0))
Yuk kita lihat hasilnya
Sepal.Length Sepal.Width Petal.Length Petal.Width Species Target y1 y2 y3 1 0.51 0.35 0.14 0.02 setosa 1 1 0 0 2 0.49 0.30 0.14 0.02 setosa 1 1 0 0 3 0.47 0.32 0.13 0.02 setosa 1 1 0 0 4 0.46 0.31 0.15 0.02 setosa 1 1 0 0 5 0.50 0.36 0.14 0.02 setosa 1 1 0 0 6 0.54 0.39 0.17 0.04 setosa 1 1 0 0
Perhatikan
- jika Setosa maka kolom y1 = 1 serta y2 dan y3 = 0
- jika versicolor maka kolom y2 = 1 serta y1 dan y3 = 0
- jika Virginica maka kolom y3 = 1 serta y1 dan y2 = 0
melalui teknik argmax yaitu mencari posisi index tertinggi pada sebuah array, misalkan ada array [0,0,1] maka argmax nya yaitu no 3 sehingga record tersebut adalah virginica.
Jika sudah selesai, kita desain lagi menjadi seperti berikut neural networknya
nn = neuralnet::neuralnet( y1+y2+y3 ~ Sepal.Length+Sepal.Width+Petal.Length+Petal.Width, data = df2, hidden = c(10,7), #jumlah node tiap layer act.fct = "logistic", learningrate = 0.25, stepmax = 10000, #maksimal epoch, threshold = 0.001, #error minimal, lifesign = 'full', #untuk nampilkan log linear.output = FALSE )
hasilnya kita akan sesuaikan dengan berikut
library(ramify) prediksi = data.frame(prediksi[1]) prediksi = ramify::argmax(as.matrix(prediksi)) hasil = data.frame(target = df2$Target,prediksi = prediksi) hasil
hasilnya
target prediksi 1 1 1 2 1 1 3 1 1 4 1 1 5 1 1 6 1 1
Ploting Error tiap Epoch
Setelah kalian memahami teknik yang digunakan untuk desain ke dalam Neural Network MLP dengan package Neuralnet, maka timbul pertanyaan: “Bagaimana kita dapat melakukan ploting error tiap epoch?”
Ternyata package neuralnet tidak mengembalikan/return error tiap epoch, kita bisa melihat dan edit source code bila perlu dengan mengetikan perintah trace(neuralnet::neuralnet, edit = T)
function (formula, data, hidden = 1, threshold = 0.01, stepmax = 1e+05, rep = 1, startweights = NULL, learningrate.limit = NULL, learningrate.factor = list(minus = 0.5, plus = 1.2), learningrate = NULL, lifesign = "none", lifesign.step = 1000, algorithm = "rprop+", err.fct = "sse", act.fct = "logistic", linear.output = TRUE, exclude = NULL, constant.weights = NULL, likelihood = FALSE) { call <- match.call() if (is.null(data)) { stop("Missing 'data' argument.", call. = FALSE) } data <- as.data.frame(data) if (is.null(formula)) { stop("Missing 'formula' argument.", call. = FALSE) } formula <- stats::as.formula(formula) if (!is.null(learningrate.limit)) { if (length(learningrate.limit) != 2) { stop("Argument 'learningrate.factor' must consist of two components.", call. = FALSE) } learningrate.limit <- as.list(learningrate.limit) names(learningrate.limit) <- c("min", "max") if (is.na(learningrate.limit$min) || is.na(learningrate.limit$max)) { stop("'learningrate.limit' must be a numeric vector", call. = FALSE) } } else { learningrate.limit <- list(min = 1e-10, max = 0.1) } if (!is.null(learningrate.factor)) { if (length(learningrate.factor) != 2) { stop("Argument 'learningrate.factor' must consist of two components.", call. = FALSE) } learningrate.factor <- as.list(learningrate.factor) names(learningrate.factor) <- c("minus", "plus") if (is.na(learningrate.factor$minus) || is.na(learningrate.factor$plus)) { stop("'learningrate.factor' must be a numeric vector", call. = FALSE) } } else { learningrate.factor <- list(minus = 0.5, plus = 1.2) } if (algorithm == "backprop") { if (is.null(learningrate) || !is.numeric(learningrate)) { stop("Argument 'learningrate' must be a numeric value, if the backpropagation algorithm is used.", call. = FALSE) } } if (!(lifesign %in% c("none", "minimal", "full"))) { stop("Argument 'lifesign' must be one of 'none', 'minimal', 'full'.", call. = FALSE) } if (!(algorithm %in% c("rprop+", "rprop-", "slr", "sag", "backprop"))) { stop("Unknown algorithm.", call. = FALSE) } if (is.na(threshold)) { stop("Argument 'threshold' must be a numeric value.", call. = FALSE) } if (any(is.na(hidden))) { stop("Argument 'hidden' must be an integer vector or a single integer.", call. = FALSE) } if (length(hidden) > 1 && any(hidden == 0)) { stop("Argument 'hidden' contains at least one 0.", call. = FALSE) } if (is.na(rep)) { stop("Argument 'rep' must be an integer", call. = FALSE) } if (is.na(stepmax)) { stop("Argument 'stepmax' must be an integer", call. = FALSE) } if (!(is.function(act.fct) || act.fct %in% c("logistic", "tanh"))) { stop("Unknown activation function.", call. = FALSE) } if (!(is.function(err.fct) || err.fct %in% c("sse", "ce"))) { stop("Unknown error function.", call. = FALSE) } model.list <- list(response = attr(terms(as.formula(call("~", formula[[2]]))), "term.labels"), variables = attr(terms(formula, data = data), "term.labels")) response <- as.matrix(model.frame(as.formula(call("~", formula[[2]])), data)) covariate <- cbind(intercept = 1, as.matrix(data[, model.list$variables])) if (is.character(response)) { class.names <- unique(response[, 1]) response <- model.matrix(~response[, 1] - 1) == 1 colnames(response) <- class.names model.list$response <- class.names } if (is.function(act.fct)) { act.deriv.fct <- Deriv::Deriv(act.fct) attr(act.fct, "type") <- "function" } else { converted.fct <- convert.activation.function(act.fct) act.fct <- converted.fct$fct act.deriv.fct <- converted.fct$deriv.fct } if (is.function(err.fct)) { attr(err.fct, "type") <- "function" err.deriv.fct <- Deriv::Deriv(err.fct) } else { converted.fct <- convert.error.function(err.fct) err.fct <- converted.fct$fct err.deriv.fct <- converted.fct$deriv.fct } if (attr(err.fct, "type") == "ce" && !all(response %in% 0:1)) { stop("Error function 'ce' only implemented for binary response.", call. = FALSE) } list.result <- lapply(1:rep, function(i) { if (lifesign != "none") { lifesign <- display(hidden, threshold, rep, i, lifesign) } calculate.neuralnet(learningrate.limit = learningrate.limit, learningrate.factor = learningrate.factor, covariate = covariate, response = response, data = data, model.list = model.list, threshold = threshold, lifesign.step = lifesign.step, stepmax = stepmax, hidden = hidden, lifesign = lifesign, startweights = startweights, algorithm = algorithm, err.fct = err.fct, err.deriv.fct = err.deriv.fct, act.fct = act.fct, act.deriv.fct = act.deriv.fct, rep = i, linear.output = linear.output, exclude = exclude, constant.weights = constant.weights, likelihood = likelihood, learningrate.bp = learningrate) }) matrix <- sapply(list.result, function(x) { x$output.vector }) if (all(sapply(matrix, is.null))) { list.result <- NULL matrix <- NULL ncol.matrix <- 0 } else { ncol.matrix <- ncol(matrix) } if (ncol.matrix < rep) { warning(sprintf("Algorithm did not converge in %s of %s repetition(s) within the stepmax.", (rep - ncol.matrix), rep), call. = FALSE) } generate.output(covariate, call, rep, threshold, matrix, startweights, model.list, response, err.fct, act.fct, data, list.result, linear.output, exclude) }
cermati kode berikut yang hanya lakukan operasi lapply hal ini untuk mempercepat looping namun return value nya tidak ada.
list.result <- lapply(1:rep, function(i) { if (lifesign != "none") { lifesign <- display(hidden, threshold, rep, i, lifesign) } calculate.neuralnet(learningrate.limit = learningrate.limit, learningrate.factor = learningrate.factor, covariate = covariate, response = response, data = data, model.list = model.list, threshold = threshold, lifesign.step = lifesign.step, stepmax = stepmax, hidden = hidden, lifesign = lifesign, startweights = startweights, algorithm = algorithm, err.fct = err.fct, err.deriv.fct = err.deriv.fct, act.fct = act.fct, act.deriv.fct = act.deriv.fct, rep = i, linear.output = linear.output, exclude = exclude, constant.weights = constant.weights, likelihood = likelihood, learningrate.bp = learningrate) })
nanti kalau ada update cara return value diatas, akan saya bahas disini biar terlihat error tiap epoch melalui grafik.