본문 바로가기
네이버 부스트캠프 AI Tech 7기/Machine Learning Basic

PyTorch 입문 - Tensor 기본 함수와 연산 이해하기

by YS_LEE 2024. 8. 9.
반응형

PyTorch를 위한 배경지식

  • 표본분산
  • 연속 균등 분포
  • 표준 정규 분포
  • 상관 관계

PyTorch 자료형과 Tensor

Data Type

  • 텐서 생성시 다음과 같이 데이터 타입을 지정할 수 있다.
  • t = torch.tensor(10.2, dtype=torch.float64)
  • Type Casting (타입 변환)은 다음과 같다.Tensor
  • t = t.type(torch.IntTensor) # torch.FloatTensor, torch.DoubleTensor
  • 0D tensor (=Scalar)
  • 1D tensor (=Vector)
  • 2D tensor (=Matrix)
  • 3D tensor
  • 4,5,6...D tensor

Tensor Basic Functions

  • min(), max(), sum(), prod(), mean(), var(), std()
  • prod()는 모든 요소의 곱을 반환한다.
  • var()std()는 지정한 차원에 대한 분산과 표준편차를 계산한다.

Tensor 생성, 복제, 삭제

Tensor 생성

  • 직접 생성
  • t = torch.tensor([[1, 2, 3], [4, 5, 6]])
  • 특정 값을 초기화
  • t = torch.zeros(5) # (5) 크기의 0로 초기화된 Tensor t = torch.ones([3, 2]) # (3,2) 크기의 1로 초기화된 Tensor
  • 특정 구간으로 초기화
  • # 0에서 10까지 1씩 증가하는 1-D Tensor t = torch.arange(start=0,end=10,step=1) # t.shape: torch.Size([10])
  • 난수로 초기화
  • torch.rand(size) # 연속 균등 분포를 따르는 난수 Tensor torch.randn(size) # 표준 정규 분포를 따르는 난수 Tensor torch.randint(low,high,size) # [low, high) 범위의 정수 난수 Tensor

torch.rand_like(t) # t와 동일한 형태의 연속 균등 분포 난수 Tensor
torch.randn_like(t) # t와 동일한 형태의 표준 정규 분포 난수 Tensor


- list, numpy에서 가져오기  
```python
s = [1, 2, 3, 4, 5, 6]
t = torch.tensor(s)

import numpy as np
s = np.array([[0, 1],[2, 3]])
v = torch.from_numpy(s)
  • 초기화하지 않고 생성
    목적: 성능 향상, 메모리 최적화
t = torch.empty([2, 4])

Tensor 복제

  • torch.clone()
    • 복사한 텐서를 새로운 메모리에 할당(deepcopy()와 유사)
    • 기존 계산 그래프를 유지
    • e.g. 딥러닝 모델 계산에서 분기를 만드는 경우에 같은 데이터가 서로 다른 layer로 들어갈 때, 이러한 구조의 가독성을 높이기 위해 사용할 수 있음
  • torch.detach()
    • 복사 대상 텐서와 메모리를 공유
    • gradient 전파가 안되는 텐서 생성 (no_grad() 적용한 텐서와 기능적으로 동일)
    • e.g. 딥러닝 모델로 부터 받은 output 값 평가를 위해 특정 메트릭을 적용하여 사용되는 연산에 포함되지 않도록 하기 위해 사용할 수 있음
  • torch.clone().detach()
    • 기존 텐서를 복사한 새로운 텐서를 생성하지만 기울기에 영향을 주지는 않겠다는 의미

Tensor 삭제(메모리 해제)

  • torch.cuda.empty_cache() 를 통해 사용되지 않은 GPU상의 cache를 정리
  • del 함수를 사용하여 메모리에서 해제
  • torch.cuda.empty_cache() t = torch.tensor(\[\[1, 2, 3\], \[4, 5, 6\]\]) del t

Tensor indexing & slicing

  • 1차원 tensor는 python list와 동일하다.
  • 2차원 tensor 조작은 다음과 같다.
t = torch.tensor([[1, 2, 3], [4, 5, 6]])

t[1, 2] # 1행, 2열의 요소에 접근 -> tensor(6)
t[0, 1:] # 0행, 1열 부터 끝열(2열)까지 -> tensor([2, 3])
t[:, -2:] # 전체 행, 뒤에서부터 두번째 열(1열) 부터 끝열(2열)까지 -> tensor([[2, 3], [5, 6]])
t[-1, ...] # 뒤에서부터 첫번째 행(1행), 전체 열 -> tensor([4, 5, 6])

Tensor 모양 변경

contiguous , storage에 대한 이해

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

new_t = t[:2, :2] # tensor([[1, 2], [4, 5]])
new_t.is_contiguous() # False
new_t_cont = new_t.contiguous()
new_t_cont.is_contiguous() # True

contiguous()를 사용하면 연속체로 만들어 주는데, 만약 연속체가 아닌 경우 새로운 storage를 만들어 할당한다.

view()

입력

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

print(t.view(1, -1))
print(t.view(2,-1))
print(t.view(3, -1))
print(t.view(6, -1))

출력

tensor([[1, 2, 3, 4, 5, 6]])

tensor([[1, 2, 3],
        [4, 5, 6]])

tensor([[1, 2],
        [3, 4],
        [5, 6]])

tensor([[1],
        [2],
        [3],
        [4],
        [5],
        [6]])
  • shape이 [2,3] 인 Tensor 't'에 대해 s = t[:2, :1]일 때, view를 사용해 s의 모양을 변경할 수 있는 이유
    PyTorch에서는 텐서의 슬라이스 연산를 통해 생성된 텐서가 메모리 연속성을 유지하고 있다면(슬라이스를 얻는 과정에서 데이터의 순서가 바뀌지 않았다면) 자동으로 메모리에서 연속적인 형태로 다뤄질 수 있다.
    1. 슬라이스 연산의 연속성 유지: t[:2, :1]과 같은 슬라이스 연산이 메모리에서 연속적인 영역을 참조하는 경우, PyTorch는 이를 자동으로 인식하고 s를 연속적인 텐서로 처리한다.
    2. 데이터의 실제 복사 없음: 슬라이스 연산은 원본 텐서의 데이터를 복사하지 않고 참조만 한다. 원본 텐서가 연속적이었고, 슬라이스된 텐서가 메모리에서 연속적인 영역을 참조하는 경우, 슬라이스된 텐서도 연속성을 가지게 된다. 따라서 view를 사용할 수 있다.
      view를 사용하려면 텐서가 연속적이어야 하지만, 슬라이스 연산으로 얻어진 텐서가 메모리에서 연속적인 부분을 참조하고 있다면 PyTorch는 이를 연속적으로 간주한다. 따라서 t[:2, :1]으로 생성된 s가 연속적인 텐서로 다뤄지기 때문에 view를 사용할 수 있다.
  • 정리: view() 메소드를 사용할 수 있는데 is_contiguous() 메소드가 False를 반환하는 이유
    • is_contiguous()는 실제로 메모리 공간에서 인접한가를 판단하는 것이기 때문에 t[0][0]의 다음 원소인t[0][1]은 인접하지만 s1[0][0]의 다음 원소인 s1[1][0]은 인접하지 않다.
    • view()는 stride(보폭)이 일정하면 연속적으로 참조할 수 있기 때문에 실제로 메모리 공간에서 인접하다면 사용 가능하고, 실제로 인접하지 않더라도 폭이 일정하다면 사용할 수 있다.

입력

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

s1 = t[:2,:1]
print(s1)
print(s1.is_contiguous())
print(s1.view(1,-1))
print(t.untyped_storage().data_ptr() == s1.untyped_storage().data_ptr())  

출력

tensor([[1],
        [4]])
False
tensor([[1, 4]])
True

입력

s2 = t[:, :2]
print(s2)
print(s2.is_contiguous())
print(s2.view(1,-1))
print(t.untyped_storage().data_ptr() == s2.untyped_storage().data_ptr())

출력


tensor([[1, 2],
        [4, 5]])
False
RuntimeError: view size is not compatible with input tensor's size and stride
True

flatten()

  • tensor 평탄화
    입력
  • t = torch.tensor([[1, 2, 3], [4, 5, 6]])

t.flatten() # 또는 torch.flatten(t)


출력
``` python
tensor([1, 2, 3, 4, 5, 6])
  • tensor 특정 차원 평탄화
    입력
  • t = torch.tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])

print(t.flatten(0))
print(t.flatten(1))
print(t.flatten(2))


출력
``` python

tensor([1, 2, 3, 4, 5, 6, 7, 8])

tensor([[1, 2, 3, 4],
        [5, 6, 7, 8]])

tensor([[[1, 2],
         [3, 4]],
         [[5, 6],
         [7, 8]]])

reshape()

  • reshape() vs view()
    view() 메서드와 달리 메모리가 연속적이지 않아도 사용 가능
    입력
t = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(t.reshape(1, 6))
print(t.reshape(3,2))
print(t.reshape(6, 1))

출력

tensor([[1, 2, 3, 4, 5, 6]])
tensor([[1, 2],
        [3, 4],
        [5, 6]])

tensor([[1],
        [2],
        [3],
        [4],
        [5],
        [6]])

transpose()

  • 특정한 두 차원의 축을 서로 바꾸는 메서드
  • t = torch.tensor([[1, 2, 3], [4, 5, 6]]) t.transpose(0,1) >>> tensor([[1, 2], [3, 4], [5, 6]])

squeeze() & unsqueeze()

  • squeeze()는 지정한 차원에 대해 크기가 1이라면 축소시키고, 따로 dim을 지정하지 않으면 모든 차원에 대해 수행한다.
  • unsqueeze()는 지정한 차원에 크기가 1인 차원을 추가하여 확장한다.
  • t = torch.tensor([[1, 2, 3], [4, 5, 6]]) t.squeeze(dim=1) >>> tensor([[1, 2], [3, 4], [5, 6]])

Tensor 결합

stack()

  • Tensor들을 새로운 차원으로 결합 - 차원의 개수가 증가
t1 = torch.zeros(2, 2)
t2 = torch.ones(2, 2)

t3 = torch.stack([t1, t2], dim=0)
t4 = torch.stack([t1, t2], dim=1)

print(t3)
>>> tensor([[[1., 1.],
     [1., 1.]],

    [[0., 0.],
     [0., 0.]]])

print(t3.shape)
>>> torch.Size([2, 2, 2])

print(t4)
>>> tensor([[[1., 1.],
     [0., 0.]],

    [[1., 1.],
     [0., 0.]]])

print(t4.shape)
>>> torch.Size([2, 2, 2])

cat()

  • Tensor들을 주어진 차원에 맞춰 연결 - 개수가 유지되고 해당 차원이 늘어남
  • dim 옵션에 따라 concat 되는 방향 설정
t1 = torch.ones(2, 2)
t2 = torch.zeros(2, 2)

t3 = torch.cat([t1, t2], dim=0)
t4 = torch.cat([t1, t2], dim=1)

print(t3)
>>> tensor([[1., 1.],
    [1., 1.],
    [0., 0.],
    [0., 0.]])

print(t3.shape)
>>> torch.Size([4, 2])

print(t4)
>>> tensor([[1., 1., 0., 0.],
    [1., 1., 0., 0.]])

print(t4.shape)
>>> torch.Size([2, 4])

이미지 출처: https://sanghyu.tistory.com/85

Tensor 확장

expand()

  • 주어진 Tensor의 차원 중 일부의 크기가 1일 때, 해당 차원의 크기를 확장repeat()
  • Tensor의 요소들을 반복하는 방식으로 크기 확장

Tensor 연산 1

기본 연산

  • add() 더하기
  • sub() 빼기
  • mul() 곱하기 (스칼라 또는 요소별 곱)
  • div() 나누기
  • pow() 거듭 제곱, 거듭 제곱근

비교 연산

  • eq(v,w) 대응 요소들이 같은지를 비교
  • ne(v,w) 대응 요소들이 다른지를 비교
  • gt(v,w) 텐서 v의 요소들이 텐서 w의 대응 요소들보다 큰지를 비교
  • ge(v,w) 텐서 v의 요소들이 텐서 w의 대응 요소들보다 크거나 같은지를 비교
  • lt(v,w) 텐서 v의 요소들이 텐서 w의 대응 요소들보다 작은지를 비교
  • le(v,w) 텐서 v의 요소들이 텐서 w의 대응 요소들보다 작거나 같은지를 비교

논리 연산

  • torch.logical_and(x,y) AND(논리곱) 연산
  • torch.logical_or(x,y) OR(논리합) 연산
  • torch.logical_xor(x,y) XOR(배타적 논리합) 연산

in-place 연산 vs out-of-place 연산

  • add_()는 in-place operation 이고 add()는 out-of-place operation이다. in-place는 메모리의 값을 바꿔주고, out-of-place는 연산 결과를 반환한다.

크기가 다른 Tensor 연산

  • 같은 크기가 되도록 확장하여 연산이 이루어짐

Tensor 연산 2

Tensor Norm (노름, 놈)

  • L1 Norm (Manhatten norm)
  • L2 Norm (euclidean norm)
  • L∞ Norm (infinite norm)
t = torch.tensor([4, 3], dtype=torch.float32)
torch.norm(t, p=1)
>>> 7.0


t = torch.tensor(\[4, 3\], dtype=torch.float32)  
torch.norm(t, p=2)

> > > 5.0

t = torch.tensor(\[4, 3\], dtype=torch.float32)  
torch.norm(t, p=float('inf')) # torch.max(t.abs())와 같음

> > > 4.0

유사도 계산

  • manhattan similarity (맨해튼 유사도)
  • euclidean similarity (유클리드 유사도)
  • cosine similarity (코사인 유사도)
t1 = torch.tensor([1, 0, 2], dtype=torch.float32)
t2= torch.tensor([0, 1, 2], dtype=torch.float32)

# 맨해튼 유사도
manhattan_distance = torch.norm(t1 - t2, p = 1)
manhattan_similarity = 1 / (1 + manhattan_distance)
manhattan_similarity
>>> 0.3333333432674408

# 유클리드 유사도
euclidean_distance = torch.norm(t1 - t2, p = 2)
euclidean_similarity = 1 / (1 + euclidean_distance)
euclidean_similarity
>>> 0.41421353816986084

# 코사인 유사도
dot_product = torch.dot(t1, t2)
norm_t1 = torch.norm(t1, p = 2)
norm_t2 = torch.norm(t2, p = 2)
cosine_similarity = dot_product / (norm_t1 * norm_t2)
cosine_similarity
>>> 0.800000011920929

행렬의 곱셈

  • matmul(), mm(), @
  • matmul()은 broadcasting을 지원
  • mm()은 broadcasting을 지원X
  • Broadcasting: 텐서 연산을 수행할 때 자동적으로 크기를 맞춰서 연산을 수행하는 기능
  • Broadcasting은 의도와 다르게 연산이 동작할 수 있어 주의가 필요하다(mm()이 비교적 안전)
weight = torch.randn(out_features, in_features)
bias = torch.randn(out_features)

x @ weight.t() + bias

x.matmul(weight.t()) + bias

x.mm(weight.t()) + bias
반응형