Tensor 인덱싱과 슬라이싱

PyTorch에서 텐서의 인덱싱과 슬라이싱은 텐서의 특정 요소나 하위 텐서에 접근할 수 있는 중요한 기능이다. 이를 통해 복잡한 데이터 구조에서 원하는 데이터만 추출하거나, 텐서의 일부를 변경할 수 있다. 파이썬의 numpy 라이브러리를 사용해본 경험이 있다면, PyTorch의 인덱싱과 슬라이싱도 매우 유사하게 사용할 수 있을 것이다.

기본 인덱싱

텐서의 기본 인덱싱은 텐서의 특정 위치에 있는 요소에 접근하는 방법이다. 일반적으로 정수 인덱스를 사용하여 특정 위치에 있는 값을 가져오거나 수정할 수 있다. 예를 들어, 다음과 같은 1차원 텐서가 있다고 하자:

import torch

x = torch.tensor([1, 2, 3, 4, 5])

이때, x[0]은 텐서의 첫 번째 요소인 1을 반환하고, x[2]는 세 번째 요소인 3을 반환한다. 인덱스는 0부터 시작한다는 점을 유의해야 한다.

다차원 텐서 인덱싱

2차원 이상의 텐서에서도 동일한 방식으로 인덱싱을 사용할 수 있다. 예를 들어, 다음과 같은 2차원 텐서를 고려하자:

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

이 텐서에서 첫 번째 행의 두 번째 요소에 접근하려면 y[0, 1]을 사용한다. 이 경우, 결과는 2가 된다. 수학적으로 표현하면, 행렬 (\mathbf{Y})의 요소는 다음과 같이 나타낼 수 있다:

[ \mathbf{Y} = \begin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 \end{bmatrix} ]

이때, (\mathbf{Y}_{0,1})은 행렬의 첫 번째 행, 두 번째 열의 요소를 의미하며, 값은 2이다.

슬라이싱

슬라이싱은 텐서의 일부를 추출하는 방법으로, 특정 범위의 요소를 선택할 수 있다. 슬라이싱의 일반적인 형식은 다음과 같다:

tensor[start:stop:step]

1차원 텐서 슬라이싱

1차원 텐서에서 슬라이싱을 사용하는 경우를 살펴보자:

위 코드에서 z[1:4]는 텐서 z의 두 번째 요소부터 네 번째 요소까지 선택하여 새로운 텐서를 반환한다. 이와 같은 방법으로 텐서의 특정 부분을 쉽게 추출할 수 있다.

다차원 텐서 슬라이싱

다차원 텐서에서도 유사하게 슬라이싱을 사용할 수 있다. 예를 들어, 2차원 텐서의 경우는 다음과 같다:

위 코드에서 a[0:2, 1:3]은 첫 번째와 두 번째 행, 그리고 두 번째와 세 번째 열을 포함하는 하위 텐서를 추출한다. 수학적으로 표현하면, 슬라이싱된 하위 텐서는 다음과 같다:

[ \mathbf{A}_{\text{sub}} = \begin{bmatrix} 2 & 3 \ 5 & 6 \end{bmatrix} ]

특정 차원에 대한 인덱싱 및 슬라이싱

PyTorch에서는 다차원 텐서에서 특정 차원에 대해 인덱싱과 슬라이싱을 개별적으로 적용할 수 있다. 예를 들어, 3차원 텐서에서 특정 "채널"에 있는 데이터를 추출하거나, 특정 "축"을 기준으로 슬라이싱할 수 있다. 이러한 작업은 특히 이미지 데이터 처리에서 유용하다.

예를 들어, 크기가 (3 \times 4 \times 5)인 3차원 텐서를 고려하자. 이 텐서를 다음과 같이 생성할 수 있다:

이 텐서는 3개의 행렬을 가지고 있으며, 각 행렬은 (4 \times 5) 크기를 가진다. 이제 첫 번째 차원(채널)에서 첫 번째 행렬을 선택해 보자:

수학적으로 이는 (\mathbf{B}_{0,:,:})와 같으며, 첫 번째 채널의 모든 행과 열을 선택하는 것이다.

축별 슬라이싱

특정 차원을 기준으로 슬라이싱할 때는 슬라이싱을 해당 차원에서만 적용할 수 있다. 예를 들어, 텐서 (\mathbf{B})의 두 번째 채널에서 두 번째와 세 번째 행만 선택하는 경우를 살펴보자:

이 경우 b[1, 1:3, :]은 두 번째 채널에서 두 번째와 세 번째 행 전체를 선택하여 새로운 하위 텐서를 반환한다. 수학적으로 표현하면, 이 하위 텐서는 다음과 같다:

[ \mathbf{B}_{\text{sub}} = \begin{bmatrix} 26 & 27 & 28 & 29 & 30 \ 31 & 32 & 33 & 34 & 35 \end{bmatrix} ]

논리적 인덱싱

논리적 인덱싱(Logical Indexing)은 조건문을 사용하여 특정 조건을 만족하는 요소를 선택하는 방법이다. 이를 통해 텐서의 값들을 필터링하거나 원하는 조건의 값만 변경할 수 있다.

예제

아래의 코드를 살펴보자:

위 예제에서 x > 25는 텐서의 각 요소가 25보다 큰지 아닌지를 판단하여 불리언 텐서 mask를 생성한다. 이 마스크를 사용하여 25보다 큰 값들만 선택할 수 있다. 수학적으로 이는 다음과 같다:

[ \mathbf{x} = \begin{bmatrix} 10 & 20 & 30 & 40 & 50 \end{bmatrix}, \quad \mathbf{x}_{\text{filtered}} = \begin{bmatrix} 30 & 40 & 50 \end{bmatrix} ]

인덱싱을 사용한 텐서 값 변경

인덱싱과 슬라이싱은 단순히 데이터를 추출하는 데 그치지 않고, 선택된 부분의 값을 변경하는 데에도 사용할 수 있다. 예를 들어, 특정 위치의 값을 변경하거나, 슬라이싱을 통해 추출된 부분을 수정할 수 있다.

예제

다음 예제에서 텐서의 특정 요소와 부분을 변경해 보자:

위 코드에서는 x[1]을 10으로 변경하고, x[2:4][20, 30]으로 변경하였다. 이를 통해 텐서의 일부 요소를 쉽게 수정할 수 있다. 수학적으로 이를 나타내면 다음과 같다:

[ \mathbf{x} = \begin{bmatrix} 1 & 2 & 3 & 4 & 5 \end{bmatrix} \rightarrow \begin{bmatrix} 1 & 10 & 20 & 30 & 5 \end{bmatrix} ]

고급 인덱싱

고급 인덱싱(Advanced Indexing)은 기본적인 정수 인덱싱이나 슬라이싱을 넘어서, 특정 패턴의 인덱스를 통해 텐서의 요소에 접근하는 방법이다. 고급 인덱싱은 브로드캐스팅, 정수 배열 인덱싱, 그리고 혼합 인덱싱을 포함한다.

정수 배열 인덱싱

정수 배열 인덱싱은 정수 배열을 사용하여 여러 위치의 요소를 동시에 선택하는 방법이다. 이를 통해 특정 위치에 있는 여러 요소를 한 번에 선택할 수 있다. 예를 들어, 다음과 같은 텐서를 생각해 보자:

위 코드에서 indices는 선택하고자 하는 위치를 나타내는 정수 배열이다. 이를 통해 텐서 x에서 첫 번째, 세 번째, 다섯 번째 요소를 선택할 수 있다. 수학적으로 표현하면 다음과 같다:

[ \mathbf{x} = \begin{bmatrix} 10 & 20 & 30 & 40 & 50 \end{bmatrix}, \quad \mathbf{x}_{\text{selected}} = \begin{bmatrix} 10 & 30 & 50 \end{bmatrix} ]

브로드캐스팅 인덱싱

PyTorch에서 고급 인덱싱은 브로드캐스팅 규칙을 따르므로, 다양한 크기의 인덱스 배열을 결합하여 더 복잡한 인덱싱을 수행할 수 있다. 예를 들어, 다음과 같은 2차원 텐서가 있을 때:

위 코드에서 rowscols는 각각 행과 열의 인덱스를 나타내는 정수 배열이다. y[rows, cols](0,1)(1,2) 위치에 있는 값을 선택하여 [2, 6]을 반환한다. 수학적으로 표현하면 다음과 같다:

[ \mathbf{Y} = \begin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 \end{bmatrix}, \quad \mathbf{Y}_{\text{selected}} = \begin{bmatrix} 2 & 6 \end{bmatrix} ]

혼합 인덱싱

PyTorch에서는 기본 인덱싱과 고급 인덱싱을 혼합하여 사용할 수 있다. 이를 통해 더 유연한 인덱싱이 가능해진다. 예를 들어, 다음과 같은 3차원 텐서에서 특정 패턴으로 요소를 선택할 수 있다:

위 코드에서 z[1, :, [1, 3]]은 두 번째 채널에서 모든 행의 두 번째와 네 번째 열을 선택하여 새로운 텐서를 반환한다. 이 경우 각 행에서 선택된 요소를 포함한 하위 텐서를 얻을 수 있다. 수학적으로 이를 나타내면 다음과 같다:

[ \mathbf{Z} = \text{3D 텐서}, \quad \mathbf{Z}_{\text{selected}} = \begin{bmatrix} 17 & 19 \ 21 & 23 \ 25 & 27 \end{bmatrix} ]

슬라이싱의 확장: ... 연산자

PyTorch에서는 ... 연산자를 사용하여 다차원 텐서에서 복잡한 슬라이싱을 간단하게 표현할 수 있다. 이 연산자는 모든 차원을 포함하는 축약 표현으로, 고차원 텐서에서 특정 차원만 슬라이싱할 때 유용하다.

예를 들어, 다음과 같은 4차원 텐서가 있을 때:

첫 번째와 두 번째 차원 전체에서 특정 슬라이싱을 수행하고 싶을 때, ...을 사용할 수 있다:

이 코드는 첫 번째와 두 번째 차원을 유지하면서, 세 번째 차원에서 2:4 범위의 슬라이싱을 수행한다.

Boolean Masking

Boolean Masking은 특정 조건에 따라 텐서의 값을 필터링하거나 선택할 수 있는 강력한 방법이다. 이를 통해 텐서 내에서 조건에 맞는 요소들만 골라내거나, 그 값을 변경할 수 있다. 이는 특히 데이터 전처리 과정에서 유용하다.

예제: Boolean Masking으로 값 선택

예를 들어, 다음과 같은 텐서가 있다고 하자:

위의 코드에서 a > 15는 각 요소가 15보다 큰지 여부를 판단하는 불리언 텐서를 생성한다. 이 불리언 텐서 mask를 이용해 15보다 큰 값들만 선택하여 새로운 텐서를 반환한다. 수학적으로 표현하면 다음과 같다:

[ \mathbf{a} = \begin{bmatrix} 5 & 10 & 15 & 20 & 25 & 30 \end{bmatrix}, \quad \mathbf{a}_{\text{filtered}} = \begin{bmatrix} 20 & 25 & 30 \end{bmatrix} ]

예제: Boolean Masking으로 값 변경

Boolean Masking을 사용하여 조건에 맞는 요소의 값을 변경할 수도 있다. 다음 코드를 살펴보자:

위 예제에서 b[b < 3] = 0은 텐서 b의 요소 중 3보다 작은 모든 값을 0으로 변경한다. 수학적으로 표현하면:

[ \mathbf{b} = \begin{bmatrix} 1 & 2 & 3 & 4 & 5 \end{bmatrix} \rightarrow \begin{bmatrix} 0 & 0 & 3 & 4 & 5 \end{bmatrix} ]

인덱스 텐서를 사용한 값 할당

PyTorch에서는 인덱싱을 사용하여 단순히 값을 가져오는 것뿐 아니라, 선택된 요소에 새로운 값을 할당할 수 있다. 이때, 인덱스 텐서를 사용하면 특정 위치에 다양한 값을 효율적으로 할당할 수 있다.

정수 배열을 통한 값 할당

예를 들어, 다음과 같은 텐서에서 인덱스를 사용하여 값을 할당해보자:

위 코드에서 indicesc의 두 번째와 네 번째 위치를 가리키며, 이 위치의 값들이 각각 9988로 변경된다. 수학적으로 표현하면:

[ \mathbf{c} = \begin{bmatrix} 10 & 20 & 30 & 40 & 50 \end{bmatrix} \rightarrow \begin{bmatrix} 10 & 99 & 30 & 88 & 50 \end{bmatrix} ]

인덱싱을 활용한 다양한 활용 예시

인덱싱과 슬라이싱의 응용은 매우 다양하다. 이를 통해 텐서의 데이터를 효율적으로 다룰 수 있으며, 다양한 형태의 연산을 빠르고 간결하게 처리할 수 있다.

예제: 특정 축을 따라 평균 계산하기

다음과 같은 2차원 텐서가 있다고 하자:

위의 코드에서 d.mean(dim=0)은 각 열의 평균을 계산하여 새로운 1차원 텐서를 반환한다. 수학적으로 표현하면:

[ \mathbf{d} = \begin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 \end{bmatrix}, \quad \text{Mean}_{\text{columns}} = \begin{bmatrix} 4.0 & 5.0 & 6.0 \end{bmatrix} ]

예제: 특정 축을 따라 최대값 위치 찾기

PyTorch의 argmax 함수를 이용하여 특정 축에서 최대값의 위치를 찾을 수도 있다:

위 코드에서 e.argmax(dim=1)은 각 행에서 가장 큰 값의 인덱스를 반환한다. 첫 번째 행에서는 9, 두 번째 행에서는 7, 세 번째 행에서는 8이 가장 크므로 인덱스는 각각 1, 0, 2가 된다.

텐서의 차원 축소와 유지

슬라이싱과 인덱싱을 할 때 차원이 줄어들거나 유지되는 것이 중요하다. PyTorch에서는 슬라이싱을 통해 차원이 감소하지만, None이나 unsqueeze() 메서드를 사용하여 차원을 유지하거나 추가할 수 있다. 예를 들어:

첫 번째 경우는 1차원 텐서 [30, 40]을 반환하고, 두 번째 경우는 unsqueeze(1)을 사용하여 2차원으로 변형된 텐서를 반환한다.

Last updated