벡터, 행렬, 텐서
스칼라 : 차원이 없는 값, 숫자가 1개
벡터 : 1차원으로 구성된 값, 숫자가 여러개 나열 됨 (그림에서 1d-tensor)
행렬 : 2차원으로 구성된 값 (2d-tensor)
텐서 : 3차원 이상부터 텐서라고 부르지만 벡터를 1차원 텐서, 행렬을 2차원 텐서로 부름
2D Tensor
딥러닝에서는 2차원 텐서를 batch size * dimension으로 표현함
즉, 행렬에서 행의 크기가 batch size, 열의 크기가 dim.
데이터 하나의 크기가 256이라면, 벡터 하나에 숫자가 256개 존재한다는 의미
만약 훈련 데이터가 총 3000개라면, 전체 훈련 데이터의 개수는 3000 * 256개
batch size = 64이면, 컴퓨터는 한번에 64 * 256개의 데이터를 처리
그럼 컴퓨터는 총 47번(3000/64 = 46.875)의 처리를 하게 됨
3D Tensor
이미지나 영상 처리에서는 가로, 세로를 가진 이미지를 가지기 때문에, 위와 같은 3차원 텐서가 됨
자연어 처리 분야에서는 (batch size, 문장 길이, 단어 벡터의 차원)의 3차원 텐서를 사용
1D with PyTorch
파이토치로 1차원 텐서 벡터 t 생성
t = torch.FloatTensor([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])
2D with PyTorch
t = torch.FloatTensor([[1., 2., 3.],
[4., 5., 6.],
[7., 8., 9.],
[10., 11., 12.]
])
print(t.dim()) # rank. 즉, 차원
print(t.size()) # shape
2
torch.Size([4, 3])
print(t[:, 1]) # 첫번째 차원을 전체 선택한 상황에서 두번째 차원의 첫번째 것만 가져온다.
print(t[:, 1].size()) # ↑ 위의 경우의 크기
tensor([ 2., 5., 8., 11.])
torch.Size([4])
print(t[:, :-1]) # 첫번째 차원을 전체 선택한 상황에서 두번째 차원에서는 맨 마지막에서 첫번째를 제외하고 다 가져온다.
tensor([[ 1., 2.],
[ 4., 5.],
[ 7., 8.],
[10., 11.]])
브로드캐스팅(Broadcasting)
행렬의 덧셈과 뺄셈에서는 두 행렬의 크기가 같아야 함
행렬의 곱셈에서는 한 행렬의 마지막 차원과 다른 행렬의 첫번째 차원이 일치해야 함
그러나, 파이토치에서는 자동으로 크기를 맞춰서 연산을 수행하게 만드는 브로드캐스팅 기능이 존재
# Vector + scalar
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([3]) # [3] -> [3, 3]
print(m1 + m2)
tensor([[4., 5.]])
# 2 x 1 Vector + 1 x 2 Vector
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([[3], [4]]) # [3] -> [3,3] , [4] -> [4, 4]
print(m1 + m2)
tensor([4., 5.],
[5., 6.]])
브로드캐스팅은 자동으로 수행되므로 사용자는 나중에 원하는 결과가 나오지 않았더라도 어디서 문제가 발생했는지 찾기가 굉장히 어려움
행렬 곱셈과 곱셈의 차이 (Matrix Multiplication Vs. Multiplication)
행렬로 곱셈을 하는 방법은 크게 두 가지
바로 행렬 곱셈(.matmul)과 원소 별 곱셈(.mul)
원소 별 곱셈 (element-wise 곱셈)
동일한 크기의 행렬이 동일한 위치에 있는 원소끼리 곱하는 것
아래는 서로 다른 크기의 행렬이 브로드캐스팅이 된 후에 element-wise 곱셈이 수행되는 예시
*이나 mul()을 이용해 사용
m1 = torch.FloatTensor([[1, 2], [3, 4]])
m2 = torch.FloatTensor([[1], [2]]) # [1] -> [1, 1] , [2] -> [2, 2]
print('Shape of Matrix 1: ', m1.shape) # 2 x 2
print('Shape of Matrix 2: ', m2.shape) # 2 x 1
print(m1 * m2) # 2 x 2
print(m1.mul(m2))
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.]])
평균 구하기
1차원 평균
t = torch.FloatTensor([1, 2])
print(t.mean()) #tensor(1.5000)
2차원 평균
t = torch.FloatTensor([[1, 2], [3, 4]])
print(t.mean()) # tensor(2.5000)
4개의 원소의 평균인 2.5가 나옴
2차원 평균을 구할 때, 차원을 인자로 주는 경우
print(t.mean(dim=0)) #tensor([2., 3.])
dim=0이라는 것은 첫번째 차원을 의미
dim을 인자로 준다는 것은 해당 차원(행)을 제거한다는 것
다시 말해 '열'만 남기겠다는 것 -> (1,2)의 행렬 크기를 가지게 됨
print(t.mean(dim=1)) # tensor([1.5000, 3.5000])
각 행(row)에 대해 열(column) 방향으로 평균을 계산
열의 차원이 제거되어야 하므로 (2, 2)의 크기에서 (2, 1)의 크기가 됨
하지만 (2 × 1)은 결국 1차원이므로 (1 × 2)와 같이 표현되면서 위와 같이 [1.5, 3.5]로 출력
덧셈
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.])
tensor([3., 7.])
tensor([3., 7.])
View
원소의 수를 유지하면서 텐서의 크기 변경
파이토치 텐서의 뷰(View)는 넘파이에서의 리쉐이프(Reshape)와 같은 역할
t = np.array([[[0, 1, 2],
[3, 4, 5]],
[[6, 7, 8],
[9, 10, 11]]])
ft = torch.FloatTensor(t)
print(ft.shape) # torch.Size([2, 2, 3])
1) 3차원 텐서에서 2차원 텐서로 변경
print(ft.view([-1, 3])) # ft라는 텐서를 (?, 3)의 크기로 변경
print(ft.view([-1, 3]).shape)
tensor([[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.],
[ 9., 10., 11.]])
torch.Size([4, 3])
-1은 첫번째 차원은 사용자가 잘 모르겠으니 파이토치에 맡기겠다는 의미이고,
3은 두번째 차원의 길이는 3을 가지도록 하라는 의미
내부적으로 크기 변환은 다음과 같이 이루어 짐. (2, 2, 3) -> (2 × 2, 3) -> (4, 3)
view의 규칙
- view는 기본적으로 변경 전과 변경 후의 텐서 안의 원소의 개수가 유지되어야 합니다.
- 파이토치의 view는 사이즈가 -1로 설정되면 다른 차원으로부터 해당 값을 유추합니다.
2) 3차원 텐서의 크기 변경
3차원 텐서에서 3차원 텐서로 차원은 유지하되, 크기(shape)를 바꾸는 작업
(2 × 2 × 3) 텐서를 (? × 1 × 3) 텐서로 변경하라고 하면 ?는 몇 차원?
?는 4
print(ft.view([-1, 1, 3]))
print(ft.view([-1, 1, 3]).shape)
tensor([[[ 0., 1., 2.]],
[[ 3., 4., 5.]],
[[ 6., 7., 8.]],
[[ 9., 10., 11.]]])
torch.Size([4, 1, 3])
Squeeze
스퀴즈는 차원이 1인 경우에는 해당 차원을 제거
ft = torch.FloatTensor([[0], [1], [2]])
print(ft)
print(ft.shape)
tensor([[0.],
[1.],
[2.]])
torch.Size([3, 1])
print(ft.squeeze())
print(ft.squeeze().shape)
tensor([0., 1., 2.])
torch.Size([3])
1이었던 두번째 차원이 제거되면서 (3,)의 크기를 가지는 텐서로 변경되어 1차원 벡터가 된 것
Unsqueeze
특정 위치에 1인 차원을 추가
ft = torch.Tensor([0, 1, 2])
print(ft.shape)
torch.Size([3])
현재는 차원이 1개인 1차원 벡터
첫번째 차원에 1인 차원을 추가
print(ft.unsqueeze(0)) # 인덱스가 0부터 시작하므로 0은 첫번째 차원을 의미한다.
print(ft.unsqueeze(0).shape)
tensor([[0., 1., 2.]])
torch.Size([1, 3])
Concat
두 텐서를 연결하는 방법
x = torch.FloatTensor([[1, 2], [3, 4]])
y = torch.FloatTensor([[5, 6], [7, 8]])
print(torch.cat([x, y], dim=0))
tensor([[1., 2.],
[3., 4.],
[5., 6.],
[7., 8.]])
dim=0을 인자로 했더니 두 개의 (2 × 2) 텐서가 (4 × 2) 텐서가 됨
print(torch.cat([x, y], dim=1))
tensor([[1., 2., 5., 6.],
[3., 4., 7., 8.]])
dim=1을 인자로 했더니 두 개의 (2 × 2) 텐서가 (2 × 4) 텐서가 됨
Stacking
x = torch.FloatTensor([1, 4])
y = torch.FloatTensor([2, 5])
z = torch.FloatTensor([3, 6])
print(torch.stack([x, y, z]))
tensor([[1., 4.],
[2., 5.],
[3., 6.]])
위 stack 방법은 아래와 같음
print(torch.cat([x.unsqueeze(0), y.unsqueeze(0), z.unsqueeze(0)], dim=0))
x, y, z는 기존에는 전부 (2,)의 크기를 가짐
그런데 .unsqueeze(0)을 하므로서 3개의 벡터는 전부 (1, 2)의 크기의 2차원 텐서로 변경
위에서는 torch.stack([x, y, z])라는 한 번의 커맨드로 수행했지만,
연결(concatenate)로 이를 구현하려고 했더니 꽤 복잡해짐
print(torch.stack([x, y, z], dim=1))
tensor([[1., 2., 3.],
[4., 5., 6.]])
ones_like와 zeros_like - 0으로 채워진 텐서와 1로 채워진 텐서
x = torch.FloatTensor([[0, 1, 2], [2, 1, 0]])
print(x)
tensor([[0., 1., 2.],
[2., 1., 0.]])
print(torch.ones_like(x)) # 입력 텐서와 크기를 동일하게 하면서 값을 1로 채우기
tensor([[1., 1., 1.],
[1., 1., 1.]])
Reference
'파이토치' 카테고리의 다른 글
[파이토치] 파이토치로 Mel-Spectrogram 생성해보기 (0) | 2025.02.20 |
---|---|
[파이토치] 파이토치로 Convolution 이해하기, torch.nn/nn.Conv1d/nn.Conv2d (0) | 2024.12.18 |
[파이토치] 파이토치에서 데이터 로드하기 (0) | 2024.11.07 |
[파이토치] 선형 회귀 모델 구현하기 (0) | 2024.11.07 |