02. Klasifikasi Sederhana¶
Apa itu klasifikasi?
Apa makanan kesukaanmu? Misalnya saja kamu menyukai es kopi susu, dapatkah kamu menyebutkan merk kopi susu ternikmat yang kamu sukai? Apakah kamu dapat membedakan kopi susu yang kamu sukai dengan kopi susu yang kamu tidak sukai? Jika kamu dapat membedakan kopi susu yang kamu sukai dengan kopi susu yang kamu tidak sukai, maka kamu telah melakukan klasifikasi.
Klasifikasi dalam kehidupan sehari-hari adalah suatu proses untuk membedakan suatu objek dengan objek lainnya. Misalnya saja, kamu dapat membedakan kopi susu yang kamu sukai dengan kopi susu yang kamu tidak sukai. Dalam dunia ilmu komputer, klasifikasi adalah suatu proses untuk membedakan suatu objek dengan objek lainnya. Objek yang akan diklasifikasikan disebut sebagai kelas. Objek yang akan diklasifikasikan dapat berupa gambar, suara, teks, dan lain-lain. Objek yang akan diklasifikasikan disebut sebagai kelas. Objek yang akan diklasifikasikan dapat berupa gambar, suara, teks, dan lain-lain.
Intermejokes
Contoh klasifikasi yang nirfaedah
Lalu apa contoh masalah klasifikasi dalam dunia ilmu komputer?
Sumber: Satyam Kumar, Medium
Banyak sekali contoh klasifikasi dalam komputer dan machine learning. Misalnya saja klasifikasi email spam dan non spam, klasifikasi gambar / foto bunga, klasifikasi hoax dan tidak hoax, atau klasifikasi multi-class misalnya klasifikasi jenis kopi berdasarkan lokasi tumbuhnya (misalnya: kopi toraja, kopi gayo, kopi flores, kopi lampung).
Nah, di modul kali ini kita akan mencoba klasifikasi sederhana untuk membedakan tobedefined.
Setelah mempelajari modul 02 ini, kamu diharapkan mampu:
- to be defined
1. Memuat Dataset Make Circles¶
Kita akan membuat data dummy dengan menggunakan fungsi make_circles
dari sklearn
. Kode di bawah mengimport fungsi make_circles
dari modul datasets pada library scikit-learn
(sklearn). Fungsi make_circles
merupakan salah satu fungsi yang tersedia dalam scikit-learn yang berguna untuk membuat data sintetis (artificial data) yang terdiri dari dua kelas. Kedua kelas tersebut ditandai dengan dua warna yang berbeda dan terpisah oleh garis lurus.
Import sklearn
¶
from sklearn.datasets import make_circles
Memuat Dataset¶
jumlah_sampel = 2500
# memuat dataset
x, y = make_circles(n_samples=jumlah_sampel, noise=0.05, random_state=2022)
Kode di atas berisi instruksi untuk memuat data sintetis (artificial data) yang terdiri dari dua kelas menggunakan fungsi make_circles
dari scikit-learn
. Pertama, variabel jumlah_sampel
dideklarasikan dengan nilai 2500. Kemudian, fungsi make_circles
dipanggil dengan menyertakan tiga argumen:
n_samples
: jumlah sampel yang akan dibuat. Nilai yang diberikan adalah jumlah_sampel yaitu 2500.noise
: tingkat noise (gangguan) yang akan ditambahkan pada data. Nilai yang diberikan adalah 0.05.random_state
: nilai yang digunakan untuk menentukan bagaimana data akan dibuat. Jika nilai ini ditentukan, maka data yang dihasilkan akan selalu sama setiap kali fungsi ini dijalankan. Nilai yang diberikan adalah 2022.
Fungsi make_circles
akan mengembalikan dua array, yaitu x
yang berisi fitur-fitur sampel dan y
yang berisi label sampel. Kedua array tersebut dapat digunakan untuk keperluan visualisasi atau pelatihan model machine learning
Contoh hasil data yang di-generate dapat dilihat pada contoh di bawah ini:
# cetak 10 data pertama
print(f"10 Data Pertama: \n{x[:10]}")
print(f"10 Label Pertama: \n{y[:10]}")
10 Data Pertama: [[ 0.2974033 0.95265289] [ 0.2654732 -0.64029611] [ 0.4960826 -0.82758725] [ 0.62708213 0.77148215] [ 0.46138206 -0.65144707] [-0.50740667 -0.99651206] [-0.91238648 0.32348719] [ 0.79511143 -0.60267453] [-1.05450073 -0.09177142] [ 0.2993444 -0.86163984]] 10 Label Pertama: [0 1 0 0 1 0 0 0 0 1]
Setelah itu, data akan sedikit kita atur dan kita tabulasikan menggunakan pandas
. Kode di bawah mengimport modul pandas
dan mengubah array x
menjadi DataFrame
dengan nama df
. Kemudian, array y
ditambahkan sebagai kolom baru dengan nama label
. Kode di bawah juga menampilkan 5 baris pertama dari DataFrame
df
.
Memuat dalam Dataframe Pandas¶
# merapikan dengan pandas
import pandas as pd
df = pd.DataFrame(x, columns=["x1", "x2"])
df["label"] = y
df.head()
x1 | x2 | label | |
---|---|---|---|
0 | 0.297403 | 0.952653 | 0 |
1 | 0.265473 | -0.640296 | 1 |
2 | 0.496083 | -0.827587 | 0 |
3 | 0.627082 | 0.771482 | 0 |
4 | 0.461382 | -0.651447 | 1 |
Visualisasi Data¶
Untuk mempermudah kita memvisualisasikan data yang ada, kita akan menggunakan matplotlib
dan fungsi plt.scatter
untuk memvisualisasikan data. Dapat dilihat pada contoh di bawah ini, terdapat scatter plot dengan dua warna berbeda untuk label 0 dan 1.
# visualisasi data dengan matplotlib
import matplotlib.pyplot as plt
plt.figure(figsize=(4, 4))
plt.scatter(df["x1"], df["x2"], c=df["label"])
plt.show()
Selanjutnya kita ingin melihat ukuran, jumlah data, dan tipe data dari x dan y.
print(f"Ukuran shape x: {x.shape} | Ukuran shape y: {y.shape}")
print(f"Jumlah data: {len(x)}")
print(f"Contoh data dari x: {x[0]} | Contoh data dari y: {y[0]}")
print(f"Tipe data dari x: {type(x)} | Tipe data dari y: {type(y)}")
Ukuran shape x: (2500, 2) | Ukuran shape y: (2500,) Jumlah data: 2500 Contoh data dari x: [0.2974033 0.95265289] | Contoh data dari y: 0 Tipe data dari x: <class 'numpy.ndarray'> | Tipe data dari y: <class 'numpy.ndarray'>
Kita perlu melakukan konversi dengan torch.from_numpy
untuk mengubah tipe data dari numpy menjadi tensor. Karena kita akan menggunakan torch
untuk membangun model machine learning, maka kita perlu mengubah tipe data dari numpy menjadi tensor.
Konversi Menjadi Tensor¶
# konversi menjadi tensor dari numpy
import torch
x = torch.from_numpy(x).float()
y = torch.from_numpy(y).float()
print(f"Tipe data dari x setelah diubah: {type(x)}")
print(f"Tipe data dari y setelah diubah: {type(y)}")
print(f"Contoh data dari x setelah diubah: {x[0]}")
print(f"Contoh data dari y setelah diubah: {y[0]}")
Tipe data dari x setelah diubah: <class 'torch.Tensor'> Tipe data dari y setelah diubah: <class 'torch.Tensor'> Contoh data dari x setelah diubah: tensor([0.2974, 0.9527]) Contoh data dari y setelah diubah: 0.0
Kode di atas mengkonversi dua array numpy, yaitu x
dan y
, ke dalam tipe tensor menggunakan library PyTorch. Tensor adalah salah satu tipe data yang digunakan dalam PyTorch yang mirip dengan array numpy, namun memiliki kemampuan yang lebih luas dalam melakukan operasi matematis dan deep learning.
Untuk mengkonversi array numpy ke dalam tipe tensor, kita dapat menggunakan fungsi from_numpy yang terdapat pada module torch. Kemudian, untuk menentukan tipe data dari tensor tersebut, kita dapat menambahkan method .float()
pada akhir perintah. Method ini akan mengkonversi tipe data tensor menjadi tipe float (tipe data numerik dengan bilangan desimal).
Contoh di atas menunjukkan bahwa array x
dan y
akan dikonversi ke dalam tipe tensor dengan tipe data float. Tensor yang dihasilkan akan dapat digunakan dalam operasi-operasi matematis dan deep learning yang ditawarkan oleh PyTorch.
Train Test Split¶
# split data menjadi training dan testing/val menggunakan train_test_split dari sklearn
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=2022)
print(f"Ukuran x_train: {x_train.shape} | Ukuran y_train: {y_train.shape}")
print(f"Ukuran x_test: {x_test.shape} | Ukuran y_test: {y_test.shape}")
Ukuran x_train: torch.Size([2000, 2]) | Ukuran y_train: torch.Size([2000]) Ukuran x_test: torch.Size([500, 2]) | Ukuran y_test: torch.Size([500])
Kode di atas menggunakan fungsi train_test_split
dari modul model_selection pada library scikit-learn (sklearn) untuk membagi data menjadi dua bagian: data latih (training data) dan data uji (test data). Fungsi ini akan menerima empat argumen yaitu:
x
: matriks atau array yang berisi fitur-fitur (features) yang akan digunakan sebagai input model.y
: array yang berisi label atau target yang akan diprediksi oleh model.test_size
: proporsi data yang akan digunakan sebagai data uji. Nilai yang diberikan adalah 0.2, artinya data uji akan mencakup 20% dari seluruh data, sedangkan sisanya (80%) akan digunakan sebagai data latih.random_state
: nilai yang digunakan untuk menentukan bagaimana data akan dipisahkan menjadi data latih dan data uji. Jika nilai ini ditentukan, maka pembagian data akan selalu sama setiap kali fungsi ini dijalankan.
Setelah fungsi train_test_split
dijalankan, akan didapatkan empat variabel baru yaitu x_train
, x_test
, y_train
, dan y_test
. Masing-masing variabel tersebut berisi data latih atau data uji untuk fitur-fitur (x
) dan label (y
). Kemudian, ukuran dari masing-masing variabel tersebut dicetak dengan menggunakan perintah print dan f-string.
Kode di atas berguna untuk membagi data menjadi dua bagian yang akan digunakan dalam proses pelatihan model dan pengujian model. Data latih digunakan untuk mempelajari model agar dapat memprediksi label yang tepat sesuai dengan fitur-fitur yang diberikan. Sedangkan data uji digunakan untuk mengevaluasi seberapa baik model tersebut dapat memprediksi label pada data yang belum pernah dilihat sebelumnya.
Membuat Model¶
Selanjutnya kita akan membuat model untuk mengklasifikasikan antara dua kelas (0 dan 1, yang kita visualisasikan dengan dua warna berbeda).
Langkah yang kita lakukan:
1. Mempersiapkan device (accalerator)2. Mengkonstruksi model
3. Menentukan fungsi loss, optimizer, dan metrik evaluasi
4. Membuat training loop dan test loop
5. Melakukan training dan testing
Catatan Kita akan mengimpor
torch.nn
. Sebelumnya di bagian atas,torch
sudah diimpor sehingga tidak perlu ditulis ulang
Import torch.nn
¶
import torch.nn as nn
Catatan Pada bagian ini, apabila anda menggunakan google colab atau online notebook, jangan lupa untuk menyalakan GPU pada menu Runtime > Change runtime type > Hardware accelerator > GPU.
Menentukan dan Memeriksa Device yang Tersedia¶
if torch.cuda.is_available():
device = torch.device("cuda")
print("Tersedia GPU")
elif torch.backends.mps.is_available():
device = torch.device("mps")
print("Tersedia MPS Apple Silicon")
else:
device = torch.device("cpu")
print("Tersedia CPU")
Tersedia MPS Apple Silicon
Membuat Model¶
# 1. Buat class yang mewarisi dari nn.Module
class KlasifikasiSederhana(nn.Module):
# 2. `init` adalah fungsi yang akan dijalankan pertama kali saat objek dibuat
def __init__(self):
super().__init__()
self.input_features = 2
self.hidden_features = 5
self.output_features = 1
self.layer1 = nn.Linear(in_features=self.input_features, out_features=self.hidden_features)
self.layer2 = nn.Linear(in_features=self.hidden_features, out_features=self.output_features)
# 3. `forward` adalah fungsi yang akan dijalankan saat objek dipanggil
def forward(self, x):
y_hat = self.layer1(x)
y_hat = self.layer2(y_hat)
return y_hat
Penjelasan
nn.Module
adalah class yang digunakan untuk membuat model machine learning. Sederhananya, kamu akan selalu menggunakan nn.Module
untuk membuat model machine learning.
__init__
adalah fungsi yang akan dijalankan pertama kali saat objek dibuat. Fungsi ini akan menginisialisasi variabel yang akan digunakan dalam model.
Di dalam fungsi __init__
, kita akan memanggil fungsi super().__init__()
untuk menginisialisasi fungsi __init__
dari class nn.Module
. Fungsi super()
akan mengembalikan objek dari class induk (parent class) dari class yang sedang kita buat. Fungsi __init__
dari class nn.Module
akan menginisialisasi beberapa variabel yang akan digunakan dalam proses training model. Sederhananya, kita selalu memanggil fungsi super().__init__()
saat membuat class baru yang mewarisi dari class nn.Module
.
Kemudian, kita akan mendeklarasikan variabel input_features
, hidden_features
, dan output_features
. Variabel ini akan digunakan untuk menentukan jumlah fitur yang akan digunakan sebagai input, jumlah fitur yang akan digunakan sebagai hidden layer, dan jumlah fitur yang akan digunakan sebagai output. Pada kasus ini, kita akan menggunakan dua fitur sebagai input, lima fitur sebagai hidden layer, dan satu fitur sebagai output. Mengapa 2? Karena data kita memiliki dua fitur masukan (lihat shape dari x). Hidden layer di set 5 karena kita ingin menghasilkan 5 buah fitur baru yang akan digunakan sebagai input untuk output layer. Mengapa 1? Karena kita ingin menghasilkan satu nilai yang akan digunakan sebagai output. Nilai ini akan berupa 0 atau 1, yang akan digunakan untuk menentukan apakah titik tersebut berada di dalam lingkaran atau di luar lingkaran.
Kemudian, kita akan mendeklarasikan variabel layer1
dan layer2
. Variabel ini akan digunakan untuk menentukan arsitektur dari model. Pada kasus ini, kita akan menggunakan dua buah layer yang akan digunakan untuk menghasilkan output. Layer pertama akan mengubah input menjadi hidden layer, dan layer kedua akan mengubah hidden layer menjadi output. Fungsi nn.Linear
akan digunakan untuk membuat layer. Fungsi nn.Linear
memiliki dua parameter yaitu in_features
dan out_features
. in_features
digunakan untuk menentukan jumlah fitur yang akan digunakan sebagai input, dan out_features
digunakan untuk menentukan jumlah fitur yang akan digunakan sebagai output.
forward
adalah fungsi yang akan dijalankan saat objek dipanggil. Fungsi ini akan mengembalikan nilai output dari model. Pada kasus ini, kita akan mengembalikan nilai output dari layer kedua.
Selanjutnya, kita akan membuat instance dari class Model
yang kita buat. Instance ini akan disimpan dalam variabel model
.
model_0.to(device)
digunakan untuk mengirim model ke device yang kita gunakan. Pada kasus ini, kita akan mengirim model ke GPU. Jika kita menggunakan CPU, kita tidak perlu menuliskan kode ini.
Catatan Untuk memvisualisasikan layer-layer pada model yang kita buat, kamu dapat mencoba visualisasi interaktif yang disediakan oleh 'framework tetangga' yaitu Tensorflow pada tautan berikut ini
Memuat Instance Model dan Memindahkannya ke Device¶
model_0 = KlasifikasiSederhana()
model_0.to(device)
KlasifikasiSederhana( (layer1): Linear(in_features=2, out_features=5, bias=True) (layer2): Linear(in_features=5, out_features=1, bias=True) )
Memeriksa Parameter Model dengan State Dict¶
Parameter dari model adalah variabel-variabel yang terkait dengan model. Pada kasus ini, parameter dari model adalah variabel-variabel yang terkait dengan layer-layer pada model. Pada kasus ini, kita memiliki dua buah layer, sehingga kita memiliki dua buah parameter. Parameter dari layer pertama disimpan dalam variabel layer1.weight
dan layer1.bias
. Parameter dari layer kedua disimpan dalam variabel layer2.weight
dan layer2.bias
.
Mari kita lihat nilai dari parameter-parameter tersebut.
# memeriksa parameter dengan state_dict
print(model_0.state_dict())
OrderedDict([('layer1.weight', tensor([[-0.1365, -0.2773], [-0.0544, 0.5842], [-0.2116, -0.3452], [ 0.2950, 0.5274], [-0.1983, -0.4617]], device='mps:0')), ('layer1.bias', tensor([-0.0412, -0.0177, -0.0247, 0.4988, -0.6339], device='mps:0')), ('layer2.weight', tensor([[-0.0726, 0.1522, -0.0905, -0.0695, 0.0399]], device='mps:0')), ('layer2.bias', tensor([-0.2607], device='mps:0'))])
/Users/martinmanullang/miniconda3/envs/py38_mps/lib/python3.8/site-packages/torch/_tensor_str.py:115: UserWarning: The operator 'aten::nonzero' is not currently supported on the MPS backend and will fall back to run on the CPU. This may have performance implications. (Triggered internally at /Users/runner/work/_temp/anaconda/conda-bld/pytorch_1670525682339/work/aten/src/ATen/mps/MPSFallback.mm:11.) nonzero_finite_vals = torch.masked_select(
Perlu diketahui bahwa weight dan bias, sebelum di training akan memiliki nilai acak. Jika kamu ingin menentukan nilai awal dari weight dan bias, kamu dapat menambahkan parameter weight
dan bias
pada saat membuat layer. Kita akan bahas ini belakangan.
Selain itu, jumlah node yang ada (yang ditentukan berdasarkan in_features
dan out_features
), masing-masing akan memiliki weight dan biasnya sendiri (sebagaimana yang kita pelajari di modul 01)
Mencoba Melakukan Forward Pass Tanpa Training¶
# Forward pass tanpa training
with torch.inference_mode():
x_test = x_test.to(device)
y_test = y_test.to(device)
prediksi_nontrain = model_0(x_test)
print(f"10 Data Pertama Hasil Prediksi: \n{prediksi_nontrain[:10].T}")
print(f"10 Data Pertama Hasil Prediksi (Pembulatan): \n{torch.round(prediksi_nontrain[:10].T)}")
print(f"Nilai seharusnya (Label): \n{y_test[:10]}")
print(f"Keterangan tepat / salah: \n{torch.round(prediksi_nontrain[:10].T) == y_test[:10]}")
10 Data Pertama Hasil Prediksi: tensor([[-0.3148, -0.2500, -0.2402, -0.3215, -0.3590, -0.3223, -0.2923, -0.2459, -0.3426, -0.2981]], device='mps:0') 10 Data Pertama Hasil Prediksi (Pembulatan): tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], device='mps:0') Nilai seharusnya (Label): tensor([1., 1., 1., 1., 1., 0., 0., 0., 0., 0.], device='mps:0') Keterangan tepat / salah: tensor([[False, False, False, False, False, True, True, True, True, True]], device='mps:0')
Wajar saja jika hasil dari forward pass ini tidak bagus. Karena kita belum melakukan training pada model. Selanjutnya kita akan menentukan fungsi loss dan optimizer yang akan digunakan.
Bagaimana model memprediksi?
Model melakukan forward pass dengan menggunakan persamaan berikut:
$$ \mathbf{y} = x \cdot \mathbf{Weights}^T + \mathbf{bias} $$Persamaan tersebut adalah persamaan linear. Persamaan linear menghitung nilai output dengan menggunakan nilai input, weight, dan bias. Pada kasus ini, nilai input adalah x
, weight adalah layer1.weight
dan layer2.weight
, dan bias adalah layer1.bias
dan layer2.bias
. Ingat, seperti yang dibahas sebelumnya, weight dan bias ini akan diubah nilainya selama proses training, dan pada tahap ini nilainya masih ngaco, jadi wajar saja kalau prediksinya keliru.
Loss Function dan Optimizer¶
Catatan Loss function juga kerap disebut sebagai cost function atau objective function.
loss_fn = nn.BCEWithLogitsLoss()
opt = torch.optim.SGD(model_0.parameters(), lr=0.1)
Penjelasan:
nn.BCEWithLogitsLoss()
adalah fungsi yang digunakan untuk menghitung loss. Fungsi ini akan menghitung loss dengan menggunakan Binary Cross Entropy. Binary Cross Entropy adalah fungsi loss yang digunakan untuk kasus klasifikasi biner. Fungsi ini akan menghitung loss dengan menggunakan logit dari output. Logit adalah nilai yang belum di transformasi dengan fungsi sigmoid. Fungsi sigmoid akan mengubah nilai menjadi nilai antara 0 dan 1.opt = torch.optim.SGD(model_0.parameters(), lr=0.1)
adalah fungsi yang digunakan untuk menghitung gradient dari loss function. Fungsi ini akan menghitung gradient dengan menggunakan algoritma Stochastic Gradient Descent (SGD). SGD adalah algoritma yang digunakan untuk melakukan update parameter dari model. Parameter dari model akan di update dengan cara mengurangi nilai dari gradient dengan learning rate yang kita tentukan. Learning rate adalah nilai yang digunakan untuk menentukan seberapa besar nilai yang akan dikurangi dari nilai gradient. Semakin besar nilai dari learning rate, maka nilai yang akan dikurangi dari nilai gradient akan semakin besar pula. Semakin kecil nilai dari learning rate, maka nilai yang akan dikurangi dari nilai gradient akan semakin kecil pula.
Catatan Ingat, loss function digunakan supaya model mengetahui seberapa besar kesalahan yang dilakukan pada prediksi.
Catatan Apa itu Logits? Logits adalah nilai yang belum di transformasi dengan fungsi sigmoid. Fungsi sigmoid akan mengubah nilai menjadi nilai antara 0 dan 1. Jadi, sederhananya, BCEWithLogitsLoss() adalah fungsi BCE yang sudah di transformasi dengan fungsi sigmoid.
Contoh beberapa loss function
|Loss Function|Cocok Untuk|Penjelasan|
|:-|:-|:-|
|nn.BCEWithLogitsLoss()
|Klasifikasi biner|Binary Cross Entropy|
|nn.CrossEntropyLoss()
|Klasifikasi multi kelas|Cross Entropy|
|nn.MSELoss()
|Regresi|Mean Squared Error|
|nn.L1Loss()
|Regresi|Mean Absolute Error|
Contoh beberapa optimizer
Optimizer | Penjelasan |
---|---|
torch.optim.SGD() |
Stochastic Gradient Descent |
torch.optim.Adam() |
Adaptive Moment Estimation |
torch.optim.Adagrad() |
Adaptive Gradient Algorithm |
torch.optim.RMSprop() |
Root Mean Square Propagation |
Metrik Evaluasi¶
Metrik evaluasi adalah suatu fungsi / cara untuk mengukur seberapa baik model. Pada modul ini kita akan membuat metrik evaluasi secara manual.
def metrik_akurasi(prediksi, label):
prediksi_benar = torch.eq(prediksi, label).sum().item()
akurasi = prediksi_benar / len(prediksi) * 100
return akurasi
Penjelasan:
torch.eq(prediksi, label)
adalah fungsi yang digunakan untuk membandingkan nilai dari prediksi dengan nilai dari label. Fungsi ini akan mengembalikan nilaiTrue
jika nilai dari prediksi sama dengan nilai dari label. Fungsi ini akan mengembalikan nilaiFalse
jika nilai dari prediksi tidak sama dengan nilai dari label.- Selanjutnya, akurasi akan dihitung dengan cara menghitung jumlah nilai
True
yang ada pada hasil perbandingan nilai dari prediksi dengan nilai dari label. Hasil dari perbandingan ini akan disimpan dalam variabelprediksi_benar
. Kemudian, nilai dariprediksi_benar
akan dibagi dengan jumlah nilai dari prediksi. Hasil dari pembagian ini akan dikalikan dengan 100. Hasil dari perkalian ini akan disimpan dalam variabelakurasi
. Nilai dariakurasi
akan di return.
Training Loop¶
# Mengatur seed agar hasilnya bisa direproduksi
torch.manual_seed(2022)
torch.cuda.manual_seed(2022)
# Menentukan banyaknya epoch
epochs = 100
# Memindahkan data ke device
x_train = x_train.to(device)
y_train = y_train.to(device)
# Training Loop
for epoch in range(epochs):
model_0.train()
# forward pass
y_logits = model_0(x_train).squeeze()
y_pred = torch.round(torch.sigmoid(y_logits))
# menghitung loss
loss = loss_fn(y_logits, y_train)
# menghitung akurasi
accuracy = metrik_akurasi(y_pred, y_train)
# optimizer zero grad
opt.zero_grad()
# backward pass
loss.backward()
# update parameter
opt.step()
# validasi
with torch.inference_mode():
model_0.eval()
# forward pass
y_logits = model_0(x_test).squeeze()
y_pred = torch.round(torch.sigmoid(y_logits))
# menghitung loss dan akurasi
val_loss = loss_fn(y_logits, y_test)
val_accuracy = metrik_akurasi(y_pred, y_test)
# print hasil setiap 20 epoch
if (epoch + 1) % 10 == 0:
print(f"Epoch: {epoch + 1} | Loss: {loss.item():.4f} | Akurasi: {accuracy:.2f}% | Val Loss: {val_loss.item():.4f} | Val Akurasi: {val_accuracy:.2f}%")
Epoch: 10 | Loss: 0.7001 | Akurasi: 49.85% | Val Loss: 0.6975 | Val Akurasi: 50.60% Epoch: 20 | Loss: 0.6969 | Akurasi: 49.85% | Val Loss: 0.6950 | Val Akurasi: 50.60% Epoch: 30 | Loss: 0.6955 | Akurasi: 41.95% | Val Loss: 0.6940 | Val Akurasi: 46.60% Epoch: 40 | Loss: 0.6948 | Akurasi: 46.30% | Val Loss: 0.6935 | Val Akurasi: 50.40% Epoch: 50 | Loss: 0.6944 | Akurasi: 47.90% | Val Loss: 0.6933 | Val Akurasi: 49.40% Epoch: 60 | Loss: 0.6942 | Akurasi: 48.75% | Val Loss: 0.6932 | Val Akurasi: 47.60% Epoch: 70 | Loss: 0.6940 | Akurasi: 49.15% | Val Loss: 0.6931 | Val Akurasi: 47.20% Epoch: 80 | Loss: 0.6938 | Akurasi: 49.10% | Val Loss: 0.6930 | Val Akurasi: 47.40% Epoch: 90 | Loss: 0.6937 | Akurasi: 49.15% | Val Loss: 0.6930 | Val Akurasi: 47.40% Epoch: 100 | Loss: 0.6936 | Akurasi: 49.50% | Val Loss: 0.6930 | Val Akurasi: 47.00%
Analisa Hasil Training¶
Mengapa model tidak dapat belajar dengan baik?
Untuk menjawab pertanyaan di atas, kita akan melakukan visualisasi dari hasil prediksi model.
Supaya memperingkas program, saya sudah menyediakan helper_function.py
pada repo di github. Agar lebih memudahkan kita untuk mengunduhnya, saya akan menggunakan fungsi request sehingga file tersebut akan langsung terunduh ke dalam notebook.
# mengunduh helper function jika belum ada
import requests
from pathlib import Path
if Path("helper_function.py").is_file():
print("helper_function.py sudah ada")
else:
print("Mengunduh helper_function.py")
request = requests.get("https://raw.githubusercontent.com/mctosima/belajarpytorch/main/docs/helper_function.py")
with open("helper_function.py", "wb") as f:
f.write(request.content)
try:
from helper_function import plot_decision_boundary, plot_predictions
except:
print("Gagal mengunduh helper_function.py")
helper_function.py sudah ada
# visualisasi hasil prediksi
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.title("Visualisasi Prediksi - Training")
plot_decision_boundary(model_0, x_train, y_train)
plt.subplot(1, 2, 2)
plt.title("Visualisasi Prediksi - Test")
plot_decision_boundary(model_0, x_test, y_test)
Nah dari gambar di atas dapat dilihat bahwa model tidak dapat belajar dengan baik. Terlihat dari hasil visualisasi, model hanya membagi area data menjadi dua bagian saja dengan sebuah garis linear. Padahal titik warna merah dan biru tidak semudah itu diklasifikasi.
Lalu bagaimana solusinya?
Kita perlu memperbaikinya. Beberapa caranya adalah:
- Menambahkan layer pada model atau menambahkan hidden unit
- Menambah jumlah epochs
- Mengubah hyperparameter seperti optimizer dan loss function
Memperbaiki Model¶
Modifikasi Model¶
# membuat instance dari class KlasifikasiSederhanaV2
model_1 = nn.Sequential(
nn.Linear(in_features=2, out_features=10),
nn.Linear(in_features=10, out_features=10),
nn.Linear(in_features=10, out_features=1)
).to(device)
loss_fn = nn.L1Loss()
opt = torch.optim.SGD(model_1.parameters(), lr=0.1)
epochs = 1000
# Training Loop
for epoch in range(epochs):
model_1.train()
# forward pass
y_pred = model_1(x_train).squeeze()
# menghitung loss
loss = loss_fn(y_pred, y_train)
# menghitung akurasi
# optimizer zero grad
opt.zero_grad()
# backward pass
loss.backward()
# update parameter
opt.step()
# validasi
with torch.inference_mode():
model_1.eval()
# forward pass
y_pred = model_1(x_test).squeeze()
# menghitung loss dan akurasi
val_loss = loss_fn(y_pred, y_test)
# print hasil setiap 20 epoch
if (epoch + 1) % 100 == 0:
print(f"Epoch: {epoch + 1} | Loss: {loss.item():.4f} | Val Loss: {val_loss.item():.4f}")
Epoch: 100 | Loss: 0.5003 | Val Loss: 0.5002 Epoch: 200 | Loss: 0.5000 | Val Loss: 0.5013 Epoch: 300 | Loss: 0.4998 | Val Loss: 0.5023 Epoch: 400 | Loss: 0.4995 | Val Loss: 0.5034 Epoch: 500 | Loss: 0.4994 | Val Loss: 0.5043 Epoch: 600 | Loss: 0.4993 | Val Loss: 0.5044 Epoch: 700 | Loss: 0.4993 | Val Loss: 0.5045 Epoch: 800 | Loss: 0.4992 | Val Loss: 0.5047 Epoch: 900 | Loss: 0.4992 | Val Loss: 0.5048 Epoch: 1000 | Loss: 0.4991 | Val Loss: 0.5049