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:
"""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:
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:
- 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. - Baris 17 (
t4.squeeze(1)):squeezemembuang sumbu yang panjangnya tepat 1 — sumbu ke-1 milikt4— sehingga(2, 1, 3, 2)menyusut menjadi(2, 3, 2). Operasi ini kerap dipakai membersihkan sisa dimensi tiruan dari hasil perhitungan. - Baris 18 (
v.unsqueeze(0)): kebalikansqueeze,unsqueeze(0)menyisipkan sumbu baru panjang 1 di posisi paling depan; pola ini akan menjadi kunci broadcasting pada chapter berikutnya. - Baris 19-20 (
permutedant):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. - Baris 21 (
reshape(-1)): nilai-1berarti “hitung sendiri panjang sumbu ini dari sisa elemen”, cara tercepat meratakan tensor apa pun menjadi vektor. - Baris 24-25 (
dim,numel,size): matriks(3, 4)memiliki 2 sumbu, 12 elemen,size(0) = 3baris, dansize(1) = 4kolom — tiga fungsi pembaca shape yang paling sering dipakai saat menulis kode. - Baris 26-31 (
viewversusreshape):m.view(4, 3)berhasil karena hasilreshapetersimpan berurutan di memori (kontigu), sedangkanm.t().view(2, 6)ditolak karena hasil transpose melompat-lompat di memori.reshapetetap berhasil pada kasus tersebut dengan menyalin data bila perlu.
Tabel Operasi dan Efeknya #
| Operasi | Efek terhadap shape | Kapan dipakai |
|---|---|---|
reshape(b...) | tata ulang; numel tetap | bentuk baru apa pun, aman selalu |
view(b...) | sama seperti reshape tanpa menyalin | tensor kontigu; tercepat |
permute(i, j, ...) | sumbu ditata ulang sesuai urutan baru | menukar NCHW ↔ NHWC |
t(), transpose(d0, d1) | dua sumbu bertukar posisi | matriks bobot, kasus khusus permute |
squeeze(i) | sumbu ke-i (panjang 1) dihapus | membersihkan dimensi 1 |
unsqueeze(i) | sumbu baru panjang 1 disisipkan | menyiapkan 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.