pip install tensorflow
Modul 7 Praktikum Sains Data: Pengantar Neural Network dengan TensorFlow & Keras
Modul 7 Praktikum Sains Data: Pengantar Neural Network dengan TensorFlow & Keras
Kembali ke Sains Data
Sekarang kita sudah masuk ke materi artificial neural network (ANN) atau biasa disebut neural network (NN), yang mendasari dunia deep learning.
Saat modul praktikum ini disusun (April 2024), ada dua framework utama untuk deep learning di Python, yaitu:
TensorFlow: https://www.tensorflow.org/
(dan Keras di dalamnya: https://keras.io/)
PyTorch: https://pytorch.org/
Kedua framework ini bersaing. Umumnya, TensorFlow lebih sering digunakan di industri, sedangkan PyTorch lebih sering digunakan dalam riset/penelitian.
Di pertemuan kali ini, kita akan membahas TensorFlow, baik penggunaannya secara sendiri (pure TensorFlow, yaitu tanpa Keras) maupun dengan bantuan Keras. Kalau belum punya, instal terlebih dahulu:
Keras terinstal bersama TensorFlow (karena Keras ada di dalamnya).
Lalu import:
import tensorflow as tf
from tensorflow import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
Teori Neural Network
Overview:
Secara umum, suatu neural network terdiri dari sejumlah layer atau lapisan (minimal dua).
Layer pertama disebut input layer, dan layer terakhir disebut output layer.
Tiap layer terdiri dari sejumlah neuron, yang masing-masing bisa menyimpan suatu nilai.
Kecuali input layer, tiap neuron terhubung dengan sejumlah neuron di layer sebelumnya.
Tiap sambungan terdiri dari nilai weight (sebagai pengali), nilai bias (sebagai pergeseran), dan suatu “fungsi aktivasi” yang menghasilkan nilai untuk neuron tujuan.
Weight maupun bias disebut parameter dari neural network.
Proses training adalah terus-menerus memperbarui parameter hingga hasil prediksi neural network sudah cukup baik, dengan meminimumkan suatu loss function atau fungsi objektif (yang intinya menghitung error).
Suatu neural network bisa memiliki sejumlah layer, masing-masing dengan banyaknya neuron tertentu dan fungsi-fungsi aktivasi tertentu. Hal-hal itu disebut hyperparameter dari neural network. Suatu arsitektur adalah suatu pilihan/konfigurasi hyperparameter.
SLP: (Single-Layer) Perceptron
ANN paling pertama adalah perceptron (juga disebut SLP atau single-layer perceptron) yang dirancang oleh Frank Rosenblatt pada tahun 1957 (Géron, 2019). Ini adalah neural network yang paling sederhana, bahkan ini bisa disebut building block dari semua ANN (apabila diberi kebebasan untuk modifikasi). Konsep dasar neural network bisa kita pelajari di sini.
Sumber gambar: Aggarwal (2018) hal. 5
Perceptron hanya terdiri dari satu input layer dan satu output layer. Bahkan, aslinya hanya ada satu neuron di output layer.
Apabila dibutuhkan lebih dari satu neuron di output layer, itu bisa dianggap menggunakan lebih dari satu perceptron (yaitu menggunakan banyaknya perceptron sesuai banyaknya neuron di output layer), yang saling “ditumpuk”:
Sumber gambar: Goodfellow, et. al. (2016) hal. 337
Perhatikan bahwa, tiap neuron di layer asal terhubung dengan tiap neuron di layer tujuan. Layer tujuan seperti ini disebut dense (padat). Kebalikan dari dense adalah sparse.
Aslinya, fungsi aktivasi yang digunakan oleh perceptron adalah Heaviside step function \(H(v)\) yang mungkin kalian kenal dari mata kuliah PDB, atau juga disebut threshold activation function:
\[H(v) = \begin{cases} 1, & v \ge 0 \\ 0, & v < 0 \end{cases}\]
Sehingga, untuk output neuron ke-\(j\) yang disambung dari \(n\) input neuron, model perceptron bisa dirumuskan sebagai berikut:
\[y_j = H\left(\left(\sum_{i=1}^{n} w_{ij} x_i \right) + b_j\right)\]
dengan
\(x_i\) adalah nilai pada input neuron ke-\(i\)
\(y_j\) adalah nilai pada output neuron ke-\(j\)
\(w_{ij}\) adalah parameter weight untuk sambungan input neuron ke-\(i\) menuju output neuron ke-\(j\)
\(b_j\) adalah parameter bias untuk output neuron ke-\(j\)
Lebih umumnya,
\[y_j = \Phi\left(\left(\sum_{i=1}^{n} w_{ij} x_i \right) + b_j\right)\]
dengan \(\Phi(v)\) adalah sembarang fungsi aktivasi.
Note: seperti di gambar, sebenarnya bias juga bisa dianggap neuron istimewa yang nilai \(x_i\) nya selalu satu.
Biasanya, semua nilai di layer selanjutnya dihitung secara sekaligus menggunakan perkalian matriks, dengan perumusan:
\[\textbf{y} = \Phi\left(W^T \textbf{x} + \textbf{b}\right)\]
dengan \(\textbf{x} = [x_i]\), \(\textbf{y} = [y_j]\), dan \(\textbf{b} = [b_j]\) adalah vektor kolom, serta \(W = \left[w_{ij}\right]\) adalah matriks.
Itu untuk satu buah data training.
Bisa saja, beberapa data training diperhitungkan sekaligus. Caranya, vektor kolom \(\textbf{x}\) itu kita “lebarkan” ke samping sehingga menjadi matriks \(X = [x_{it}]\), sehingga data training ke-\(t\) ada di kolom ke-\(t\). Dengan demikian, output nya akan berupa matriks \(Y = [y_{jt}]\) dengan hasil untuk data training ke-\(t\) ada di kolom ke-\(t\). Selain itu, vektor \(\textbf{b}\) perlu diperluas menjadi matriks \(B\) dengan tiap kolom identik, dan fungsi aktivasi \(\Phi\) dihitung per kolom.
\[Y = \Phi\left(W^T X + B\right)\]
Kembali ke kasus satu buah data training. Biasanya, dataset disajikan dengan tiap fitur di kolom sendiri, tidak seperti perumusan kita sejauh ini dengan tiap fitur di baris tersendiri. Untuk menyesuaikan, kita bisa men-transpose semuanya:
\[\textbf{y} = \Phi\left(\textbf{x} W + \textbf{b}\right)\]
dengan \(\textbf{x} = [x_i]\), \(\textbf{y} = [y_j]\), dan \(\textbf{b} = [b_j]\) adalah vektor baris, serta \(W = \left[w_{ji}\right]\) adalah matriks berisi bobot untuk menyambung ke output neuron ke-\(j\) dari input neuron ke-\(i\).
MLP: Multilayer Perceptron
Konsep single-layer perceptron bisa diperumum menjadi multilayer perceptron atau neural network yang biasa kita kenal, dengan menambahkan beberapa layer di antara input layer dan output layer. Semua layer selain input layer dan output layer disebut hidden layer.
Sumber gambar: Aggarwal (2018) hal. 18
Konsep perhitungan antara tiap layer tetap sama, yaitu
\[\textbf{y} = \Phi\left(\textbf{w}^T \textbf{x} + \textbf{b}\right)\]
(versi vektor kolom), atau
\[\textbf{y} = \Phi\left(\textbf{x} W + \textbf{b}\right)\]
(versi vektor baris)
Fungsi Aktivasi
Sumber gambar: Aggarwal (2018) hal. 13
Beberapa fungsi aktivasi adalah (Aggarwal, 2018, hal. 12-13):
- “Linier” atau identitas
\[\Phi(v) = v\]
- Sign (fungsi tanda): \(\text{sign}(v)\) atau \(\text{sgn}(v)\)
\[ \Phi(v) = \text{sign}(v) = \begin{cases} 1, & v > 0 \\ 0, & v = 0 \\ -1, & v < 0 \end{cases} \]
- Sigmoid, terkadang dilambangkan \(\sigma(v)\) dan terkadang disebut fungsi aktivasi logistik
\[\Phi(v) = \frac{1}{1 + e^{-v}}\]
- (Soft) tanh: \(\tanh(v)\)
\[\Phi(v) = \frac{e^{2v} - 1}{e^{2v} + 1} = 2 * \text{sigmoid}(2v) - 1\]
- Rectified Linear Unit (ReLU)
\[\Phi(v) = \max\{v, 0\}\]
- Hard tanh
\[\Phi(v) = \max\{\min\{v, 1\}, -1\}\]
Fungsi aktivasi yang paling sering digunakan adalah ReLU, kecuali untuk output layer.
Untuk output layer, biasanya,
untuk regresi, banyaknya neuron sesuai banyaknya nilai prediksi (umumnya hanya satu), dan digunakan fungsi aktivasi linier
untuk klasifikasi multiclass (lebih dari dua kelas), biasanya banyaknya output neuron sesuai banyaknya kelas, dan digunakan fungsi aktivasi softmax sebagai berikut, agar output berupa peluang tiap kelas:
\[\Phi(\overline{v})_i = \frac{\exp(v_i)}{\sum_{j=1}^k \exp(v_j)}\]
- untuk klasifikasi biner, hanya ada satu neuron di output layer, dan digunakan fungsi aktivasi sigmoid. (Keberadaan hanya satu output neuron lebih hemat daripada menggunakan dua output neuron)
Loss function
Misalkan \(y_i\) adalah nilai sebenarnya dan \(\hat{y}_i\) adalah hasil prediksi.
Untuk regresi, biasa digunakan MSE (mean squared error), juga disebut L2 loss:
\[\text{MSE}(y, \hat{y}) = \frac{1}{n} \sum_{i=1}^{n} \left( y_i - \hat{y}_i \right)^2\]
Untuk klasifikasi, biasa digunakan yang namanya cross-entropy loss, juga disebut logistic loss atau log loss:
\[L_{\text{log}}(y,\hat{y}) = -(y \ln (\hat{y}) + (1 - y) \ln (1 - \hat{y}))\]
Proses training
Proses training untuk neural network dilakukan secara iteratif, yaitu tiap iterasi akan memperbarui parameter sehingga nilai loss function menjadi lebih kecil.
Tiap iterasi melakukan langkah-langkah berikut untuk tiap data training:
Forward pass: menghitung nilai output akhir, yaitu \(\hat{y}\) (hasil prediksi), berdasarkan input data training.
Menghitung loss antara \(y\) (nilai asli) dan \(\hat{y}\)
Backpropagation: menghitung gradien dari loss terhadap tiap parameter, secara “mundur”
Update optimizer: menggunakan algoritma optimizer seperti gradient descent untuk memperbarui parameter-parameter (weights and biases) berdasarkan gradien dari loss
Note: ada banyak optimizer, seperti gradient descent, SGD (stochastic gradient descent), dan Adam (adaptive moment estimation). Pilihan optimizer (serta parameter-parameter yang bisa diatur untuk optimizer, seperti learning rate) juga menjadi hyperparameter untuk neural network.
Note: istilah backward pass meliputi langkah backpropagation dan update optimizer.
Apabila data training sangat banyak, terkadang data training tersebut dibagi menjadi beberapa batch, dan tiap iterasi menggunakan batch yang berbeda. Apabila semua batch sudah diproses, sebutannya adalah satu epoch. Sehingga, satu epoch terdiri dari sejumlah iterasi sesuai banyaknya batch.
(Apabila data training tidak dibagi menjadi batch, maka satu epoch sama dengan satu iterasi.)
Contoh optimizer: metode gradient descent
Metode gradient descent mencari minimum lokal dari suatu fungsi \(g\) (dalam hal ini, loss function) dengan rumus iterasi seperti berikut:
\[\textbf{x}_{i+1} = \textbf{x}_i - \eta \nabla g\left(\textbf{x}_i\right)\]
dengan \(\eta\) adalah learning rate. Simbol nabla (\(\nabla\)) menandakan perhitungan gradien.
Perhatikan bahwa gradien menandakan arah tercepat untuk kenaikan fungsi, seringkali disebut direction of steepest ascent. Di sini, justru kita mengurangi; atau sama saja, menambah dengan kebalikannya, yaitu arah tercepat untuk penurunan fungsi. Sedangkan, learning rate melambangkan seberapa jauh kita melangkah ke arah penurunan tersebut. Harapannya, kita akan cepat konvergen menuju minimum fungsi, karena kita terus melangkah ke arah penurunan tercepat.
Variasi gradient descent adalah SGD (stochastic gradient descent). Bedanya sederhana saja:
Gradient descent selalu memanfaatkan keseluruhan data training yang diberikan (lebih tepatnya, keseluruhan batch) di tiap iterasi.
Sedangkan, SGD selalu memilih sebagian data training saja (lebih tepatnya, sebagian dari batch), dan cara memilihnya bersifat random atau disebut stokastik.
Keuntungan SGD dibandingkan gradient descent biasa:
Waktu training menjadi lebih cepat
Tidak rawan terjebak di minimum lokal: https://www.youtube.com/watch?v=UmathvAKj80&t=102
Train-Validation-Test Split
Ketika menggunakan metode machine learning yang di-training secara iteratif, seperti neural network, biasanya ada juga yang namanya validation data. Sehingga, di awal, dataset dipisah menjadi data train, data validation, dan data test.
Gunanya, kita bisa menguji akurasi model di akhir tiap epoch, menggunakan data validation daripada data test.
Rasio yang paling sering digunakan adalah 80-10-10, yaitu 80% data train, 10% data validation, dan 10% data test.
Apabila menggunakan scikit-learn, untuk melakukan train-validation-test split, caranya adalah dengan split dua kali, yaitu
Split menjadi data “train” dan data test
Data “train” itu di-split lagi menjadi data train sesungguhnya dan data validation
atau bisa juga
Split menjadi data train dan data “test”
Data “test” itu di-split lagi menjadi data validation dan data test sesungguhnya
Mengenal TensorFlow
import tensorflow as tf
Tensor, Konstanta
Tensor adalah semacam perumuman dari array/vektor ataupun matriks.
Skalar (bilangan) adalah tensor berdimensi nol (atau rank nol).
Array atau vektor adalah tensor berdimensi satu (atau rank satu).
Matriks adalah tensor berdimensi dua (atau rank dua).
Istilah “tensor” biasanya merujuk pada tensor berdimensi tiga (atau rank tiga), yaitu semacam matriks tapi tiga dimensi, sehingga ada baris, kolom, dan satu dimensi lagi.
Fitur tensor di TensorFlow mirip dengan fitur array di numpy, yang memang juga bisa multidimensi.
= tf.zeros(shape = (3,4))
x print(x)
tf.Tensor(
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]], shape=(3, 4), dtype=float32)
= tf.ones(shape = (3,4))
x print(x)
tf.Tensor(
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]], shape=(3, 4), dtype=float32)
Untuk menentukan array kita sendiri, di numpy digunakan numpy.array
.
Untuk menentukan tensor kita sendiri, di TensorFlow digunakan tensorflow.constant
(agar nilainya tidak bisa diubah) atau tensorflow.Variable
(nilainya bisa diubah).
Pada umumnya (apabila tidak ada keterangan), tensor di TensorFlow berupa tensorflow.constant
= tf.constant(1.5)
const0 print(const0)
tf.Tensor(1.5, shape=(), dtype=float32)
print(tf.rank(const0))
tf.Tensor(0, shape=(), dtype=int32)
= tf.constant([2.31, 4.567, 8.9])
const1 print(const1)
tf.Tensor([2.31 4.567 8.9 ], shape=(3,), dtype=float32)
print(tf.rank(const1))
tf.Tensor(1, shape=(), dtype=int32)
0] = 52.5 const1[
TypeError: 'tensorflow.python.framework.ops.EagerTensor' object does not support item assignment
= tf.constant([
const2 1, 2.718, 3.14],
[4, 5, 6.28]
[
])print(const2)
tf.Tensor(
[[1. 2.718 3.14 ]
[4. 5. 6.28 ]], shape=(2, 3), dtype=float32)
print(tf.rank(const2))
tf.Tensor(2, shape=(), dtype=int32)
Variabel dan assignment untuk tensor
= tf.Variable(initial_value = tf.zeros(shape = (2,3)))
v print(v)
<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[0., 0., 0.],
[0., 0., 0.]], dtype=float32)>
Assignment untuk variabel di TensorFlow dilakukan dengan .assign
= (2,3)))
v.assign(tf.ones(shape print(v)
<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[1., 1., 1.],
[1., 1., 1.]], dtype=float32)>
0, 0].assign(9)
v[print(v)
<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[9., 1., 1.],
[1., 1., 1.]], dtype=float32)>
Ada juga .assign_add
, sama saja dengan +=
= (2,3)))
v.assign_add(tf.ones(shape print(v)
<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[10., 2., 2.],
[ 2., 2., 2.]], dtype=float32)>
Serupa, ada .assign_sub
yaitu -=
= (2,3)))
v.assign_sub(tf.ones(shape print(v)
<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[9., 1., 1.],
[1., 1., 1.]], dtype=float32)>
Tensor random
Kita bisa membuat tensor dengan nilai yang random, misalnya dari distribusi normal atau dari distribusi uniform
# dari distribusi normal
= tf.random.normal(shape = (2,3), mean = 0, stddev = 1)
x print(x)
tf.Tensor(
[[ 1.2542483 -0.41693744 1.0116149 ]
[-1.4155766 0.17204648 -0.6892854 ]], shape=(2, 3), dtype=float32)
# dari distribusi uniform
= tf.random.uniform(shape = (2,3), minval = 0, maxval = 1)
x print(x)
tf.Tensor(
[[0.51321495 0.26164746 0.09113109]
[0.81229377 0.67134035 0.36057925]], shape=(2, 3), dtype=float32)
Operasi TensorFlow seperti numpy
Operasi di TensorFlow mirip dengan numpy
= 4 * tf.ones((2, 2))
a print(a)
tf.Tensor(
[[4. 4.]
[4. 4.]], shape=(2, 2), dtype=float32)
= tf.square(a)
b print(b)
tf.Tensor(
[[16. 16.]
[16. 16.]], shape=(2, 2), dtype=float32)
= tf.sqrt(a)
c print(c)
tf.Tensor(
[[2. 2.]
[2. 2.]], shape=(2, 2), dtype=float32)
= b + c
d print(d)
tf.Tensor(
[[18. 18.]
[18. 18.]], shape=(2, 2), dtype=float32)
# perkalian matriks
= tf.matmul(a, c)
e print(e)
tf.Tensor(
[[16. 16.]
[16. 16.]], shape=(2, 2), dtype=float32)
# perkalian per elemen
*= d
e print(e)
tf.Tensor(
[[288. 288.]
[288. 288.]], shape=(2, 2), dtype=float32)
Automatic differentiation dengan GradientTape
TensorFlow memiliki fitur yang bernama automatic differentiation, juga disebut autodiff atau autograd. Dengan fitur ini, TensorFlow bisa menghitung turunan/gradien secara otomatis. Fitur ini membedakan antara TensorFlow dengan numpy.
Caranya adalah menggunakan GradientTape
seperti berikut. Semua operasi di dalam with statement dicatat oleh GradientTape
, yang kemudian bisa menghitung gradiennya.
Contohnya, turunan \(x^3\) terhadap \(x\) di \(x=4\) adalah \(3(4)^2 = 48\).
= tf.Variable(4.0)
x with tf.GradientTape() as tape:
= x ** 3
y = tape.gradient(y, x)
dy_dx print(dy_dx)
tf.Tensor(48.0, shape=(), dtype=float32)
Tidak harus dengan tensorflow.Variable
, bahkan dengan tensorflow.constant
juga bisa. Namun, kita harus secara eksplisit meminta TensorFlow untuk memperhatikan nilai x
, yaitu dengan .watch
= tf.constant(4.0)
x with tf.GradientTape() as tape:
tape.watch(x)= x ** 3
y = tape.gradient(y, x)
dy_dx print(dy_dx)
tf.Tensor(48.0, shape=(), dtype=float32)
Kita bisa menghitung turunan kedua dengan nested with statement seperti berikut, contohnya turunan kedua dari \(x^3\) terhadap \(x\) di \(x=4\) adalah \(6(4) = 24\)
= tf.Variable(4.0)
x with tf.GradientTape() as tape2:
with tf.GradientTape() as tape1:
= x ** 3
y = tape1.gradient(y, x)
dy_dx = tape2.gradient(dy_dx, x)
dy2_dx2 print(dy2_dx2)
tf.Tensor(24.0, shape=(), dtype=float32)
(Pure) TensorFlow: klasifikasi biner dengan perceptron
Perceptron digunakan untuk klasifikasi biner. Mari kita coba buat model perceptron dengan pure TensorFlow, menggunakannya untuk memprediksi kelas dari titik-titik dua dimensi.
Generate dataset
Dataset titik-titik dua dimensi, dengan dua kelas (misalnya “negatif” dan “positif”), bisa kita generate:
= 1000, 2
num_samples_per_class, num_classes = np.random.multivariate_normal(mean = [0,3], cov = [[1,0.5],[0.5,1]], size = num_samples_per_class)
negative_samples = np.random.multivariate_normal(mean = [3,0], cov = [[1,0.5],[0.5,1]], size = num_samples_per_class)
positive_samples
= np.vstack((negative_samples, positive_samples)).astype(np.float32)
inputs = np.vstack((
targets 1), dtype = 'float32'),
np.zeros((num_samples_per_class, 1), dtype = 'float32')
np.ones((num_samples_per_class, ))
print(inputs.shape)
print(targets.shape)
(2000, 2)
(2000, 1)
0], inputs[:, 1], c=targets[:, 0])
plt.scatter(inputs[:, plt.show()
Kalau mau, kita bisa susun data ini ke dalam bentuk pandas DataFrame, lalu export ke CSV:
= pd.DataFrame(
titik_negatif_positif_df
np.hstack([inputs, targets]),= ["x", "y", "kelas"]
columns )
titik_negatif_positif_df
x | y | kelas | |
---|---|---|---|
0 | 1.173375 | 4.570637 | 0.0 |
1 | 0.195961 | 3.504604 | 0.0 |
2 | 0.121400 | 2.163783 | 0.0 |
3 | -1.170182 | 3.882771 | 0.0 |
4 | -0.424403 | 0.534641 | 0.0 |
... | ... | ... | ... |
1995 | 2.423160 | -0.337196 | 1.0 |
1996 | 1.949836 | -0.627813 | 1.0 |
1997 | 2.109928 | -0.382492 | 1.0 |
1998 | 4.178664 | 0.486168 | 1.0 |
1999 | 2.326363 | 1.228249 | 1.0 |
2000 rows × 3 columns
"./titik_negatif_positif.csv", index=False) titik_negatif_positif_df.to_csv(
Import kembali dataset
Tentunya, karena titik-titiknya di-generate secara random, mungkin saja titik-titik yang kalian peroleh akan sedikit berbeda, bahkan tiap kali di-run ulang akan berbeda.
Kalau kalian mau menyamakan dengan modul ini, CSV nya bisa di-download dari GitHub Pages ini: titik_negatif_positif.csv
Kita bisa import kembali:
= pd.read_csv("./titik_negatif_positif.csv", dtype="float32") df
Kali ini, kita tambahkan keterangan dtype="float32"
. Ini penting, karena TensorFlow biasanya menangani float32
(yaitu tipe data float
dengan penyimpanan 32-bit), bukan float64
yang biasa digunakan oleh pandas.
df
x | y | kelas | |
---|---|---|---|
0 | 1.173375 | 4.570637 | 0.0 |
1 | 0.195961 | 3.504604 | 0.0 |
2 | 0.121400 | 2.163783 | 0.0 |
3 | -1.170182 | 3.882771 | 0.0 |
4 | -0.424403 | 0.534641 | 0.0 |
... | ... | ... | ... |
1995 | 2.423160 | -0.337196 | 1.0 |
1996 | 1.949836 | -0.627813 | 1.0 |
1997 | 2.109928 | -0.382492 | 1.0 |
1998 | 4.178664 | 0.486168 | 1.0 |
1999 | 2.326363 | 1.228249 | 1.0 |
2000 rows × 3 columns
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2000 entries, 0 to 1999
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 x 2000 non-null float32
1 y 2000 non-null float32
2 kelas 2000 non-null float32
dtypes: float32(3)
memory usage: 23.6 KB
= df.drop(columns=["kelas"])
inputs_df = df[["kelas"]] targets_df
inputs_df
x | y | |
---|---|---|
0 | 1.173375 | 4.570637 |
1 | 0.195961 | 3.504604 |
2 | 0.121400 | 2.163783 |
3 | -1.170182 | 3.882771 |
4 | -0.424403 | 0.534641 |
... | ... | ... |
1995 | 2.423160 | -0.337196 |
1996 | 1.949836 | -0.627813 |
1997 | 2.109928 | -0.382492 |
1998 | 4.178664 | 0.486168 |
1999 | 2.326363 | 1.228249 |
2000 rows × 2 columns
targets_df
kelas | |
---|---|
0 | 0.0 |
1 | 0.0 |
2 | 0.0 |
3 | 0.0 |
4 | 0.0 |
... | ... |
1995 | 1.0 |
1996 | 1.0 |
1997 | 1.0 |
1998 | 1.0 |
1999 | 1.0 |
2000 rows × 1 columns
"x"], inputs_df["y"], c=targets["kelas"])
plt.scatter(inputs_df[ plt.show()
TensorFlow kurang bisa menangani pandas DataFrame, sehingga harus kita ubah jadi array numpy:
= inputs_df.to_numpy()
inputs = targets_df.to_numpy() targets
print(inputs.shape)
print(targets.shape)
(2000, 2)
(2000, 1)
Menyusun model dan training
Untuk input dua dimensi dan klasifikasi biner, kita perlu perceptron dengan dua neuron di input layer dan satu neuron di output layer. Sebelum proses training dimulai, nilai matriks \(W\) dan vektor kolom \(b\) diisi secara random terlebih dahulu.
= 2
input_dim = 1
output_dim = tf.Variable(tf.random.normal(shape = (input_dim, output_dim)))
W = tf.Variable(tf.random.normal(shape = (output_dim,))) b
# forward pass
def model(inputs):
return tf.sigmoid(
+ b
tf.matmul(inputs, W) )
# cross entropy loss
def entropy_loss(y, yhat):
= - y * tf.math.log(yhat) - (1-y) * tf.math.log(1-yhat)
per_sample_losses return tf.reduce_mean(per_sample_losses)
# satu epoch di training loop
= 0.1
learning_rate def training_step(inputs, targets):
with tf.GradientTape() as tape:
= model(inputs)
predictions = entropy_loss(targets, predictions)
loss
= tape.gradient(loss, [W, b])
grad_loss_wrt_W, grad_loss_wrt_b
# update menggunakan gradient descent
* grad_loss_wrt_W)
W.assign_sub(learning_rate * grad_loss_wrt_b)
b.assign_sub(learning_rate
return loss
# training loop
for epoch in range(100):
= training_step(inputs, targets)
loss print(f"Loss at epoch {epoch}: {loss}")
Loss at epoch 0: 3.254241466522217
Loss at epoch 1: 2.841676712036133
Loss at epoch 2: 2.446164608001709
Loss at epoch 3: 2.0740654468536377
Loss at epoch 4: 1.7329306602478027
Loss at epoch 5: 1.4304780960083008
Loss at epoch 6: 1.1727027893066406
Loss at epoch 7: 0.9617642760276794
Loss at epoch 8: 0.7950283288955688
Loss at epoch 9: 0.6661190390586853
Loss at epoch 10: 0.5672049522399902
Loss at epoch 11: 0.4909631013870239
Loss at epoch 12: 0.43148183822631836
Loss at epoch 13: 0.38434457778930664
Loss at epoch 14: 0.34636563062667847
Loss at epoch 15: 0.31527045369148254
Loss at epoch 16: 0.2894296944141388
Loss at epoch 17: 0.26766350865364075
Loss at epoch 18: 0.24910621345043182
Loss at epoch 19: 0.23311251401901245
Loss at epoch 20: 0.21919457614421844
Loss at epoch 21: 0.2069779932498932
Loss at epoch 22: 0.19617150723934174
Loss at epoch 23: 0.18654564023017883
Loss at epoch 24: 0.1779175102710724
Loss at epoch 25: 0.17013971507549286
Loss at epoch 26: 0.16309219598770142
Loss at epoch 27: 0.1566763073205948
Loss at epoch 28: 0.15081030130386353
Loss at epoch 29: 0.14542590081691742
Loss at epoch 30: 0.1404656320810318
Loss at epoch 31: 0.13588076829910278
Loss at epoch 32: 0.13162976503372192
Loss at epoch 33: 0.12767699360847473
Loss at epoch 34: 0.12399168312549591
Loss at epoch 35: 0.12054720520973206
Loss at epoch 36: 0.11732034385204315
Loss at epoch 37: 0.11429077386856079
Loss at epoch 38: 0.11144062876701355
Loss at epoch 39: 0.10875413566827774
Loss at epoch 40: 0.10621732473373413
Loss at epoch 41: 0.10381780564785004
Loss at epoch 42: 0.10154449194669724
Loss at epoch 43: 0.0993875041604042
Loss at epoch 44: 0.09733790904283524
Loss at epoch 45: 0.09538772702217102
Loss at epoch 46: 0.09352975338697433
Loss at epoch 47: 0.09175743162631989
Loss at epoch 48: 0.09006485342979431
Loss at epoch 49: 0.08844659477472305
Loss at epoch 50: 0.08689778298139572
Loss at epoch 51: 0.08541391044855118
Loss at epoch 52: 0.08399088680744171
Loss at epoch 53: 0.08262495696544647
Loss at epoch 54: 0.0813126489520073
Loss at epoch 55: 0.08005079627037048
Loss at epoch 56: 0.07883644849061966
Loss at epoch 57: 0.07766692340373993
Loss at epoch 58: 0.07653970271348953
Loss at epoch 59: 0.07545248419046402
Loss at epoch 60: 0.07440309226512909
Loss at epoch 61: 0.07338955998420715
Loss at epoch 62: 0.07241000235080719
Loss at epoch 63: 0.0714627057313919
Loss at epoch 64: 0.07054606825113297
Loss at epoch 65: 0.0696585550904274
Loss at epoch 66: 0.06879876554012299
Loss at epoch 67: 0.06796539574861526
Loss at epoch 68: 0.06715719401836395
Loss at epoch 69: 0.06637301295995712
Loss at epoch 70: 0.06561177223920822
Loss at epoch 71: 0.06487242877483368
Loss at epoch 72: 0.06415403634309769
Loss at epoch 73: 0.06345568597316742
Loss at epoch 74: 0.06277652084827423
Loss at epoch 75: 0.06211574003100395
Loss at epoch 76: 0.061472587287425995
Loss at epoch 77: 0.06084632873535156
Loss at epoch 78: 0.06023630499839783
Loss at epoch 79: 0.05964187532663345
Loss at epoch 80: 0.0590624064207077
Loss at epoch 81: 0.05849733576178551
Loss at epoch 82: 0.057946112006902695
Loss at epoch 83: 0.05740822106599808
Loss at epoch 84: 0.05688317120075226
Loss at epoch 85: 0.056370481848716736
Loss at epoch 86: 0.05586971715092659
Loss at epoch 87: 0.0553804449737072
Loss at epoch 88: 0.05490226671099663
Loss at epoch 89: 0.05443479120731354
Loss at epoch 90: 0.05397764965891838
Loss at epoch 91: 0.0535304993391037
Loss at epoch 92: 0.05309300124645233
Loss at epoch 93: 0.05266483128070831
Loss at epoch 94: 0.05224568769335747
Loss at epoch 95: 0.051835279911756516
Loss at epoch 96: 0.05143332853913307
Loss at epoch 97: 0.05103955790400505
Loss at epoch 98: 0.05065372586250305
Loss at epoch 99: 0.05027557164430618
Prediksi
Sekarang training sudah selesai, kita bisa gunakan model kita untuk memprediksi kelas berdasarkan inputs
(koordinat titik-titik)
= model(inputs) predictions
Akibat penggunaan fungsi aktivasi sigmoid, hasil prediksi cukup jelas, apakah kelas pertama (kelas 0) atau kelas kedua (kelas 1):
print(predictions)
tf.Tensor(
[[0.0185734 ]
[0.01658478]
[0.06427375]
...
[0.94514835]
[0.99050665]
[0.7908128 ]], shape=(2000, 1), dtype=float32)
Kita bisa menampilkan hasil prediksi ini dengan aturan pemilihan warna (c
) seperti berikut:
apabila nilai prediksinya lebih dari 0.5 (pernyataan “lebih besar dari 0.5” bernilai benar), ia tergolong kelas 1 (atau sama saja nilai True);
selain itu (pernyataan “lebih besar dari 0.5” bernilai salah), ia tergolong kelas 0 (atau sama saja nilai False).
0], inputs[:, 1], c=predictions[:, 0] > 0.5)
plt.scatter(inputs[:, plt.show()
Kinerja perceptron cukup mirip regresi logistik, ataupun SVM dengan kernel linier. Perhatikan bahwa, di hasil prediksi ini, seolah-olah ada perbatasan atau garis pemisah antara kedua kelas. Kalau kita bandingkan dengan data aslinya, sebenarnya ada beberapa titik yang melewati perbatasan tersebut, dan akhirnya terjadi misklasifikasi.
Mengenal Keras dengan Sequential API
Dengan pure TensorFlow, banyak hal yang harus kita susun secara manual. Untuk neural network kecil seperti perceptron, mungkin tidak masalah. Namun, neural network pada umumnya sangat “dalam” atau deep, dengan puluhan hidden layer yang bervariasi.
Daripada benar-benar membuatnya semua secara manual, ada yang namanya Keras yang sangat menyederhanakan proses penyusunan neural network. Biasanya, daripada benar-benar membuat neural network secara manual dalam pure TensorFlow seperti tadi, pengguna TensorFlow memanfaatkan Keras.
Keras tersedia di dalam TensorFlow:
from tensorflow import keras
Perlu dicatat, ketika menggunakan Keras, sebaiknya semua fungsi/operasi yang kita gunakan juga dari dalam Keras daripada langsung dari TensorFlow. Misalnya, daripada tf.matmul
, gunakan keras.ops.matmul
Tapi kalau error, tidak masalah masih menggunakan tf
karena Keras masih dalam pengembangan (menuju Keras versi 3, bisa dibaca di sini: https://keras.io/guides/migrating_to_keras_3/). Mungkin, di versi yang akan datang, sudah tidak error lagi.
Dalam Keras, ada tiga “cara” atau API (application programming interface) yang bisa kita gunakan untuk menyusun neural network, yaitu
Sequential API
Functional API
Subclassing API (yaitu dengan OOP)
Di pertemuan kali ini, kita akan mencoba cara yang paling sederhana, yaitu dengan Sequential API.
Datanya sudah siap dari yang tadi:
print(inputs.shape)
print(targets.shape)
(2000, 2)
(2000, 1)
Menyusun layer
Kita susun layer nya terlebih dahulu. Kali ini, kita akan membuat perceptron seperti yang cara manual / pure TensorFlow tadi. Untuk itu, kedua kode ini ekuivalen:
# langsung menentukan semua layer di awal, dengan memasukkan list
= keras.Sequential(
model2
[= (2,)),
keras.layers.InputLayer(input_shape = 1, activation = 'sigmoid')
keras.layers.Dense(units
] )
# menambahkan layer secara berangsur-angsur
= keras.Sequential()
model2 = (2,)))
model2.add(keras.layers.InputLayer(input_shape = 1, activation = 'sigmoid')) model2.add(keras.layers.Dense(units
Daripada menggunakan string, untuk menentukan fungsi aktivasi di kedua cara di atas, kita juga bisa mengetik keras.activations.sigmoid
seperti berikut:
# langsung menentukan semua layer di awal, dengan memasukkan list
= keras.Sequential(
model2
[= (2,)),
keras.layers.InputLayer(input_shape = 1, activation = keras.activations.sigmoid)
keras.layers.Dense(units
] )
# menambahkan layer secara berangsur-angsur
= keras.Sequential()
model2 = (2,)))
model2.add(keras.layers.InputLayer(input_shape = 1, activation = keras.activations.sigmoid)) model2.add(keras.layers.Dense(units
Ringkasan dan diagram model
Kemudian, kita bisa melihat ringkasan bentuk model yang dihasilkan:
model2.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 1) 3
=================================================================
Total params: 3
Trainable params: 3
Non-trainable params: 0
_________________________________________________________________
Kita juga bisa menampilkan semacam diagram, bahkan menyimpannya ke dalam file:
keras.utils.plot_model(
model2,= True,
show_shapes = True,
show_layer_activations = "keras_sequential_model2.png"
to_file )
Fun fact: Keras menggunakan Graphviz untuk membuat diagramnya :)
Memilih hyperparameter
Untuk memilih hyperparameter yaitu optimizer dan loss function (dan metrik evaluasi), kedua kode berikut ini ekuivalen:
# dengan string
compile(
model2.= "sgd",
optimizer = "binary_crossentropy",
loss = ["binary_accuracy"]
metrics )
# dengan objek dari class
compile(
model2.= keras.optimizers.SGD(),
optimizer = keras.losses.BinaryCrossentropy(),
loss = [keras.metrics.BinaryAccuracy()]
metrics )
Dengan cara yang kedua, kita juga bisa menentukan hyperparameter seperti learning rate:
# dengan objek dari class
compile(
model2.= keras.optimizers.SGD(learning_rate = 0.01),
optimizer = keras.losses.BinaryCrossentropy(),
loss = [keras.metrics.BinaryAccuracy()]
metrics )
Training
Selanjutnya, tinggal training, menggunakan .fit
seperti di scikit-learn. Bedanya, .fit
di sini me-return suatu objek “history” yang berisi catatan loss di tiap epoch
= inputs
x_train = targets
y_train = model2.fit(x_train, y_train, epochs=100, validation_split=0.2) history2
Epoch 1/100
50/50 [==============================] - 2s 19ms/step - loss: 0.9972 - binary_accuracy: 0.4119 - val_loss: 0.3660 - val_binary_accuracy: 0.9975
Epoch 2/100
50/50 [==============================] - 1s 11ms/step - loss: 0.3971 - binary_accuracy: 0.9488 - val_loss: 0.2875 - val_binary_accuracy: 0.9975
Epoch 3/100
50/50 [==============================] - 0s 8ms/step - loss: 0.2427 - binary_accuracy: 0.9975 - val_loss: 0.2355 - val_binary_accuracy: 0.9925
Epoch 4/100
50/50 [==============================] - 0s 10ms/step - loss: 0.1804 - binary_accuracy: 0.9962 - val_loss: 0.1990 - val_binary_accuracy: 0.9925
Epoch 5/100
50/50 [==============================] - 0s 5ms/step - loss: 0.1459 - binary_accuracy: 0.9962 - val_loss: 0.1726 - val_binary_accuracy: 0.9925
Epoch 6/100
50/50 [==============================] - 0s 5ms/step - loss: 0.1238 - binary_accuracy: 0.9962 - val_loss: 0.1529 - val_binary_accuracy: 0.9925
Epoch 7/100
50/50 [==============================] - 0s 6ms/step - loss: 0.1082 - binary_accuracy: 0.9969 - val_loss: 0.1376 - val_binary_accuracy: 0.9925
Epoch 8/100
50/50 [==============================] - 1s 11ms/step - loss: 0.0966 - binary_accuracy: 0.9962 - val_loss: 0.1254 - val_binary_accuracy: 0.9925
Epoch 9/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0875 - binary_accuracy: 0.9962 - val_loss: 0.1156 - val_binary_accuracy: 0.9925
Epoch 10/100
50/50 [==============================] - 1s 11ms/step - loss: 0.0803 - binary_accuracy: 0.9962 - val_loss: 0.1075 - val_binary_accuracy: 0.9925
Epoch 11/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0744 - binary_accuracy: 0.9962 - val_loss: 0.1006 - val_binary_accuracy: 0.9925
Epoch 12/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0695 - binary_accuracy: 0.9962 - val_loss: 0.0948 - val_binary_accuracy: 0.9925
Epoch 13/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0653 - binary_accuracy: 0.9962 - val_loss: 0.0898 - val_binary_accuracy: 0.9925
Epoch 14/100
50/50 [==============================] - 1s 14ms/step - loss: 0.0617 - binary_accuracy: 0.9962 - val_loss: 0.0854 - val_binary_accuracy: 0.9925
Epoch 15/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0585 - binary_accuracy: 0.9962 - val_loss: 0.0815 - val_binary_accuracy: 0.9925
Epoch 16/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0558 - binary_accuracy: 0.9962 - val_loss: 0.0781 - val_binary_accuracy: 0.9925
Epoch 17/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0533 - binary_accuracy: 0.9962 - val_loss: 0.0750 - val_binary_accuracy: 0.9925
Epoch 18/100
50/50 [==============================] - 1s 17ms/step - loss: 0.0511 - binary_accuracy: 0.9962 - val_loss: 0.0722 - val_binary_accuracy: 0.9925
Epoch 19/100
50/50 [==============================] - 1s 17ms/step - loss: 0.0492 - binary_accuracy: 0.9962 - val_loss: 0.0697 - val_binary_accuracy: 0.9925
Epoch 20/100
50/50 [==============================] - 1s 12ms/step - loss: 0.0474 - binary_accuracy: 0.9962 - val_loss: 0.0674 - val_binary_accuracy: 0.9925
Epoch 21/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0458 - binary_accuracy: 0.9962 - val_loss: 0.0653 - val_binary_accuracy: 0.9925
Epoch 22/100
50/50 [==============================] - 1s 27ms/step - loss: 0.0443 - binary_accuracy: 0.9962 - val_loss: 0.0634 - val_binary_accuracy: 0.9925
Epoch 23/100
50/50 [==============================] - 0s 10ms/step - loss: 0.0429 - binary_accuracy: 0.9969 - val_loss: 0.0616 - val_binary_accuracy: 0.9925
Epoch 24/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0417 - binary_accuracy: 0.9969 - val_loss: 0.0600 - val_binary_accuracy: 0.9925
Epoch 25/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0405 - binary_accuracy: 0.9969 - val_loss: 0.0585 - val_binary_accuracy: 0.9925
Epoch 26/100
50/50 [==============================] - 1s 12ms/step - loss: 0.0394 - binary_accuracy: 0.9969 - val_loss: 0.0571 - val_binary_accuracy: 0.9925
Epoch 27/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0384 - binary_accuracy: 0.9969 - val_loss: 0.0558 - val_binary_accuracy: 0.9925
Epoch 28/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0375 - binary_accuracy: 0.9969 - val_loss: 0.0545 - val_binary_accuracy: 0.9925
Epoch 29/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0366 - binary_accuracy: 0.9969 - val_loss: 0.0534 - val_binary_accuracy: 0.9925
Epoch 30/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0358 - binary_accuracy: 0.9969 - val_loss: 0.0523 - val_binary_accuracy: 0.9925
Epoch 31/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0351 - binary_accuracy: 0.9969 - val_loss: 0.0513 - val_binary_accuracy: 0.9925
Epoch 32/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0343 - binary_accuracy: 0.9969 - val_loss: 0.0503 - val_binary_accuracy: 0.9925
Epoch 33/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0337 - binary_accuracy: 0.9969 - val_loss: 0.0494 - val_binary_accuracy: 0.9925
Epoch 34/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0330 - binary_accuracy: 0.9969 - val_loss: 0.0485 - val_binary_accuracy: 0.9925
Epoch 35/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0324 - binary_accuracy: 0.9969 - val_loss: 0.0477 - val_binary_accuracy: 0.9925
Epoch 36/100
50/50 [==============================] - 1s 11ms/step - loss: 0.0318 - binary_accuracy: 0.9969 - val_loss: 0.0469 - val_binary_accuracy: 0.9925
Epoch 37/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0313 - binary_accuracy: 0.9969 - val_loss: 0.0462 - val_binary_accuracy: 0.9925
Epoch 38/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0308 - binary_accuracy: 0.9969 - val_loss: 0.0455 - val_binary_accuracy: 0.9925
Epoch 39/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0303 - binary_accuracy: 0.9969 - val_loss: 0.0448 - val_binary_accuracy: 0.9925
Epoch 40/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0298 - binary_accuracy: 0.9969 - val_loss: 0.0442 - val_binary_accuracy: 0.9925
Epoch 41/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0293 - binary_accuracy: 0.9969 - val_loss: 0.0435 - val_binary_accuracy: 0.9925
Epoch 42/100
50/50 [==============================] - 0s 4ms/step - loss: 0.0289 - binary_accuracy: 0.9969 - val_loss: 0.0429 - val_binary_accuracy: 0.9925
Epoch 43/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0285 - binary_accuracy: 0.9969 - val_loss: 0.0424 - val_binary_accuracy: 0.9925
Epoch 44/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0281 - binary_accuracy: 0.9969 - val_loss: 0.0418 - val_binary_accuracy: 0.9925
Epoch 45/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0277 - binary_accuracy: 0.9969 - val_loss: 0.0413 - val_binary_accuracy: 0.9925
Epoch 46/100
50/50 [==============================] - 1s 10ms/step - loss: 0.0274 - binary_accuracy: 0.9969 - val_loss: 0.0408 - val_binary_accuracy: 0.9925
Epoch 47/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0270 - binary_accuracy: 0.9969 - val_loss: 0.0403 - val_binary_accuracy: 0.9925
Epoch 48/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0267 - binary_accuracy: 0.9969 - val_loss: 0.0399 - val_binary_accuracy: 0.9925
Epoch 49/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0263 - binary_accuracy: 0.9969 - val_loss: 0.0394 - val_binary_accuracy: 0.9925
Epoch 50/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0260 - binary_accuracy: 0.9969 - val_loss: 0.0390 - val_binary_accuracy: 0.9925
Epoch 51/100
50/50 [==============================] - 0s 4ms/step - loss: 0.0257 - binary_accuracy: 0.9969 - val_loss: 0.0386 - val_binary_accuracy: 0.9925
Epoch 52/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0254 - binary_accuracy: 0.9969 - val_loss: 0.0382 - val_binary_accuracy: 0.9925
Epoch 53/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0252 - binary_accuracy: 0.9969 - val_loss: 0.0378 - val_binary_accuracy: 0.9925
Epoch 54/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0249 - binary_accuracy: 0.9969 - val_loss: 0.0374 - val_binary_accuracy: 0.9925
Epoch 55/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0246 - binary_accuracy: 0.9969 - val_loss: 0.0371 - val_binary_accuracy: 0.9925
Epoch 56/100
50/50 [==============================] - 1s 10ms/step - loss: 0.0244 - binary_accuracy: 0.9969 - val_loss: 0.0367 - val_binary_accuracy: 0.9925
Epoch 57/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0241 - binary_accuracy: 0.9969 - val_loss: 0.0364 - val_binary_accuracy: 0.9925
Epoch 58/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0239 - binary_accuracy: 0.9969 - val_loss: 0.0360 - val_binary_accuracy: 0.9925
Epoch 59/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0237 - binary_accuracy: 0.9969 - val_loss: 0.0357 - val_binary_accuracy: 0.9925
Epoch 60/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0234 - binary_accuracy: 0.9969 - val_loss: 0.0354 - val_binary_accuracy: 0.9925
Epoch 61/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0232 - binary_accuracy: 0.9969 - val_loss: 0.0351 - val_binary_accuracy: 0.9925
Epoch 62/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0230 - binary_accuracy: 0.9969 - val_loss: 0.0348 - val_binary_accuracy: 0.9925
Epoch 63/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0228 - binary_accuracy: 0.9969 - val_loss: 0.0345 - val_binary_accuracy: 0.9925
Epoch 64/100
50/50 [==============================] - 0s 4ms/step - loss: 0.0226 - binary_accuracy: 0.9969 - val_loss: 0.0342 - val_binary_accuracy: 0.9925
Epoch 65/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0224 - binary_accuracy: 0.9969 - val_loss: 0.0340 - val_binary_accuracy: 0.9925
Epoch 66/100
50/50 [==============================] - 1s 10ms/step - loss: 0.0222 - binary_accuracy: 0.9969 - val_loss: 0.0337 - val_binary_accuracy: 0.9925
Epoch 67/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0220 - binary_accuracy: 0.9969 - val_loss: 0.0335 - val_binary_accuracy: 0.9925
Epoch 68/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0219 - binary_accuracy: 0.9969 - val_loss: 0.0332 - val_binary_accuracy: 0.9925
Epoch 69/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0217 - binary_accuracy: 0.9969 - val_loss: 0.0330 - val_binary_accuracy: 0.9925
Epoch 70/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0215 - binary_accuracy: 0.9969 - val_loss: 0.0327 - val_binary_accuracy: 0.9925
Epoch 71/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0214 - binary_accuracy: 0.9969 - val_loss: 0.0325 - val_binary_accuracy: 0.9925
Epoch 72/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0212 - binary_accuracy: 0.9969 - val_loss: 0.0323 - val_binary_accuracy: 0.9925
Epoch 73/100
50/50 [==============================] - 1s 15ms/step - loss: 0.0211 - binary_accuracy: 0.9969 - val_loss: 0.0320 - val_binary_accuracy: 0.9925
Epoch 74/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0209 - binary_accuracy: 0.9969 - val_loss: 0.0318 - val_binary_accuracy: 0.9925
Epoch 75/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0208 - binary_accuracy: 0.9969 - val_loss: 0.0316 - val_binary_accuracy: 0.9925
Epoch 76/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0206 - binary_accuracy: 0.9969 - val_loss: 0.0314 - val_binary_accuracy: 0.9925
Epoch 77/100
50/50 [==============================] - 1s 20ms/step - loss: 0.0205 - binary_accuracy: 0.9969 - val_loss: 0.0312 - val_binary_accuracy: 0.9925
Epoch 78/100
50/50 [==============================] - 1s 11ms/step - loss: 0.0203 - binary_accuracy: 0.9969 - val_loss: 0.0310 - val_binary_accuracy: 0.9925
Epoch 79/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0202 - binary_accuracy: 0.9969 - val_loss: 0.0308 - val_binary_accuracy: 0.9925
Epoch 80/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0201 - binary_accuracy: 0.9969 - val_loss: 0.0306 - val_binary_accuracy: 0.9925
Epoch 81/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0199 - binary_accuracy: 0.9969 - val_loss: 0.0304 - val_binary_accuracy: 0.9925
Epoch 82/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0198 - binary_accuracy: 0.9969 - val_loss: 0.0303 - val_binary_accuracy: 0.9925
Epoch 83/100
50/50 [==============================] - 0s 4ms/step - loss: 0.0197 - binary_accuracy: 0.9969 - val_loss: 0.0301 - val_binary_accuracy: 0.9925
Epoch 84/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0196 - binary_accuracy: 0.9969 - val_loss: 0.0299 - val_binary_accuracy: 0.9925
Epoch 85/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0194 - binary_accuracy: 0.9969 - val_loss: 0.0298 - val_binary_accuracy: 0.9925
Epoch 86/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0193 - binary_accuracy: 0.9969 - val_loss: 0.0296 - val_binary_accuracy: 0.9925
Epoch 87/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0192 - binary_accuracy: 0.9969 - val_loss: 0.0294 - val_binary_accuracy: 0.9925
Epoch 88/100
50/50 [==============================] - 1s 12ms/step - loss: 0.0191 - binary_accuracy: 0.9969 - val_loss: 0.0293 - val_binary_accuracy: 0.9925
Epoch 89/100
50/50 [==============================] - 0s 9ms/step - loss: 0.0190 - binary_accuracy: 0.9969 - val_loss: 0.0291 - val_binary_accuracy: 0.9925
Epoch 90/100
50/50 [==============================] - 1s 14ms/step - loss: 0.0189 - binary_accuracy: 0.9969 - val_loss: 0.0290 - val_binary_accuracy: 0.9925
Epoch 91/100
50/50 [==============================] - 1s 13ms/step - loss: 0.0188 - binary_accuracy: 0.9969 - val_loss: 0.0288 - val_binary_accuracy: 0.9925
Epoch 92/100
50/50 [==============================] - 0s 6ms/step - loss: 0.0187 - binary_accuracy: 0.9969 - val_loss: 0.0287 - val_binary_accuracy: 0.9925
Epoch 93/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0186 - binary_accuracy: 0.9969 - val_loss: 0.0285 - val_binary_accuracy: 0.9925
Epoch 94/100
50/50 [==============================] - 0s 4ms/step - loss: 0.0185 - binary_accuracy: 0.9969 - val_loss: 0.0284 - val_binary_accuracy: 0.9925
Epoch 95/100
50/50 [==============================] - 0s 3ms/step - loss: 0.0184 - binary_accuracy: 0.9969 - val_loss: 0.0282 - val_binary_accuracy: 0.9925
Epoch 96/100
50/50 [==============================] - 0s 8ms/step - loss: 0.0183 - binary_accuracy: 0.9969 - val_loss: 0.0281 - val_binary_accuracy: 0.9925
Epoch 97/100
50/50 [==============================] - 0s 5ms/step - loss: 0.0182 - binary_accuracy: 0.9969 - val_loss: 0.0280 - val_binary_accuracy: 0.9925
Epoch 98/100
50/50 [==============================] - 0s 3ms/step - loss: 0.0181 - binary_accuracy: 0.9969 - val_loss: 0.0278 - val_binary_accuracy: 0.9925
Epoch 99/100
50/50 [==============================] - 0s 7ms/step - loss: 0.0180 - binary_accuracy: 0.9969 - val_loss: 0.0277 - val_binary_accuracy: 0.9925
Epoch 100/100
50/50 [==============================] - 1s 19ms/step - loss: 0.0179 - binary_accuracy: 0.9969 - val_loss: 0.0276 - val_binary_accuracy: 0.9925
Objek “history” tersebut memiliki dictionary .history
. Kita bisa lihat, apa saja key yang ada:
print(history2.history.keys())
dict_keys(['loss', 'binary_accuracy', 'val_loss', 'val_binary_accuracy'])
Tiap key menyimpan data per epoch, sehingga ukurannya sama semua. Oleh karena itu, sebenarnya dictionary ini bisa diubah menjadi pandas DataFrame, yang kemudian bisa kita simpan ke CSV:
"./keras_sequential_history2.csv", index=False) pd.DataFrame(history2.history).to_csv(
Kalau mau menyamakan, file nya bisa kalian download dari GitHub Pages ini: keras_sequential_history2.csv
Kemudian, kita bisa load kembali:
= pd.read_csv("./keras_sequential_history2.csv") history2_df
history2_df
loss | binary_accuracy | val_loss | val_binary_accuracy | |
---|---|---|---|---|
0 | 0.997174 | 0.411875 | 0.365997 | 0.9975 |
1 | 0.397132 | 0.948750 | 0.287524 | 0.9975 |
2 | 0.242701 | 0.997500 | 0.235491 | 0.9925 |
3 | 0.180374 | 0.996250 | 0.199048 | 0.9925 |
4 | 0.145923 | 0.996250 | 0.172641 | 0.9925 |
... | ... | ... | ... | ... |
95 | 0.018302 | 0.996875 | 0.028094 | 0.9925 |
96 | 0.018211 | 0.996875 | 0.027959 | 0.9925 |
97 | 0.018120 | 0.996875 | 0.027828 | 0.9925 |
98 | 0.018030 | 0.996875 | 0.027700 | 0.9925 |
99 | 0.017943 | 0.996875 | 0.027570 | 0.9925 |
100 rows × 4 columns
Dua catatan yang paling sering diperhatikan adalah loss
(training loss) dan juga val_loss
(validation loss). Bahkan, seringkali kedua nilai ini dibuat gambar plotnya (terhadap epoch), untuk menganalisis bagaimana proses training model.
"loss"], label = "training loss")
plt.plot(history2_df["val_loss"], label = "validation loss")
plt.plot(history2_df["epoch")
plt.xlabel(
plt.legend() plt.show()
Proses training tenryata berjalan dengan sangat baik! Kali ini, baik training loss maupun validation loss turun secara drastis dan terus menuju nol.
Biasanya, walaupun training loss tidak mungkin naik, terkadang validation loss naik turun, yang bisa jadi pertanda overfitting.
Menggunakan model
Seperti di scikit-learn, panggil .predict()
untuk melakukan prediksi
= model2.predict(inputs) predictions2
63/63 [==============================] - 1s 6ms/step
Ada sedikit progress bar, karena proses prediksi sebenarnya adalah forward pass. Kita bisa matikan progress bar dengan verbose=False
= model2.predict(inputs, verbose=False) predictions2
print(predictions2)
[[9.8937179e-04]
[1.2094462e-03]
[1.4012366e-02]
...
[9.8900378e-01]
[9.9885350e-01]
[8.5612518e-01]]
0], inputs[:, 1], c=predictions2[:, 0] > 0.5)
plt.scatter(inputs[:, plt.show()
Menyimpan keseluruhan model
Perintahnya adalah .save(path_tempat_penyimpanan)
dengan file format .keras
"./keras_sequential_model2.keras") model2.save(
Kita bisa load kembali model tersebut:
= keras.models.load_model("keras_sequential_model2.keras") model3
Hasil prediksinya akan sama (karena modelnya memang sama):
= model3.predict(inputs) predictions3
63/63 [==============================] - 1s 7ms/step
np.array_equal(predictions2, predictions3)
True
Menyimpan parameter model (saja)
Daripada menyimpan keseluruhan model, kita bisa menyimpan weights atau parameternya saja, dengan perintah .save_weights(path_tempat_penyimpanan)
dan file format .weights.h5
"keras_sequential_model2.weights.h5") model2.save_weights(
Untuk load kembali, kita perlu menyusun layer model terlebih dahulu, sama persis dengan susunan yang aslinya:
= keras.Sequential(
model4
[= (2,)),
keras.layers.InputLayer(input_shape = 1, activation = keras.activations.sigmoid)
keras.layers.Dense(units
] )
Barulah kita gunakan perintah .load_weights(path_tempat_penyimpanan)
"./keras_sequential_model2.weights.h5") model4.load_weights(
Lagi-lagi, hasil prediksinya akan sama:
= model4.predict(inputs) predictions4
63/63 [==============================] - 0s 3ms/step
np.array_equal(predictions2, predictions4)
True
Perhatikan bahwa kita belum memanggil model4.compile
, artinya kita belum memasang hyperparameter. Meskipun demikian, kita masih bisa melakukan prediksi, karena proses prediksi hanyalah forward pass, yang hanya membutuhkan parameter (weights and biases), yang memang sudah di-load.
Setelah melakukan model4.compile
, dengan hyperparameter yang bahkan tidak harus sama dengan yang aslinya, kita bisa melanjutkan proses training kalau mau.
Mengapa tidak save keseluruhan model saja? Selain lebih hemat memori, contoh kasusnya, kita ingin menyimpan progress dari training model, yang sebenarnya susunan layer nya kita ketahui dengan pasti, seperti contoh model4
di atas.
(Pengayaan) Daftar pilihan hyperparameter di Keras
Pilihan fungsi aktivasi
Umum digunakan
Linier (ideentitas):
keras.activations.linear
Sigmoid:
keras.activations.sigmoid
ReLU:
keras.activations.relu
(Soft) tanh:
keras.activations.tanh
Softmax:
keras.activations.softmax
Lainnya
Relu6:
keras.activations.relu6
\[\Phi(x) = \min \{ \text{ReLU}(x), 6 \}\]
Leaky ReLU:
keras.activations.leaky_relu
bisa dipasang hyperparameter \(\alpha \ge 0\):
negative_slope
\[\Phi(x) = \max \{x, \alpha x\}\]
ELU (Exponential Linear Unit):
keras.activations.elu
bisa dipasang hyperparameter \(\alpha \ge 0\):
alpha
\[ \Phi(x) = \begin{cases} x & x > 0 \\ \alpha (e^x - 1) & \text{otherwise} \end{cases} \]
Softplus:
keras.activations.softplus
\[\Phi(x) = \ln (e^x + 1)\]
Softsign:
keras.activations.softsign
\[\Phi(x) = \frac{x}{|x| + 1}\]
Mish:
keras.activations.mish
\[\Phi(x) = x \tanh (\text{softplus} (x))\]
Exponential:
keras.activations.exponential
SELU (Scaled Exponential Linear Unit):
keras.activations.selu
GELU (Gaussian error linear unit):
keras.activations.gelu
Swish / Silu:
keras.activatins.silu
Hard Silu:
keras.activations.hard_silu
Hard sigmoid:
keras.activations.hard_sigmoid
Log softmax:
keras.activations.log_softmax
Pilihan optimizer
Umum digunakan
SGD:
keras.optimizers.SGD
Adam:
keras.optimizers.Adam
(saat ini dianggap optimizer terbaik)RMSprop:
keras.optimizers.RMSprop
Adagrad:
keras.optimizers.Adagrad
Lainnya
AdamW:
keras.optimizers.AdamW
Adadelta:
keras.optimizers.Adadelta
Adamax:
keras.optimizers.Adamax
Adafactor:
keras.optimizers.Adafactor
Nadam:
keras.optimizers.Nadam
Ftrl:
keras.optimizers.Ftrl
Lion:
keras.optimizers.Lion
Loss Scale Optimizer:
keras.optimizers.LossScaleOptimizer
Kecuali Loss Scale Optimizer, semua optimizer bisa dipasang learning rate. Contohnya seperti berikut:
=0.01) keras.optimizers.SGD(learning_rate
Sumber: https://keras.io/api/optimizers/
Pilihan loss function
Umum digunakan
Binary cross-entropy (untuk klasifikasi biner)
class:
keras.losses.BinaryCrossentropy
fungsi:
keras.losses.binary_crossentropy
Categorial cross-entropy (untuk klasifikasi multiclass)
class:
keras.losses.CategoricalCrossentropy
fungsi:
keras.losses.categorical_crossentropy
MSE / mean squared error (untuk regresi)
class:
keras.losses.MeanSquaredError
fungsi:
keras.losses.mean_squared_error
Lainnya, untuk klasifikasi
Sparse categorical cross-entropy
class:
keras.losses.SparseCategoricalCrossentropy
fungsi:
keras.losses.spare_categorical_crossentropy
Poisson loss
class:
keras.losses.Poisson
fungsi:
keras.losses.poisson
Kullback-Leibler divergence loss
class:
keras.losses.KLDivergence
fungsi:
keras.losses.kl_divergence
Lainnya, untuk regresi
MAE / mean absolute error
class:
keras.losses.MeanAbsoluteError
fungsi:
keras.losses.mean_absolute_error
Mean absolute percentage error
class:
keras.losses.MeanAbsolutePercentageError
fungsi:
keras.losses.mean_absolute_percentage_error
Mean squared logarithmic error
class:
keras.losses.MeanSquaredLogarithmicError
fungsi:
keras.losses.mean_squared_logarithmic_error
Cosine similarity
class:
keras.losses.CosineSimilarity
fungsi:
keras.losses.cosine_similarity
Huber loss
class:
keras.losses.Huber
fungsi:
keras.losses.huber
Log Cosh loss
class:
keras.losses.LogCosh
fungsi:
keras.losses.log_cosh
Sumber: https://keras.io/api/losses/
Beberapa pilihan metrik evaluasi
Umum digunakan
Accuracy:
keras.metrics.Accuracy
\(R^2\):
keras.metrics.R2Score
Binary accuracy:
keras.metrics.BinaryAccuracy
Categorical accuracy:
keras.metrics.CategoricalAccuracy
Lainnya, untuk klasifikasi multiclass
Sparse categorical accuracy:
keras.metrics.SpareCategoricalAccuracy
Top K categorical accuracy:
keras.metrics.TopKCategoricalAccuracy
Spare top K categorical accuracy:
keras.metrics.SpareTopKCategoricalAccuracy
Lainnya, untuk klasifikasi biner atau True/False
AUC:
keras.metrics.AUC
Precision:
keras.metrics.Precision
Recall:
keras.metrics.Recall
True Positives:
keras.metrics.TruePositives
True Negatives:
keras.metrics.TrueNegatives
False Positives:
keras.metrics.FalsePositives
False Negatives:
keras.metrics.FalseNegatives
Precision at recall:
keras.metrics.PrecisionAtRecall
Recall at precision:
keras.metrics.RecallAtPrecision
Sensitivity at specificity:
keras.metrics.SensitivityAtSpecificity
Specificity at sensitivity:
keras.metrics.SpecificityAtSensitivity
F-1 score:
keras.metrics.F1Score
F-Beta score:
keras.metrics.FBetaScore
Semua pilihan loss function juga bisa digunakan sebagai metrik evaluasi.
Sumber: https://keras.io/api/metrics/
Referensi
Sumber gambar
Aggarwal, C. Charu. 2018. Neural Networks and Deep Learning: A Textbook. Edisi Pertama. Springer.
Goodfellow, Ian; Bengio, Yoshua; & Courville, Aaron. 2016. Deep Learning. MIT Press.
Buku lainnya
- Géron, Aurélien. 2019. Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow: Concepts, Tools, and Techniques to Build Intelligent Systems. Edisi Kedua. O’Reilly Media.
Internet