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.
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 1tensor([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.
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 kolomtensor([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.
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.
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 berubahtensor([[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.
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 2tensor([[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,)danx[2, 1]menghasilkan skalar. - Rentang
a:bberhenti sebelumb, 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#
- PyTorch documentation — PyTorch — Rujukan API untuk pengindeksan tensor, termasuk mask boolean dan fancy indexing.
- Learn the Basics — PyTorch — Tutorial resmi yang bagian Tensors memperkenalkan pengindeksan dan slice ala NumPy.