Komputer menyimpan angka pecahan dalam format titik mengambang yang hampir selalu hanya mendekati nilai sebenarnya. Pada aplikasi biasa selisih kecil tersebut tidak terasa, tetapi deep learning menjumlahkan jutaan pecahan berulang kali sehingga pembulatan menumpuk, dan satu nilai NaN cukup untuk membuat seluruh pelatihan gagal. Chapter ini membuktikan dengan contoh nyata bahwa float32 dan float64 memberikan hasil berbeda, menunjukkan cara mendeteksi serta membersihkan NaN dan inf, lalu menstabilkan softmax — teknik wajib sebelum membangun model klasifikasi. Pemahaman ini menjadi bekal saat membaca kurva loss yang tiba-tiba tidak bergerak.
Presisi float32 vs float64 #
Angka 0.1 tidak memiliki representasi biner yang persis, sehingga komputer menyimpan nilai terdekat. Selisih miliaran dari sekian persen tersebut biasanya tidak terlihat, tetapi perhitungan ulang dapat mengungkapnya, seperti pada berkas presisi-float.py berikut.
import numpy as np
# 0.1 tidak punya representasi biner yang persis
total = 0.0
for _ in range(10):
total += 0.1
print("0.1 ditambahkan 10 kali:", total)
print("nilai yang diharapkan : 1.0")
# Penyerapan bilangan kecil oleh bilangan besar pada float32
besar32 = np.float32(1e9)
besar64 = np.float64(1e9)
print("1e9 + 1 == 1e9 pada float32:", besar32 + np.float32(1.0) == besar32)
print("1e9 + 1 == 1e9 pada float64:", besar64 + 1.0 == besar64)
# Akumulasi kesalahan pada perhitungan rata-rata
np.random.seed(42)
X = np.random.rand(100_000)
X32 = X.astype(np.float32)
print("mean float64 :", X.mean())
print("mean float32 :", X32.mean())
print("selisih :", abs(X.mean() - float(X32.mean())))Output yang Dihasilkan:
0.1 ditambahkan 10 kali: 0.9999999999999999
nilai yang diharapkan : 1.0
1e9 + 1 == 1e9 pada float32: True
1e9 + 1 == 1e9 pada float64: False
mean float64 : 0.49948825007353703
mean float32 : 0.49948823
selisih : 1.5553578536220414e-08Mari kita telaah kode di atas baris per baris:
- Baris 5-8 (
for _ in range(10)):0.1disimpan sebagai0.1000000000000000055511151231257827..., dan penjumlahan beruntun sepuluh kali mengakumulasi selisih pembulatannya — hasilnya0.9999999999999999, bukan1.0. - Baris 12-15 (
besar32 + np.float32(1.0)):float32hanya menyimpan sekitar 7 angka signifikan, sehingga pada besaran satu miliar jarak antar nilai yang bisa direpresentasikan lebih besar dari 1. Akibatnya1e9 + 1bernilai sama dengan1e9— angka1terserap. Padafloat64yang menyimpan sekitar 16 angka signifikan, penjumlahan tersebut tetap terlihat. - Baris 19-21 (
np.random.rand(100_000)): rata-rata dihitung dua kali dari data yang sama. Selisih hasilfloat32terhadapfloat64sebesar1.56e-08kecil, tetapi kesalahan sejenis ini menumpuk sepanjang ribuan langkah pelatihan — alasan utama mengapa verifikasi implementasi biasa dilakukan difloat64.
Overflow, Underflow, dan Nilai NaN #
Titik mengambang punya rentang terbatas: hasil yang melampaui batas atas menjadi inf (overflow), dan hasil yang terlalu dekat nol menjadi 0.0 (underflow). Nilai inf dan NaN lalu menular ke seluruh perhitungan berikutnya. Berkas overflow-nan-inf.py memperlihatkan ketiganya beserta alat pemeriksa dan pembersihnya.
import numpy as np
# Overflow: hasil melampaui rentang float64 menjadi inf
with np.errstate(over="ignore"):
besar = np.exp(1000.0)
print("np.exp(1000) :", besar)
# Underflow: bilangan terlalu kecil menjadi nol
print("np.exp(-1000) :", np.exp(-1000.0))
# inf dan NaN menular lewat operasi berikutnya
print("inf - inf :", besar - besar)
print("inf * 0 :", besar * 0.0)
print("nan + 1 :", np.nan + 1.0)
# Pendeteksi nilai bermasalah pada array
data = np.array([1.0, np.nan, np.inf, -np.inf, 3.0])
print("data :", data)
print("isfinite :", np.isfinite(data))
print("isnan :", np.isnan(data))
# Pembersihan: ganti NaN dan inf dengan nilai pengganti
bersih = np.nan_to_num(data, nan=0.0, posinf=1e6, neginf=-1e6)
print("setelah dibersihkan:", bersih)Output yang Dihasilkan:
np.exp(1000) : inf
np.exp(-1000) : 0.0
inf - inf : nan
inf * 0 : nan
nan + 1 : nan
data : [ 1. nan inf -inf 3.]
isfinite : [ True False False False True]
isnan : [False True False False False]
setelah dibersihkan: [ 1.e+00 0.e+00 1.e+06 -1.e+06 3.e+00]Mari kita telaah kode di atas baris per baris:
- Baris 5-7 (
np.errstate(over="ignore")):np.exp(1000.0)melampaui batas atasfloat64(sekitar1.8e308) sehingga hasilnyainf. Blokerrstatemematikan peringatan agar keluaran bersih; tanpa blok tersebut NumPy mencetakRuntimeWarning: overflow encountered in expke keluaran galat standar. - Baris 11 (
np.exp(-1000.0)): hasil matematisnya sangat kecil sehingga pembulatan terdekat adalah0.0— underflow berlangsung senyap tanpa peringatan. - Baris 14-16 (
besar - besar, …): operasi yang melibatkaninfmenghasilkanNaN, danNaNmenular —nan + 1tetapnan. Satu elemenNaNpada loss membuat rata-rata batch, gradien, dan bobot model menjadiNaNseluruhnya. - Baris 20-22 (
np.isfinite,np.isnan):isfinitebernilaiTruehanya untuk bilangan biasa, sedangkanisnanmelacak keberadaanNaN. Kedua fungsi ini menjadi pemeriksa rutin pada pipeline data: kolom yang mengandung nilai kosong sering dibaca sebagaiNaN. - Baris 26 (
np.nan_to_num(...)): fungsi pembersih menggantiNaNdengan0.0,infdengan1e6, dan-infdengan-1e6sesuai parameter yang kita tentukan. Nilai pengganti selalu dipilih sesuai konteks data, bukan asal terisi.
Stabilisasi Softmax #
Softmax mengubah skor bebas (logits) menjadi probabilitas dengan membagi eksponen setiap skor terhadap jumlahnya. Eksponen tumbuh sangat cepat — np.exp(1000) sudah meluap menjadi inf — sehingga versi naif gagal untuk logits besar. Trik stabilisasinya sederhana: kurangi setiap logit dengan nilai terbesar sebelum mengambil eksponen, karena hasil matematis softmax tidak berubah.
import numpy as np
def softmax_naif(z):
eksponen = np.exp(z)
return eksponen / eksponen.sum()
def softmax_stabil(z):
z_geser = z - np.max(z)
eksponen = np.exp(z_geser)
return eksponen / eksponen.sum()
# Logits bernilai besar: versi naif meluap
logits = np.array([1000.0, 1001.0, 1002.0])
print("softmax naif :", softmax_naif(logits))
print("softmax stabil :", softmax_stabil(logits))
# Logits bernilai kecil: kedua versi sepakat
kecil = np.array([1.0, 2.0, 3.0])
print("naif (kecil) :", softmax_naif(kecil))
print("stabil (kecil) :", softmax_stabil(kecil))
print("jumlah prob. :", softmax_stabil(kecil).sum())Output yang Dihasilkan:
softmax naif : [nan nan nan]
softmax stabil : [0.09003057 0.24472847 0.66524096]
naif (kecil) : [0.09003057 0.24472847 0.66524096]
stabil (kecil) : [0.09003057 0.24472847 0.66524096]
jumlah prob. : 0.9999999999999999Mari kita telaah kode di atas baris per baris:
- Baris 5-7 (
softmax_naif): fungsi menghitungnp.exp(z)langsung. Untuk logits[1000, 1001, 1002], ketiga eksponen meluap menjadiinf, lalu pembagianinf / infmenghasilkannan— prediksi model rusak total tanpa pesan galat dari Python. - Baris 10-13 (
softmax_stabil):np.max(z)bernilai1002, sehinggaz_gesermenjadi[-2, -1, 0]. Eksponen terbesar kininp.exp(0) = 1, jauh dari batas overflow, dan hasil pembagian menghasilkan probabilitas valid: sekitar0.09,0.24, dan0.67. - Baris 22-23 (pencetakan logits besar): versi stabil mengurangi setiap elemen dengan konstanta yang sama. Menurut sifat eksponen, pembagian pembilang dan penyebut oleh
exp(1002)saling menghilangkan, sehingga hasil matematisnya identik — hanya kestabilan numeriknya yang berubah. - Baris 27-29 (logits kecil): untuk logits
[1, 2, 3], kedua versi menghasilkan keluaran yang sama persis — bukti stabilisasi tidak mengubah hasil, hanya memperluas rentang nilai yang aman. - Baris 30 (
softmax_stabil(kecil).sum()): jumlah probabilitas tercetak0.9999999999999999, bukan1.0— sekali lagi efek pembulatan titik mengambang.
Panduan Memilih dtype #
Pemilihan dtype adalah kompromi antara presisi, kecepatan, dan memori. Tabel berikut merangkum pemakaian umum di deep learning:
| dtype | Ukuran | Pemakaian umum di deep learning |
|---|---|---|
float64 | 8 byte | Analisis data dan verifikasi implementasi; jarang untuk pelatihan |
float32 | 4 byte | Standar pelatihan dan inference pada CPU maupun GPU |
float16 / bfloat16 | 2 byte | Pelatihan presisi campuran (mixed precision) |
int64 | 8 byte | Label kelas dan indeks dataset |
int32 / int16 | 4 / 2 byte | Indeks dataset berukuran sangat besar |
bool | 1 byte | Mask: pemfilteran data dan attention mask |
Penjelasan resmi tentang perilaku galat NumPy tersedia di numpy.org/doc/stable/user/errors
dan detail nan_to_num di numpy.org/doc/stable/reference/generated/numpy.nan_to_num
. Semua framework besar — PyTorch, TensorFlow, JAX — mengikuti semantik IEEE 754 yang sama, sehingga kebiasaan membaca inf, NaN, dan toleransi pembandingan dari chapter ini berlaku juga di sana.
Kita kini memiliki seluruh bekal NumPy: array, broadcasting, matriks, pandas sebagai jembatan data, visualisasi, dan kesadaran numerik. Section berikutnya, Dasar Neural Network, menggabungkan semuanya — kita akan membangun neuron dan layer pertama dari nol menggunakan murni NumPy, lalu melatihnya dengan gradien yang dihitung sendiri.