
Dalam tutorial PyTorch ini, kita akan membahas fungsi inti yang menggerakkan jaringan saraf dan membangun sendiri dari nol. Tujuan utama artikel ini adalah menunjukkan dasar-dasar PyTorch, sebuah pustaka tensor pembelajaran mendalam yang dioptimalkan, sambil memberikan latar belakang terperinci tentang cara kerja jaringan saraf.
Catatan: Lihat workspace DataCamp ini untuk mengikuti kode yang ditulis dalam artikel ini.
Apa itu jaringan saraf?
Jaringan saraf juga disebut artificial neural networks (ANNs). Arsitektur ini menjadi fondasi deep learning, yang merupakan subset dari machine learning yang berfokus pada algoritma yang terinspirasi oleh struktur dan fungsi otak manusia. Sederhananya, jaringan saraf membentuk dasar arsitektur yang meniru cara neuron biologis saling memberi sinyal.
Akibatnya, Anda sering menemukan sumber yang menghabiskan lima menit pertama untuk memetakan struktur neural otak manusia guna membantu Anda memvisualisasikan cara kerja jaringan saraf. Namun ketika Anda tidak punya waktu lima menit, lebih mudah untuk mendefinisikan jaringan saraf sebagai fungsi yang memetakan masukan ke keluaran yang diinginkan.
Arsitektur jaringan saraf generik terdiri dari hal-hal berikut:
- Lapisan input: Data dimasukkan ke jaringan melalui lapisan input. Jumlah neuron pada lapisan input setara dengan jumlah fitur pada data. Lapisan input secara teknis tidak dianggap sebagai salah satu lapisan dalam jaringan karena tidak ada komputasi yang terjadi pada titik ini.
- Lapisan tersembunyi: Lapisan di antara lapisan input dan output disebut lapisan tersembunyi. Sebuah jaringan dapat memiliki jumlah lapisan tersembunyi yang sewenang-wenang - semakin banyak lapisan tersembunyi, semakin kompleks jaringannya.
- Lapisan output: Lapisan output digunakan untuk membuat prediksi.
- Neuron: Setiap lapisan memiliki kumpulan neuron yang berinteraksi dengan neuron di lapisan lain.
- Fungsi aktivasi: Melakukan transformasi non-linear untuk membantu model mempelajari pola yang kompleks dari data.

Perhatikan jaringan saraf pada gambar di atas akan dianggap sebagai jaringan saraf tiga lapis dan bukan empat - ini karena kita tidak memasukkan lapisan input sebagai lapisan. Jadi, jumlah lapisan dalam sebuah jaringan adalah jumlah lapisan tersembunyi ditambah lapisan output.
Bagaimana jaringan saraf bekerja?
Mari kita uraikan algoritme menjadi komponen yang lebih kecil untuk lebih memahami cara kerja jaringan saraf.
Inisialisasi bobot
Inisialisasi bobot adalah komponen pertama dalam arsitektur jaringan saraf. Bobot awal yang kita tetapkan menentukan titik awal proses optimisasi model jaringan saraf.
Cara kita menetapkan bobot sangat penting, terutama saat membangun jaringan yang dalam. Ini karena jaringan dalam lebih rentan mengalami masalah gradien yang meledak atau menghilang. masalah gradien menghilang dan meledak adalah dua konsep di luar cakupan artikel ini, tetapi keduanya menggambarkan skenario di mana algoritme gagal belajar.
Walaupun inisialisasi bobot tidak sepenuhnya menyelesaikan masalah gradien menghilang atau meledak, hal itu jelas berkontribusi pada pencegahannya.
Berikut beberapa pendekatan umum untuk inisialisasi bobot:
Inisialisasi nol
Inisialisasi nol berarti bobot diinisialisasi sebagai nol. Ini bukan solusi yang baik karena jaringan saraf kita akan gagal memecah simetri - ia tidak akan belajar.
Setiap kali nilai konstan digunakan untuk menginisialisasi bobot jaringan saraf, kita dapat memperkirakan kinerjanya buruk karena semua lapisan akan mempelajari hal yang sama. Jika semua keluaran unit tersembunyi memiliki pengaruh yang sama pada biaya, maka gradien akan identik.
Inisialisasi acak
Inisialisasi acak memecah simetri, yang berarti lebih baik daripada inisialisasi nol, tetapi beberapa faktor dapat menentukan kualitas model secara keseluruhan.
Misalnya, jika bobot diinisialisasi secara acak dengan nilai besar, maka kita dapat memperkirakan setiap perkalian matriks menghasilkan nilai yang jauh lebih besar. Saat fungsi aktivasi sigmoid diterapkan dalam skenario seperti itu, hasilnya adalah nilai mendekati satu, yang memperlambat laju pembelajaran.
Skenario lain di mana inisialisasi acak dapat menimbulkan masalah adalah jika bobot diinisialisasi secara acak ke nilai kecil. Dalam kasus ini, setiap perkalian matriks akan menghasilkan nilai yang jauh lebih kecil, dan menerapkan fungsi sigmoid akan menghasilkan nilai yang lebih dekat ke nol, yang juga memperlambat laju pembelajaran.
Inisialisasi Xavier/Glorot
Inisialisasi Xavier atau Glorot - bisa disebut dengan kedua nama tersebut - adalah pendekatan heuristik yang digunakan untuk menginisialisasi bobot. Pendekatan ini umum digunakan ketika fungsi aktivasi tanh atau sigmoid diterapkan pada rata-rata berbobot. Pendekatan ini pertama kali diusulkan pada 2010 dalam makalah riset berjudul Understanding the difficulty of training deep feedforward neural networks oleh Xavier Glorot dan Yoshua Bengio. Teknik inisialisasi ini bertujuan menjaga varians di seluruh jaringan tetap sama untuk mencegah gradien meledak atau menghilang.
Inisialisasi He/Kaiming
Inisialisasi He atau Kaiming adalah pendekatan heuristik lain. Perbedaannya dengan heuristik He dan Xavier adalah bahwa inisialisasi He menggunakan faktor skala berbeda untuk bobot yang mempertimbangkan non-linearitas fungsi aktivasi.
Dengan demikian, ketika fungsi aktivasi ReLU digunakan di lapisan, inisialisasi He adalah pendekatan yang direkomendasikan. Anda dapat mempelajari lebih lanjut tentang pendekatan ini dalam Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification oleh He dkk.
Propagasi maju
Jaringan saraf bekerja dengan mengambil rata-rata berbobot ditambah suku bias dan menerapkan fungsi aktivasi untuk menambahkan transformasi non-linear. Dalam formulasi rata-rata berbobot, setiap bobot menentukan pentingnya setiap fitur (yaitu, seberapa besar kontribusinya terhadap prediksi keluaran).

Rumus di atas adalah rata-rata berbobot ditambah suku bias di mana,
- z adalah jumlah berbobot dari masukan sebuah neuron
- Wn menyatakan bobot
- Xn menyatakan variabel bebas, dan
- b adalah suku bias.
Jika rumusnya terlihat familier, itu karena ini adalah regresi linear. Tanpa memperkenalkan non-linearitas ke dalam neuron, kita hanya akan memiliki regresi linear, yang merupakan model sederhana. Transformasi non-linear memungkinkan jaringan saraf kita mempelajari pola yang kompleks.
Fungsi aktivasi
Kita sudah menyinggung beberapa fungsi aktivasi pada bagian inisialisasi bobot, tetapi kini Anda tahu betapa pentingnya fungsi-fungsi tersebut dalam arsitektur jaringan saraf.
Mari kita dalami beberapa fungsi aktivasi umum yang kemungkinan besar akan Anda lihat saat membaca makalah penelitian dan kode orang lain.
Sigmoid
Fungsi sigmoid dicirikan oleh kurva berbentuk “S” yang dibatasi antara nilai nol dan satu. Fungsi ini dapat didiferensiasikan, artinya kemiringan kurva dapat ditemukan pada titik mana pun, dan monotonik, yang berarti tidak sepenuhnya meningkat maupun menurun. Anda biasanya menggunakan fungsi aktivasi sigmoid untuk masalah klasifikasi biner.
Berikut cara memvisualisasikan fungsi sigmoid Anda sendiri menggunakan Python:
# Sigmoid function in Python
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-5, 5, 50)
z = 1/(1 + np.exp(-x))
plt.subplots(figsize=(8, 5))
plt.plot(x, z)
plt.grid()
plt.show()

Tanh
Hyperbolic tangent (tanh) memiliki kurva berbentuk “S” yang sama seperti fungsi sigmoid, kecuali nilainya dibatasi antara -1 dan 1. Jadi, masukan kecil dipetakan lebih dekat ke -1, dan masukan yang lebih besar dipetakan lebih dekat ke 1.

Berikut contoh fungsi tanh yang divisualisasikan menggunakan Python:
# tanh function in Python
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-5, 5, 50)
z = np.tanh(x)
plt.subplots(figsize=(8, 5))
plt.plot(x, z)
plt.grid()
plt.show()

Softmax
Fungsi softmax umumnya digunakan sebagai fungsi aktivasi di lapisan output. Ini adalah generalisasi fungsi sigmoid ke beberapa dimensi. Maka dari itu, fungsi ini digunakan dalam jaringan saraf untuk memprediksi keanggotaan kelas pada lebih dari dua label.
Rectified Linear Unit (ReLU)
Menggunakan fungsi sigmoid atau tanh untuk membangun jaringan saraf dalam berisiko karena lebih mungkin mengalami masalah gradien menghilang. Fungsi aktivasi rectified linear unit (ReLU) hadir sebagai solusi untuk masalah ini dan sering menjadi fungsi aktivasi default untuk beberapa jaringan saraf.
Berikut contoh visual fungsi ReLU menggunakan Python:
# ReLU in Python
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-5, 5, 50)
z = [max(0, i) for i in x]
plt.subplots(figsize=(8, 5))
plt.plot(x, z)
plt.grid()
plt.show()

ReLU dibatasi antara nol dan tak hingga: perhatikan bahwa untuk nilai masukan kurang dari atau sama dengan nol, fungsi mengembalikan nol, dan untuk nilai di atas nol, fungsi mengembalikan nilai masukan yang diberikan (mis., jika Anda memasukkan dua maka dua yang akan dikembalikan). Pada akhirnya, fungsi ReLU berperilaku sangat mirip dengan fungsi linear, sehingga jauh lebih mudah dioptimalkan dan diimplementasikan.
Proses dari lapisan input ke lapisan output dikenal sebagai forward pass atau propagasi maju. Selama fase ini, keluaran yang dihasilkan model digunakan untuk menghitung fungsi biaya guna menentukan kinerja jaringan saraf setelah setiap iterasi. Informasi ini kemudian diteruskan kembali melalui model untuk mengoreksi bobot agar model dapat membuat prediksi yang lebih baik dalam proses yang dikenal sebagai backpropagation.
Backpropagation
Pada akhir forward pass pertama, jaringan membuat prediksi menggunakan bobot yang diinisialisasi, yang belum disetel. Jadi, sangat mungkin prediksi yang dibuat model tidak akurat. Menggunakan loss yang dihitung dari propagasi maju, kita meneruskan informasi kembali melalui jaringan untuk menyetel bobot dalam proses yang dikenal sebagai backpropagation.
Pada akhirnya, kita menggunakan fungsi optimisasi untuk membantu mengidentifikasi bobot yang dapat mengurangi tingkat kesalahan, membuat model lebih andal dan meningkatkan kemampuannya untuk menggeneralisasi ke instance baru. Matematika tentang cara kerjanya di luar cakupan artikel ini, tetapi bagi yang tertarik dapat mempelajari lebih lanjut tentang backpropagation dalam kursus Introduction to Deep Learning in Python kami.
Tutorial PyTorch: Langkah demi langkah membangun jaringan saraf dari nol
Pada bagian artikel ini, kita akan membangun model artificial neural network sederhana menggunakan pustaka PyTorch. Lihat workspace DataCamp ini untuk mengikuti kodenya
PyTorch adalah salah satu pustaka paling populer untuk deep learning. Pustaka ini memberikan pengalaman debug yang jauh lebih langsung dibandingkan TensorFlow. PyTorch juga memiliki beberapa kelebihan lain seperti pelatihan terdistribusi, ekosistem yang kuat, dukungan cloud, memungkinkan Anda menulis kode siap produksi, dan lain-lain. Anda dapat mempelajari lebih lanjut tentang PyTorch dalam skill track Introduction to Deep Learning with PyTorch.
Mari masuk ke tutorialnya.
Definisi & persiapan data
Dataset yang akan kita gunakan dalam tutorial adalah make_circles dari scikit-learn - lihat dokumentasi. Ini adalah dataset sederhana yang berisi sebuah lingkaran besar dengan lingkaran lebih kecil pada bidang dua dimensi dan dua fitur. Untuk demonstrasi, kami menggunakan 10.000 sampel dan menambahkan deviasi standar 0,05 dari noise Gaussian ke data.
Sebelum kita membangun jaringan saraf, praktik yang baik adalah membagi data menjadi himpunan pelatihan dan pengujian agar kita dapat mengevaluasi kinerja model pada data yang belum pernah dilihat.
import matplotlib.pyplot as plt
from sklearn.datasets import make_circles
from sklearn.model_selection import train_test_split
# Create a dataset with 10,000 samples.
X, y = make_circles(n_samples = 10000,
noise= 0.05,
random_state=26)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.33, random_state=26)
# Visualize the data.
fig, (train_ax, test_ax) = plt.subplots(ncols=2, sharex=True, sharey=True, figsize=(10, 5))
train_ax.scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap=plt.cm.Spectral)
train_ax.set_title("Training Data")
train_ax.set_xlabel("Feature #0")
train_ax.set_ylabel("Feature #1")
test_ax.scatter(X_test[:, 0], X_test[:, 1], c=y_test)
test_ax.set_xlabel("Feature #0")
test_ax.set_title("Testing data")
plt.show()

Langkah berikutnya adalah mengonversi data pelatihan dan pengujian dari array NumPy ke tensor PyTorch. Untuk melakukan ini, kita akan membuat dataset kustom untuk berkas pelatihan dan uji. Kita juga akan memanfaatkan modul Dataloader PyTorch agar dapat melatih data dalam batch. Berikut kodenya:
import warnings
warnings.filterwarnings("ignore")
!pip install torch -q
import torch
import numpy as np
from torch.utils.data import Dataset, DataLoader
# Convert data to torch tensors
class Data(Dataset):
def __init__(self, X, y):
self.X = torch.from_numpy(X.astype(np.float32))
self.y = torch.from_numpy(y.astype(np.float32))
self.len = self.X.shape[0]
def __getitem__(self, index):
return self.X[index], self.y[index]
def __len__(self):
return self.len
batch_size = 64
# Instantiate training and test data
train_data = Data(X_train, y_train)
train_dataloader = DataLoader(dataset=train_data, batch_size=batch_size, shuffle=True)
test_data = Data(X_test, y_test)
test_dataloader = DataLoader(dataset=test_data, batch_size=batch_size, shuffle=True)
# Check it's working
for batch, (X, y) in enumerate(train_dataloader):
print(f"Batch: {batch+1}")
print(f"X shape: {X.shape}")
print(f"y shape: {y.shape}")
break
"""
Batch: 1
X shape: torch.Size([64, 2])
y shape: torch.Size([64])
"""
Sekarang mari lanjut ke implementasi dan pelatihan jaringan saraf kita.
Implementasi jaringan saraf & pelatihan model
Kita akan mengimplementasikan jaringan saraf dua lapis sederhana yang menggunakan fungsi aktivasi ReLU (torch.nn.functional.relu). Untuk melakukannya kita akan membuat kelas bernama NeuralNetwork yang mewarisi dari nn.Module yang merupakan kelas dasar untuk semua modul jaringan saraf yang dibangun di PyTorch.
Berikut kodenya:
import torch
from torch import nn
from torch import optim
input_dim = 2
hidden_dim = 10
output_dim = 1
class NeuralNetwork(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(NeuralNetwork, self).__init__()
self.layer_1 = nn.Linear(input_dim, hidden_dim)
nn.init.kaiming_uniform_(self.layer_1.weight, nonlinearity="relu")
self.layer_2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
x = torch.nn.functional.relu(self.layer_1(x))
x = torch.nn.functional.sigmoid(self.layer_2(x))
return x
model = NeuralNetwork(input_dim, hidden_dim, output_dim)
print(model)
"""
NeuralNetwork(
(layer_1): Linear(in_features=2, out_features=10, bias=True)
(layer_2): Linear(in_features=10, out_features=1, bias=True)
)
"""
Dan itu saja.
Untuk melatih model kita harus mendefinisikan fungsi loss untuk menghitung gradien dan sebuah optimizer untuk memperbarui parameter. Untuk demonstrasi, kita akan menggunakan binary crossentropy dan stochastic gradient descent dengan laju pembelajaran 0,1.
learning_rate = 0.1
loss_fn = nn.BCELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=lea
Mari latih model kita
num_epochs = 100
loss_values = []
for epoch in range(num_epochs):
for X, y in train_dataloader:
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
pred = model(X)
loss = loss_fn(pred, y.unsqueeze(-1))
loss_values.append(loss.item())
loss.backward()
optimizer.step()
print("Training Complete")
"""
Training Complete
"""
Karena kita melacak nilai loss, kita dapat memvisualisasikan loss model dari waktu ke waktu.
step = range(len(loss_values))
fig, ax = plt.subplots(figsize=(8,5))
plt.plot(step, np.array(loss_values))
plt.title("Step-wise Loss")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.show()

Visualisasi di atas menunjukkan loss model kita selama 100 epoch. Awalnya, loss dimulai pada 0,7 dan berangsur turun - ini memberi tahu kita bahwa model kita telah meningkatkan prediksinya dari waktu ke waktu. Namun, model tampaknya stagnan sekitar epoch ke-60, yang mungkin disebabkan oleh berbagai alasan, misalnya model berada di wilayah minimum lokal atau global dari fungsi loss.
Meski demikian, model telah dilatih dan siap membuat prediksi pada instance baru - mari lihat caranya di bagian berikutnya.
Prediksi & evaluasi model
Membuat prediksi dengan jaringan saraf PyTorch kita cukup sederhana.
import itertools # Import this at the top of your script
# Initialize required variables
y_pred = []
y_test = []
correct = 0
total = 0
"""
We're not training so we don't need to calculate the gradients for our outputs
"""
with torch.no_grad():
for X, y in test_dataloader:
outputs = model(X) # Get model outputs
predicted = np.where(outputs.numpy() < 0.5, 0, 1) # Convert to NumPy and apply threshold
predicted = list(itertools.chain(*predicted)) # Flatten predictions
y_pred.append(predicted) # Append predictions
y_test.append(y.numpy()) # Append true labels as NumPy
total += y.size(0) # Increment total count
correct += (predicted == y.numpy()).sum().item() # Count correct predictions
print(f'Accuracy of the network on the 3300 test instances: {100 * correct // total}%')
"""
Accuracy of the network on the 3300 test instances: 97%
"""
Catatan: Setiap menjalankan kode dapat menghasilkan keluaran yang berbeda sehingga Anda mungkin tidak mendapatkan hasil yang sama.
Kode di atas melakukan loop melalui batch uji yang disimpan dalam variabel test_dataloader, tanpa menghitung gradien. Kita kemudian memprediksi instance dalam batch dan menyimpan hasilnya dalam variabel bernama outputs. Selanjutnya, kita menetapkan semua nilai yang kurang dari 0,5 menjadi 0 dan yang sama dengan atau lebih besar dari 0,5 menjadi 1. Nilai-nilai ini kemudian ditambahkan ke daftar untuk prediksi kita.
Setelah itu, kita menambahkan prediksi aktual dari instance dalam batch ke variabel bernama total. Lalu kita menghitung jumlah prediksi yang benar dengan mengidentifikasi jumlah prediksi yang sama dengan kelas aktual dan menjumlahkannya. Jumlah total prediksi benar untuk setiap batch ditingkatkan dan disimpan dalam variabel correct kita.
Untuk menghitung akurasi model secara keseluruhan, kita mengalikan jumlah prediksi benar dengan 100 (untuk mendapatkan persentase) lalu membaginya dengan jumlah instance dalam himpunan uji kita. Model kita memiliki akurasi 97%. Kita menggali lebih jauh menggunakan confusion matrix dan classification_report dari scikit-learn untuk mendapatkan pemahaman yang lebih baik tentang kinerja model kita.
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
import seaborn as sns
y_pred = list(itertools.chain(*y_pred))
y_test = list(itertools.chain(*y_test))
print(classification_report(y_test, y_pred))
"""
precision recall f1-score support
0.0 0.98 0.97 0.98 1635
1.0 0.98 0.98 0.98 1665
accuracy 0.98 3300
macro avg 0.98 0.98 0.98 3300
weighted avg 0.98 0.98 0.98 3300
"""
cf_matrix = confusion_matrix(y_test, y_pred)
plt.subplots(figsize=(8, 5))
sns.heatmap(cf_matrix, annot=True, cbar=False, fmt="g")
plt.show()

Model kita berkinerja cukup baik. Saya mendorong Anda untuk mengeksplorasi kode dan melakukan beberapa perubahan agar materi yang telah kita bahas dalam artikel ini semakin melekat.
Dalam tutorial PyTorch ini, kita membahas dasar-dasar jaringan saraf dan menggunakan PyTorch, pustaka Python untuk deep learning, untuk mengimplementasikan jaringan kita. Kita menggunakan dataset lingkaran dari scikit-learn untuk melatih jaringan saraf dua lapis untuk klasifikasi. Lalu kita membuat prediksi pada data dan mengevaluasi hasil menggunakan metrik akurasi.
