Sebagian besar galat pada kode deep learning bukan salah rumus, melainkan salah bentuk: layer menerima (N, C, H, W) padahal datanya (N, H, W, C), atau matmul menemukan k yang berbeda di kedua operand. Shape adalah identitas utama sebuah tensor — lebih sering kita periksa daripada isinya. Chapter ini membiasakan kita membaca shape lewat dim, numel, dan size, lalu menelusuri satu vektor yang disusun ulang menjadi matriks, tensor 3D, dan tensor 4D sebelum dikembalikan ke bentuk semula, sembari membedah kapan view berhasil dan kapan operasi tersebut menolak jalan.

Membaca Shape, dim, dan size #

Tiga pertanyaan dasar selalu bisa dijawab tanpa melihat isi tensor. Berapa jumlah sumbunya? Panggil t.dim(). Berapa panjang tiap sumbu? Baca t.shape atau t.size(i) untuk satu sumbu tertentu. Berapa total elemennya? Kalikan semua angka shape, atau langsung panggil t.numel(). Sebuah tensor berbentuk (2, 3, 2, 2) memiliki 4 sumbu dan 2×3×2×2 = 24 elemen. Aturan pentingnya: operasi bentuk tidak boleh mengubah numel — elemen yang sama hanya ditata ulang.

Diagram berikut merangkum rute perubahan bentuk yang akan kita jalankan pada satu vektor 12 elemen:

Menelusuri Perubahan Bentuk #

Berkas ubah_bentuk.py menjalankan seluruh rute pada diagram di atas dengan satu tensor yang sama, mencetak shape setelah setiap langkah:

python
"""Menelusuri efek operasi bentuk terhadap shape satu tensor yang sama."""

import torch

v = torch.arange(12, dtype=torch.float32)
print("awal v                -> shape", tuple(v.shape))

m = v.reshape(3, 4)
print("v.reshape(3, 4)       -> shape", tuple(m.shape))

t3 = v.reshape(2, 3, 2)
print("v.reshape(2, 3, 2)    -> shape", tuple(t3.shape))

t4 = v.reshape(2, 1, 3, 2)
print("v.reshape(2, 1, 3, 2) -> shape", tuple(t4.shape))

print("t4.squeeze(1)         -> shape", tuple(t4.squeeze(1).shape))
print("v.unsqueeze(0)        -> shape", tuple(v.unsqueeze(0).shape))
print("m.permute(1, 0)       -> shape", tuple(m.permute(1, 0).shape))
print("m.t()                 -> shape", tuple(m.t().shape))
print("t4.reshape(-1)        -> shape", tuple(t4.reshape(-1).shape))

print()
print("m.dim() =", m.dim(), "| m.numel() =", m.numel(),
      "| m.size(0) =", m.size(0), "| m.size(1) =", m.size(1))
print("m.view(4, 3)          -> shape", tuple(m.view(4, 3).shape))
try:
    m.t().view(2, 6)
except RuntimeError as galat:
    print("m.t().view(2, 6) gagal ->", str(galat).split(".")[0] + ".")
print("m.t().reshape(2, 6)   -> shape", tuple(m.t().reshape(2, 6).shape))

Output yang Dihasilkan:

text
awal v                -> shape (12,)
v.reshape(3, 4)       -> shape (3, 4)
v.reshape(2, 3, 2)    -> shape (2, 3, 2)
v.reshape(2, 1, 3, 2) -> shape (2, 1, 3, 2)
t4.squeeze(1)         -> shape (2, 3, 2)
v.unsqueeze(0)        -> shape (1, 12)
m.permute(1, 0)       -> shape (4, 3)
m.t()                 -> shape (4, 3)
t4.reshape(-1)        -> shape (12,)

m.dim() = 2 | m.numel() = 12 | m.size(0) = 3 | m.size(1) = 4
m.view(4, 3)          -> shape (4, 3)
m.t().view(2, 6) gagal -> view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces).
m.t().reshape(2, 6)   -> shape (2, 6)

Mari kita telaah kode di atas baris per baris:

  1. Baris 5-15 (tiga reshape): vektor 12 elemen ditata ulang menjadi matriks (3, 4), kubus (2, 3, 2), lalu tensor 4D (2, 1, 3, 2); jumlah elemen tidak pernah berubah, hanya tata letaknya.
  2. Baris 17 (t4.squeeze(1)): squeeze membuang sumbu yang panjangnya tepat 1 — sumbu ke-1 milik t4 — sehingga (2, 1, 3, 2) menyusut menjadi (2, 3, 2). Operasi ini kerap dipakai membersihkan sisa dimensi tiruan dari hasil perhitungan.
  3. Baris 18 (v.unsqueeze(0)): kebalikan squeeze, unsqueeze(0) menyisipkan sumbu baru panjang 1 di posisi paling depan; pola ini akan menjadi kunci broadcasting pada chapter berikutnya.
  4. Baris 19-20 (permute dan t): m.permute(1, 0) menukar urutan sumbu — sumbu 1 lama menjadi sumbu 0 baru — sehingga (3, 4) menjadi (4, 3); m.t() adalah singkatan untuk kasus matriks.
  5. Baris 21 (reshape(-1)): nilai -1 berarti “hitung sendiri panjang sumbu ini dari sisa elemen”, cara tercepat meratakan tensor apa pun menjadi vektor.
  6. Baris 24-25 (dim, numel, size): matriks (3, 4) memiliki 2 sumbu, 12 elemen, size(0) = 3 baris, dan size(1) = 4 kolom — tiga fungsi pembaca shape yang paling sering dipakai saat menulis kode.
  7. Baris 26-31 (view versus reshape): m.view(4, 3) berhasil karena hasil reshape tersimpan berurutan di memori (kontigu), sedangkan m.t().view(2, 6) ditolak karena hasil transpose melompat-lompat di memori. reshape tetap berhasil pada kasus tersebut dengan menyalin data bila perlu.

Tabel Operasi dan Efeknya #

OperasiEfek terhadap shapeKapan dipakai
reshape(b...)tata ulang; numel tetapbentuk baru apa pun, aman selalu
view(b...)sama seperti reshape tanpa menyalintensor kontigu; tercepat
permute(i, j, ...)sumbu ditata ulang sesuai urutan barumenukar NCHW ↔ NHWC
t(), transpose(d0, d1)dua sumbu bertukar posisimatriks bobot, kasus khusus permute
squeeze(i)sumbu ke-i (panjang 1) dihapusmembersihkan dimensi 1
unsqueeze(i)sumbu baru panjang 1 disisipkanmenyiapkan broadcasting

Dokumentasi tiap operasi tersedia di docs.pytorch.org/docs/stable/generated/torch.Tensor.reshape.html dan docs.pytorch.org/docs/stable/generated/torch.Tensor.view.html . Shape yang kita kendalikan lewat operasi di atas baru terasa manfaatnya saat data diambil sebagian — baris tertentu, channel tertentu, atau sekumpulan nilai yang memenuhi syarat. Cara mengambilnya adalah topik Chapter indexing dan slicing berikutnya.