본문 바로가기
Artificial Intelligence/PyTorch

텐서 조작하기(2)

by YUNZEE 2024. 1. 3.
728x90
넘파이로 텐서 만들기(벡터와 행렬 만들기)

우선 numpy를 임포트 합니다.

import numpy as np

1차원 벡터를 만들어주고 벡터의 차원과 크기도 출력해 줍니다.

#numpy로 1차원 텐서인 벡터를 만들어준다.
t = np.array([0.,1.,2.,3.,4.,5.])
print(t)
print('Rank of t: ', t.ndim)#.ndim 몇차원인지 알려준다. 1차원 벡터
print('Shape of t: ', t.shape) #.shape 크기를 알려준다. 총 6개의 벡터

# [0. 1. 2. 3. 4. 5.]
# Rank of t:  1
# Shape of t:  (6,)

- 텐서의 크기(shape)를 표현할 때는, 1차원은 벡터, 2차원은 행렬, 3차원은 3차원 텐서라고 부른다. 현재는 벡터이므로 1차원이 출력된다.. shape는 크기를 출력한다. (6,)의 의미는 (1,6)을 의미한다. 다시 말해 (1 x 6)의 크기를 가진 벡터라는 의미다. ex) (5, ) = (1 x 5)

 

Numpy 기초 이해하기

- Numpy는 인덱스 시작을 0부터 한다.

- 0.0은 0번째 인덱스 가진 원소를 의미한다.

- -1은 맨 뒤부터 세는데 맨 마지막의 숫자를 가리킨다.(5)

#0번 인덱스를 가진 0,0원소가 출력된다.
print('t[0] t[1] t[-1]= ', t[0], t[1], t[-1]) #인덱스를 통한 원소 접근
# t[0] t[1] t[-1]=  0.0 1.0 5.0

범위 지정으로 원소를 불러올 수 있다. 이를 슬라이싱(slicing)이라고 한다. 사용 방법은 [시작 번호 : 끝 번호]를 통해 사용한다.  (* 알아야 될 점은 여기서 '끝 번호는 포함되지 않는다.')

ex) t [2:5] => [2,3,4]  /  t[2:6] => [2,3,5]

그리고 시작 번호나 끝 번호를 생략해서 슬라이싱을 하기도 한다. 시작 번호를 생략하면 처음부터 끝 번호까지, 끝 번호를 생략하면 시작 번호부터 끝까지 출력된다. (여기서는 지정한 범위가 없으니 끝 번호까지 나온다. )

# [시작 번호 : 끝 번호]로 범위 지정을 통해 가져온다.
print('t[2:5] t[4:-1]  = ', t[2:5], t[4:-1]) #끝 번호는 출력할 때 포함되지 않는다.
# t[2:5] t[4:-1]  =  [2. 3. 4.] [4.]

# 시작 번호를 생략한 경우와 끝 번호를 생략한 경우
print('t[:2] t[3:]     = ', t[:2], t[3:]) 
# t[:2] t[3:]     =  [0. 1.] [3. 4. 5.]

 

 

2D with Numpy

Numpy로 2차원 행렬을 만들기

t = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.], [10., 11., 12.]])
print(t, '\n')

print('Rank  of t: ', t.ndim)
print('Shape of t: ', t.shape)

# [[ 1.  2.  3.]
#  [ 4.  5.  6.]
#  [ 7.  8.  9.]
#  [10. 11. 12.]]

# Rank  of t:  2
# Shape of t:  (4, 3)

앞에서 설명했 던. ndim은 몇 차원인지 설명해 준다. 위에 코드에서 행렬은 2차원으로 출력된다.. shape는 크기를 출력한다. (4,3)으로 다른 표현으로는 (4 x 3)으로 4행 3열을 의미한다.

 

Numpy로 3차원 텐서도 만들 수는 있지만 이 시점에서 Numpy와 PyTorch를 비교하기 위해 PyTorch로 실습을 넘어가겠습니다.

1D with PyTorch
pip install torch #PyTorch 설치
import torch #선언 해주기
t = torch.FloatTensor([0., 1., 2., 3., 4., 5., 6.])
print(t, '\n')
#tensor([0., 1., 2., 3., 4., 5., 6.])

print(t.dim()) #rank. 차원
print(t.shape) #shape
print(t.size())#shape
# 1
# torch.Size([7])
# torch.Size([7])

dim()을 사용하면 현재 텐서의 차원을 보여준다. shape나 size()를 사용하면 크기를 확인할 수 있다.

1차원 텐서이며, 원소는 7개이다. 인덱스로 접근하는 것과 슬라이싱 하는 것은 Numpy실습과 같다.

2D with PyTorch

PyTorch로 2차원 텐서인 행렬을 만들어준다.

dim()과 size()를 사용하여 크기를 확인해 준다.

t = torch.FloatTensor([[1., 2., 3.],
                       [4., 5., 6.],
                       [7., 8., 9.],
                       [10., 11., 12.]
                      ])
print(t,'\n')
# tensor([[ 1.,  2.,  3.],
#         [ 4.,  5.,  6.],
#         [ 7.,  8.,  9.],
#         [10., 11., 12.]]) 

print(t.dim())  # rank. 즉, 차원
print(t.size()) # shape (4,3)
# 2
# torch.Size([4, 3])

현재 텐서는 2차원이며, (4,3)의 크기를 갖고 있다. 슬라이싱을 해보자

print(t[:,1]) #첫 번째 차원을 전체 선택한 상황에서 두 번째 것만 가져온다.
print(t[:,1].dim()) #위 경우의 차원은?: 1차원
print(t[:,1].size()) #위 경우의 크기는?: 4

# tensor([ 2.,  5.,  8., 11.])
# 1
# torch.Size([4])

슬라이싱의 결과로는 첫 번째 차원을 전체 선택하고, 그 상황에서 두 번째 차원의 1번 인덱스 값만을 가져온 경우를 보여준다. 이렇게 선택해서 가져온 값은 1차원 벡터의 크기는 4가 된다.

 

print(t[0:2, 0:2],'\n')

# tensor([[1., 2.],
#         [4., 5.]]) 

#첫 번째 차원을 전체 선택한 상황에서 두 번째 차원에서는 맨 마지막에서 첫 번째를 제외하고 다 가져온다.
print(t[:, :-1])

# tensor([[ 1.,  2.],
#         [ 4.,  5.],
#         [ 7.,  8.],
#         [10., 11.]])

 

브로드캐스팅(Broadcasting)

두 행렬 A, B가 있다고 가정해 보면 행렬의 덧셈과 뺄셈에 대해 알고 계신다면, 이 덧셈과 뺄셈을 할 때에는 두 행렬 A, B의 크기가 같아야 한다. 물론 이러한 규칙들은 딥 러닝을 하게 되면 불가피하게 크기가 다른 행렬 또는 텐서에 대해서 사칙 연산을 수행할 필요가 있는 경우가 생긴다. 이를 위해 파이토치에서는 자동으로 크기를 맞춰서 연산을 수행하게 만드는 브로드캐스팅이라는 기능을 제공한다.

#같은 크기일 때 연산을 하는 경우
m1 = torch.FloatTensor([[3,3]])
m2 = torch.FloatTensor([[2,2]])
print(m1 + m2)

# tensor([[5., 5.]])

브로드캐스팅을 통해 이를 연산한다.

#크기가 다른 텐서들 간의 연산
#Vector + Scalar
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([3]) #-> [3,3]
print(m1 + m2)

# tensor([[4., 5.]])
#2x1 Vector + 1x2 Vector
m1 = torch.FloatTensor([[1, 2]])#->[[1,2],[1,2]]
m2 = torch.FloatTensor([[3],[4]])# ->[[3,3],[4,4]]
print(m1 + m2)

# tensor([[4., 5.],
#         [5., 6.]])

브로드캐스팅은 자동으로 실행되는 기능이므로 편리하지만, 나중에 원하지 않은 결과가 나왔을 때 어디서 문제가 발생했는지 찾기가 굉장히 어려울 수 있다.

자주 사용되는 기능들

1) 행렬 곱셈과 곱셈의 차이(Matrix Multiplication Vs. Multiplication)

파이토치 텐서의 행렬 곱셈은 matmul()을 통해 수행한다.

#위의 결과는 2 x 2 행렬과 2 x 1 행렬(벡터)의 행렬 곱셈의 결과를 보여줍니다.
m1=torch.FloatTensor([[1, 2], [3, 4]])
m2=torch.FloatTensor([[1], [2]])
print('Shape of Matrix 1: ', m1.shape) # 2 x 2
print('Shape of Matrix 2: ', m2.shape) # 2 x 1
print(m1.matmul(m2)) # 2 x 1

# Shape of Matrix 1:  torch.Size([2, 2])
# Shape of Matrix 2:  torch.Size([2, 1])
# tensor([[ 5.],
#         [11.]])

행렬 곱셈이 아니라 element-wise곱셈이라는 것이 존재한다. 이는 동일한 크기의 행렬이 동일한 위치에 있는 원소끼리 곱하는 것을 말한다. 아래는 서로 다른 크기의 행렬이 브로드캐스팅이 된 후에 element-wise곱셈이 수행되는 것을 보여준다. 이는 *또는 mul()을 통해 수행한다.

m1=torch.FloatTensor([[1, 2], [3, 4]])
m2=torch.FloatTensor([[1], [2]])
print('Shape of Matrix 1: ', m1.shape) # 2 x 2
print('Shape of Matrix 2: ', m2.shape) # 2 x 1 ->[[1,1],[2,2]]
print(m1 * m2)
print(m1.mul(m2)) # 2 x 1

# Shape of Matrix 1:  torch.Size([2, 2])
# Shape of Matrix 2:  torch.Size([2, 1])
# tensor([[1., 2.],
#         [6., 8.]])
# tensor([[1., 2.],
#         [6., 8.]])

m1 행렬의 크기는(2,2)이고, m2행렬의 크기는 (2,1)이다. 이때 element-wise곱셈을 수행하면, 두 행렬의 크기는 브로드캐스팅이 된 후에 곱셈이 수행된다. 더 정확히는 여기서 m2의 크기가 변환된다.

 

2) 평균(Mean)

평균을 구하는 방법으로는 Numpy에서의 사용법과 유사하다. 우선 1차원인 벡터를 선언하여. mean()을 사용하여 원소의 평균을 구한다.

t = torch.FloatTensor([1, 2])
print(t.mean())

#tensor(1.5000)

1과 2인 평균인 1.5가 나옵니다. 이번에는 2차원인 행렬을 선언하여 .mean()을 사용해 봅시다. 우선 2차원 행렬을 선언한다.

#2차원 행렬을 선언하여 .mean()을 사용
t = torch.FloatTensor([[1,2],[3,4]])
print(t,"\n")
# tensor([[1., 2.],
#         [3., 4.]]) 

print(t.mean())
#tensor(2.5000)

#이번에는 dim. 즉,차원(dimension)을 인자로 주는 경우를 확인
print(t.mean(dim=0)) #첫 번째 차원을 제거한다.
#tensor([2., 3.])#1과 3의 평균을 구하고, 2와 4의 평균을 구한다.

dim=0이라는 것은 첫 번째 차원을 의미한다. 행렬에서 첫번째 차원은 '행'을 의미한다. 그리고 인자로 dim을 준다면 해당 차원을 제거한다는 의미가 된다. '열'만 남기겠다는 의미가 된다. 기존의 행렬의 크기는 (2,2)였지만 이를 수행하면 열의 차원만 보존되면서(1,2)가 된다. 이는 (2,)와 같으면 벡터이다.

# 두 번째 차원을 제거한다.
print(t.mean(dim=1)) #(dim=-1)일때 와도 출력 결과는 같다.

#열의 차원이 제거되어야 하므로(2,2)의 크기에서 (2,1)의 크기가 된다. 
# tensor([1.5000, 3.5000])

열 차원이 제거되어야 하므로(2, 2)의 크기에서 (2,1)의 크기가 된다.

 

3) 덧셈(Sum)

덧셈(Sum)은 평균(Mean)과 연산 방법이나 인자가 의미하는 바는 정확하게 동일하다. 다만, 평균이 아니라 덧셈일 뿐

t = torch.FloatTensor([[1, 2], [3, 4]])

print(t.sum()) # 단순히 원소 전체의 덧셈을 수행
print(t.sum(dim=0)) # 행을 제거
print(t.sum(dim=1)) # 열을 제거
print(t.sum(dim=-1)) # 열을 제거

# tensor(10.)
# tensor([4., 6.]) 1+3=4 / 2+4=6
# tensor([3., 7.]) 1+2=3 / 3+4=7
# tensor([3., 7.]) 1+2=3 / 3+4=7

4) 최대(MAX)와 아그맥스(ArgMax)

최대는 원소의 최댓값을 리턴하고, 아그맥스는 최대값을 가진 인덱스를 러턴 합니다. 

(2,2) 크기의 행렬을 선언하고 Max를 사용해 봅시다.

t = torch.FloatTensor([[1, 2], [3, 4]])
print(t, "\n")

print(t.max(), "\n")#최대값 선언
print(t.max(dim=0)) #행의 0번 째 차원을 제거 그럼 남는건? '1번 째'

# tensor([[1., 2.],
#         [3., 4.]]) 

# tensor(4.) 

# torch.return_types.max(
# values=tensor([3., 4.]),
# indices=tensor([1, 1]))

만약 두 개를 함께 리턴 받는 것이 아니라 max 또는 argumax만 리턴 받고 싶다면 다음과 같이 리턴값에도 인덱스를 부여하면 된다. 0번 인덱스를 사용하면 max값만 받아올 수 있고, 1번 인덱스를 사용하면 argmax값만 받아올 수 있다.

print('Max: ', t.max(dim=0)[0])
print('Argmax: ', t.max(dim=0)[1])

# Max:  tensor([3., 4.])
# Argmax:  tensor([1, 1])
728x90

'Artificial Intelligence > PyTorch' 카테고리의 다른 글

텐서 조작하기(3)  (4) 2024.01.10
텐서 조작하기  (2) 2023.12.29
데이터의 분리  (4) 2023.12.26
파이썬, 아나콘다, jupyter notebook 설치방법  (4) 2023.12.23