pip install torch torchvision
Modul 9 Praktikum Sains Data: Pengantar PyTorch
Modul 9 Praktikum Sains Data: Pengantar PyTorch
Kembali ke Sains Data
Ini adalah pertemuan terakhir praktikum Sains Data tahun ini.
Di dua pertemuan sebelumnya, kita sudah membahas tentang neural network dan deep learning dengan TensorFlow dan Keras. Dari segi materi mata kuliah Sains Data, sebenarnya kita sudah selesai.
Namun, di awal Modul 7, telah disebutkan bahwa ada dua framework utama yang umum digunakan untuk deep learning di Python, yaitu TensorFlow (dengan Keras) dan PyTorch.
Agar wawasan kita lebih luas dan tidak terbatas satu framework saja, tidak ada salahnya kita coba mempelajari PyTorch juga :)
Lagipula, untuk riset/penelitian di dunia deep learning, saat ini PyTorch jauh lebih sering digunakan dibandingkan TensorFlow:
Gambar tren November 2014 hingga April 2024: dari semua implementasi atau kode yang telah dibuat untuk paper deep learning, berapa persen menggunakan framework tertentu. Pada April 2024, persentase PyTorch lebih dari 50%, sedangkan persentase TensorFlow kurang dari 10%.
Kalian bisa explore di link berikut: https://paperswithcode.com/trends
Apabila sewaktu-waktu kalian ingin menjalani skripsi atau semacamnya di dunia deep learning, atau setidaknya ingin membaca riset terbaru, kami harap wawasan tentang PyTorch ini bermanfaat.
Kami sengaja mengajarkan TensorFlow dan Keras terlebih dahulu, baru mengajarkan PyTorch, karena penyusunan model di PyTorch mirip dengan penggunaan subclassing API di Keras.
Sebelum kita mulai, instal terlebih dahulu PyTorch, dengan menginstal torch
dan torchvision
torch
adalah PyTorch.torchvision
menyediakan fitur-fitur yang membantu ketika berurusan dengan gambar, bahkan seperti sudah menjadi bagian yang tidak terpisahkan daritorch
.Sebenarnya ada juga
torchaudio
yang membantu ketika berurusan dengan data suara. Boleh saja kalian instal juga:pip install torch torchvision torchaudio
Terkait penginstalan PyTorch, kalian bisa membaca lebih lanjut di sini: https://pytorch.org/get-started/locally/
Kalau sudah instal, jangan lupa import:
import torch, torchvision
Kita bisa lihat versinya (mungkin di kalian akan lebih baru):
print(torch.__version__)
2.1.0
print(torchvision.__version__)
0.16.0
Jangan lupa import juga library yang biasa kita gunakan:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
Mengenal PyTorch
CPU, GPU, dan device-agnostic code
Tiap komputer bisa memiliki kekuatan yang berbeda, termasuk ada/tiadanya komponen yang bernama GPU.
Tiap komputer pasti punya yang namanya CPU atau Central Processing Unit, yang biasanya menjadi pusat segala komputasi di komputer itu, sesuai namanya.
Namun, untuk perhitungan dengan matriks, tensor atau semacamnya, GPU atau Graphics Processing Unit lebih cepat. Perhitungan seperti itu biasa dilakukan untuk tampilan atau graphics ketika sedang bermain game, dan juga biasa dilakukan ketika berurusan dengan neural network.
Sehingga, daripada menggunakan CPU, ada baiknya menggunakan GPU, kalau ada.
Di Google Colaboratory, di menu Runtime > Change runtime type
, di bagian hardware accelerator, kalian bisa mengubah setting, apakah ingin menggunakan CPU atau GPU. (Bahkan, ada juga TPU atau tensor processing unit yang sepertinya lebih dikhususkan lagi untuk komputasi dengan tensor.)
Apapun yang tersedia, tiap kali kita ingin menggunakan PyTorch, sebaiknya kita beritahu, mana yang ingin kita gunakan. Agar tidak berantakan, caranya bisa dengan kode berikut:
# Setup device-agnostic code
if torch.cuda.is_available():
= "cuda" # NVIDIA GPU
device elif torch.backends.mps.is_available():
= "mps" # Apple GPU
device else:
= "cpu" # Defaults to CPU if NVIDIA GPU/Apple GPU aren't available
device
print(f"Using device: {device}")
Using device: cuda
Sumber kode: https://www.learnpytorch.io/pytorch_cheatsheet/#device-agnostic-code-using-pytorch-on-cpu-gpu-or-mps
Output nya akan sesuai dengan pilihan terbaik yang ada, antara CPU atau GPU.
Kode di atas sebenarnya hanya menyimpan pilihan tersebut sebagai string ke dalam variabel device
. Namun, variabel ini nantinya akan digunakan selama berurusan dengan PyTorch.
Sehingga, apabila kita ingin ganti dari CPU ke GPU atau sebaliknya, kita tinggal mengubah isi variabel device
ini (misalnya menggunakan kode di atas), tidak perlu mengubah kode PyTorch yang sudah kita buat.
Dengan demikian, kode PyTorch yang sudah kita buat menjadi tidak tergantung device yang digunakan (apakah CPU atau GPU), atau disebut device-agnostic.
Tensor
Seperti di TensorFlow, di PyTorch juga ada tensor. Cara membuatnya adalah dengan torch.tensor
Contohnya, skalar:
= torch.tensor(1.5) tensor0
print(tensor0)
tensor(1.5000)
print(type(tensor0))
<class 'torch.Tensor'>
Kita bisa melihat dimensinya (rank/dimensi tensor) melalui .ndim
print(tensor0.ndim)
0
Tipe datanya berupa torch.tensor
tapi kita bisa memperoleh nilai skalarnya dengan .item()
tensor0.item()
1.5
type(tensor0.item())
float
Contoh lain, array/vektor:
= torch.tensor([2.31, 4.567, 8.9]) tensor1
print(tensor1)
tensor([2.3100, 4.5670, 8.9000])
print(tensor1.ndim)
1
Selain dimensi tensor, ada juga .shape
yang di sini melambangkan ukuran array/vektor, seperti .shape
di numpy
print(tensor1.shape)
torch.Size([3])
Nilai suatu elemen di tensor juga bisa diubah:
0] = 52.5 tensor1[
print(tensor1)
tensor([52.5000, 4.5670, 8.9000])
Ada juga tipe data:
print(tensor1.dtype)
torch.float32
Seperti tensor di TensorFlow, tensor di PyTorch juga biasanya menggunakan bilangan float yang 32-bit daripada 64-bit.
Contoh lagi, matriks:
= torch.tensor([
mat1 1, 2.718, 3.14],
[4, 5, 6.28]
[ ])
print(mat1)
tensor([[1.0000, 2.7180, 3.1400],
[4.0000, 5.0000, 6.2800]])
print(mat1.ndim)
2
print(mat1.shape)
torch.Size([2, 3])
Kita bisa buat matriks kedua, lalu mengalikannya dengan perkalian matriks menggunakan torch.matmul
= torch.tensor([
mat2 9, 8],
[7, 6],
[5, 4.3]
[ ])
print(mat2.ndim)
print(mat2.shape)
print(mat2.dtype)
2
torch.Size([3, 2])
torch.float32
= torch.matmul(mat1, mat2) mat3
print(mat3)
tensor([[ 43.7260, 37.8100],
[102.4000, 89.0040]])
Matriks juga bisa ditranspos dengan .T
= mat3.T
mat4 print(mat4)
tensor([[ 43.7260, 102.4000],
[ 37.8100, 89.0040]])
Konversi dari/ke numpy
Kita bisa mengonversi tensor PyTorch menjadi array numpy dan sebaliknya:
.numpy()
untuk mengubah suatu tensor PyTorch menjadi array numpytorch.from_numpy(...)
untuk mengubah suatu array numpy menjadi tensor PyTorch
= mat4.numpy()
arr5 print(arr5)
print(type(arr5))
print(arr5.dtype)
[[ 43.725998 102.4 ]
[ 37.809998 89.004 ]]
<class 'numpy.ndarray'>
float32
= torch.from_numpy(arr5)
mat6 print(mat6)
print(type(mat6))
print(mat6.dtype)
tensor([[ 43.7260, 102.4000],
[ 37.8100, 89.0040]])
<class 'torch.Tensor'>
torch.float32
ones, zeros, rand, randn
Kita bisa membuat tensor berisi satu semua, atau nol semua, atau nilai random, dengan ukuran size
yang bisa kita tentukan
= torch.ones(size = (3,4))
x print(x)
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
= torch.zeros(size = (3,4))
x print(x)
tensor([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
= torch.rand(size = (3,4))
x print(x)
tensor([[0.0698, 0.2004, 0.3279, 0.2525],
[0.2461, 0.4263, 0.4870, 0.4371],
[0.9545, 0.7487, 0.3990, 0.0412]])
torch.rand
memilih nilai secara random dari distribusi uniform pada interval \([0,1)\)
Untuk memilih nilai secara random dari distribusi normal (dengan \(\mu = 0\) dan \(\sigma = 1\)), gunakan torch.randn
= torch.randn(size = (3,4))
x print(x)
tensor([[ 1.5820, -0.1806, 0.1820, -0.6891],
[ 1.2161, -0.5755, -0.5172, -0.7454],
[ 0.3047, -0.5932, -0.3343, -1.1461]])
Operasi tensor seperti numpy
Secara umum, operasi tensor di PyTorch memang mirip dengan array di numpy, sebagaimana operasi tensor di TensorFlow juga mirip dengan array di numpy.
= 4 * torch.ones((2, 2))
a print(a)
tensor([[4., 4.],
[4., 4.]])
= torch.square(a)
b print(b)
tensor([[16., 16.],
[16., 16.]])
= torch.sqrt(a)
c print(c)
tensor([[2., 2.],
[2., 2.]])
= b + c
d print(d)
tensor([[18., 18.],
[18., 18.]])
# perkalian matriks
= torch.matmul(a, c)
e print(e)
tensor([[16., 16.],
[16., 16.]])
# perkalian per elemen
*= d
e print(e)
tensor([[288., 288.],
[288., 288.]])
# penjumlahan per elemen
= e + 2
f print(f)
tensor([[290., 290.],
[290., 290.]])
Autograd dengan .backward()
Sebagaimana ada automatic differentiation atau autodiff di TensorFlow, ada juga autograd di PyTorch. Namun, syntax nya cukup berbeda.
Caranya, inputnya kita jadikan tensor dengan parameter requires_grad=True
, lalu kita operasikan (misal dengan fungsi f) dan menghasilkan tensor baru, lalu di tensor baru ini kita panggil .backward()
agar turunan f dihitung dan disimpan di variabel input .grad
Contohnya, turunan \(x^3\) terhadap \(x\) di \(x=4\) adalah \(3(4)^2 = 48\).
# siapkan input, dengan requires_grad=True
= torch.tensor(4.0, requires_grad=True)
x
# hitung fungsi F
= x**3
F
# jalankan perhitungan turunan F (terhadap input)
F.backward()
# tampilkan turunan yang tersimpan di input .grad
print(x.grad)
tensor(48.)
Nama method nya adalah .backward()
dan memang digunakan di backward pass (akan kita lihat nanti).
Contoh lain, turunan \(3x^3 - y^2\) terhadap \(x\) dengan \(x=2\) adalah \(9(2)^2 = 36\), dan turunannya terhadap \(y\) dengan \(y=6\) adalah \(-2(6) = -12\)
= torch.tensor(2.0, requires_grad=True)
x = torch.tensor(6.0, requires_grad=True)
y
= 3 * x**3 - y**2
F
F.backward()
print(x.grad)
print(y.grad)
tensor(36.)
tensor(-12.)
Klasifikasi biner dengan perceptron
Persiapan data, TensorData
, DataLoader
Untuk model pertama kita, mari kita coba buat kembali model perceptron untuk klasifikasi biner yang pernah kita buat di Modul 7, dengan dataset titik_negatif_positif.csv
yang sama, bisa kalian download dari GitHub Pages ini: titik_negatif_positif.csv
Kita buka datasetnya, jangan lupa dengan dtype="float32"
karena itulah tipe data yang biasa digunakan oleh PyTorch, seperti TensorFlow:
= pd.read_csv("./titik_negatif_positif.csv", dtype="float32") titik_df
titik_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
Memisahkan antara inputs (prediktor) dan targets (variabel target):
= titik_df.drop(columns=["kelas"])
titik_inputs_df = titik_df[["kelas"]] titik_targets_df
titik_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
titik_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
Mengubahnya menjadi array numpy:
= titik_inputs_df.to_numpy()
titik_inputs_arr = titik_targets_df.to_numpy() titik_targets_arr
Kemudian mengubahnya menjadi tensor PyTorch:
= torch.from_numpy(titik_inputs_arr)
titik_inputs_tensor = torch.from_numpy(titik_targets_arr) titik_targets_tensor
Tujuan mengubah dari DataFrame menjadi tensor, tentunya supaya bisa diproses dengan PyTorch.
Kita bisa periksa, bentuknya sesuai:
print(titik_inputs_tensor)
tensor([[ 1.1734, 4.5706],
[ 0.1960, 3.5046],
[ 0.1214, 2.1638],
...,
[ 2.1099, -0.3825],
[ 4.1787, 0.4862],
[ 2.3264, 1.2282]])
print(titik_targets_tensor)
tensor([[0.],
[0.],
[0.],
...,
[1.],
[1.],
[1.]])
Kemudian, kita bisa melakukan train-validation-test split, misalnya dengan rasio 80:10:10
from sklearn.model_selection import train_test_split
# data utuh menjadi data train 80% dan data "test" 20%
= train_test_split(
titik_X_train, titik_X_test, titik_y_train, titik_y_test =0.2, random_state=42
titik_inputs_tensor, titik_targets_tensor, test_size
)
# data "test" dibagi dua, menjadi data validation dan data test sesungguhnya
= train_test_split(
titik_X_val, titik_X_test, titik_y_val, titik_y_test =0.5, random_state=42
titik_X_test, titik_y_test, test_size )
Kita bisa periksa ukurannya, rasionya sudah sesuai yang kita tetapkan:
print(titik_X_train.shape)
print(titik_X_val.shape)
print(titik_X_test.shape)
torch.Size([1600, 2])
torch.Size([200, 2])
torch.Size([200, 2])
Sebelum dataset kita benar-benar siap untuk diproses dengan PyTorch, ada dua hal yang perlu kita lakukan:
mengubahnya menjadi objek
Dataset
denganTensorDataset
Dataset
di sini adalah format dataset umum yang dikenal oleh PyTorch.mengubah objek
Dataset
menjadi objekDataLoader
DataLoader
membuat objekDataset
menjadi “iterable” yaitu bisa diiterasikan dengan for loop, agar bisa digunakan dalam proses training maupun testing.
Baik TensorDataset
maupun DataLoader
bisa kita import dari torch.utils.data
from torch.utils.data import TensorDataset, DataLoader
Masing-masing dari data train, data validation, dan data test bisa kita ubah menjadi objek Dataset
dengan menentukan mana prediktor dan mana target:
= TensorDataset(titik_X_train, titik_y_train)
titik_train_dataset = TensorDataset(titik_X_val, titik_y_val)
titik_val_dataset = TensorDataset(titik_X_test, titik_y_test) titik_test_dataset
Kemudian, masing-masing bisa kita ubah menjadi DataLoader
, sekasligus menentukan batch size, dan juga menentukan apakah perlu ada shuffling di tiap epoch (biasanya dilakukan di data train untuk mengurangi overfitting):
= DataLoader(titik_train_dataset, batch_size=32, shuffle=True)
titik_train_dataloader = DataLoader(titik_val_dataset, batch_size=32, shuffle=False)
titik_val_dataloader = DataLoader(titik_test_dataset, batch_size=32, shuffle=False) titik_test_dataloader
Menyusun model
Ingat bahwa perceptron yang ingin kita susun hanya terdiri dari
input layer dengan dua neuron,
tidak ada hidden layer,
output layer dengan satu neuron dan fungsi aktivasi sigmoid
Sehingga matriks bobot yang sesuai berukuran \(2 \times 1\), dan vektor bias yang sesuai berukuran \(1 \times 1\).
Di PyTorch, tiap model berupa class dengan ketentuan:
meng-inherit dari
torch.nn.Module
(yaitu base class untuk semua model PyTorch)baris pertama di constructor
__init__
adalahsuper().__init__()
harus mendefinisikan
forward()
untuk forward passkomponen/variabel/atribut/parameter yang diperlukan biasanya didefinisikan di constructor
Catatan: parameter biasa didefinisikan dengan
torch.nn.Parameter
daripadatorch.tensor
Ini cukup mirip dengan subclassing API di Keras, yang meng-inherit dari keras.Model
dan harus mendefinisikan call()
untuk forward pass.
Mari kita susun perceptron kita, yang memiliki matriks bobot, vektor bias, dan fungsi aktivasi sigmoid:
class MyPerceptron(torch.nn.Module):
def __init__(self):
super().__init__()
self.weights = torch.nn.Parameter(
= (2, 1), dtype = torch.float32),
torch.randn(size =True # agar autgrad aktif
requires_grad
)
self.bias = torch.nn.Parameter(
= (1,), dtype = torch.float32),
torch.randn(size =True
requires_grad
)
def forward(self, x):
return torch.sigmoid(
self.weights) + self.bias
torch.matmul(x, )
Kita bisa buat suatu instance atau objek dari class di atas:
= MyPerceptron() pisah_titik
Ini adalah model yang siap di-train. Sebelum training, kita bisa memeriksa parameter model (saat ini masih random sesuai torch.randn
):
list(pisah_titik.parameters())
[Parameter containing:
tensor([[-0.8412],
[-1.1300]], requires_grad=True),
Parameter containing:
tensor([-0.0640], requires_grad=True)]
Mirip, ada juga yang namanya .state_dict()
pisah_titik.state_dict()
OrderedDict([('weights',
tensor([[-0.8412],
[-1.1300]])),
('bias', tensor([-0.0640]))])
Menyiapkan hyperparameter
Untuk klasifikasi, loss function yang biasa digunakan adalah crossentropy loss, atau mungkin lebih spesifiknya binary crossentropy loss, yang disebut torch.nn.BCELoss
di PyTorch. Kita bisa menyiapkannya sebagai objek.
= torch.nn.BCELoss() titik_loss_fn
Serupa, optimizer juga disiapkan sebagai objek, misalnya torch.optim.SGD
untuk SGD (stochastic gradient descent):
= torch.optim.SGD(params=pisah_titik.parameters(), # merujuk ke parameter model
titik_opt =0.01) # learning rate lr
Training loop
Mungkin agak mengejutkan: di PyTorch, kita dibebaskan untuk membuat training loop sendiri. Artinya, tidak ada fungsi .fit()
yang tinggal kita panggil; kita perlu menyusun for loop sendiri, seperti di Modul 7 :)
Ingat kembali, tiap iterasi (yaitu per batch) di proses training melibatkan:
Forward pass: menghitung nilai output (hasil prediksi) dari input (batch).
Menghitung loss antara hasil prediksi dan nilai sebenarnya
Backpropagation: menghitung gradien dari loss terhadap tiap parameter
Update optimizer: memperbarui nilai parameter berdasarkan gradien dari loss, menggunakan rumus atau optimizer yang dipilih
Ingat juga, selain training, ada juga yang namanya validation (atau terkadang disebut testing di beberapa sumber). Tahap validation ini menguji model di akhir tiap epoch, setelah semua batch melalui proses training. Langkahnya terdiri dari:
Forward pass
Menghitung loss dan/atau accuracy antara hasil prediksi dan nilai sebenarnya
Perhatikan bahwa validation juga bisa dilakukan per batch, sehingga kedua langkah di atas dilakukan per batch. Kalau begitu, nilai akhir untuk loss dan/atau accuracy menjadi rata-rata dari nilainya di tiap batch.
Penjelasan di atas menjadi kode PyTorch sebagai berikut:
= 10
epochs
= []
train_loss_list = []
val_loss_list
# untuk tiap epoch,
for epoch in range(epochs):
# siapkan model untuk tahap training
pisah_titik.to(device)
pisah_titik.train()= 0
sum_train_loss
# untuk tiap batch,
for X, y in titik_train_dataloader:
= X.to(device)
X = y.to(device)
y
# Forward pass
= pisah_titik(X)
y_pred
# Hitung loss
= titik_loss_fn(y_pred, y)
loss += loss
sum_train_loss
# "Optimizer zero grad": nolkan dulu hasil hitung gradien
titik_opt.zero_grad()
# Backpropagation
loss.backward()
# Update optimizer, juga disebut "optimizer step"
titik_opt.step()
# hitung rata-rata train loss berdasarkan banyaknya batch
= sum_train_loss / len(titik_train_dataloader)
avg_train_loss
# simpan train loss
train_loss_list.append(avg_train_loss)
# siapkan model untuk tahap validaion
pisah_titik.to(device)eval()
pisah_titik.= 0
sum_val_loss
# selalu digunakan ketika hendak testing
with torch.inference_mode():
# untuk tiap batch,
for X, y in titik_val_dataloader:
= X.to(device)
X = y.to(device)
y
# Forward pass
= pisah_titik(X)
y_pred
# Hitung loss
+= titik_loss_fn(y_pred, y)
sum_val_loss
# hitung rata-rata val loss berdasarkan banyaknya batch
= sum_val_loss / len(titik_val_dataloader)
avg_val_loss
# simpan val loss
val_loss_list.append(avg_val_loss)
# tampilkan train loss dan val loss untuk epoch ini
print(f"Epoch: {epoch} | avg train loss: {avg_train_loss} | avg val loss: {avg_val_loss}")
Epoch: 0 | avg train loss: 1.0023550987243652 | avg val loss: 0.6549612879753113
Epoch: 1 | avg train loss: 0.424757719039917 | avg val loss: 0.3022421300411224
Epoch: 2 | avg train loss: 0.2212503552436828 | avg val loss: 0.1808699667453766
Epoch: 3 | avg train loss: 0.1497089713811874 | avg val loss: 0.1308697611093521
Epoch: 4 | avg train loss: 0.1175468564033508 | avg val loss: 0.1053687334060669
Epoch: 5 | avg train loss: 0.0995666906237602 | avg val loss: 0.0898655503988266
Epoch: 6 | avg train loss: 0.0879358351230621 | avg val loss: 0.0794256925582885
Epoch: 7 | avg train loss: 0.0796592533588409 | avg val loss: 0.0717914551496505
Epoch: 8 | avg train loss: 0.0733261257410049 | avg val loss: 0.0658656656742096
Epoch: 9 | avg train loss: 0.0682612806558609 | avg val loss: 0.061109509319067
Perhatikan bahwa hasil loss di tiap epoch sudah kita simpan ke dalam list.
print(train_loss_list)
print(val_loss_list)
[tensor(1.0024, grad_fn=<DivBackward0>), tensor(0.4248, grad_fn=<DivBackward0>), tensor(0.2213, grad_fn=<DivBackward0>), tensor(0.1497, grad_fn=<DivBackward0>), tensor(0.1175, grad_fn=<DivBackward0>), tensor(0.0996, grad_fn=<DivBackward0>), tensor(0.0879, grad_fn=<DivBackward0>), tensor(0.0797, grad_fn=<DivBackward0>), tensor(0.0733, grad_fn=<DivBackward0>), tensor(0.0683, grad_fn=<DivBackward0>)]
[tensor(0.6550), tensor(0.3022), tensor(0.1809), tensor(0.1309), tensor(0.1054), tensor(0.0899), tensor(0.0794), tensor(0.0718), tensor(0.0659), tensor(0.0611)]
Maka, kita bisa ubah jadi DataFrame lalu menyimpannya sebagai CSV:
= pd.DataFrame(
titik_loss_df
[for loss in train_loss_list],
[loss.item() for loss in val_loss_list]
[loss.item()
],={0: "train_loss", 1: "val_loss"}) ).transpose().rename(columns
titik_loss_df
train_loss | val_loss | |
---|---|---|
0 | 1.002355 | 0.654961 |
1 | 0.424758 | 0.302242 |
2 | 0.221250 | 0.180870 |
3 | 0.149709 | 0.130870 |
4 | 0.117547 | 0.105369 |
5 | 0.099567 | 0.089866 |
6 | 0.087936 | 0.079426 |
7 | 0.079659 | 0.071791 |
8 | 0.073326 | 0.065866 |
9 | 0.068261 | 0.061110 |
"./pytorch_titik_loss_df.csv", index=False) titik_loss_df.to_csv(
Kalau mau menyamakan dengan modul, kalian bisa download melalui GitHub Pages ini: pytorch_titik_loss_df.csv
Kita bisa import kembali:
= pd.read_csv("./pytorch_titik_loss_df.csv") pytorch_titik_loss_df
Lalu kita bisa plot:
"train_loss"], label = "training loss")
plt.plot(pytorch_titik_loss_df["val_loss"], label = "validation loss")
plt.plot(pytorch_titik_loss_df["epoch")
plt.xlabel(
plt.legend() plt.show()
Prediksi
Menyimpan parameter model (saja)
Ingat kembali, kita bisa melihat parameter model dengan .state_dict()
pisah_titik.state_dict()
OrderedDict([('weights',
tensor([[ 0.9372],
[-1.2340]])),
('bias', tensor([0.3439]))])
Kita bisa menyimpan parameter model ke dalam file berakhiran .pth
menggunakan torch.save
"./pisah_titik_state.pth") torch.save(pisah_titik.state_dict(),
Apabila ingin load kembali, kita perlu membuat instance terlebih dahulu…
= MyPerceptron() pisah_titik2
Barulah kita panggil .load_state_dict(torch.load(PATH))
seperti berikut:
"./pisah_titik_state.pth")) pisah_titik2.load_state_dict(torch.load(
<All keys matched successfully>
Parameternya akan sama:
pisah_titik2.state_dict()
OrderedDict([('weights',
tensor([[ 0.9372],
[-1.2340]])),
('bias', tensor([0.3439]))])
torch.nn.Linear
dan torch.nn.Sequential
class MyPerceptron_v2(torch.nn.Module):
def __init__(self):
super().__init__()
# satu dense layer dengan fungsi aktivasi linier
self.linear_layer = torch.nn.Linear(in_features=2, out_features=1)
# fungsi aktivasi sigmoid
self.sigmoid = torch.nn.Sigmoid()
def forward(self, x):
= self.linear_layer(x)
x = self.sigmoid(x)
x return x
class MyPerceptron_v3(torch.nn.Module):
def __init__(self):
super().__init__()
# perhatikan: inputnya bukan berupa list, langsung saja tiap layer
self.layer = torch.nn.Sequential(
=2, out_features=1),
torch.nn.Linear(in_features
torch.nn.Sigmoid()
)
def forward(self, x):
= self.layer(x)
x return x
Fungsi train step dan val step
Training loop kita terdiri dari dua bagian utama, yaitu tahap training dan tahap validation. Kita bisa menyusun training loop yang telah kita buat dengan lebih rapi, dengan memasukkan tiap tahap ke dalam fungsi yang nantinya tinggal dipanggil. Modifikasi yang dilakukan:
pisah_titik
menjadimodel
titik_train_dataloader
menjaditrain_dataloader
titik_val_dataloader
menjadival_dataloader
titik_loss_fn
menjadiloss_fn
titik_opt
menjadioptimizer
def train_step(model, train_dataloader, loss_fn, optimizer, device=device):
# siapkan model untuk tahap training
model.to(device)
model.train()= 0
sum_train_loss
# untuk tiap batch,
for X, y in train_dataloader:
= X.to(device)
X = y.to(device)
y
# Forward pass
= model(X)
y_pred
# Hitung loss
= loss_fn(y_pred, y)
loss += loss
sum_train_loss
# "Optimizer zero grad": nolkan dulu hasil hitung gradien
optimizer.zero_grad()
# Backpropagation
loss.backward()
# Update optimizer, juga disebut "optimizer step"
optimizer.step()
# hitung rata-rata train loss berdasarkan banyaknya batch
= sum_train_loss / len(train_dataloader)
avg_train_loss
# sedikit modifikasi: return train loss
return avg_train_loss
def val_step(model, val_dataloader, loss_fn, device=device):
# siapkan model untuk tahap validaion
model.to(device)eval()
model.= 0
sum_val_loss
# selalu digunakan ketika hendak testing
with torch.inference_mode():
# untuk tiap batch,
for X, y in val_dataloader:
= X.to(device)
X = y.to(device)
y
# Forward pass
= model(X)
y_pred
# Hitung loss
+= loss_fn(y_pred, y)
sum_val_loss
# hitung rata-rata val loss berdasarkan banyaknya batch
= sum_val_loss / len(val_dataloader)
avg_val_loss
# sedikit modifikasi: return val loss
return avg_val_loss
Setelah mendefinisikan kedua fungsi di atas, training loop menjadi lebih sederhana:
= 10
epochs
= []
train_loss_list = []
val_loss_list
# untuk tiap epoch,
for epoch in range(epochs):
# training step
= train_step(
avg_train_loss = pisah_titik,
model = titik_train_dataloader,
train_dataloader = titik_loss_fn,
loss_fn = titik_opt,
optimizer = device
device
)
train_loss_list.append(avg_train_loss)
# validation step
= val_step(
avg_val_loss = pisah_titik,
model = titik_val_dataloader,
val_dataloader = titik_loss_fn,
loss_fn = device
device
)
val_loss_list.append(avg_val_loss)
# tampilkan train loss dan val loss untuk epoch ini
print(f"Epoch: {epoch} | avg train loss: {avg_train_loss} | avg val loss: {avg_val_loss}")
Bahkan, training loop secara keseluruhan bisa kita jadikan fungsi juga.
def training_loop(
model, train_dataloader, val_dataloader,= device
loss_fn, optimizer, epochs, device
):
= []
train_loss_list = []
val_loss_list
# untuk tiap epoch,
for epoch in range(epochs):
# training step
= train_step(
avg_train_loss
model, train_dataloader,=device
loss_fn, optimizer, device
)
train_loss_list.append(avg_train_loss)
# validation step
= val_step(
avg_val_loss
model, val_dataloader,=device
loss_fn, device
)
val_loss_list.append(avg_val_loss)
# tampilkan train loss dan val loss untuk epoch ini
print(f"Epoch: {epoch} | avg train loss: {avg_train_loss} | avg val loss: {avg_val_loss}")
# menyimpan kedua list ke dalam dictionary yang kemudian di-return
= {
results_dict "train_loss": [loss.item() for loss in train_loss_list],
"val_loss": [loss.item() for loss in val_loss_list]
}return results_dict
Klasifikasi Gambar
Sekarang kita akan mencoba melakukan klasifikasi gambar dengan dataset Fashion MNIST seperti di pertemuan sebelumnya.
Persiapan data
Langkah pertama adalah menyiapkan data, yaitu menyiapkan objek Dataset
, yang kemudian diubah menjadi DataLoader
. Untungnya, objek Dataset
untuk Fashion MNIST sudah tersedia di torchvision.datasets.FashionMNIST
sehingga tinggal kita download seperti berikut.
Perkiraan storage yang dibutuhkan: 90 MB
Apabila download di laptop kalian terlalu pelan, silakan gunakan Google Colaboratory saja, selesai kurang dari 30 detik
= torchvision.datasets.FashionMNIST(
fashion_train = "./fashion_data", # folder tempat download
root = True, # data training
train = True, # karena belum ada
download # agar file gambar otomatis diubah menjadi tensor
= torchvision.transforms.ToTensor()
transform )
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to ./fashion_data/FashionMNIST/raw/train-images-idx3-ubyte.gz
Extracting ./fashion_data/FashionMNIST/raw/train-images-idx3-ubyte.gz to ./fashion_data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to ./fashion_data/FashionMNIST/raw/train-labels-idx1-ubyte.gz
Extracting ./fashion_data/FashionMNIST/raw/train-labels-idx1-ubyte.gz to ./fashion_data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to ./fashion_data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
Extracting ./fashion_data/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to ./fashion_data/FashionMNIST/raw
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to ./fashion_data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
Extracting ./fashion_data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to ./fashion_data/FashionMNIST/raw
100%|██████████| 26421880/26421880 [00:06<00:00, 3856770.39it/s]
100%|██████████| 29515/29515 [00:00<00:00, 147617.02it/s]
100%|██████████| 4422102/4422102 [00:01<00:00, 2747505.87it/s]
100%|██████████| 5148/5148 [00:00<00:00, 5877048.72it/s]
= torchvision.datasets.FashionMNIST(
fashion_val = "./fashion_data", # folder yang sama untuk tempat download
root = False, # bukan data training
train = True,
download = torchvision.transforms.ToTensor()
transform )
Banyaknya baris di data training dan data validation
print(len(fashion_train.data), len(fashion_train.targets))
60000 60000
print(len(fashion_val.data), len(fashion_val.targets))
10000 10000
Kelas-kelas:
= fashion_train.classes
nama_kelas print(nama_kelas)
['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
Kita bisa lihat salah satu gambarnya:
#@title Slider to look for some image examples {run: "auto"}
= 21402 #@param {type:"slider", min:0, max:49999, step:1}
idx
= fashion_train[idx]
image, label ='gray')
plt.imshow(image.squeeze(), cmap
plt.title(nama_kelas[label])'OFF')
plt.axis( plt.show()
Sedikit berbeda dengan pertemuan sebelumnya, kali ini kita gunakan .squeeze()
karena gambar yang diberikan berukuran 1 x 28 x 28 (yaitu dengan satu color channel: hitam-putih) daripada 28 x 28, sehingga perlu diubah menjadi 28 x 28.
Jangan lupa menyiapkan DataLoader
= 32
BATCH_SIZE
= DataLoader(
fashion_train_dataloader
fashion_train,= BATCH_SIZE,
batch_size = True
shuffle
)
= DataLoader(
fashion_val_dataloader
fashion_val,= BATCH_SIZE,
batch_size = False
shuffle )
Menyusun model
class ModelFashionMNIST(torch.nn.Module):
def __init__(self):
super().__init__()
self.layer_stack = torch.nn.Sequential(
torch.nn.Flatten(),=784, out_features=15),
torch.nn.Linear(in_features
torch.nn.ReLU(),=15, out_features=10),
torch.nn.Linear(in_features=1)
torch.nn.Softmax(dim
)
def forward(self, x):
= self.layer_stack(x)
x return x
= ModelFashionMNIST() fashion_model
Menyiapkan hyperparameter
= torch.nn.CrossEntropyLoss()
fashion_loss_fn = torch.optim.Adam(params=fashion_model.parameters(),
fashion_opt =0.01) lr
Training
= training_loop(
fashion_results
fashion_model, fashion_train_dataloader, fashion_val_dataloader,
fashion_loss_fn, fashion_opt,= 10,
epochs = device
device )
Epoch: 0 | avg train loss: 1.9308573007583618 | avg val loss: 1.9090994596481323
Epoch: 1 | avg train loss: 1.9126436710357666 | avg val loss: 1.9180550575256348
Epoch: 2 | avg train loss: 1.8876198530197144 | avg val loss: 1.8480236530303955
Epoch: 3 | avg train loss: 1.7515285015106201 | avg val loss: 1.722522497177124
Epoch: 4 | avg train loss: 1.7178850173950195 | avg val loss: 1.715074062347412
Epoch: 5 | avg train loss: 1.6621224880218506 | avg val loss: 1.6644155979156494
Epoch: 6 | avg train loss: 1.6579434871673584 | avg val loss: 1.6573517322540283
Epoch: 7 | avg train loss: 1.6579766273498535 | avg val loss: 1.675686240196228
Epoch: 8 | avg train loss: 1.6498321294784546 | avg val loss: 1.6481648683547974
Epoch: 9 | avg train loss: 1.6487261056900024 | avg val loss: 1.692258358001709
= pd.DataFrame(
fashion_loss_df
[for loss in fashion_results["train_loss"]],
[loss.item() for loss in fashion_results["val_loss"]]
[loss.item()
],={0: "train_loss", 1: "val_loss"}) ).transpose().rename(columns
fashion_loss_df
train_loss | val_loss | |
---|---|---|
0 | 1.930857 | 1.909099 |
1 | 1.912644 | 1.918055 |
2 | 1.887620 | 1.848024 |
3 | 1.751529 | 1.722522 |
4 | 1.717885 | 1.715074 |
5 | 1.662122 | 1.664416 |
6 | 1.657943 | 1.657352 |
7 | 1.657977 | 1.675686 |
8 | 1.649832 | 1.648165 |
9 | 1.648726 | 1.692258 |
"./pytorch_fashion_loss_df.csv", index=False) fashion_loss_df.to_csv(
Kalau mau menyamakan dengan modul, bisa download dari GitHub Pages ini: pytorch_fashion_loss_df.csv
= pd.read_csv("./pytorch_fashion_loss_df.csv") pytorch_fashion_loss_df
pytorch_fashion_loss_df
train_loss | val_loss | |
---|---|---|
0 | 1.930857 | 1.909099 |
1 | 1.912644 | 1.918055 |
2 | 1.887620 | 1.848024 |
3 | 1.751529 | 1.722522 |
4 | 1.717885 | 1.715074 |
5 | 1.662122 | 1.664416 |
6 | 1.657943 | 1.657352 |
7 | 1.657977 | 1.675686 |
8 | 1.649832 | 1.648165 |
9 | 1.648726 | 1.692258 |
"train_loss"], label = "training loss")
plt.plot(pytorch_fashion_loss_df["val_loss"], label = "validation loss")
plt.plot(pytorch_fashion_loss_df["epoch")
plt.xlabel(
plt.legend() plt.show()
Prediksi
= list(fashion_val_dataloader) fashion_val_list
= fashion_val_list[123] tes_batch_gambar, tes_batch_label
print(tes_batch_gambar.shape)
torch.Size([32, 1, 28, 28])
print(tes_batch_label.shape)
torch.Size([32])
= tes_batch_gambar[0, :, :, :].to(device) tes_gambar
print(tes_gambar.shape)
torch.Size([1, 28, 28])
= tes_batch_label[0] tes_label
print(tes_label)
print(nama_kelas[tes_label])
tensor(3)
Dress
= fashion_model(tes_gambar) y_pred
print(y_pred)
tensor([[4.4959e-07, 0.0000e+00, 0.0000e+00, 1.0000e+00, 0.0000e+00, 0.0000e+00,
0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00]], device='cuda:0',
grad_fn=<SoftmaxBackward0>)
print(torch.argmax(y_pred).item())
3
#@title Slider to look for some prediction examples {run: "auto"}
= 123 #@param {type:"slider", min:0, max:9999, step:1}
batch_idx = 0 #@param {type:"slider", min:0, max:31, step:1}
pic_idx
= fashion_val_list[batch_idx]
batch_gambar, batch_label
= batch_gambar[pic_idx].to(device)
satu_gambar = batch_label[pic_idx].item()
label_true_idx = torch.argmax(fashion_model(satu_gambar)).item()
label_pred_idx
='gray')
plt.imshow(satu_gambar.cpu().squeeze(), cmap
plt.title(f"Predicted class: {nama_kelas[label_pred_idx]}\n" +
f"True class: {nama_kelas[label_true_idx]}"
)'OFF')
plt.axis( plt.show()
Menyimpan parameter model
"./fashion_model_state.pth") torch.save(fashion_model.state_dict(),
Referensi
Internet
Sumber belajar PyTorch, deep learning, atau semacamnya, untuk belajar lebih lanjut
Buku Dive into Deep Learning (biasa disebut D2L), utamanya menggunakan PyTorch: https://d2l.ai/
Situs bernama “weights and biases” (wandb) menyediakan layanan pemantauan proses training: https://wandb.ai/site