pip install tensorflowModul 6 Sains Data: Neural Network dengan Tensorflow dan Keras
Pengantar Neural Network dengan TensorFlow dan 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 plttfversion = tf.__version__
print(tfversion)2.18.0
kerasversion = keras.__version__
print(kerasversion)3.8.0
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 tfTensor, 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.
x = tf.zeros(shape = (3,4))
print(x)tf.Tensor(
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]], shape=(3, 4), dtype=float32)
x = tf.ones(shape = (3,4))
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
const0 = tf.constant(1.5)
print(const0)tf.Tensor(1.5, shape=(), dtype=float32)
print(tf.rank(const0))tf.Tensor(0, shape=(), dtype=int32)
const1 = tf.constant([2.31, 4.567, 8.9])
print(const1)tf.Tensor([2.31 4.567 8.9 ], shape=(3,), dtype=float32)
print(tf.rank(const1))tf.Tensor(1, shape=(), dtype=int32)
const1[0] = 52.5--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-8-6babe8920968> in <cell line: 0>() ----> 1 const1[0] = 52.5 TypeError: 'tensorflow.python.framework.ops.EagerTensor' object does not support item assignment
const2 = tf.constant([
[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
v = tf.Variable(initial_value = tf.zeros(shape = (2,3)))
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
v.assign(tf.ones(shape = (2,3)))
print(v)<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[1., 1., 1.],
[1., 1., 1.]], dtype=float32)>
v[0, 0].assign(9)
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 +=
v.assign_add(tf.ones(shape = (2,3)))
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 -=
v.assign_sub(tf.ones(shape = (2,3)))
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
x = tf.random.normal(shape = (2,3), mean = 0, stddev = 1)
print(x)tf.Tensor(
[[ 0.1701715 -1.5403947 0.5286183 ]
[-0.6036802 0.27208146 -1.4331723 ]], shape=(2, 3), dtype=float32)
# dari distribusi uniform
x = tf.random.uniform(shape = (2,3), minval = 0, maxval = 1)
print(x)tf.Tensor(
[[0.81854975 0.95575297 0.9605453 ]
[0.36875296 0.45838344 0.62123 ]], shape=(2, 3), dtype=float32)
Operasi TensorFlow seperti numpy
Operasi di TensorFlow mirip dengan numpy
a = 4 * tf.ones((2, 2))
print(a)tf.Tensor(
[[4. 4.]
[4. 4.]], shape=(2, 2), dtype=float32)
b = tf.square(a)
print(b)tf.Tensor(
[[16. 16.]
[16. 16.]], shape=(2, 2), dtype=float32)
c = tf.sqrt(a)
print(c)tf.Tensor(
[[2. 2.]
[2. 2.]], shape=(2, 2), dtype=float32)
d = b + c
print(d)tf.Tensor(
[[18. 18.]
[18. 18.]], shape=(2, 2), dtype=float32)
# perkalian matriks
e = tf.matmul(a, c)
print(e)tf.Tensor(
[[16. 16.]
[16. 16.]], shape=(2, 2), dtype=float32)
# perkalian per elemen
e *= d
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\).
x = tf.Variable(4.0)
with tf.GradientTape() as tape:
y = x ** 3
dy_dx = tape.gradient(y, x)
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
x = tf.constant(4.0)
with tf.GradientTape() as tape:
tape.watch(x)
y = x ** 3
dy_dx = tape.gradient(y, x)
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\)
x = tf.Variable(4.0)
with tf.GradientTape() as tape2:
with tf.GradientTape() as tape1:
y = x ** 3
dy_dx = tape1.gradient(y, x)
dy2_dx2 = tape2.gradient(dy_dx, x)
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:
num_samples_per_class, num_classes = 1000, 2
negative_samples = np.random.multivariate_normal(mean = [0,3], cov = [[1,0.5],[0.5,1]], size = num_samples_per_class)
positive_samples = np.random.multivariate_normal(mean = [3,0], cov = [[1,0.5],[0.5,1]], size = num_samples_per_class)
inputs = np.vstack((negative_samples, positive_samples)).astype(np.float32)
targets = np.vstack((
np.zeros((num_samples_per_class, 1), dtype = 'float32'),
np.ones((num_samples_per_class, 1), dtype = 'float32')
))print(inputs.shape)
print(targets.shape)(2000, 2)
(2000, 1)
plt.scatter(inputs[:, 0], inputs[:, 1], c=targets[:, 0])
plt.show()
Kalau mau, kita bisa susun data ini ke dalam bentuk pandas DataFrame, lalu export ke CSV:
titik_negatif_positif_df = pd.DataFrame(
np.hstack([inputs, targets]),
columns = ["x", "y", "kelas"]
)titik_negatif_positif_df| x | y | kelas | |
|---|---|---|---|
| 0 | -0.109873 | 2.904561 | 0.0 |
| 1 | -0.070282 | 3.484931 | 0.0 |
| 2 | -0.556087 | 3.335495 | 0.0 |
| 3 | -0.314094 | 3.379053 | 0.0 |
| 4 | -0.636380 | 2.201054 | 0.0 |
| ... | ... | ... | ... |
| 1995 | 3.338591 | 0.450919 | 1.0 |
| 1996 | 3.168565 | 0.507815 | 1.0 |
| 1997 | 1.506407 | -0.884150 | 1.0 |
| 1998 | 1.901459 | -0.520556 | 1.0 |
| 1999 | 1.771375 | -0.421099 | 1.0 |
2000 rows × 3 columns
titik_negatif_positif_df.to_csv("./titik_negatif_positif.csv", index=False)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:
df = pd.read_csv("./titik_negatif_positif.csv", dtype="float32")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 | -0.109873 | 2.904561 | 0.0 |
| 1 | -0.070282 | 3.484931 | 0.0 |
| 2 | -0.556087 | 3.335495 | 0.0 |
| 3 | -0.314094 | 3.379053 | 0.0 |
| 4 | -0.636380 | 2.201054 | 0.0 |
| ... | ... | ... | ... |
| 1995 | 3.338591 | 0.450919 | 1.0 |
| 1996 | 3.168565 | 0.507815 | 1.0 |
| 1997 | 1.506407 | -0.884150 | 1.0 |
| 1998 | 1.901459 | -0.520556 | 1.0 |
| 1999 | 1.771375 | -0.421099 | 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
inputs_df = df.drop(columns=["kelas"])
targets_df = df[["kelas"]]inputs_df| x | y | |
|---|---|---|
| 0 | -0.109873 | 2.904561 |
| 1 | -0.070282 | 3.484931 |
| 2 | -0.556087 | 3.335495 |
| 3 | -0.314094 | 3.379053 |
| 4 | -0.636380 | 2.201054 |
| ... | ... | ... |
| 1995 | 3.338591 | 0.450919 |
| 1996 | 3.168565 | 0.507815 |
| 1997 | 1.506407 | -0.884150 |
| 1998 | 1.901459 | -0.520556 |
| 1999 | 1.771375 | -0.421099 |
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
plt.scatter(inputs_df["x"], inputs_df["y"], c=targets_df["kelas"])
plt.show()
TensorFlow kurang bisa menangani pandas DataFrame, sehingga harus kita ubah jadi array numpy:
inputs = inputs_df.to_numpy()
targets = targets_df.to_numpy()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.
input_dim = 2
output_dim = 1
W = tf.Variable(tf.random.normal(shape = (input_dim, output_dim)))
b = tf.Variable(tf.random.normal(shape = (output_dim,)))# forward pass
def model(inputs):
return tf.sigmoid(
tf.matmul(inputs, W) + b
)# cross entropy loss
def entropy_loss(y, yhat):
per_sample_losses = - y * tf.math.log(yhat) - (1-y) * tf.math.log(1-yhat)
return tf.reduce_mean(per_sample_losses)# satu epoch di training loop
learning_rate = 0.1
def training_step(inputs, targets):
with tf.GradientTape() as tape:
predictions = model(inputs)
loss = entropy_loss(targets, predictions)
grad_loss_wrt_W, grad_loss_wrt_b = tape.gradient(loss, [W, b])
# update menggunakan gradient descent
W.assign_sub(learning_rate * grad_loss_wrt_W)
b.assign_sub(learning_rate * grad_loss_wrt_b)
return loss# training loop
for epoch in range(100):
loss = training_step(inputs, targets)
print(f"Loss at epoch {epoch}: {loss}")Loss at epoch 0: 0.37649667263031006
Loss at epoch 1: 0.33892661333084106
Loss at epoch 2: 0.3085114061832428
Loss at epoch 3: 0.28344419598579407
Loss at epoch 4: 0.2624468505382538
Loss at epoch 5: 0.24460428953170776
Loss at epoch 6: 0.2292504459619522
Loss at epoch 7: 0.21589185297489166
Loss at epoch 8: 0.2041565477848053
Loss at epoch 9: 0.19375956058502197
Loss at epoch 10: 0.1844790279865265
Loss at epoch 11: 0.1761399507522583
Loss at epoch 12: 0.1686023324728012
Loss at epoch 13: 0.16175292432308197
Loss at epoch 14: 0.15549910068511963
Loss at epoch 15: 0.14976432919502258
Loss at epoch 16: 0.14448483288288116
Loss at epoch 17: 0.13960705697536469
Loss at epoch 18: 0.13508561253547668
Loss at epoch 19: 0.13088177144527435
Loss at epoch 20: 0.1269623339176178
Loss at epoch 21: 0.12329864501953125
Loss at epoch 22: 0.119865782558918
Loss at epoch 23: 0.1166420504450798
Loss at epoch 24: 0.11360841244459152
Loss at epoch 25: 0.11074810475111008
Loss at epoch 26: 0.10804630815982819
Loss at epoch 27: 0.10548984259366989
Loss at epoch 28: 0.10306701809167862
Loss at epoch 29: 0.1007673367857933
Loss at epoch 30: 0.09858141094446182
Loss at epoch 31: 0.09650078415870667
Loss at epoch 32: 0.09451782703399658
Loss at epoch 33: 0.09262565523386002
Loss at epoch 34: 0.09081799536943436
Loss at epoch 35: 0.08908917009830475
Loss at epoch 36: 0.08743401616811752
Loss at epoch 37: 0.08584779500961304
Loss at epoch 38: 0.08432614803314209
Loss at epoch 39: 0.08286512643098831
Loss at epoch 40: 0.08146108686923981
Loss at epoch 41: 0.08011066168546677
Loss at epoch 42: 0.07881075888872147
Loss at epoch 43: 0.07755852490663528
Loss at epoch 44: 0.07635130733251572
Loss at epoch 45: 0.07518665492534637
Loss at epoch 46: 0.0740623027086258
Loss at epoch 47: 0.07297613471746445
Loss at epoch 48: 0.07192617654800415
Loss at epoch 49: 0.07091060280799866
Loss at epoch 50: 0.06992770731449127
Loss at epoch 51: 0.06897588819265366
Loss at epoch 52: 0.06805365532636642
Loss at epoch 53: 0.06715962290763855
Loss at epoch 54: 0.06629245728254318
Loss at epoch 55: 0.06545095145702362
Loss at epoch 56: 0.06463393568992615
Loss at epoch 57: 0.06384031474590302
Loss at epoch 58: 0.06306910514831543
Loss at epoch 59: 0.062319304794073105
Loss at epoch 60: 0.06159002706408501
Loss at epoch 61: 0.06088041514158249
Loss at epoch 62: 0.060189660638570786
Loss at epoch 63: 0.05951699614524841
Loss at epoch 64: 0.05886170268058777
Loss at epoch 65: 0.058223091065883636
Loss at epoch 66: 0.057600509375333786
Loss at epoch 67: 0.056993357837200165
Loss at epoch 68: 0.05640103667974472
Loss at epoch 69: 0.055823005735874176
Loss at epoch 70: 0.05525872856378555
Loss at epoch 71: 0.05470770224928856
Loss at epoch 72: 0.05416945740580559
Loss at epoch 73: 0.05364353582262993
Loss at epoch 74: 0.053129516541957855
Loss at epoch 75: 0.05262697488069534
Loss at epoch 76: 0.05213551968336105
Loss at epoch 77: 0.05165477842092514
Loss at epoch 78: 0.05118439346551895
Loss at epoch 79: 0.050724029541015625
Loss at epoch 80: 0.050273347645998
Loss at epoch 81: 0.049832046031951904
Loss at epoch 82: 0.049399811774492264
Loss at epoch 83: 0.04897637292742729
Loss at epoch 84: 0.048561446368694305
Loss at epoch 85: 0.04815478250384331
Loss at epoch 86: 0.04775610938668251
Loss at epoch 87: 0.047365207225084305
Loss at epoch 88: 0.0469818189740181
Loss at epoch 89: 0.04660574346780777
Loss at epoch 90: 0.04623674973845482
Loss at epoch 91: 0.04587464779615402
Loss at epoch 92: 0.045519232749938965
Loss at epoch 93: 0.045170318335294724
Loss at epoch 94: 0.04482771083712578
Loss at epoch 95: 0.044491250067949295
Loss at epoch 96: 0.04416074976325035
Loss at epoch 97: 0.043836068361997604
Loss at epoch 98: 0.04351703077554703
Loss at epoch 99: 0.04320349544286728
Prediksi
Sekarang training sudah selesai, kita bisa gunakan model kita untuk memprediksi kelas berdasarkan inputs (koordinat titik-titik)
predictions = model(inputs)Akibat penggunaan fungsi aktivasi sigmoid, hasil prediksi cukup jelas, apakah kelas pertama (kelas 0) atau kelas kedua (kelas 1):
print(predictions)tf.Tensor(
[[0.01171741]
[0.00699979]
[0.00357984]
...
[0.8950965 ]
[0.9203457 ]
[0.8933534 ]], 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).
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:, 0] > 0.5)
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 kerasPerlu 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
model2 = keras.Sequential(
[
keras.layers.InputLayer(input_shape = (2,)),
keras.layers.Dense(units = 1, activation = 'sigmoid')
]
)/usr/local/lib/python3.11/dist-packages/keras/src/layers/core/input_layer.py:27: UserWarning: Argument `input_shape` is deprecated. Use `shape` instead.
warnings.warn(
# menambahkan layer secara berangsur-angsur
model2 = keras.Sequential()
model2.add(keras.layers.InputLayer(input_shape = (2,)))
model2.add(keras.layers.Dense(units = 1, activation = 'sigmoid'))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
model2 = keras.Sequential(
[
keras.layers.InputLayer(input_shape = (2,)),
keras.layers.Dense(units = 1, activation = keras.activations.sigmoid)
]
)# menambahkan layer secara berangsur-angsur
model2 = keras.Sequential()
model2.add(keras.layers.InputLayer(input_shape = (2,)))
model2.add(keras.layers.Dense(units = 1, activation = keras.activations.sigmoid))Ringkasan dan diagram model
Kemudian, kita bisa melihat ringkasan bentuk model yang dihasilkan:
model2.summary()Model: "sequential_2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ dense_2 (Dense) │ (None, 1) │ 3 │ └─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 3 (12.00 B)
Trainable params: 3 (12.00 B)
Non-trainable params: 0 (0.00 B)
Kita juga bisa menampilkan semacam diagram, bahkan menyimpannya ke dalam file:
keras.utils.plot_model(
model2,
show_shapes = True,
show_layer_activations = True,
to_file = "keras_sequential_model2.png"
)

Memilih hyperparameter
Untuk memilih hyperparameter yaitu optimizer dan loss function (dan metrik evaluasi), kedua kode berikut ini ekuivalen:
# dengan string
model2.compile(
optimizer = "sgd",
loss = "binary_crossentropy",
metrics = ["binary_accuracy"]
)# dengan objek dari class
model2.compile(
optimizer = keras.optimizers.SGD(),
loss = keras.losses.BinaryCrossentropy(),
metrics = [keras.metrics.BinaryAccuracy()]
)Dengan cara yang kedua, kita juga bisa menentukan hyperparameter seperti learning rate:
# dengan objek dari class
model2.compile(
optimizer = keras.optimizers.SGD(learning_rate = 0.01),
loss = keras.losses.BinaryCrossentropy(),
metrics = [keras.metrics.BinaryAccuracy()]
)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
x_train = inputs
y_train = targets
history2 = model2.fit(x_train, y_train, epochs=100, validation_split=0.2)Epoch 1/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 1s 7ms/step - binary_accuracy: 0.9829 - loss: 0.1602 - val_binary_accuracy: 0.9775 - val_loss: 0.2491 Epoch 2/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9909 - loss: 0.1308 - val_binary_accuracy: 0.9875 - val_loss: 0.1955 Epoch 3/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9930 - loss: 0.1074 - val_binary_accuracy: 0.9925 - val_loss: 0.1616 Epoch 4/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9975 - loss: 0.0915 - val_binary_accuracy: 0.9950 - val_loss: 0.1385 Epoch 5/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9981 - loss: 0.0834 - val_binary_accuracy: 1.0000 - val_loss: 0.1218 Epoch 6/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9963 - loss: 0.0775 - val_binary_accuracy: 1.0000 - val_loss: 0.1092 Epoch 7/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9953 - loss: 0.0732 - val_binary_accuracy: 1.0000 - val_loss: 0.0993 Epoch 8/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9981 - loss: 0.0700 - val_binary_accuracy: 1.0000 - val_loss: 0.0914 Epoch 9/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9993 - loss: 0.0607 - val_binary_accuracy: 1.0000 - val_loss: 0.0847 Epoch 10/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9990 - loss: 0.0581 - val_binary_accuracy: 1.0000 - val_loss: 0.0792 Epoch 11/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9981 - loss: 0.0590 - val_binary_accuracy: 1.0000 - val_loss: 0.0744 Epoch 12/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9994 - loss: 0.0585 - val_binary_accuracy: 1.0000 - val_loss: 0.0703 Epoch 13/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9999 - loss: 0.0521 - val_binary_accuracy: 1.0000 - val_loss: 0.0667 Epoch 14/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9987 - loss: 0.0479 - val_binary_accuracy: 1.0000 - val_loss: 0.0635 Epoch 15/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9990 - loss: 0.0466 - val_binary_accuracy: 1.0000 - val_loss: 0.0607 Epoch 16/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9997 - loss: 0.0442 - val_binary_accuracy: 1.0000 - val_loss: 0.0582 Epoch 17/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9995 - loss: 0.0446 - val_binary_accuracy: 1.0000 - val_loss: 0.0559 Epoch 18/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9993 - loss: 0.0423 - val_binary_accuracy: 1.0000 - val_loss: 0.0538 Epoch 19/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9993 - loss: 0.0410 - val_binary_accuracy: 1.0000 - val_loss: 0.0519 Epoch 20/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9994 - loss: 0.0414 - val_binary_accuracy: 1.0000 - val_loss: 0.0501 Epoch 21/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9999 - loss: 0.0396 - val_binary_accuracy: 1.0000 - val_loss: 0.0485 Epoch 22/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9995 - loss: 0.0345 - val_binary_accuracy: 1.0000 - val_loss: 0.0470 Epoch 23/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9998 - loss: 0.0372 - val_binary_accuracy: 1.0000 - val_loss: 0.0456 Epoch 24/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9998 - loss: 0.0358 - val_binary_accuracy: 1.0000 - val_loss: 0.0443 Epoch 25/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9995 - loss: 0.0342 - val_binary_accuracy: 1.0000 - val_loss: 0.0431 Epoch 26/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - binary_accuracy: 0.9999 - loss: 0.0330 - val_binary_accuracy: 1.0000 - val_loss: 0.0420 Epoch 27/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - binary_accuracy: 0.9999 - loss: 0.0319 - val_binary_accuracy: 1.0000 - val_loss: 0.0409 Epoch 28/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - binary_accuracy: 0.9999 - loss: 0.0316 - val_binary_accuracy: 1.0000 - val_loss: 0.0399 Epoch 29/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - binary_accuracy: 0.9991 - loss: 0.0316 - val_binary_accuracy: 1.0000 - val_loss: 0.0390 Epoch 30/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - binary_accuracy: 0.9999 - loss: 0.0280 - val_binary_accuracy: 1.0000 - val_loss: 0.0381 Epoch 31/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 7ms/step - binary_accuracy: 0.9996 - loss: 0.0302 - val_binary_accuracy: 1.0000 - val_loss: 0.0372 Epoch 32/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9985 - loss: 0.0287 - val_binary_accuracy: 1.0000 - val_loss: 0.0364 Epoch 33/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9994 - loss: 0.0277 - val_binary_accuracy: 1.0000 - val_loss: 0.0357 Epoch 34/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9995 - loss: 0.0282 - val_binary_accuracy: 1.0000 - val_loss: 0.0349 Epoch 35/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9990 - loss: 0.0257 - val_binary_accuracy: 1.0000 - val_loss: 0.0342 Epoch 36/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9998 - loss: 0.0273 - val_binary_accuracy: 1.0000 - val_loss: 0.0336 Epoch 37/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9993 - loss: 0.0269 - val_binary_accuracy: 1.0000 - val_loss: 0.0330 Epoch 38/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9987 - loss: 0.0259 - val_binary_accuracy: 1.0000 - val_loss: 0.0324 Epoch 39/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9994 - loss: 0.0287 - val_binary_accuracy: 1.0000 - val_loss: 0.0318 Epoch 40/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9990 - loss: 0.0263 - val_binary_accuracy: 1.0000 - val_loss: 0.0312 Epoch 41/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9972 - loss: 0.0269 - val_binary_accuracy: 1.0000 - val_loss: 0.0307 Epoch 42/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9995 - loss: 0.0229 - val_binary_accuracy: 1.0000 - val_loss: 0.0302 Epoch 43/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9998 - loss: 0.0241 - val_binary_accuracy: 1.0000 - val_loss: 0.0297 Epoch 44/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9996 - loss: 0.0246 - val_binary_accuracy: 1.0000 - val_loss: 0.0292 Epoch 45/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9991 - loss: 0.0222 - val_binary_accuracy: 1.0000 - val_loss: 0.0288 Epoch 46/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9995 - loss: 0.0240 - val_binary_accuracy: 1.0000 - val_loss: 0.0283 Epoch 47/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9999 - loss: 0.0226 - val_binary_accuracy: 1.0000 - val_loss: 0.0279 Epoch 48/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9987 - loss: 0.0213 - val_binary_accuracy: 1.0000 - val_loss: 0.0275 Epoch 49/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9988 - loss: 0.0217 - val_binary_accuracy: 1.0000 - val_loss: 0.0271 Epoch 50/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9987 - loss: 0.0250 - val_binary_accuracy: 1.0000 - val_loss: 0.0267 Epoch 51/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9984 - loss: 0.0224 - val_binary_accuracy: 1.0000 - val_loss: 0.0264 Epoch 52/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9996 - loss: 0.0217 - val_binary_accuracy: 1.0000 - val_loss: 0.0260 Epoch 53/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9986 - loss: 0.0192 - val_binary_accuracy: 1.0000 - val_loss: 0.0256 Epoch 54/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9999 - loss: 0.0205 - val_binary_accuracy: 1.0000 - val_loss: 0.0253 Epoch 55/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9998 - loss: 0.0210 - val_binary_accuracy: 1.0000 - val_loss: 0.0250 Epoch 56/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9999 - loss: 0.0185 - val_binary_accuracy: 1.0000 - val_loss: 0.0247 Epoch 57/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9986 - loss: 0.0193 - val_binary_accuracy: 1.0000 - val_loss: 0.0244 Epoch 58/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9991 - loss: 0.0210 - val_binary_accuracy: 1.0000 - val_loss: 0.0241 Epoch 59/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 1.0000 - loss: 0.0216 - val_binary_accuracy: 1.0000 - val_loss: 0.0238 Epoch 60/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9999 - loss: 0.0200 - val_binary_accuracy: 1.0000 - val_loss: 0.0235 Epoch 61/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9989 - loss: 0.0207 - val_binary_accuracy: 1.0000 - val_loss: 0.0232 Epoch 62/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9991 - loss: 0.0184 - val_binary_accuracy: 1.0000 - val_loss: 0.0230 Epoch 63/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9990 - loss: 0.0198 - val_binary_accuracy: 1.0000 - val_loss: 0.0227 Epoch 64/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9997 - loss: 0.0180 - val_binary_accuracy: 1.0000 - val_loss: 0.0224 Epoch 65/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9992 - loss: 0.0187 - val_binary_accuracy: 1.0000 - val_loss: 0.0222 Epoch 66/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9999 - loss: 0.0181 - val_binary_accuracy: 1.0000 - val_loss: 0.0220 Epoch 67/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9996 - loss: 0.0166 - val_binary_accuracy: 1.0000 - val_loss: 0.0217 Epoch 68/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9989 - loss: 0.0183 - val_binary_accuracy: 1.0000 - val_loss: 0.0215 Epoch 69/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9998 - loss: 0.0176 - val_binary_accuracy: 1.0000 - val_loss: 0.0213 Epoch 70/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - binary_accuracy: 1.0000 - loss: 0.0161 - val_binary_accuracy: 1.0000 - val_loss: 0.0211 Epoch 71/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - binary_accuracy: 0.9993 - loss: 0.0180 - val_binary_accuracy: 1.0000 - val_loss: 0.0208 Epoch 72/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - binary_accuracy: 0.9989 - loss: 0.0192 - val_binary_accuracy: 1.0000 - val_loss: 0.0206 Epoch 73/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 1s 5ms/step - binary_accuracy: 1.0000 - loss: 0.0165 - val_binary_accuracy: 1.0000 - val_loss: 0.0204 Epoch 74/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 5ms/step - binary_accuracy: 1.0000 - loss: 0.0179 - val_binary_accuracy: 1.0000 - val_loss: 0.0202 Epoch 75/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9988 - loss: 0.0169 - val_binary_accuracy: 1.0000 - val_loss: 0.0200 Epoch 76/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9996 - loss: 0.0157 - val_binary_accuracy: 1.0000 - val_loss: 0.0198 Epoch 77/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9991 - loss: 0.0186 - val_binary_accuracy: 1.0000 - val_loss: 0.0197 Epoch 78/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9996 - loss: 0.0164 - val_binary_accuracy: 1.0000 - val_loss: 0.0195 Epoch 79/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9998 - loss: 0.0157 - val_binary_accuracy: 1.0000 - val_loss: 0.0193 Epoch 80/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9992 - loss: 0.0143 - val_binary_accuracy: 1.0000 - val_loss: 0.0191 Epoch 81/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 1.0000 - loss: 0.0157 - val_binary_accuracy: 1.0000 - val_loss: 0.0190 Epoch 82/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9998 - loss: 0.0157 - val_binary_accuracy: 1.0000 - val_loss: 0.0188 Epoch 83/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9996 - loss: 0.0153 - val_binary_accuracy: 1.0000 - val_loss: 0.0186 Epoch 84/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 1.0000 - loss: 0.0149 - val_binary_accuracy: 1.0000 - val_loss: 0.0185 Epoch 85/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9989 - loss: 0.0161 - val_binary_accuracy: 1.0000 - val_loss: 0.0183 Epoch 86/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9978 - loss: 0.0161 - val_binary_accuracy: 1.0000 - val_loss: 0.0182 Epoch 87/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9998 - loss: 0.0156 - val_binary_accuracy: 1.0000 - val_loss: 0.0180 Epoch 88/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9997 - loss: 0.0140 - val_binary_accuracy: 1.0000 - val_loss: 0.0179 Epoch 89/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9996 - loss: 0.0151 - val_binary_accuracy: 1.0000 - val_loss: 0.0177 Epoch 90/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9999 - loss: 0.0154 - val_binary_accuracy: 1.0000 - val_loss: 0.0176 Epoch 91/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9999 - loss: 0.0136 - val_binary_accuracy: 1.0000 - val_loss: 0.0174 Epoch 92/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9993 - loss: 0.0138 - val_binary_accuracy: 1.0000 - val_loss: 0.0173 Epoch 93/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9991 - loss: 0.0152 - val_binary_accuracy: 1.0000 - val_loss: 0.0172 Epoch 94/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9999 - loss: 0.0134 - val_binary_accuracy: 1.0000 - val_loss: 0.0170 Epoch 95/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 4ms/step - binary_accuracy: 0.9991 - loss: 0.0143 - val_binary_accuracy: 1.0000 - val_loss: 0.0169 Epoch 96/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9987 - loss: 0.0169 - val_binary_accuracy: 1.0000 - val_loss: 0.0168 Epoch 97/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 1.0000 - loss: 0.0140 - val_binary_accuracy: 1.0000 - val_loss: 0.0166 Epoch 98/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9997 - loss: 0.0142 - val_binary_accuracy: 1.0000 - val_loss: 0.0165 Epoch 99/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9990 - loss: 0.0130 - val_binary_accuracy: 1.0000 - val_loss: 0.0164 Epoch 100/100 50/50 ━━━━━━━━━━━━━━━━━━━━ 0s 3ms/step - binary_accuracy: 0.9994 - loss: 0.0154 - val_binary_accuracy: 1.0000 - val_loss: 0.0163
Objek “history” tersebut memiliki dictionary .history. Kita bisa lihat, apa saja key yang ada:
print(history2.history.keys())dict_keys(['binary_accuracy', 'loss', 'val_binary_accuracy', 'val_loss'])
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:
pd.DataFrame(history2.history).to_csv("./keras_sequential_history2.csv", index=False)Kalau mau menyamakan, file nya bisa kalian download dari GitHub Pages ini: keras_sequential_history2.csv
Kemudian, kita bisa load kembali:
history2_df = pd.read_csv("./keras_sequential_history2.csv")history2_df| binary_accuracy | loss | val_binary_accuracy | val_loss | |
|---|---|---|---|---|
| 0 | 0.985000 | 0.150636 | 0.9775 | 0.249056 |
| 1 | 0.993125 | 0.122728 | 0.9875 | 0.195455 |
| 2 | 0.993750 | 0.105114 | 0.9925 | 0.161598 |
| 3 | 0.996250 | 0.092837 | 0.9950 | 0.138501 |
| 4 | 0.996875 | 0.083684 | 1.0000 | 0.121808 |
| ... | ... | ... | ... | ... |
| 95 | 0.999375 | 0.014503 | 1.0000 | 0.016765 |
| 96 | 0.999375 | 0.014412 | 1.0000 | 0.016639 |
| 97 | 0.999375 | 0.014322 | 1.0000 | 0.016516 |
| 98 | 0.999375 | 0.014234 | 1.0000 | 0.016394 |
| 99 | 0.999375 | 0.014146 | 1.0000 | 0.016273 |
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.
plt.plot(history2_df["loss"], label = "training loss")
plt.plot(history2_df["val_loss"], label = "validation loss")
plt.xlabel("epoch")
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
predictions2 = model2.predict(inputs)63/63 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step
Ada sedikit progress bar, karena proses prediksi sebenarnya adalah forward pass. Kita bisa matikan progress bar dengan verbose=False
predictions2 = model2.predict(inputs, verbose=False)print(predictions2)[[2.0281430e-03]
[6.6396926e-04]
[3.5774685e-04]
...
[9.9074566e-01]
[9.9079442e-01]
[9.8560268e-01]]
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions2[:, 0] > 0.5)
plt.show()
Menyimpan keseluruhan model
Perintahnya adalah .save(path_tempat_penyimpanan) dengan file format .keras
model2.save("./keras_sequential_model2.keras")Kita bisa load kembali model tersebut:
model3 = keras.models.load_model("keras_sequential_model2.keras")Hasil prediksinya akan sama (karena modelnya memang sama):
predictions3 = model3.predict(inputs)63/63 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/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
model2.save_weights("keras_sequential_model2.weights.h5")Untuk load kembali, kita perlu menyusun layer model terlebih dahulu, sama persis dengan susunan yang aslinya:
model4 = keras.Sequential(
[
keras.layers.InputLayer(input_shape = (2,)),
keras.layers.Dense(units = 1, activation = keras.activations.sigmoid)
]
)/usr/local/lib/python3.11/dist-packages/keras/src/layers/core/input_layer.py:27: UserWarning: Argument `input_shape` is deprecated. Use `shape` instead.
warnings.warn(
Barulah kita gunakan perintah .load_weights(path_tempat_penyimpanan)
model4.load_weights("./keras_sequential_model2.weights.h5")Lagi-lagi, hasil prediksinya akan sama:
predictions4 = model4.predict(inputs)63/63 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/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 (identitas):
keras.activations.linearSigmoid:
keras.activations.sigmoidReLU:
keras.activations.relu(Soft) tanh:
keras.activations.tanhSoftmax:
keras.activations.softmax
Lainnya
Relu6:
keras.activations.relu6\[\Phi(x) = \min \{ \text{ReLU}(x), 6 \}\]
Leaky ReLU:
keras.activations.leaky_relubisa dipasang hyperparameter \(\alpha \ge 0\):
negative_slope\[\Phi(x) = \max \{x, \alpha x\}\]
ELU (Exponential Linear Unit):
keras.activations.elubisa 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.exponentialSELU (Scaled Exponential Linear Unit):
keras.activations.seluGELU (Gaussian error linear unit):
keras.activations.geluSwish / Silu:
keras.activatins.siluHard Silu:
keras.activations.hard_siluHard sigmoid:
keras.activations.hard_sigmoidLog softmax:
keras.activations.log_softmax
Pilihan optimizer
Umum digunakan
SGD:
keras.optimizers.SGDAdam:
keras.optimizers.Adam(saat ini dianggap optimizer terbaik)RMSprop:
keras.optimizers.RMSpropAdagrad:
keras.optimizers.Adagrad
Lainnya
AdamW:
keras.optimizers.AdamWAdadelta:
keras.optimizers.AdadeltaAdamax:
keras.optimizers.AdamaxAdafactor:
keras.optimizers.AdafactorNadam:
keras.optimizers.NadamFtrl:
keras.optimizers.FtrlLion:
keras.optimizers.LionLoss Scale Optimizer:
keras.optimizers.LossScaleOptimizer
Kecuali Loss Scale Optimizer, semua optimizer bisa dipasang learning rate. Contohnya seperti berikut:
keras.optimizers.SGD(learning_rate=0.01)Sumber: https://keras.io/api/optimizers/
Pilihan loss function
Umum digunakan
Binary cross-entropy (untuk klasifikasi biner)
class:
keras.losses.BinaryCrossentropyfungsi:
keras.losses.binary_crossentropyCategorial cross-entropy (untuk klasifikasi multiclass)
class:
keras.losses.CategoricalCrossentropyfungsi:
keras.losses.categorical_crossentropyMSE / mean squared error (untuk regresi)
class:
keras.losses.MeanSquaredErrorfungsi:
keras.losses.mean_squared_error
Lainnya, untuk klasifikasi
Sparse categorical cross-entropy
class:
keras.losses.SparseCategoricalCrossentropyfungsi:
keras.losses.spare_categorical_crossentropyPoisson loss
class:
keras.losses.Poissonfungsi:
keras.losses.poissonKullback-Leibler divergence loss
class:
keras.losses.KLDivergencefungsi:
keras.losses.kl_divergence
Lainnya, untuk regresi
MAE / mean absolute error
class:
keras.losses.MeanAbsoluteErrorfungsi:
keras.losses.mean_absolute_errorMean absolute percentage error
class:
keras.losses.MeanAbsolutePercentageErrorfungsi:
keras.losses.mean_absolute_percentage_errorMean squared logarithmic error
class:
keras.losses.MeanSquaredLogarithmicErrorfungsi:
keras.losses.mean_squared_logarithmic_errorCosine similarity
class:
keras.losses.CosineSimilarityfungsi:
keras.losses.cosine_similarityHuber loss
class:
keras.losses.Huberfungsi:
keras.losses.huberLog Cosh loss
class:
keras.losses.LogCoshfungsi:
keras.losses.log_cosh
Sumber: https://keras.io/api/losses/
Beberapa pilihan metrik evaluasi
Umum digunakan
Accuracy:
keras.metrics.Accuracy\(R^2\):
keras.metrics.R2ScoreBinary accuracy:
keras.metrics.BinaryAccuracyCategorical accuracy:
keras.metrics.CategoricalAccuracy
Lainnya, untuk klasifikasi multiclass
Sparse categorical accuracy:
keras.metrics.SpareCategoricalAccuracyTop K categorical accuracy:
keras.metrics.TopKCategoricalAccuracySpare top K categorical accuracy:
keras.metrics.SpareTopKCategoricalAccuracy
Lainnya, untuk klasifikasi biner atau True/False
AUC:
keras.metrics.AUCPrecision:
keras.metrics.PrecisionRecall:
keras.metrics.RecallTrue Positives:
keras.metrics.TruePositivesTrue Negatives:
keras.metrics.TrueNegativesFalse Positives:
keras.metrics.FalsePositivesFalse Negatives:
keras.metrics.FalseNegativesPrecision at recall:
keras.metrics.PrecisionAtRecallRecall at precision:
keras.metrics.RecallAtPrecisionSensitivity at specificity:
keras.metrics.SensitivityAtSpecificitySpecificity at sensitivity:
keras.metrics.SpecificityAtSensitivityF-1 score:
keras.metrics.F1ScoreF-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