Referensi cepat neural network dengan PyTorch. Tensor, forward pass, activation, loss, backpropagation, regularisasi, training loop, dan debugging model. Buat kamu yang belajar AI engineering.
Neural network menerima angka, menjalankan serangkaian operasi matriks, lalu menghasilkan angka baru. Saat klasifikasi gambar kucing, input bukan "gambar kucing", melainkan tensor piksel. Saat prediksi harga rumah, input bisa berisi luas, jumlah kamar, dan lokasi yang sudah diubah jadi fitur numerik.
Satu neuron menghitung kombinasi linear lalu melewatkannya ke activation function:
z = x1*w1 + x2*w2 + ... + xn*wn + b
a = activation(z)w adalah parameter yang dipelajari. b adalah bias. Neural network berlapis mengulang operasi ini berkali-kali agar mampu memetakan hubungan non-linear. Tanpa activation function, tumpukan Linear tetap setara satu transformasi linear. Banyak layer, tapi kemampuan bentuk fungsinya tetap lurus.
| Istilah | Bentuk umum | Makna praktis |
|---|---|---|
| Sample | satu baris data | Satu rumah, satu gambar, atau satu kalimat |
| Feature | satu kolom input | Luas rumah, nilai piksel, token ID |
| Batch | (batch, features) | Sekelompok sample yang diproses bareng |
| Logit | output mentah | Nilai sebelum sigmoid atau softmax |
| Parameter | weight dan bias | Nilai yang diubah optimizer |
| Gradient | turunan loss | Arah perubahan parameter agar loss turun |
| Epoch | satu putaran data | Semua batch training dilalui sekali |
Bentuk tensor adalah sumber bug yang sering muncul. Biasakan cek shape setelah transformasi besar. PyTorch memakai format (batch, features) untuk data tabular, sedangkan citra biasanya (batch, channels, height, width).
import torch
x_tabular = torch.randn(32, 12) # 32 sample, 12 fitur
x_image = torch.randn(32, 3, 64, 64) # 32 gambar RGB 64 x 64
labels = torch.randint(0, 4, (32,)) # target kelas 0 sampai 3
print(x_tabular.shape) # torch.Size([32, 12])
print(x_image.shape) # torch.Size([32, 3, 64, 64])Kalau model mengharapkan 12 fitur dan menerima (32, 1, 12), jangan langsung pakai squeeze() tanpa tahu dimensinya. Tulis transformasi yang jelas, misalnya x = x.flatten(start_dim=1) untuk meratakan tiap sample sambil menjaga dimensi batch.
nn.LinearLayer dense menghitung y = xA^T + b. Untuk input dengan shape (*, in_features), dimensi terakhir harus sama dengan in_features. Output memiliki shape (*, out_features).
from torch import nn
layer = nn.Linear(in_features=12, out_features=64)
x = torch.randn(8, 12)
y = layer(x)
assert y.shape == (8, 64)
print(layer.weight.shape) # torch.Size([64, 12])
print(layer.bias.shape) # torch.Size([64])Jumlah parameter layer linear dengan bias adalah out_features * (in_features + 1). Layer Linear(12, 64) menyimpan 64 * 13 = 832 parameter. Hitung ini saat ukuran model tiba-tiba melonjak.
Activation menentukan bagaimana jaringan melengkungkan hubungan input ke output. Untuk hidden layer, ReLU atau GELU biasanya pilihan awal yang aman. Untuk output layer, activation bergantung pada tugas dan loss.
| Tugas | Output model | Loss yang cocok | Activation di model |
|---|---|---|---|
| Regresi satu nilai | (N, 1) | MSELoss atau L1Loss | Tidak ada |
| Klasifikasi biner | (N, 1) | BCEWithLogitsLoss | Tidak ada |
| Multi-label | (N, C) | BCEWithLogitsLoss | Tidak ada |
| Multi-class tunggal | (N, C) | CrossEntropyLoss | Tidak ada |
| Probabilitas untuk tampilan | sesuai tugas | sesudah inferensi | sigmoid atau softmax |
import torch.nn.functional as F
values = torch.tensor([[-2.0, 0.0, 2.0]])
print(F.relu(values)) # tensor([[0., 0., 2.]])
print(torch.sigmoid(values))
print(F.softmax(values, dim=1)) # jumlah tiap baris = 1Jangan taruh Sigmoid sebelum BCEWithLogitsLoss. Loss itu sudah menggabungkan sigmoid dan binary cross entropy dengan perhitungan yang lebih stabil secara numerik. Begitu juga CrossEntropyLoss: kirim logits mentah, bukan hasil softmax.
Ini baseline nyata untuk klasifikasi biner dengan 12 fitur. Hidden layer memakai ReLU, dropout menonaktifkan sebagian aktivasi saat training, dan output hanya satu logit.
import torch
from torch import nn
class TabularBinaryClassifier(nn.Module):
def __init__(self, input_dim: int) -> None:
super().__init__()
self.network = nn.Sequential(
nn.Linear(input_dim, 64),
nn.ReLU(),
nn.Dropout(p=0.2),
nn.Linear(64, 32),
nn.ReLU(),
nn.Linear(32, 1),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.network(x).squeeze(dim=1)
model = TabularBinaryClassifier(input_dim=12)
logits = model(torch.randn(16, 12))
assert logits.shape == (16,)forward() mendefinisikan aliran data, tetapi pemanggilan normalnya adalah model(x), bukan model.forward(x). Pemanggilan lewat module menjalankan hook PyTorch yang berguna saat debugging atau instrumentasi.
Model yang bagus tidak menyelamatkan data bocor. Pisahkan train, validation, dan test sebelum menghitung statistik normalisasi. Mean dan standar deviasi hanya dihitung dari data train. Kalau test ikut dipakai menghitung mean, informasi distribusi test sudah masuk ke proses training.
import numpy as np
import torch
from torch.utils.data import DataLoader, TensorDataset
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
rng = np.random.default_rng(7)
features = rng.normal(size=(1000, 12)).astype(np.float32)
targets = (features[:, 0] + 0.4 * features[:, 1] > 0).astype(np.float32)
X_train, X_temp, y_train, y_temp = train_test_split(
features, targets, test_size=0.30, random_state=7, stratify=targets
)
X_val, X_test, y_val, y_test = train_test_split(
X_temp, y_temp, test_size=0.50, random_state=7, stratify=y_temp
)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train).astype(np.float32)
X_val = scaler.transform(X_val).astype(np.float32)
X_test = scaler.transform(X_test).astype(np.float32)
train_ds = TensorDataset(torch.from_numpy(X_train), torch.from_numpy(y_train))
val_ds = TensorDataset(torch.from_numpy(X_val), torch.from_numpy(y_val))
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
val_loader = DataLoader(val_ds, batch_size=256, shuffle=False)shuffle=True cocok untuk train loader karena urutan batch berubah tiap epoch. Validation dan test tak perlu diacak. Untuk data time series, jangan pakai split acak. Gunakan data lama untuk train lalu data yang lebih baru untuk validation dan test, supaya evaluasi menyerupai pemakaian nanti.
CrossEntropyLoss menerima target indeks kelas bertipe torch.long, misalnya [0, 2, 1]. BCEWithLogitsLoss menerima target float dengan nilai 0.0 atau 1.0. Banyak error loss ternyata cuma salah dtype.
# Multi-class: logits (N, C), target (N,) berupa indeks kelas
multiclass_logits = torch.randn(4, 3)
multiclass_target = torch.tensor([0, 2, 1, 0], dtype=torch.long)
multiclass_loss = nn.CrossEntropyLoss()(multiclass_logits, multiclass_target)
# Biner: logits (N,), target (N,) float
binary_logits = torch.randn(4)
binary_target = torch.tensor([1.0, 0.0, 1.0, 0.0])
binary_loss = nn.BCEWithLogitsLoss()(binary_logits, binary_target)Training batch punya urutan tetap: pindahkan data ke device, nolkan gradient lama, jalankan forward pass, hitung loss, panggil backward(), lalu step(). Jangan tukar dua langkah terakhir.
from copy import deepcopy
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = TabularBinaryClassifier(input_dim=12).to(DEVICE)
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
@torch.no_grad()
def evaluate(model: nn.Module, loader: DataLoader) -> tuple[float, float]:
model.eval()
total_loss = 0.0
correct = 0
total = 0
for x_batch, y_batch in loader:
x_batch = x_batch.to(DEVICE)
y_batch = y_batch.to(DEVICE)
logits = model(x_batch)
loss = criterion(logits, y_batch)
predictions = (torch.sigmoid(logits) >= 0.5).float()
total_loss += loss.item() * x_batch.size(0)
correct += (predictions == y_batch).sum().item()
total += x_batch.size(0)
return total_loss / total, correct / total
best_state = None
best_val_loss = float("inf")
patience = 8
wait = 0
for epoch in range(1, 101):
model.train()
for x_batch, y_batch in train_loader:
x_batch = x_batch.to(DEVICE)
y_batch = y_batch.to(DEVICE)
optimizer.zero_grad(set_to_none=True)
logits = model(x_batch)
loss = criterion(logits, y_batch)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
val_loss, val_accuracy = evaluate(model, val_loader)
print(f"epoch={epoch:03d} val_loss={val_loss:.4f} val_acc={val_accuracy:.3f}")
if val_loss < best_val_loss:
best_val_loss = val_loss
best_state = deepcopy(model.state_dict())
wait = 0
else:
wait += 1
if wait >= patience:
print("early stopping")
break
model.load_state_dict(best_state)model.train() mengaktifkan perilaku training untuk layer seperti dropout dan batch normalization. model.eval() mengubah perilaku itu saat validasi atau inferensi. torch.no_grad() mencegah PyTorch menyimpan graph gradient, jadi evaluasi lebih hemat memori.
AdamW memisahkan weight decay dari pembaruan momentum Adam. Sebagai titik awal, coba lr=1e-3 untuk MLP kecil. Jika loss naik turun liar atau nan, turunkan learning rate sepuluh kali. Kalau loss nyaris tak bergerak, cek data dan label lebih dulu sebelum membesarkan model.
Loss menjawab seberapa jauh prediksi dari target. Backpropagation menghitung turunan loss terhadap setiap parameter dengan chain rule. Optimizer lalu menggeser parameter berlawanan arah gradient.
parameter_baru = parameter_lama - learning_rate * gradientPyTorch menyimpan graph operasi saat tensor memiliki requires_grad=True. loss.backward() mengisi .grad pada parameter. optimizer.step() membaca gradient itu. Gradient harus dibersihkan sebelum batch berikutnya karena PyTorch menambahkannya secara akumulatif.
x = torch.tensor([2.0], requires_grad=True)
y = x.pow(2) + 3 * x
y.backward()
print(x.grad) # 7.0, karena turunan x^2 + 3x saat x=2 adalah 2*2 + 3Kalau gradient tidak pernah berubah dari nol, cek activation yang mati, learning rate, label, dan apakah parameter benar-benar masuk ke optimizer. Kalau gradient meledak, lakukan gradient clipping dan turunkan learning rate. Transformer dan RNN sering memakai clipping karena satu batch buruk dapat mendorong norm gradient terlalu besar.
Loss dipakai untuk melatih. Metrik dipakai untuk menilai hasil dari sudut produk. Accuracy bisa menipu pada data tidak seimbang. Jika hanya 2 persen transaksi fraud, model yang selalu menjawab "bukan fraud" punya accuracy 98 persen dan tetap tidak berguna.
| Masalah | Metrik yang sering dipakai | Catatan |
|---|---|---|
| Kelas seimbang | Accuracy | Mudah dibaca, jangan pakai sendirian bila kelas timpang |
| Fraud atau penyakit langka | Precision, recall, PR-AUC | Tentukan biaya false positive dan false negative |
| Ranking | ROC-AUC, PR-AUC, NDCG | Nilai urutan prediksi, bukan threshold tunggal |
| Regresi harga | MAE, RMSE | MAE lebih mudah dijelaskan dalam satuan target |
Threshold 0.5 bukan hukum alam. Pilih berdasarkan kebutuhan. Sistem screening penyakit biasanya ingin recall tinggi. Filter spam mungkin ingin precision tinggi supaya email baik tidak ikut tersingkir.
@torch.no_grad()
def binary_predictions(logits: torch.Tensor, threshold: float = 0.5) -> torch.Tensor:
probabilities = torch.sigmoid(logits)
return (probabilities >= threshold).to(torch.int64)
sample_logits = torch.tensor([-1.2, 0.0, 1.8])
print(binary_predictions(sample_logits, threshold=0.7))Untuk data tidak seimbang, BCEWithLogitsLoss(pos_weight=...) dapat memberi bobot lebih besar pada kelas positif. Jika ada 900 negatif dan 100 positif pada data train, titik awal pos_weight sekitar 900 / 100 = 9. Hitung dari train set saja.
Overfitting terjadi saat train loss terus turun, tetapi validation loss mulai naik. Model menghafal detail train yang tidak muncul konsisten pada data baru. Tambah data berkualitas biasanya lebih ampuh dari trik arsitektur, tapi beberapa teknik ini tetap berguna.
| Teknik | Dipakai saat | Efek utama |
|---|---|---|
| Weight decay | Default untuk banyak model | Menahan bobot agar tidak tumbuh liar |
| Dropout | Dense layer atau classifier head | Mematikan aktivasi acak saat training |
| Data augmentation | Image, audio, teks tertentu | Membuat variasi train yang masuk akal |
| Early stopping | Validation loss memburuk | Menghentikan sebelum model menghafal |
| Smaller model | Data sedikit | Mengurangi kapasitas hafalan |
Dropout aktif hanya ketika model.train(). Jangan lupa model.eval() saat produksi. Batch normalization juga sensitif terhadap mode model karena menyimpan running mean dan variance dari training.
regularized_model = nn.Sequential(
nn.Linear(12, 128),
nn.ReLU(),
nn.BatchNorm1d(128),
nn.Dropout(0.3),
nn.Linear(128, 1),
)Jangan menambah semua teknik sekaligus. Mulai dari baseline sederhana, catat metrik validation, lalu ubah satu hal per eksperimen. Kalau hasil memburuk, kamu tahu penyebab yang mungkin.
Simpan state_dict, bukan seluruh objek model Python. File hasilnya lebih tahan terhadap perubahan lokasi class dan lebih aman untuk dipindahkan.
from pathlib import Path
checkpoint_path = Path("tabular_binary_classifier.pt")
torch.save(
{
"model_state": model.state_dict(),
"input_dim": 12,
"threshold": 0.5,
},
checkpoint_path,
)
checkpoint = torch.load(checkpoint_path, map_location=DEVICE, weights_only=True)
loaded_model = TabularBinaryClassifier(input_dim=checkpoint["input_dim"]).to(DEVICE)
loaded_model.load_state_dict(checkpoint["model_state"])
loaded_model.eval()
@torch.no_grad()
def predict_probability(features: torch.Tensor) -> torch.Tensor:
features = features.to(DEVICE)
return torch.sigmoid(loaded_model(features))Model saja tidak cukup. Simpan juga urutan kolom, StandardScaler, tokenizer, label mapping, threshold, dan versi data. Input produksi yang kolomnya tertukar tetap punya shape benar, namun prediksinya bisa ngawur. Bug seperti ini sunyi, jadi catat kontrak input secara eksplisit.
Mulai dari tes yang kecil. Ambil 16 sampai 64 sample dan lihat apakah model mampu menghafalnya. Kalau tidak bisa, jangan dulu training dengan jutaan baris data.
# Tes overfit batch kecil. Loss semestinya turun tajam.
small_x, small_y = next(iter(train_loader))
small_x, small_y = small_x.to(DEVICE), small_y.to(DEVICE)
toy_model = TabularBinaryClassifier(12).to(DEVICE)
toy_optimizer = torch.optim.AdamW(toy_model.parameters(), lr=1e-2)
for _ in range(200):
toy_optimizer.zero_grad(set_to_none=True)
toy_loss = criterion(toy_model(small_x), small_y)
toy_loss.backward()
toy_optimizer.step()
print(toy_loss.item())Cek daftar ini ketika training aneh:
CrossEntropyLoss dan BCEWithLogitsLoss.eval() saat validasi dan inferensi.nan dengan torch.isfinite(loss) dan cek learning rate.Activation function: fungsi non-linear setelah kombinasi linear, misalnya ReLU atau GELU.
Backpropagation: perhitungan gradient dari loss ke parameter dengan chain rule.
Batch: sekumpulan sample yang diproses dalam satu langkah optimizer.
Bias: parameter tambahan pada layer linear yang menggeser output.
Epoch: satu putaran penuh melewati data training.
Feature: variabel input yang dipakai model untuk membuat prediksi.
Gradient clipping: membatasi norm gradient agar pembaruan parameter tidak meledak.
Logit: skor mentah model sebelum diubah menjadi probabilitas.
Loss: nilai yang diminimalkan saat training.
Overfitting: model terlalu cocok dengan data train dan gagal pada data baru.
Parameter: nilai yang dipelajari model, biasanya weight dan bias.
Regularisasi: cara membatasi hafalan model, misalnya weight decay atau dropout.
State dict: kamus PyTorch yang menyimpan tensor parameter dan buffer model.
Tensor: array berdimensi banyak yang dipakai PyTorch.
Validation set: data yang dipakai memilih hyperparameter tanpa menyentuh test set.
Login atau daftar akun gratis untuk membaca cheat sheet ini.