JINWOOJUNG

[EECS 498] Assignment 1. PyTorch 101...(2) 본문

딥러닝/Michigan EECS 498

[EECS 498] Assignment 1. PyTorch 101...(2)

Jinu_01 2024. 12. 22. 18:42
728x90
반응형

본 포스팅은 Michigan Univ.의 EECS 498 강의를 수강하면서 공부한 내용을 정리하는 포스팅입니다.


https://jinwoo-jung.tistory.com/118

 

[EECS 498] Assignment 1. PyTorch 101...(1)

본 포스팅은 Michigan Univ.의 EECS 498 강의를 수강하면서 공부한 내용을 정리하는 포스팅입니다.0. 개발 환경OS : Ubuntu 20.04GPU : GeForce RTX 3070cuda  version: 12.1torch version : 2.3.0+cu121 1. Tensor Basicsdef create_sam

jinwoo-jung.com


5. Integer tensor indexing

Interger tensor indexing을 통해 순서를 Reorder하거나, 특정 부분을 복사하여 새롭게 할당할 수 있다.

 

a = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print('Original tensor:')
print(a)
# tensor([[ 1,  2,  3,  4],
#         [ 5,  6,  7,  8],
#         [ 9, 10, 11, 12]])

idx = [0, 0, 2, 1, 1] 
print('\nReordered rows:')
print(a[idx])
# Reordered rows:
# tensor([[ 1,  2,  3,  4],
#         [ 1,  2,  3,  4],
#         [ 9, 10, 11, 12],
#         [ 5,  6,  7,  8],
#         [ 5,  6,  7,  8]])

 

idx는 index arrays이다. 즉 새롭게 만들 Tensor 객체에 할당할 원본 Tensor 객체 a의 Index이다. a[idx]로 Integer tensor indexing 수행하기에 특정 열을 가져옴을 알 수 있으며, 따라서 처음 두 행은 원본 Tensor 객체 a의 0번째 행, 다음 행은 2번째 행, 다음은 1번째 행을 가지는 Tensor 객체가 생성됨을 확인할 수 있다. 

 

a = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print('Original tensor:')
print(a)
# tensor([[ 1,  2,  3,  4],
#         [ 5,  6,  7,  8],
#         [ 9, 10, 11, 12]])

idx = torch.tensor([3, 2, 1, 0]) 
print('\nReordered columns:')
print(a[:, idx])
# tensor([[ 4,  3,  2,  1],
#         [ 8,  7,  6,  5],
#         [12, 11, 10,  9]])

 

만약, 열 정보를 가져오고 싶으면 a[:, idx]와 같이 행에 대해서는 " : "로 전체를 할당하면 된다.

 

a = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print('Original tensor:')
print(a)
# tensor([[1, 2, 3],
#         [4, 5, 6],
#         [7, 8, 9]])

idx = [0, 1, 2]
print('\nGet the diagonal:')
print(a[idx, idx])
# Get the diagonal:
# tensor([1, 5, 9])

# Modify the diagonal
a[idx, idx] = torch.tensor([11, 22, 33])
print('\nAfter setting the diagonal:')
print(a)
# After setting the diagonal:
# tensor([[11,  2,  3],
#         [ 4, 22,  6],
#         [ 7,  8, 33]])

 

index arrays idx를 활용하면 a[idx, idx]와 같이 특정 행, 열의 원소를 가져올 수 있다. 이때, 반환되는 Tensor 객체는 1 Dimension이기 때문에, a[idx, idx] = torch.tensor([11,22,33])과 같이 동일한 크기를 가지는 tensor 객체를 할당하여 값을 변경시킬 수 있다. 

 

def shuffle_cols(x: Tensor) -> Tensor:
    """
    Re-order the columns of an input tensor as described below.

    Your implementation should construct the output tensor using a single
    integer array indexing operation. The input tensor should not be modified.

    Args:
        x: A tensor of shape (M, N) with N >= 3

    Returns:
        A tensor y of shape (M, 4) where:
        - The first two columns of y are copies of the first column of x
        - The third column of y is the same as the third column of x
        - The fourth column of y is the same as the second column of x
    """

    # 주어진 조건에 맞는 index array 생성
    idx = [0, 0, 2, 1]
    y = x[:,idx]

    return y

 

suffle_cols는 입력받은 Tensor 객체에 대하여 주어진 조건에 맞도록 Integer tensor indexing을 수행하는 함수이다. 

 

torch.arange(start=0, end, step=1, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor

def reverse_rows(x: Tensor) -> Tensor:
    
    # Reverse를 위해 Row Index를 거꾸로 가지는 Index Array 생성
    # index는 0부터 시작함을 고려.
    idx = torch.arange(x.shape[0]-1,-1,-1)
    y = x[idx]

    return y

 

reverse_rows는 입력받은 Tensor 객체의 행이 거꾸로 된 행렬을 반환하는 함수이다. 이를 위해 Row Index를 거꾸로 가지는 Index array를 torch.arange를 이용하여 생성하였다. 이때, index는 0부터 시작하고, end는 포함되지 않음을 고려하여 Index array를 생성하였다.

 

def take_one_elem_per_col(x: Tensor) -> Tensor:
    """
    Construct a new tensor by picking out one element from each column of the
    input tensor as described below.

    The input tensor should not be modified, and you should only access the
    data of the input tensor using a single indexing operation.

    Args:
        x: A tensor of shape (M, N) with M >= 4 and N >= 3.

    Returns:
        A tensor y of shape (3,) such that:
        - The first element of y is the second element of the first column of x
        - The second element of y is the first element of the second column of x
        - The third element of y is the fourth element of the third column of x
    """

    # 주어진 조건에 맞는 idx0, idx1 생성
    idx0 = [1,0,3]
    idx1 = [0,1,2]
    y = x[idx0,idx1]

    return y

 

take_one_elem_per_col은 Tensor 객체 x에 대하여 주어진 조건에 맞는 특정 원소들을 가지는 Tensor를 반환하는 함수이다. Integer tensor indexing을 통해 쉽게 구현할 수 있다.

 

def make_one_hot(x: List[int]) -> Tensor:

    # Index는 0부터 시작하므로 Tensor의 shape는 len(x) x max(x)+1을 가짐.
    y = torch.zeros(len(x),max(x)+1, dtype=torch.float32)
    idx0 = torch.arange(0,y.shape[0])
    y[idx0,x] = 1

    return y

 

make_one_hot은 각 행에 대하여 입력으로 주어진 List의 Index에 해당되는 원소만 1이고, 나머지는 0인 Tensor 객체를 반환하는 함수이다. 우선 모든 원소가 0인 Tensor를 생성하는데, Index는 0부터 시작됨을 고려하여 shape를 len(x) x max(x)+1로 설정하였다. Integer tensor indexing을 이용하는데, 열에 대한 Index는 입력 x로 받아오므로, 행에 대한 index idx0를 torch.arange를 통해 생성한 뒤 y[idx0, x]를 1로 할당하였다. 

 

6. Boolean tensor indexing

a = torch.tensor([[1,2], [3, 4], [5, 6]])
print(a)
# tensor([[1, 2],
#         [3, 4],
#         [5, 6]])

mask = (a > 3)
print(mask)
# tensor([[False, False],
#         [False,  True],
#         [ True,  True]])

print(a[mask])
# tensor([4, 5, 6])

a[a <= 3] = 0
print(a)
# tensor([[0, 0],
#         [0, 4],
#         [5, 6]])

 

Boolean Mask를 통해 Slicing을 진행할 수 있다. mask의 경우 Tensor 객체와 동일한 shape를 가지며, mask 생성 시 조건에 부합하는 원소에 대해서는 True, 아니면 False를 가진다. 따라서 생성만 Mask를 기반으로 a[mask]와 같이 True를 가지는 원소만 선택할 수 있으며, 반환되는 Tensor 객체는 Mask의 Rank-1을 가진다. 또한, Mask를 할당하여 True인 원소에 값을 할당할 수 있다.

 

def sum_positive_entries(x: Tensor) -> Tensor:

    # tensor.sum -> 합계, tensor.item -> Python Scalar
    pos_sum = x[x>0].sum().item()

    return pos_sum

 

sum_positive_entries는 입력 Tensor x에 대하여 0이 아닌 원소의 합을 int64 datatype, Python Scalar 형태로 반환하는 것이다. 

먼저 x>0인 Mask를 생성하여, 양수인 원소만 가지는 x[x>0]을 생성한 뒤 tensor.sum을 통해 모든 원소의 합을 계산한다. 이후 tensor.item을 통해 PyTorch Scalar에서 Python Scalar로 변환한다.

 

7. Reshaping operations

x0 = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 8]])
print(x0)
print('shape:', x0.shape)
# tensor([[1, 2, 3, 4],
#         [5, 6, 7, 8]])
# shape: torch.Size([2, 4])

x1 = x0.view(8)
print(x1)
print('shape:', x1.shape)
# tensor([1, 2, 3, 4, 5, 6, 7, 8])
# shape: torch.Size([8])

 

torch.view는 Tensor 객체의 차원을 변형하는 것이다. 

원본 Tensor 객체 x0는 2차원, 2x4 크기를 가진다. x0.view(8)을 통해 1차원, 8의 크기를 가지는 Tensor로 변형할 수 있다. 

 

x2 = x1.view(1, 8)
print('\nRow vector:')
print(x2)
print('shape:', x2.shape)
# tensor([[1, 2, 3, 4, 5, 6, 7, 8]])
# shape: torch.Size([1, 8])

x4 = x1.view(2, 2, 2)
print('\nRank 3 tensor:')
print(x4)
print('shape:', x4.shape)
# tensor([[[1, 2],
#          [3, 4]],

#         [[5, 6],
#          [7, 8]]])
# shape: torch.Size([2, 2, 2])

 

만약 2차원의 1x8 크기로 변형시키고 싶으면 view(1, 8)을 통해 변형시킬 수 있다. 만약, 3차원으로 변형시키고 싶으면 view(2,2,2)로 변형시킬 수 있다.

정리하면 view(각 차원의 크기)를 통해 내가 원하는 차원과 크기를 가지도록 변형시킬 수 있다. 

 

def flatten(x):
    return x.view(-1)

def make_row_vec(x):
    return x.view(1, -1)

x0 = torch.tensor([[1, 2, 3], [4, 5, 6]])
# x0:
# tensor([[1, 2, 3],
        # [4, 5, 6]])
x0_flat = flatten(x0)
# x0_flat:
# tensor([1, 2, 3, 4, 5, 6])
x0_row = make_row_vec(x0)
# x0_row:
# tensor([[1, 2, 3, 4, 5, 6]])

x1 = torch.tensor([[1, 2], [3, 4]])
# x1:
# tensor([[1, 2],
        # [3, 4]])
x1_flat = flatten(x1)
# x1_flat:
# tensor([1, 2, 3, 4])
x1_row = make_row_vec(x1)
# x1_row:
# tensor([[1, 2, 3, 4]])

 

차원을 변형할 때 flatten(), make_row_vec()과 같이 -1이 들어갈 수 있다. 이때, 변형 전과 후의 Tensor 객체의 총 원소의 개수는 동일해야 하기 때문에 이를 기반으로 계산하면 된다. 

예를 들어, 2x2 shape를 가지는 Tensor x1은 총 4개의 원소를 가진다. 따라서 make_row_vec()의 결과는 1x4의 shape를 가짐을 예측할 수 있다. 

 

x = torch.tensor([[1, 2, 3], [4, 5, 6]])
x_flat = x.view(-1)
# x before modifying:
# tensor([[1, 2, 3],
#         [4, 5, 6]])
# x_flat before modifying:
# tensor([1, 2, 3, 4, 5, 6])

x[0, 0] = 10   # x[0, 0] and x_flat[0] point to the same data
x_flat[1] = 20 # x_flat[1] and x[0, 1] point to the same data
# x after modifying:
# tensor([[10, 20,  3],
#         [ 4,  5,  6]])
# x_flat after modifying:
# tensor([10, 20,  3,  4,  5,  6])

 

변형된 Tensor 객체 역시 torch.clone으로 복사된 것이 아니기 때문에, 동일한 원본 Tensor 객체를 가르킨다. 따라서 값을 변형시키면 원본에도 영향을 줌을 기억하자. 

 

8. Swapping axes

x = torch.tensor([[1, 2, 3], [4, 5, 6]])
print('Original matrix:')
print(x)
print('\nTransposing with view DOES NOT WORK!')
print(x.view(3, 2))
print('\nTransposed matrix:')
print(torch.t(x))
print(x.t())

# Original matrix:
# tensor([[1, 2, 3],
#         [4, 5, 6]])

# Transposing with view DOES NOT WORK!
# tensor([[1, 2],
#         [3, 4],
#         [5, 6]])

# Transposed matrix:
# tensor([[1, 4],
#         [2, 5],
#         [3, 6]])
# tensor([[1, 4],
#         [2, 5],
#         [3, 6]])

 

torch.view()를 통해 차원을 변형시킬 수 있음을 배웠다. 

만약 우리가 Transpose(전치)를 구현하고 싶으면, 단순히 torch.view()를 통해 변경할 수 없다. 이는 torch.view의 경우 row-major order 즉, 행에 우선순위를 두기 때문에 x.view(3,2) 결과가 반환되어 정확한 Transpose 구현이 어려움을 확인할 수 있다. 

따라서 Transpose와 같이 Tensor의 축을 변형시키는 방법은 torch.t()가 있다. 

 

torch.transpose(input, dim0, dim1) → Tensor

x0 = torch.tensor([
     [[1,  2,  3,  4],
      [5,  6,  7,  8],
      [9, 10, 11, 12]],
     [[13, 14, 15, 16],
      [17, 18, 19, 20],
      [21, 22, 23, 24]]])

# tensor([[[ 1,  2,  3,  4],
#          [ 5,  6,  7,  8],
#          [ 9, 10, 11, 12]],

#         [[13, 14, 15, 16],
#          [17, 18, 19, 20],
#          [21, 22, 23, 24]]])
# shape: torch.Size([2, 3, 4])

x1 = x0.transpose(1, 2)
# tensor([[[ 1,  5,  9],
#          [ 2,  6, 10],
#          [ 3,  7, 11],
#          [ 4,  8, 12]],

#         [[13, 17, 21],
#          [14, 18, 22],
#          [15, 19, 23],
#          [16, 20, 24]]])
# torch.Size([2, 4, 3])

 

x0의 경우 3차원, 2x3x4의 크기를 가진다. 이처럼 3차원 이상의 차원을 가지는 Tensor에 대하여 축을 변형시키기 위해서는 torch.transpose를 통해 구현할 수 있다. 

이는 입력받은 두 차원을 바꾸는데, x0.transpose(1,2)의 경우 x0의 첫번째와 두번째의 차원을 swap했기 때문에, 동일한 3차원이지만 2x4x3의 크기를 가짐을 확인할 수 있다. 

 

torch.permute(input, dims) → Tensor

x0 = torch.tensor([
     [[1,  2,  3,  4],
      [5,  6,  7,  8],
      [9, 10, 11, 12]],
     [[13, 14, 15, 16],
      [17, 18, 19, 20],
      [21, 22, 23, 24]]])

# tensor([[[ 1,  2,  3,  4],
#          [ 5,  6,  7,  8],
#          [ 9, 10, 11, 12]],

#         [[13, 14, 15, 16],
#          [17, 18, 19, 20],
#          [21, 22, 23, 24]]])
# shape: torch.Size([2, 3, 4])

x2 = x0.permute(1, 2, 0)
# tensor([[[ 1, 13],
#          [ 2, 14],
#          [ 3, 15],
#          [ 4, 16]],

#         [[ 5, 17],
#          [ 6, 18],
#          [ 7, 19],
#          [ 8, 20]],

#         [[ 9, 21],
#          [10, 22],
#          [11, 23],
#          [12, 24]]])
# shape: torch.Size([3, 4, 2])

 

만약 두 축을 swap하는 것이 아닌, 여러개의 축을 swap하고 싶을 때에는 torch.permute()를 사용하면 된다.  x0.permute(1,2,0)은 0번째 차원을 원본의 첫번째 차원, 첫번째 차원을 원본의 두번째 차원, 두번째 차원을 원본의 0번째 차원으로 할당한다는 의미이다. 따라서 shape는 3x4x2를 가짐을 예측할 수 있다. 

 

9. Contiguous tensors

x0 = torch.randn(2, 3, 4)

try:
  # This sequence of reshape operations will crash
  x1 = x0.transpose(1, 2).view(8, 3)
except RuntimeError as e:
  print(type(e), e)
  
# We can solve the problem using either .contiguous() or .reshape()
x1 = x0.transpose(1, 2).contiguous().view(8, 3)
x2 = x0.transpose(1, 2).reshape(8, 3)
print('x1 shape: ', x1.shape)
print('x2 shape: ', x2.shape)

# <class '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.
# x1 shape:  torch.Size([8, 3])
# x2 shape:  torch.Size([8, 3])

 

View 없이 차원의 순러를 변경하는 transpose와 같은 연산의 경우 비연속 메모리(Non-Contiguous Tensor)를 생성하기 때문에 torch.view를 사용할 수 없다. 

 

[1 2 3]
[4 5 6]

-> [1,2,3,4,5,6]

[1 4]
[2 5]
[3 6]
-> [1,4,2,5,3,6]

 

Transpose가 되면 다음과 같이 섞이기 때문에 비연속 메모리가 발생한다. 따라서 torch.contiguous를 통해 비연속 메모리 텐서를 새로운 메모리 블록에 복사하여 연속 메모리로 만들거나, torch.reshape를 사용해서 자동으로 torch.contiguous를 호출하도록 할 수 있다. 따라서 일반적으로는 torch.reshape를 많이 사용한다. 

 

x = torch.arange(24)
print('Here is x:')
print(x)
y = mytorch.reshape_practice(x)
print('Here is y:')
print(y)

expected = [
    [0, 1,  2,  3, 12, 13, 14, 15],
    [4, 5,  6,  7, 16, 17, 18, 19],
    [8, 9, 10, 11, 20, 21, 22, 23]]
print('Correct:', y.tolist() == expected)

 

torch.arange(24)로 생성된 1 Dimension Tensor x를 expected와 같이 차원을 변경하여 생성하는 함수가 reshape_practice이다.

 

def reshape_practice(x: Tensor) -> Tensor:
    """
    Given an input tensor of shape (24,), return a reshaped tensor y of shape
    (3, 8) such that

    y = [[x[0], x[1], x[2],  x[3],  x[12], x[13], x[14], x[15]],
         [x[4], x[5], x[6],  x[7],  x[16], x[17], x[18], x[19]],
         [x[8], x[9], x[10], x[11], x[20], x[21], x[22], x[23]]]

    You must construct y by performing a sequence of reshaping operations on
    x (view, t, transpose, permute, contiguous, reshape, etc). The input
    tensor should not be modified.

    Args:
        x: A tensor of shape (24,)

    Returns:
        y: A reshaped version of x of shape (3, 8) as described above.
    """

    y = x.reshape(2, 3, 4).permute(1,0,2).reshape(3, 8)

    return y

 

해당 함수가 동작하는 과정을 이해하려면 각각의 과정을 이해해야 한다. 

 

x = torch.arange(24)

print(x.reshape(2, 3, 4))
print(x.reshape(2, 3, 4).permute(1,0,2))
print(x.reshape(2, 3, 4).permute(1,0,2).reshape(3,8))

# tensor([[[ 0,  1,  2,  3],
#          [ 4,  5,  6,  7],
#          [ 8,  9, 10, 11]],

#         [[12, 13, 14, 15],
#          [16, 17, 18, 19],
#          [20, 21, 22, 23]]])
# tensor([[[ 0,  1,  2,  3],
#          [12, 13, 14, 15]],

#         [[ 4,  5,  6,  7],
#          [16, 17, 18, 19]],

#         [[ 8,  9, 10, 11],
#          [20, 21, 22, 23]]])
# tensor([[ 0,  1,  2,  3, 12, 13, 14, 15],
#         [ 4,  5,  6,  7, 16, 17, 18, 19],
#         [ 8,  9, 10, 11, 20, 21, 22, 23]])

 

먼저 torch.reshape(2,3,4)를 통해 2x3x4의 shape를 가지는 Tensor를 생성한다. 이는 3x4 shape가 2 층을 구성하는 형태이다. 

이후 torch.permute(1,0,2)를 통해 Depth와 행의 차원을 서로 swap 한다. 이후 torch.reshape(3,8)를 통해 Depth 정보는 유지하고, 각 층의 2x4 shape를 8로 만들어 원하는 형태의 Tensor로 변형시킬 수 있다. 

 

이해가 되지 않으면 아래 그림을 참고하기 바란다. 

10. Elementwise operations

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

print('Elementwise sum:')
print(x + y)
print(torch.add(x, y))
print(x.add(y))
# tensor([[ 6.,  8., 10., 12.]])

print('\nElementwise difference:')
print(x - y)
print(torch.sub(x, y))
print(x.sub(y))
# tensor([[-4., -4., -4., -4.]])

print('\nElementwise product:')
print(x * y)
print(torch.mul(x, y))
print(x.mul(y))
# tensor([[ 5., 12., 21., 32.]])

print('\nElementwise division')
print(x / y)
print(torch.div(x, y))
print(x.div(y))
# tensor([[0.2000, 0.3333, 0.4286, 0.5000]])

print('\nElementwise power')
print(x ** y)
print(torch.pow(x, y))
print(x.pow(y))
# tensor([[1.0000e+00, 6.4000e+01, 2.1870e+03, 6.5536e+04]])

 

Tensor 객체끼리의 연산도 가능하다. 이는 각각의 연산에 대한 Method와 동일한 결과를 반환하게 된다. 

 

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

print('Square root:')
print(torch.sqrt(x))
print(x.sqrt())
# tensor([[1.0000, 1.4142, 1.7321, 2.0000]])

print('\nTrig functions:')
print(torch.sin(x))
print(x.sin())
# tensor([[ 0.8415,  0.9093,  0.1411, -0.7568]])

print(torch.cos(x))
print(x.cos())
# tensor([[ 0.5403, -0.4161, -0.9900, -0.6536]])

 

제곱근이나 삼각함수 등 standard mathematical function을 적용시킬 수 있다. 

 

11. Reduction operations

torch.sum(input, dim, keepdim=False, *, dtype=None) → Tensor

x = torch.tensor([[1, 2, 3], 
                  [4, 5, 6]], dtype=torch.float32)
# tensor([[1., 2., 3.],
#         [4., 5., 6.]])

print(torch.sum(x))
print(x.sum())
# tensor(21.)

print(torch.sum(x, dim=0))
print(x.sum(dim=0))
# tensor([5., 7., 9.])

print(torch.sum(x, dim=1))
print(x.sum(dim=1))
# tensor([ 6., 15.])

 

앞서 활용한 것처럼 torch.sum Method를 통해 Tensor의 모든 원소의 합을 구할 수 있다. 이때, 각 차원별로 계산하고 싶으면 dim=을 통해 원하는 차원을 설정 해 줄 수 있다. 

 

x = torch.randn(3, 4, 5, 6)
print('x.shape: ', x.shape)
# x.shape:  torch.Size([3, 4, 5, 6])

print('x.sum(dim=0).shape: ', x.sum(dim=0).shape)
# x.sum(dim=0).shape:  torch.Size([4, 5, 6])
print('x.sum(dim=1).shape: ', x.sum(dim=1).shape)
# x.sum(dim=1).shape:  torch.Size([3, 5, 6])
print('x.sum(dim=2).shape: ', x.sum(dim=2).shape)
# x.sum(dim=2).shape:  torch.Size([3, 4, 6])
print('x.sum(dim=3).shape: ', x.sum(dim=3).shape)
# x.sum(dim=3).shape:  torch.Size([3, 4, 5])

 

다차원 Tensor에 대하여 특정 차원으로 sum을 한 결과의 크기를 예측 해 보자. 이는 원본 Tensor의 크기에서 해당 차원의 크기를 없앰으로써 쉽게 구할 수 있다. 

예를 들어, 3차원 Cube라고 생각하고, dim=0으로 sum을 구한다고 생각 해 보자. 이는 Depth에 대하여 sum을 수행하기 때문에 결국에는 한 층의 shape가 반환될 것이다. 즉, 원본 shape에서 dim=0의 크기를 제외시킨 크기이다. 

 

torch.min(input, dim, keepdim=False, *, out=None)

x = torch.tensor([[2, 4, 3, 5], [3, 3, 5, 2]], dtype=torch.float32)
print('Original tensor:')
print(x, x.shape)
# tensor([[2., 4., 3., 5.],
        # [3., 3., 5., 2.]]) torch.Size([2, 4])

print('\nOverall minimum: ', x.min())       # tensor(2.)

col_min_vals, col_min_idxs = x.min(dim=0)
print('values:', col_min_vals)              # values: tensor([2., 3., 3., 2.])
print('idxs:', col_min_idxs)                # idxs: tensor([0, 1, 0, 1])

row_min_vals, row_min_idxs = x.min(dim=1)   
print('values:', row_min_vals)              # values: tensor([2., 2.])
print('idxs:', row_min_idxs)                # idxs: tensor([0, 3])

 

또한, mean,max,min 과 같은 연산도 수행할 수 있다. 단순히 torch.min()으로 수행하면, Tensor의 최소값을 구할 수 있다. 

만약, dim= 을 활용해서 특정 차원에 대해서 수행한다면, 현재 2 차원이므로 dim=0에 대해서 수행한다면 최소 값을 가지는 열의 Index와 그에 해당되는 최소 값이 반환된다.

 

x = torch.randn(128, 10, 3, 64, 64)
print(x.shape)      # torch.Size([128, 10, 3, 64, 64])

x = x.mean(dim=1)
print(x.shape)      # torch.Size([128, 3, 64, 64])

x = x.sum(dim=2)
print(x.shape)      # torch.Size([128, 3, 64])

x = x.mean(dim=1, keepdim=True)
print(x.shape)      # torch.Size([128, 1, 64])

 

만약 다차원의 경우, mean/max/min을 계산하는 차원을 제외한 크기가 반환되는 Tensor의 크기임을 쉽게 유추할 수 있다. 이는 해당 차원에 대한 mean/max/min을 계산하기 때문이다. 이때, keepdim을 True로 하게 되면, 해당 차원을 유지해야 하기에 크기는 1로 유지된다. 

 

def zero_row_min(x: Tensor) -> Tensor:
    
    # 원본을 변형시키면 안되기에 torch.clone() 사용
    y = x.clone()
    
    # row_min을 계산하기 위해 dim을 1로 설정
    row_argmin = torch.argmin(x, dim=1)

    # integer indexing을 통해 최소값을 0으로 설정
    idx0 = torch.arange(0, x.shape[0])
    y[idx0, row_argmin] = 0

    return y

 

zero_row_min은 입력 Tensor에 대하여 각 행의 최소값을 찾아 0으로 설정하는 함수이다. 

원본을 변형시키지 않기 위해 torch.clone() 을 통해 복사본을 생성하고, torch.argmin을 통해 각 행의 가장 작은 값의 Index를 가져온다. 이를 Column Index로 하고, Row Index는 torch.arange를 통해 생성하여, integer tensor indexing을 통해 최소값을 0으로 할당한다. 

728x90
반응형