Pengindeksan dan slice

Pengindeksan dan slice tensor dengan konvensi NumPy, disertai anotasi bentuk.

Daftar isi

Topik ini membahas pengambilan sebagian isi tensor melalui pengindeksan dan slice, yaitu pemotongan rentang sebagaimana konvensi NumPy. Pembaca yang terbiasa dengan NumPy dapat menggunakan kebiasaan yang sama di PyTorch, sedangkan pembaca yang baru pertama kali menemuinya akan memperoleh anotasi bentuk di samping setiap operasi sebagai penuntun. Setelah membaca topik ini, indeks tunggal, rentang, langkah, mask boolean, dan fancy indexing, yakni pengambilan banyak posisi sekaligus lewat daftar indeks, dapat digunakan dengan bentuk keluarannya terbaca jelas.

Indeks tunggal#

Satu pasang tanda kurung siku mengambil satu posisi dari tensor, dan konvensinya sama dengan list Python: dimensi pertama dihitung dari nol. Pengindeksan satu dimensi menghilangkan dimensi tersebut, sehingga tensor (3, 3) yang diindeks satu kali menjadi (3,) dan diindeks dua kali menjadi skalar. Contoh berikut disimpan sebagai file indeks_slice.py.

python indeks_slice.py
import torch

x = torch.tensor([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])  # (3, 3)

print(x[0])      # (3,), baris pertama
print(x[2, 1])   # (), elemen baris 2, kolom 1
print(x[:, 1])   # (3,), seluruh baris, kolom 1
print(x[0:2])    # (2, 3), baris 0 sampai 1
text Keluaran
tensor([1, 2, 3])
tensor(8)
tensor([2, 5, 8])
tensor([[1, 2, 3],
        [4, 5, 6]])

Empat baris keluaran memperlihatkan pola penurunan dimensi tersebut. x[0] menghasilkan baris pertama berbentuk (3,), sedangkan x[2, 1] mengambil satu elemen sehingga bentuknya (), yaitu skalar tanpa dimensi. Tanda koma di dalam kurung siku memisahkan dimensi: sebelum koma untuk baris, sesudah koma untuk kolom. Ekspresi x[:, 1] menggunakan tanda titik dua untuk memilih seluruh baris pada dimensi pertama, sehingga hasilnya adalah kolom kedua berbentuk (3,).

Slice dengan rentang dan langkah#

Rentang a:b memilih posisi dari a sampai sebelum b, mengikuti aturan rentang setengah terbuka Python. Rentang yang ujungnya dilepas berarti tanpa batas, misalnya x[0:2] sama dengan x[:2], sedangkan argumen ketiga yang dipisahkan tanda titik dua menambahkan langkah, yaitu jarak antara posisi yang diambil. Indeks negatif menghitung posisi dari belakang, sehingga -1 menunjuk elemen terakhir.

python Contoh: indeks negatif dan langkah
import torch

x = torch.tensor([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])  # (3, 3)

print(x[-1])        # (3,), baris terakhir
print(x[::2, ::2])  # (2, 2), setiap dua baris dan kolom
text Keluaran
tensor([7, 8, 9])
tensor([[1, 3],
        [7, 9]])

Ekspresi x[::2, ::2] menerapkan langkah 2 pada kedua dimensi sekaligus, sehingga hanya posisi 0 dan 2 yang diambil dari setiap dimensi dan bentuknya menjadi (2, 2). Perhatikan anotasinya: bentuk masukan (3, 3) dengan langkah 2 menghasilkan ukuran 2 pada tiap dimensi karena posisi terakhir yang terambil adalah indeks 2. Gambar berikut merangkum kasus x[0:2, :] yang mengambil dua baris pertama dengan seluruh kolom.

x berbentuk (3, 3)123456789baris 0 sampai 1baris 2 tidak ikutx[0:2, :]hasil berbentuk (2, 3)123456dua baris dipertahankan,seluruh kolom ikut
Efek x[0:2, :] terhadap tensor berbentuk (3, 3)

Slice adalah view, bukan salinan#

Slice tensor tidak menyalin data, melainkan membuat view, yaitu jendela pembaca ke memori tensor asalnya. Properti ini menguntungkan karena pengambilan sebagian tensor besar tidak memerlukan memori tambahan, tetapi menyimpan jebakan saat tulis: mengubah isi view juga mengubah tensor asalnya. Contoh berikut memperlihatkan efek itu secara langsung.

python Contoh: perubahan pada slice ikut mengubah tensor asal
import torch

x = torch.tensor([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])  # (3, 3)

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

Nilai pertama x berubah menjadi 99 meskipun yang ditulis adalah baris, karena kedua nama tersebut menunjuk memori yang sama. Perilaku ini konsisten dengan NumPy dan berlaku untuk seluruh bentuk slice. Apabila salinan mandiri diinginkan, metode .clone() harus dipanggil secara eksplisit, misalnya baris = x[0:2, :].clone(), sehingga perubahan selanjutnya tidak menjangkau tensor asalnya.

Mask boolean dan fancy indexing#

Selain indeks angka, tensor menerima mask boolean, yaitu tensor berisi nilai True dan False dengan bentuk yang sama seperti tensor asalnya. Mask digunakan untuk menyaring elemen menurut kondisi tanpa perulangan eksplisit, dan hasilnya selalu tensor satu dimensi yang berisi elemen yang lolos seleksi. Cara lain adalah fancy indexing, yaitu memberikan daftar indeks agar beberapa baris diambil sekaligus dalam urutan yang ditentukan.

python Contoh: mask boolean dan fancy indexing
import torch

x = torch.tensor([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])  # (3, 3)

mask = x > 4      # (3, 3), nilai kebenaran per elemen
print(mask)       # (3, 3), pola True dan False per elemen
print(x[mask])    # (5,), elemen yang lolos mask dalam satu dimensi

idx = [0, 2]      # daftar indeks baris
print(x[idx])     # (2, 3), baris 0 dan baris 2
text Keluaran
tensor([[False, False, False],
        [False,  True,  True],
        [ True,  True,  True]])
tensor([5, 6, 7, 8, 9])
tensor([[1, 2, 3],
        [7, 8, 9]])

Ekspresi x > 4 menghasilkan mask berbentuk (3, 3) yang menandai lima elemen bernilai di atas 4, dan x[mask] mengumpulkan kelima elemen itu menjadi tensor (5,). Perhatikan bahwa keluaran mask ikut tercetak pada contoh di atas agar pola True dan False-nya terlihat. Sementara itu, x[idx] mengambil baris 0 dan baris 2 sekaligus dan menghasilkan tensor (2, 3) karena daftar indeks berisi dua posisi. Kedua mekanisme ini dibawa langsung dari NumPy, sehingga pola penyaringan data yang sudah dikenal dapat digunakan tanpa penyesuaian.

Ringkasan#

  • Indeks tunggal menghilangkan satu dimensi, sehingga x[0] pada tensor (3, 3) menghasilkan (3,) dan x[2, 1] menghasilkan skalar.
  • Rentang a:b berhenti sebelum b, ujung yang dilepas berarti tanpa batas, dan argumen langkah ketiga serta indeks negatif mengikuti konvensi Python dan NumPy.
  • Hasil slice adalah view yang berbagi memori dengan tensor asal, sehingga perubahan pada slice ikut mengubah tensor asalnya, sedangkan .clone() membuat salinan mandiri.
  • Mask boolean menyaring elemen menurut kondisi dan menghasilkan tensor satu dimensi, sedangkan fancy indexing mengambil beberapa posisi sekaligus sesuai daftar indeks.

Referensi#