NumPyインデクシング&スライシング:テンソルを自在に切り取る方法
1. なぜインデクシング/スライシングが重要なのか?
ディープラーニングを行うと、テンソルを頻繁に操作します。
- バッチから先頭数サンプルだけを抜き出す
- 画像から特定のチャネル(R/G/B)だけを選択する
- シーケンスデータから一部のタイムステップだけを切り取る
- ラベルから特定のクラスだけを抽出する
これらすべての作業は結局「インデクシング(indexing) & スライシング(slicing)」です。
そしてPyTorchのTensorのインデクシング/スライシング構文はNumPyとほぼ同じため、NumPyでしっかり学んでおくとディープラーニングコードの作成が格段に楽になります。
2. 基本インデクシング:1次元から把握
2.1 1次元配列インデクシング
import numpy as np
x = np.array([10, 20, 30, 40, 50])
print(x[0]) # 10
print(x[1]) # 20
print(x[4]) # 50
- インデックスは0から始まる
x[i]はi番目の要素
2.2 負数インデクシング
後ろから数えたいときは負数インデックスを使います。
print(x[-1]) # 50 (最後)
print(x[-2]) # 40
PyTorchでも同様に動作します。
3. スライシングの基本:start:stop:step
スライシングはx[start:stop:step]の形で使用します。
x = np.array([10, 20, 30, 40, 50])
print(x[1:4]) # [20 30 40], 1以上4未満
print(x[:3]) # [10 20 30], 最初から3未満
print(x[2:]) # [30 40 50], 2から終わりまで
print(x[:]) # 全体コピーのような感覚
stepを入れると間隔を置くことができます。
print(x[0:5:2]) # [10 30 50], 0~4まで2つおき
print(x[::2]) # [10 30 50], 上と同じ
ディープラーニングでは、例えばタイムステップを2個飛ばしたり、特定の間隔でサンプルを選択したりするときに便利です。
4. 2次元以上の配列インデクシング:行と列を扱う
2次元配列からが実質的に「行列/バッチ」になるので、ディープラーニング的な感覚が出てきます。
import numpy as np
X = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]) # shape: (3, 3)
4.1 行/列単一インデクシング
print(X[0]) # 最初の行: [1 2 3]
print(X[1]) # 2番目の行: [4 5 6]
print(X[0, 0]) # 1行1列: 1
print(X[1, 2]) # 2行3列: 6
X[i]→ i番目の行(1D配列)X[i, j]→ i番目の行、j番目の列の値(スカラー)
4.2 行スライシング
print(X[0:2]) # 0~1行
# [[1 2 3]
# [4 5 6]]
print(X[1:]) # 1行から終わりまで
# [[4 5 6]
# [7 8 9]]
これは通常バッチで前/後の一部サンプルだけを切るときにPyTorchでそのまま使うパターンです。
4.3 列スライシング
print(X[:, 0]) # 全行, 0番目の列 → [1 4 7]
print(X[:, 1]) # 全行, 1番目の列 → [2 5 8]
:は「その次元全体」を意味X[:, 0]は「行は全部、列は0番目」という意味
ディープラーニングで:
(batch_size, feature_dim)配列で 特定のfeatureだけを抜きたいとき:X[:, k]
5. 3次元以上:バッチ × チャネル × 高さ × 幅
画像データを例に見てみましょう。
# (batch, height, width) と仮定
images = np.random.randn(32, 28, 28) # 32個, 28x28画像
5.1 特定サンプル1つだけ抜く
img0 = images[0] # 最初の画像, shape: (28, 28)
img_last = images[-1] # 最後の画像
5.2 バッチの一部だけ使う
first_8 = images[:8] # 最初の8個だけ, shape: (8, 28, 28)
5.3 画像の一部領域だけ切る(crop)
# 中央の20x20領域だけ
crop = images[:, 4:24, 4:24] # shape: (32, 20, 20)
PyTorchでも:
# images_torch: (32, 1, 28, 28) などのテンソルと仮定
center_crop = images_torch[:, :, 4:24, 4:24]
とインデクシング/スライシングの概念がほぼそのままです。
6. スライシングは通常「ビュー(view)」です
重要なポイント1つ:
スライシング結果は通常元の配列の「ビュー(view)」です。 つまり、新しくデータをコピーしたのではなく元を見ている窓という意味です。
x = np.array([10, 20, 30, 40, 50])
y = x[1:4] # ビュー
print(y) # [20 30 40]
y[0] = 999
print(y) # [999 30 40]
print(x) # [ 10 999 30 40 50] ← 元も変わる!
この特徴は:
- メモリを節約し高速だが
- うっかり元を変えてしまうリスクがある
完全に別の配列にしたいときはcopy()を使います。
x = np.array([10, 20, 30, 40, 50])
y = x[1:4].copy()
y[0] = 999
print(x) # [10 20 30 40 50], 元保持
PyTorchでも似た概念があるので、 「ビュー vs コピー」の感覚を掴んでおくとデバッグがずっと楽になります。
7. ブールインデクシング:条件で要素を抜く
ブールインデクシングは「条件を満たす要素だけを選択」するときに使います。
import numpy as np
x = np.array([1, -2, 3, 0, -5, 6])
mask = x > 0
print(mask) # [ True False True False False True]
pos = x[mask]
print(pos) # [1 3 6]
x > 0→True/Falseで構成された配列x[mask]→Trueの位置だけ抜く
組み合わせ例:
X = np.array([[1, 2, 3],
[4, 5, 6],
[-1, -2, -3]])
pos = X[X > 0]
print(pos) # [1 2 3 4 5 6]
ディープラーニングでよく使われるパターン:
- 特定条件を満たすサンプルだけ抽出(例:ラベルが特定値のものだけ)
- 損失計算時にマスクをかけて一部値だけ平均を取るなど
PyTorchでも:
import torch
x = torch.tensor([1, -2, 3, 0, -5, 6])
mask = x > 0
pos = x[mask]
ほぼ同じように使えます。
8. 整数配列/リストインデクシング(ファンシーインデクシング)
整数インデックスの配列/リストで一度に欲しい位置を抜くこともできます。
x = np.array([10, 20, 30, 40, 50])
idx = [0, 2, 4]
print(x[idx]) # [10 30 50]
2次元でも可能です。
X = np.array([[1, 2],
[3, 4],
[5, 6]]) # shape: (3, 2)
rows = [0, 2]
print(X[rows])
# [[1 2]
# [5 6]]
ディープラーニングで例:
- ランダムに混ぜたインデックス配列でバッチサンプルを抜く
- 特定位置のラベル/予測だけ集めて統計を取る
PyTorchでも同じスタイルで使えます。
9. よく使うインデクシングパターンまとめ
ディープラーニング視点でよく登場するパターンをまとめると:
import numpy as np
# (batch, feature)
X = np.random.randn(32, 10)
# 1) 最初の8個サンプルだけ
X_head = X[:8] # (8, 10)
# 2) 特定feature(例:3番目の列)だけ
f3 = X[:, 3] # (32,)
# 3) 偶数インデックスサンプルだけ
X_even = X[::2] # (16, 10)
# 4) ラベルが1のサンプルだけ選択
labels = np.random.randint(0, 3, size=(32,))
mask = labels == 1
X_cls1 = X[mask] # ラベル1に該当するサンプルだけ
# 5) ランダムシャッフル後、最初24個をtrain、残り8個をval
indices = np.random.permutation(len(X))
train_idx = indices[:24]
val_idx = indices[24:]
X_train = X[train_idx]
X_val = X[val_idx]
上記パターンはPyTorchテンソルでもほぼそのまま使えます。 結局NumPyインデクシングに慣れること = テンソルデータを自由に「切り分け、混ぜ、選択」できる力を養うことです。
10. まとめ
今回見た内容をまとめると:
- インデクシング:
x[i],x[i, j], 負数インデクス - スライシング:
start:stop:step,:, 多次元インデクシング(X[:, 0],X[:8],X[:, 4:8]) - スライシングは通常ビュー(view)なので元が変わる可能性 → 必要なら
copy() - ブールインデクシング:条件でフィルタリング(
x[x > 0],X[labels == 1]) - 整数配列インデクシング:欲しいインデックスをまとめて一度に選択(
x[[0,2,4]])
これを身につければ、PyTorchテンソルで「バッチ切り、チャネル選択、マスク適用、サンプル混ぜ」などの作業がずっと楽になります。

コメントはありません。