Neural Network MLP dengan package Neuralnet

By | November 12, 2022
948 Views

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

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

  1. setosa
  2. versicolor
  3. virginica
See also  Pengertian dan Penjelasan Deteksi Autokorelasi

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

  1. normalisasi nilai input
  2. 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

  1. cara pertama, hal mudahnya untuk membedakan kelas 1, 2, dan 3 dengan cara sum()
    1. kelas 1 : [1,0,0]
    2. kelas 2: [1,1,0]
    3. kelas 3: [1,1,1]
  2. cara kedua, caranya cukup unik untuk membedakan kelas 1, 2, dan 3 yaitu argmax()
    1. kelas 1: [1,0,0]
    2. kelas 2: [0,1,0]
    3. kelas 3: [0,0,1]
See also  Mempercepat Eksekusi Kode

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

  1. jika Setosa maka kolom  y1 = 1 serta y2 dan y3 = 0
  2. jika versicolor maka kolom  y2 = 1 serta y1 dan y3 = 0
  3. 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.

See also  Manajemen File dan Folder di R