Pengubahan bentuk

Mengubah bentuk tensor dengan view, reshape, squeeze, unsqueeze, dan permute.

Daftar isi

Data yang masuk ke sebuah operasi jarang memiliki bentuk yang persis dituntut, sehingga pengubahan bentuk menjadi keterampilan harian di PyTorch. Topik ini membahas lima operasinya, yaitu view dan reshape untuk menata ulang elemen, squeeze dan unsqueeze untuk menghapus serta menambah dimensi berukuran satu, dan permute untuk menukar urutan dimensi. Setiap contoh kembali menggunakan anotasi bentuk dengan gaya # (6,) -> (2, 3) yang menyatakan bentuk sebelum dan sesudah operasi.

view dan reshape#

Metode .view() menyusun ulang elemen tensor ke dalam bentuk baru tanpa menyalin data, dengan syarat jumlah elemennya tetap sama. Tensor (6,) dapat ditata menjadi (2, 3) maupun (3, 2) karena keduanya menampung enam elemen. Metode .reshape() melakukan hal yang sama, tetapi lebih toleran: bila penataan memungkinkan tanpa salinan, hasilnya berupa view, sedangkan bila tidak, salinan baru dibuat secara diam-diam. Contoh berikut disimpan sebagai file ubah_bentuk.py.

python ubah_bentuk.py
import torch

x = torch.arange(6)     # (6,)
y = x.view(2, 3)        # (6,) -> (2, 3)
print(y)

z = x.reshape(3, 2)     # (6,) -> (3, 2)
print(z)

y[0, 0] = 99            # (2, 3), perubahan pada view
print(x)                # (6,), x ikut berubah: memori yang sama
text Keluaran
tensor([[0, 1, 2],
        [3, 4, 5]])
tensor([[0, 1],
        [2, 3],
        [4, 5]])
tensor([99,  1,  2,  3,  4,  5])

Keluaran baris ketiga memperlihatkan konsekuensi view: mengubah y juga mengubah x, karena view menunjuk memori yang sama dengan tensor asalnya. Sifat ini berlaku untuk view dan untuk reshape pada kasus yang tidak menyalin. Aturan praktisnya, reshape digunakan sebagai pilihan bawaan karena selalu berhasil, sedangkan view dipilih saat kemampuan membaca kode lebih diutamakan, karena kegagalannya berarti memang ada penataan yang tidak dapat dilakukan tanpa salinan.

x berbentuk (6,)012345view(2, 3)012345y = x.view(2, 3)bentuk (2, 3)permute(1, 0)031425b = y.permute(1, 0)bentuk (3, 2)Jumlah elemen tetap enam, hanya tata letak dimensinya yang diubah.
Alur view dan permute pada tensor enam elemen

view membutuhkan memori kontigu#

Memori tensor dikatakan kontigu (contiguous), yakni berurutan rapi, bila elemen-elemennya tersusun berurutan tanpa lompatan sesuai urutan dimensinya. Operasi seperti permute tidak memindahkan data sama sekali, melainkan hanya mengubah cara PyTorch melangkahi memori, sehingga hasilnya sering kali tidak kontigu. Pada tensor yang tidak kontigu, view menolak bekerja karena penataan barunya tidak dapat dipetakan ke memori tanpa salinan, sedangkan reshape menangani kasus tersebut dengan membuat salinan.

python Contoh: view menolak tensor yang tidak kontigu
import torch

a = torch.arange(6).view(2, 3)  # (2, 3)
b = a.permute(1, 0)             # (2, 3) -> (3, 2), memori tidak kontigu
try:
    b.view(6)                   # (3, 2) -> (6,), memicu error
except RuntimeError as e:
    print("RuntimeError:", e)

print(b.reshape(6))  # (3, 2) -> (6,), reshape menyalin bila perlu
text Keluaran
RuntimeError: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Use .reshape(...) instead.
tensor([0, 3, 1, 4, 2, 5])

Pesan error tersebut bahkan menyarankan solusinya, yaitu menggunakan reshape. Perhatikan pula isi hasil reshape: barisannya 0, 3, 1, 4, 2, 5 mengikuti susunan memori tensor hasil permute, bukan 0, 1, 2, 3, 4, 5. Pengetahuan ini penting saat membaca error pada kode model, karena error view semacam ini paling sering muncul setelah permute atau transpose tanpa .contiguous().

squeeze dan unsqueeze#

Dimensi berukuran satu, misalnya dimensi kedua pada bentuk (2, 1, 3), tidak menambah isi data tetapi mengubah tata letaknya, dan operasi tertentu menuntut kehadiran atau ketiadaannya. Metode .squeeze() menghapus seluruh dimensi berukuran satu, sedangkan .squeeze(i) hanya menghapus dimensi ke-i bila ukurannya memang satu. Kebalikannya, .unsqueeze(i) menyisipkan dimensi berukuran satu pada posisi i, dan indeks -1 berarti posisi paling akhir.

python Contoh: squeeze dan unsqueeze
import torch

x = torch.zeros(2, 1, 3)  # (2, 1, 3)
print(x.squeeze().shape)      # (2, 1, 3) -> (2, 3), seluruh dimensi 1 dihapus
print(x.squeeze(1).shape)     # (2, 1, 3) -> (2, 3), hanya dimensi 1
print(x.squeeze(0).shape)     # (2, 1, 3) -> (2, 1, 3), dimensi 0 berukuran 2

y = torch.zeros(2, 3)     # (2, 3)
print(y.unsqueeze(0).shape)   # (2, 3) -> (1, 2, 3), dimensi batch baru
print(y.unsqueeze(-1).shape)  # (2, 3) -> (2, 3, 1), dimensi terakhir baru
text Keluaran
torch.Size([2, 3])
torch.Size([2, 3])
torch.Size([2, 1, 3])
torch.Size([1, 2, 3])
torch.Size([2, 3, 1])

Panggilan squeeze(0) tidak mengubah apa pun karena dimensi 0 berukuran dua, dan PyTorch membiarkannya tanpa error. Pasangan ini paling sering digunakan di sekitar dimensi batch: tensor tunggal (2, 3) dinaikkan menjadi (1, 2, 3) dengan unsqueeze(0) agar dapat masuk ke layer yang menuntut dimensi batch, lalu hasil (1, 2, 3) diturunkan kembali menjadi (2, 3) dengan squeeze(0) saat keluar.

permute#

Metode .permute() menukar urutan dimensi dengan menerima urutan baru dari indeks dimensi lama, tanpa menyalin data. Untuk tensor (2, 3, 4), pemanggilan permute(2, 0, 1) menghasilkan bentuk (4, 2, 3) karena dimensi lama ke-2 menjadi yang pertama. Pada tensor dua dimensi, properti .T adalah singkatan penukarannya, yaitu transpose matriks.

python Contoh: permute dan transpose
import torch

torch.manual_seed(0)

x = torch.rand(2, 3, 4)  # (2, 3, 4)
y = x.permute(2, 0, 1)   # (2, 3, 4) -> (4, 2, 3)
print(y.shape)

a = torch.rand(3, 4)  # (3, 4)
b = a.T               # (3, 4) -> (4, 3), transpose dua dimensi
print(b.shape)
print(b.is_contiguous())
text Keluaran
torch.Size([4, 2, 3])
torch.Size([4, 3])
False

Nilai False pada baris terakhir menegaskan bahwa hasil permute tidak kontigu, sebagaimana dibahas pada bagian sebelumnya. Persoalan klasik yang diselesaikan permute adalah perbedaan konvensi susunan data citra: PyTorch menuntut (C, H, W) untuk satu citra, yaitu kanal, tinggi, lalu lebar, sedangkan banyak pustaka pengolah citra menyusun (H, W, C). Perpindahan antara konvensi itu dilakukan dengan satu permute, tanpa satu byte data pun berpindah.

Ringkasan#

  • view menata ulang elemen tanpa salinan dan berbagi memori dengan tensor asal, sedangkan reshape melakukan hal yang sama dengan izin menyalin bila penataan menuntutnya.
  • view menolak tensor yang tidak kontigu dengan RuntimeError, dan hasil permute atau transpose umumnya tidak kontigu.
  • squeeze menghapus dimensi berukuran satu, sedangkan unsqueeze menyisipkannya, dan keduanya digunakan di sekitar dimensi batch.
  • permute menukar urutan dimensi tanpa memindahkan data, dengan .T sebagai singkatan transpose pada tensor dua dimensi.

Referensi#