Model deep learning memproses data dalam bentuk array angka, dan NumPy adalah pustaka Python yang paling banyak dipakai untuk mengelolanya. Baik PyTorch maupun TensorFlow meniru cara kerja NumPy, dari nama fungsi sampai aturan bentuk array. Menguasai NumPy membantu kita memahami cara framework menyimpan data, menghitung statistik batch, dan membaca pesan galat tentang shape. Chapter ini membahas pembuatan array, atribut dasar shape, dtype, dan ndim, lalu membandingkan performa list Python dengan ndarray.

Dari List Python ke ndarray #

List Python bersifat fleksibel: satu list boleh berisi angka, teks, bahkan list lain. Fleksibilitas tersebut membuat setiap elemen disimpan sebagai objek Python lengkap yang tersebar di memori dan harus diproses satu per satu — lambat dan boros memori saat data berukuran besar.

Class ndarray dari NumPy menyimpan data secara berbeda: seluruh elemen menempati satu blok memori berdampingan, seluruh elemen wajib bertipe sama (dtype), dan operasi aritmetika dijalankan oleh kode C yang sudah dikompilasi. Desain inilah yang menjadikan NumPy fondasi komputasi ilmiah di Python.

Membuat dan Membaca Array #

Kita mulai dari bentuk paling dasar: membuat array dari list Python lalu memeriksa tiga atribut utamanya.

python
import numpy as np

# Membuat ndarray dari list Python
vektor = np.array([1.0, 2.0, 3.0])
matriks = np.array([[1, 2, 3], [4, 5, 6]])

print("vektor :", vektor)
print("matriks:\n", matriks)

# Tiga atribut paling sering diperiksa
print("shape vektor  :", vektor.shape)
print("dtype vektor  :", vektor.dtype)
print("ndim vektor   :", vektor.ndim)
print("shape matriks :", matriks.shape)
print("dtype matriks :", matriks.dtype)

# float32: dtype standar deep learning
fitur = np.array([1.0, 2.0, 3.0], dtype=np.float32)
print("dtype float32 :", fitur.dtype)
print("itemsize      :", fitur.itemsize, "byte per elemen")

# Fungsi pembuatan array yang paling sering dipakai
print("zeros    :\n", np.zeros((2, 3)))
print("ones     :", np.ones(4))
print("arange   :", np.arange(0, 10, 2))
print("linspace :", np.linspace(0.0, 1.0, 5))

Output yang Dihasilkan:

text
vektor : [1. 2. 3.]
matriks:
 [[1 2 3]
 [4 5 6]]
shape vektor  : (3,)
dtype vektor  : float64
ndim vektor   : 1
shape matriks : (2, 3)
dtype matriks : int64
dtype float32 : float32
itemsize      : 4 byte per elemen
zeros    :
 [[0. 0. 0.]
 [0. 0. 0.]]
ones     : [1. 1. 1. 1.]
arange   : [0 2 4 6 8]
linspace : [0.   0.25 0.5  0.75 1.  ]

Mari kita telaah kode di atas baris per baris:

  1. Baris 5 (vektor = np.array([1.0, 2.0, 3.0])): fungsi np.array mengubah list Python menjadi ndarray. NumPy menebak tipe data dari isi list; angka desimal menghasilkan float64.
  2. Baris 6 (matriks = np.array([[1, 2, 3], [4, 5, 6]])): list bersarang dua tingkat menjadi array dua dimensi. Angka bulat menghasilkan dtype int64.
  3. Baris 11-15 (print("shape ...")): atribut shape melaporkan ukuran per dimensi, dtype tipe elemen, dan ndim jumlah dimensi. Vektor satu dimensi ber-shape (3,) — koma menandakan tuple satu elemen.
  4. Baris 18 (fitur = np.array(..., dtype=np.float32)): parameter dtype memaksa tipe data tertentu. Nilai float32 hanya memakai 4 byte per elemen (itemsize), setengah dari float64.
  5. Baris 23-26: np.zeros membuat array nol, np.ones membuat array berisi satu, np.arange(0, 10, 2) membuat nilai berjarak tetap, dan np.linspace(0.0, 1.0, 5) membuat 5 nilai berjarak rata dari 0.0 sampai 1.0.

Batch Fitur dalam Bentuk Array #

Kerangka data paling umum di deep learning adalah batch: baris array mewakili contoh data dan kolom mewakili fitur. Berkas batch-fitur.py menunjukkan pola tersebut pada batch 4x3.

python
import numpy as np

np.random.seed(42)

# Satu batch berisi 4 contoh data, tiap contoh punya 3 fitur
X = np.random.rand(4, 3).astype(np.float32)

print("X:")
print(X)
print("shape :", X.shape)   # (jumlah contoh, jumlah fitur)
print("dtype :", X.dtype)
print("ndim  :", X.ndim)

# Satu baris = satu contoh data
contoh_pertama = X[0]
print("contoh pertama :", contoh_pertama, "| shape:", contoh_pertama.shape)

# Satu kolom = nilai satu fitur di seluruh contoh
fitur_kedua = X[:, 1]
print("fitur kedua    :", fitur_kedua, "| shape:", fitur_kedua.shape)

# Statistik per fitur tanpa perulangan manual
print("rata-rata per fitur :", X.mean(axis=0))
print("nilai maksimum      :", X.max(axis=0))

Output yang Dihasilkan:

text
X:
[[0.37454012 0.9507143  0.7319939 ]
 [0.5986585  0.15601864 0.15599452]
 [0.05808361 0.8661761  0.601115  ]
 [0.7080726  0.02058449 0.96990985]]
shape : (4, 3)
dtype : float32
ndim  : 2
contoh pertama : [0.37454012 0.9507143  0.7319939 ] | shape: (3,)
fitur kedua    : [0.9507143  0.15601864 0.8661761  0.02058449] | shape: (4,)
rata-rata per fitur : [0.4348387 0.4983734 0.6147533]
nilai maksimum      : [0.7080726  0.9507143  0.96990985]

Mari kita telaah kode di atas baris per baris:

  1. Baris 4 (np.random.seed(42)): generator pseudorandom NumPy menghasilkan urutan sama setiap eksekusi bila seed ditetapkan, sehingga contoh ini deterministik.
  2. Baris 7 (X = np.random.rand(4, 3).astype(np.float32)): np.random.rand(4, 3) membuat array 4x3 berisi nilai acak [0, 1), lalu .astype(np.float32) mengubah tipenya.
  3. Baris 15 (contoh_pertama = X[0]): indeks satu angka mengambil satu baris, yaitu satu contoh data dengan 3 fitur — bentuknya menjadi (3,).
  4. Baris 19 (fitur_kedua = X[:, 1]): titik dua memilih seluruh baris, angka 1 memilih kolom kedua — hasilnya nilai fitur kedua dari keempat contoh.
  5. Baris 23-24 (X.mean(axis=0)): parameter axis=0 menyuruh NumPy menghitung statistik menuruni baris, menghasilkan satu nilai per fitur tanpa perulangan manual.

List Python vs ndarray: Memori dan Kecepatan #

List Python dan ndarray tampak mirip sampai kita mengolah satu juta elemen. Berkas list-vs-ndarray.py menyimpan data yang sama di kedua wadah, lalu mengukur pemakaian memori dan waktu penjumlahan seluruh elemen.

python
import sys
import time

import numpy as np

np.random.seed(42)

N = 1_000_000

# Data yang sama disimpan di dua wadah berbeda
data_numpy = np.random.rand(N)
data_list = data_numpy.tolist()

# --- Perbandingan memori ---
memori_list = sys.getsizeof(data_list) + N * sys.getsizeof(data_list[0])
memori_numpy = data_numpy.nbytes
print(f"memori list Python : {memori_list / 1e6:.1f} MB")
print(f"memori ndarray     : {memori_numpy / 1e6:.1f} MB")
print(f"rasio              : {memori_list / memori_numpy:.1f}x lebih besar")

# --- Perbandingan kecepatan penjumlahan ---
mulai = time.perf_counter()
total_loop = 0.0
for nilai in data_list:
    total_loop += nilai
waktu_loop = time.perf_counter() - mulai

mulai = time.perf_counter()
total_numpy = float(np.sum(data_numpy))
waktu_numpy = time.perf_counter() - mulai

print(f"waktu perulangan Python : {waktu_loop:.4f} detik")
print(f"waktu np.sum            : {waktu_numpy:.4f} detik")
print(f"percepatan              : {waktu_loop / waktu_numpy:.0f}x")
print("hasil penjumlahan sama  :", np.isclose(total_loop, total_numpy))

Output yang Dihasilkan:

text
memori list Python : 32.0 MB
memori ndarray     : 8.0 MB
rasio              : 4.0x lebih besar
waktu perulangan Python : 0.0782 detik
waktu np.sum            : 0.0007 detik
percepatan              : 108x
hasil penjumlahan sama  : True

Mari kita telaah kode di atas baris per baris:

  1. Baris 15-16 (data_numpy = np.random.rand(N) …): satu juta nilai acak disimpan sebagai ndarray float64, lalu .tolist() menyalinnya sebagai list Python agar keduanya terbandingkan adil.
  2. Baris 19-20 (sys.getsizeof ...): memori list dihitung dari ukuran wadah list ditambah ukuran setiap objek float (24 byte per objek); memori ndarray cukup dibaca dari atribut nbytes — 8 byte per elemen float64 karena seluruh elemen berdampingan.
  3. Baris 25-33 (for nilai in data_list: dan np.sum): perulangan Python menjumlahkan per elemen — setiap iterasi membaca satu objek float dan mengikat hasil baru. np.sum menjumlahkan seluruh elemen dalam satu panggilan kode C tanpa perulangan Python, sehingga jauh lebih cepat.
  4. Baris 38 (np.isclose(...)): hasil kedua cara diperiksa dengan toleransi numerik, bukan operator ==, karena penjumlahan pecahan biner bisa berbeda di digit-digit terakhir.

Angka waktu di atas diukur sekali dengan time.perf_counter() pada satu mesin, jadi nilai pastinya berbeda pada komputer Anda — namun polanya konsisten: ndarray jauh lebih ringan dan lebih cepat, dan itulah alasan framework deep learning membangun perhitungan di atas operasi array, bukan perulangan Python.

Fungsi Pembuatan Array untuk Deep Learning #

Tabel berikut merangkum fungsi pembuatan array yang paling sering muncul di kode deep learning:

FungsiContohKegunaan umum di deep learning
np.array(data, dtype=...)np.array([[1.0], [2.0]], dtype=np.float32)Mengubah dataset manual menjadi array siap latih
np.zeros(shape)np.zeros((128, 10))Menampung hasil prediksi atau akumulasi gradien
np.ones(shape)np.ones(3)Nilai awal bias dan tensor konstanta
np.arange(...)np.arange(5)Urutan indeks, misalnya penomoran epoch
np.linspace(...)np.linspace(0.0, 1.0, 5)Titik ambang dan sumbu kurva evaluasi
np.random.rand(...)np.random.rand(4, 3)Data sintetis untuk menguji bentuk kode
np.random.randn(...)np.random.randn(100)Data berdistribusi normal, menyerupai inisialisasi bobot

Dokumentasi resmi NumPy memuat daftar lengkap fungsi pembuatan array di numpy.org/doc/stable/reference/routines.array-creation dan penjelasan atribut ndarray di numpy.org/doc/stable/reference/arrays.ndarray .

Sejauh ini kita memproses array yang bentuknya sudah sama. Pertanyaan berikutnya: apa yang terjadi ketika NumPy menjumlahkan dua array berbentuk berbeda, misalnya vektor dengan matriks? Jawabannya adalah aturan broadcasting — bahan Chapter berikutnya.