2025. 1. 6. 17:48ㆍLLM(Large Language Model)의 기초/머신러닝과 딥러닝
1.파이토치
* PyTorch는 파이썬 기반의 오픈소스 딥러닝 프레임워크로, 파이썬 코드로 AI 모델을 직관적으로 만들고 학습할 수 있도록 도와주는 도구입니다.
* 특히 동적 계산 그래프 방식을 사용하기 때문에 코드 실행 시점에 실시간으로 계산 흐름이 결정되어 디버깅과 수정이 쉽고, GPU 가속과 자동 미분 기능을 통해 대규모 모델도 빠르게 학습할 수 있습니다.
> 동적 계산 그래프 방식
* 동적 계산 그래프 방식은 딥러닝 모델이 학습 및 예측을 수행할 때 계산 그래프를 실행 시점(runtime)에 실시간으로 생성 및 수정하는 방식입니다.
* 이 방식은 조건문, 반복문 등 복잡한 논리 구조를 유연하게 처리할 수 있으며, 주로 PyTorch와 같은 프레임워크에서 사용됩니다.
* 계산 그래프는 입력 데이터를 바탕으로 연산을 수행하면서 그래프를 생성하고, 역전파를 통해 미분을 계산하며, 최종적으로 가중치를 업데이트하는 과정을 거칩니다.
* 이러한 특성 덕분에 디버깅이 용이하고 연구 및 개발 속도가 빠르며 직관적인 코드 작성이 가능합니다.
1-1. 스칼라(Scalar)
* 스칼라(Scalar)는 단 하나의 숫자(정수, 실수 등)만을 담는 자료형을 말합니다.
* 파이토치(PyTorch)에서 스칼라는 0차원 텐서(0-dimensional Tensor)로 표현합니다
* 즉, 텐서의 차원(Shape)이 전혀 없는 상태를 의미합니다.
1. 파이토치 설치
!pip install torch
2. 파이토치 import
import torch
파이토치
예시 1)
import torch
var1 = torch.tensor(5)
print(var1)
print(var1.shape) # 0 차원 텐서
설명
-->
import torch
# 정수 5를 값으로 가지는 0차원 텐서를 생성합니다.
var1 = torch.tensor(5)
# var1의 값을 출력합니다. (값: tensor(5))
print(var1) #tensor(5)
# var1의 모양(shape)을 출력합니다. 0차원 텐서이므로 shape은 빈 튜플()로 나타납니다.
# 0차원 텐서란 스칼라 값을 표현하는 텐서로, 단일 숫자 값을 가진다고 볼 수 있습니다.
print(var1.shape) # 0차원 텐서 # torch.Size([])
예시 2)
var2 = torch.tensor([10])
print(var2.shape) # torch.Size([1]) 1차원 텐서, 스칼라가 아님
-->
import torch
# 정수 10을 요소로 가지는 1차원 텐서를 생성합니다.
# 여기서 리스트 [10]을 사용했기 때문에, 결과적으로 길이가 1인 1차원 텐서가 생성됩니다.
var2 = torch.tensor([10])
# var2의 모양(shape)을 출력합니다.
# shape이 torch.Size([1])로 출력되며, 이는 1개의 요소를 가진 1차원 텐서를 의미합니다.
# 이 텐서는 스칼라(0차원 텐서)가 아니라, 1개의 요소를 가진 1차원 텐서입니다.
print(var2.shape) # torch.Size([1]) 1차원 텐서, 스칼라가 아님
예시 3)
var3 = torch.tensor(3)
result = var1 + var2 #5 + 3
print(result) # 차원이 큰 데이터로 계산
-->
import torch
# 정수 3을 값으로 가지는 0차원 텐서를 생성합니다.
var3 = torch.tensor(3)
# 0차원 텐서 var1 (값: 5)와 1차원 텐서 var2 (값: [10])를 더합니다.
# var1은 0차원 텐서, var2는 1차원 텐서이므로,
# PyTorch는 브로드캐스팅(broadcasting)을 수행하여 연산을 진행합니다.
# 0차원 텐서 var1의 값(5)을 1차원 텐서 var2의 각 요소에 더합니다.
result = var1 + var2 # 5 + [10] = [15]
# result는 1차원 텐서이며, 값은 [15]입니다.
print(result) # 차원이 큰 데이터(var2)로 계산 결과를 반환
예시 4)
result = var1 + var3
print(result)
print(result.item()) # 파이썬 숫자로 추출함
-->
import torch
# 0차원 텐서 var1 (값: 5)와 0차원 텐서 var3 (값: 3)을 더합니다.
# 두 텐서 모두 0차원이므로 브로드캐스팅이 필요하지 않으며,
# 결과는 0차원 텐서로 반환됩니다.
result = var1 + var3 # 5 + 3 = 8
# result는 0차원 텐서이며, 값은 tensor(8)입니다.
print(result) # tensor(8)
# result의 값을 파이썬의 기본 숫자 타입(int)으로 추출합니다.
# 0차원 텐서에서 값을 추출할 때는 .item() 메서드를 사용합니다.
print(result.item()) # 값: 8 #8
1-2.벡터(Vector)
* 벡터(Vector)는 하나 이상의 원소가 일렬로 나열된 1차원 텐서(1D Tensor)를 의미합니다.
* 파이토치(PyTorch)에서 벡터는 일반적으로 torch.tensor([...]) 형태로 만들며, 이때 텐서의 shape(차원)가 (n,) 형태입니다.
* 즉, 원소가 n개 들어 있으면 1차원 벡터가 됩니다.
예 1)
var1 = torch.tensor([1.0, 2.0, 3.0])
print(var1)
print(var1.shape)
var2 = var1 + 10
print(var2)
var3 = var1 * 2
print(var3)
var4 = torch.tensor([4.0, 5.0, 6.0])
result = var1 + var4
print(result)
-->
import torch
# 1차원 텐서를 생성합니다. 값은 [1.0, 2.0, 3.0]이며, 데이터 타입은 float입니다.
var1 = torch.tensor([1.0, 2.0, 3.0])
# var1의 값을 출력합니다. 결과: tensor([1., 2., 3.])
print(var1) #tensor([1., 2., 3.])
# var1의 모양(shape)을 출력합니다. 1차원 텐서로, 3개의 요소를 포함하고 있으므로 shape은 torch.Size([3])입니다.
print(var1.shape) #torch.Size([3])
# 텐서 var1에 스칼라 값을 더합니다. (브로드캐스팅 발생)
# 각 요소에 10이 더해져서 새로운 텐서 var2가 생성됩니다.
# 결과: tensor([11.0, 12.0, 13.0])
var2 = var1 + 10
print(var2) #tensor([11.0, 12.0, 13.0])
# 텐서 var1의 각 요소에 2를 곱합니다. (브로드캐스팅 발생)
# 각 요소에 스칼라 곱셈이 적용되어 새로운 텐서 var3가 생성됩니다.
# 결과: tensor([2.0, 4.0, 6.0])
var3 = var1 * 2
print(var3) #tensor([2.0, 4.0, 6.0])
# 또 다른 1차원 텐서 var4를 생성합니다. 값은 [4.0, 5.0, 6.0]입니다.
var4 = torch.tensor([4.0, 5.0, 6.0])
# 두 1차원 텐서 var1과 var4를 element-wise(요소별)로 더합니다.
# 결과는 두 텐서의 대응되는 요소끼리 더한 값으로 이루어진 새로운 텐서입니다.
# 결과: tensor([5.0, 7.0, 9.0])
result = var1 + var4
print(result) #tensor([5.0, 7.0, 9.0])
1-3. 행렬
* 행렬(Matrix)은 2차원 형태의 텐서로, 파이토치(PyTorch)에서는 shape가 (m, n)처럼 2개의 차원을 가진 텐서를 의미합니다.
* 예를 들어, torch.tensor([[1, 2], [3, 4]])는 2행×2열 형태의 행렬입니다.
* 행렬 연산에서는 행렬 곱셈, 원소별 연산, 전치(Transpose) 등이 자주 활용되며, 파이토치는 torch.mm 또는 @ 연산자를 통해 행렬 곱셈을 수행할 수 있습니다.
예시 1)
var1 = torch.tensor([[1, 2],
[3, 4]])
var2 = torch.tensor([[5, 6],
[7, 8]])
print(var1)
print(var1.shape)
-->
import torch
# 2차원 텐서를 생성합니다.
# 텐서 var1의 값은 2x2 형태의 행렬([[1, 2], [3, 4]])입니다.
var1 = torch.tensor([[1, 2],
[3, 4]])
# 또 다른 2차원 텐서를 생성합니다.
# 텐서 var2의 값은 2x2 형태의 행렬([[5, 6], [7, 8]])입니다.
var2 = torch.tensor([[5, 6],
[7, 8]])
# var1의 값을 출력합니다.
# 결과: tensor([[1, 2],
# [3, 4]])
print(var1)
# var1의 모양(shape)을 출력합니다.
# 2차원 텐서이므로 shape은 torch.Size([2, 2])로 출력됩니다.
# 이는 2개의 행(row)과 2개의 열(column)을 가진 텐서라는 의미입니다.
print(var1.shape) #torch.Size([2, 2])
예시 2)
result1 = var1 + var2
print(result1)
result2 = var1 * var2
print(result2)
-->
import torch
# 2차원 텐서 var1과 var2를 element-wise(요소별)로 더합니다.
# 각 위치의 값들이 더해져 새로운 텐서 result1이 생성됩니다.
# 결과: tensor([[1+5, 2+6], [3+7, 4+8]]) -> tensor([[6, 8], [10, 12]])
result1 = var1 + var2
print(result1)
# 2차원 텐서 var1과 var2를 element-wise(요소별)로 곱합니다.
# 각 위치의 값들이 곱해져 새로운 텐서 result2가 생성됩니다.
# 결과: tensor([[1*5, 2*6], [3*7, 4*8]]) -> tensor([[5, 12], [21, 32]])
result2 = var1 * var2
print(result2)
예시 3)
result3 = torch.mm(var1, var2)
print(result3)
result4 = var1 @ var2
print(result4)
-->
import torch
# 2차원 텐서 var1과 var2를 행렬 곱셈(matrix multiplication)합니다.
# torch.mm() 함수는 2차원 텐서(행렬) 간의 행렬 곱을 수행합니다.
# 행렬 곱셈의 결과는 각 행(row)과 열(column)에 대해 내적(dot product)을 계산한 값으로 이루어집니다.
# 계산 과정:
# result3[0, 0] = (1*5) + (2*7) = 5 + 14 = 19
# result3[0, 1] = (1*6) + (2*8) = 6 + 16 = 22
# result3[1, 0] = (3*5) + (4*7) = 15 + 28 = 43
# result3[1, 1] = (3*6) + (4*8) = 18 + 32 = 50
result3 = torch.mm(var1, var2)
print(result3)
# 행렬 곱셈을 수행하는 또 다른 방법은 @ 연산자를 사용하는 것입니다.
# var1 @ var2는 torch.mm(var1, var2)와 동일한 연산을 수행합니다.
# 결과는 result3와 동일한 값으로, tensor([[19, 22], [43, 50]])입니다.
result4 = var1 @ var2
print(result4) #tensor([[19, 22], [43, 50]])
1-4. 다차원 텐서
* 파이토치(PyTorch)에서 다차원 텐서란, 여러 축(차원)을 가지는 텐서를 의미합니다.
* 예를 들어, 0차원 텐서는 “스칼라(Scalar)”, 1차원 텐서는 “벡터(Vector)”, 2차원 텐서는 “행렬(Matrix)”, 그 이상의 3차원, 4차원 텐서 등을 통틀어 “다차원 텐서(Multi-dimensional Tensor)”라고 부릅니다.
* 다차원 텐서는 이미지, 음성, 동영상, 시계열 데이터를 비롯하여 여러 축을 필요로 하는 다양한 형태의 데이터를 표현할 때 쓰입니다.
예시 1)
var1 = torch.tensor([
[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]
])
print(var1)
print(var1.shape) #2행 2열 짜리가 2개
-->
import torch
# 3차원 텐서를 생성합니다.
# 텐서 var1은 2개의 2x2 행렬(2행 2열)을 포함합니다.
# 각 행렬은 2차원 텐서이며, 이러한 2차원 텐서가 2개 쌓여 있는 형태로 3차원 텐서가 생성됩니다.
var1 = torch.tensor([
[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]
])
# 텐서 var1의 값을 출력합니다.
# 결과는 3차원 텐서로 표현되며, 각 "블록"은 2차원 행렬로 표시됩니다.
print(var1) # tensor([[[1, 2],
[3, 4]],
[[5, 6],
[7, 8]]])
# 텐서 var1의 모양(shape)을 출력합니다.
# shape은 torch.Size([2, 2, 2])이며, 이는 다음을 의미합니다:
# - 첫 번째 차원: 2개의 2차원 행렬
# - 두 번째 차원: 각 행렬의 행(row) 개수 (2행)
# - 세 번째 차원: 각 행렬의 열(column) 개수 (2열)
print(var1.shape) # 2행 2열 짜리가 2개 #torch.Size([2, 2, 2])
텐서는 그림과 같이 여러 경우가 있습니다.
2. 텐서
* PyTorch의 텐서(Tensor)는 딥러닝 모델에서 데이터를 다룰 때 사용되는 기본 데이터 구조입니다.
* 텐서는 다차원 배열로, NumPy의 배열과 비슷하지만, GPU에서 연산을 수행할 수 있다는 점에서 차이가 있습니다.
* PyTorch의 텐서는 데이터의 표현뿐만 아니라, 자동 미분(autograd) 기능을 제공하여 딥러닝 모델의 학습을 도와줍니다.
예시 1)
# 2차원 리스트 데이터를 정의합니다.
data = [
[1, 2],
[3, 4]
]
# PyTorch 텐서를 생성합니다.
# 2차원 리스트를 기반으로 2차원 텐서를 생성합니다.
# 결과는 2행 2열의 텐서로 표현됩니다.
t1 = torch.tensor(data)
print(t1)
# tensor([[1, 2],
# [3, 4]])
예시 2)
# 스칼라 값을 가진 1차원 텐서를 생성합니다.
t1 = torch.tensor([5]) # 값: [5]
t2 = torch.tensor([7]) # 값: [7]
# t1과 t2를 더한 결과를 NumPy 배열로 변환합니다.
# .numpy()는 PyTorch 텐서를 NumPy 배열로 변환합니다.
# 결과: (5 + 7) = [12], NumPy 배열 ndarr1은 [12]입니다.
ndarr1 = (t1 + t2).numpy()
print(ndarr1) # 출력: [12]
# ndarr1의 데이터 타입을 출력합니다.
# NumPy 배열로 변환되었으므로 타입은 <class 'numpy.ndarray'>입니다.
print(type(ndarr1)) # 출력: <class 'numpy.ndarray'>
# NumPy 배열 ndarr1의 값을 10배로 곱합니다.
result = ndarr1 * 10 # 결과: [12] * 10 = [120]
# NumPy 배열 result를 다시 PyTorch 텐서로 변환합니다.
# torch.from_numpy()는 NumPy 배열을 PyTorch 텐서로 변환합니다.
t3 = torch.from_numpy(result)
print(t3) #출력: tensor([120], dtype=torch.int32) (데이터 타입은 NumPy의 dtype에 따라 결정됨)
print(type(t3)) #출력: <class 'torch.Tensor'>
예시 3)
# 2차원 텐서를 생성합니다.
# 텐서 t1은 3행 4열의 형태를 가집니다.
t1 = torch.tensor([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
])
# 첫 번째 행(0번 인덱스)을 가져옵니다.
# 첫 번째 행 전체
print(t1[0]) #결과: tensor([1, 2, 3, 4])
# 첫 번째 행(0번 인덱스)의 모든 열을 가져옵니다.
# 결과는 t1[0]과 동일하며, tensor([1, 2, 3, 4])를 반환합니다.
# 첫 번째 행 전체
print(t1[0, :]) # tensor([1, 2, 3, 4])
# 모든 행에서 첫 번째 열(0번 인덱스)을 가져옵니다.
# 결과: tensor([1, 5, 9]) (각 행의 첫 번째 요소를 추출)
# 첫 번째 열 전체
print(t1[:, 0]) # tensor([1, 5, 9])
# `...`은 다차원 슬라이싱에서 앞의 모든 차원을 유지하겠다는 의미입니다.
# 여기서는 2차원 텐서에서 앞의 모든 차원(행)을 유지하고 마지막 열(-1번 인덱스)을 가져옵니다.
# 결과는 t1[:, -1]과 동일합니다: tensor([4, 8, 12])
# 마지막 열 전체
print(t1[:, -1])
print(t1[..., -1]) # tensor([4, 8, 12])
예시 4)
# 3차원 텐서를 생성합니다.
# t1은 2개의 2행 3열 텐서를 포함하는 3차원 텐서입니다.
t1 = torch.tensor([[[1, 2, 3],
[4, 5, 6]],
[[7, 8, 9],
[10, 11, 12]]])
# t1의 shape(모양)을 출력합니다.
# 2행 3열이 2개
print (t1.shape) #torch.Size([2, 2, 3])
# `...`은 앞의 모든 차원을 유지하며, 마지막 차원의 마지막 열(-1)을 선택합니다.
# 결과:
# - 첫 번째 2차원 행렬: 마지막 열은 [3, 6]
# - 두 번째 2차원 행렬: 마지막 열은 [9, 12]
print(t1[..., -1])
#tensor([[ 3, 6],
[ 9, 12]])
3. GPU 사용
* GPU (Graphics Processing Unit)는 그래픽 처리 장치로, 주로 이미지 렌더링과 같은 대규모 병렬 계산을 수행하는 데 최적화된 하드웨어입니다.
* 원래는 그래픽 처리를 위해 설계되었지만, 최근에는 인공지능(AI) 및 딥러닝의 연산 가속기로 널리 사용되고 있습니다.
* 딥러닝은 수천, 수만 개의 행렬 및 벡터 연산을 필요로 합니다.
* GPU는 여러 개의 코어를 사용하여 이 연산을 병렬로 처리할 수 있습니다.
* 따라서 GPU는 딥러닝에 최적화된 구조를 가지고 있습니다.
Google Colab을 이용하면, 손쉽게 PyTorch를 시작할 수 있습니다.
예시 1)
# 2차원 리스트 데이터를 정의합니다.
# PyTorch 텐서를 생성합니다.
# 2차원 리스트를 기반으로 2차원 텐서를 생성합니다.
data = [
[1, 2],
[3, 4]
]
# 텐서 t1이 CUDA(즉, GPU)에서 생성되었는지 확인합니다.
# .is_cuda 속성은 텐서가 GPU(CUDA)를 사용하는지 여부를 반환합니다.
# 여기서 False가 출력되며, 이는 텐서가 CPU에서 생성되어 연산되고 있다는 것을 의미합니다
t1 = torch.tensor(data)
print(t1) # tensor([[1, 2],
[3, 4]])
print(t1.is_cuda) # False #즉, cpu에서 연산하구 있다
예시 2)
# 기존의 CPU에서 생성된 텐서 t1이 있다고 가정합니다.
# t1 = torch.tensor([[1, 2], [3, 4]])
# 텐서 t1을 GPU(CUDA)로 이동시킵니다.
# .cuda() 메서드는 텐서를 GPU(CUDA 디바이스)로 옮깁니다.
# 이 코드를 실행하려면 CUDA를 사용할 수 있는 환경과 PyTorch에서 GPU 지원이 활성화되어 있어야 합니다.
t1 = t1.cuda()
# 텐서 t1이 GPU에서 연산되고 있는지 확인합니다.
# t1.is_cuda가 True를 반환하며, 이는 텐서가 GPU로 성공적으로 이동했음을 의미합니다.
print(t1.is_cuda) # True
예시 3)
# 기존의 GPU(CUDA)에서 연산되던 텐서 t1이 있다고 가정합니다.
# t1 = t1.cuda()
# 텐서 t1을 CPU로 다시 이동시킵니다.
# .cpu() 메서드는 텐서를 GPU(CUDA)에서 CPU로 옮깁니다.
t1 = t1.cpu()
# 텐서 t1이 GPU에서 연산되고 있는지 확인합니다.
# t1.is_cuda가 False를 반환하며, 이는 텐서가 CPU로 성공적으로 이동했음을 의미합니다.
print(t1.is_cuda) # False
예시 4)
# 텐서 t1을 GPU에서 생성합니다.
# 값은 [[1, 1],
# [2, 2]]이며, GPU에서 연산을 수행합니다.
t1 = torch.tensor([
[1, 1],
[2, 2]
]).cuda()
# 텐서 t2를 CPU에서 생성합니다.
# 값은 [[5, 6],
# [7, 8]]이며, CPU에서 연산을 수행합니다.
t2 = torch.tensor([
[5, 6],
[7, 8]
])
# t1을 .cpu()를 사용해 GPU에서 CPU로 이동시킨 후,
# CPU에 있는 t2와 행렬 곱셈을 수행합니다.
# torch.matmul()은 두 텐서 간의 행렬 곱을 수행합니다.
# 계산 과정:
# [[1*5 + 1*7, 1*6 + 1*8], -> [[12, 14],
# [2*5 + 2*7, 2*6 + 2*8]] [24, 28]]
print(torch.matmul(t1.cpu(), t2)) # 결과: tensor([[12, 14], [24, 28]])
print(f'Device: {t1.device}') # 출력: Device: cuda:0
# 주석 처리된 코드: GPU에 있는 t1과 CPU에 있는 t2를 그대로 사용하여 연산하려고 하면 에러가 발생합니다.
# PyTorch는 서로 다른 디바이스(CPU와 GPU)에 있는 텐서 간 연산을 지원하지 않습니다.
# 따라서 두 텐서가 동일한 디바이스(CPU 또는 GPU)에 있어야 합니다.
# print(torch.matmul(t1, t2)) # 에러 발생: RuntimeError: Expected all tensors to be on the same device.
4. 텐서의 연상과 함수
예시 1)
# 2차원 텐서 t1과 t2를 생성합니다.
t1 = torch.tensor([
[1, 2],
[3, 4]
])
t2 = torch.tensor([
[5, 6],
[7, 8]
])
# 요소별(element-wise) 덧셈
# t1과 t2의 같은 위치에 있는 요소끼리 더합니다.
print(t1 + t2) # 결과: [[1+5, 2+6], [3+7, 4+8]] -> [[6, 8], [10, 12]]
# 요소별(element-wise) 뺄셈
# t1과 t2의 같은 위치에 있는 요소끼리 뺍니다.
print(t1 - t2) # 결과: [[1-5, 2-6], [3-7, 4-8]] -> [[-4, -4], [-4, -4]]
# 요소별(element-wise) 곱셈
# t1과 t2의 같은 위치에 있는 요소끼리 곱합니다.
print(t1 * t2) # 결과: [[1*5, 2*6], [3*7, 4*8]] -> [[5, 12], [21, 32]]
# 요소별(element-wise) 나눗셈
# t1과 t2의 같은 위치에 있는 요소끼리 나눕니다.
print(t1 / t2) # 결과: [[1/5, 2/6], [3/7, 4/8]] -> [[0.2, 0.3333], [0.4286, 0.5]]
예시 2)
# 2차원 텐서 t1과 t2를 생성합니다.
# 행렬 곱셈(matrix multiplication) 연산 1: t1.matmul(t2)
# t1과 t2의 행렬 곱을 수행합니다.
# [[(1*5 + 2*7), (1*6 + 2*8)],
# [(3*5 + 4*7), (3*6 + 4*8)]]
print(t1.matmul(t2)) # [[19, 22],
[43, 50]]
# 행렬 곱셈(matrix multiplication) 연산 2: torch.matmul(t1, t2)
# torch.matmul은 t1.matmul(t2)와 동일한 연산을 수행합니다.
print(torch.matmul(t1, t2)) # [[19, 22], [43, 50]]
예시 3)
# 2차원 텐서 t1을 생성합니다.
t1 = torch.Tensor([
[1, 2, 3, 4],
[5, 6, 7, 8]
])
print(t1) # tensor([[1., 2., 3., 4.],
[5., 6., 7., 8.]])
# t1의 전체 원소에 대한 평균을 계산합니다.
# (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8) / 8 = 36 / 8 = 4.5
print(t1.mean()) # tensor(4.5000) # 전체 원소에 대한 평균
# t1의 각 열(column)에 대한 평균을 계산합니다.
# 첫 번째 열: (1 + 5) / 2 = 6 / 2 = 3.0
# 두 번째 열: (2 + 6) / 2 = 8 / 2 = 4.0
# 세 번째 열: (3 + 7) / 2 = 10 / 2 = 5.0
# 네 번째 열: (4 + 8) / 2 = 12 / 2 = 6.0
print(t1.mean(dim=0)) # tensor([3., 4., 5., 6.]) # 각 열에 대하여 평균 계산
# t1의 각 행(row)에 대한 평균을 계산합니다.
# 첫 번째 행: (1 + 2 + 3 + 4) / 4 = 10 / 4 = 2.5
# 두 번째 행: (5 + 6 + 7 + 8) / 4 = 26 / 4 = 6.5
# 결과: tensor([2.5, 6.5])
print(t1.mean(dim=1)) # tensor([2.5000, 6.5000]) # 각 행에 대하여 평균 계산
예시 4)
# 2차원 텐서 t1을 생성합니다.
# [[ 1, 2, 3, 4],
# [ 5, 6, 7, 8]]
# t1의 모든 원소의 합을 계산합니다.
# (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8) = 36
print(t1.sum()) # 전체 원소의 합: 36
# t1의 각 열(column)에 대한 합을 계산합니다.
# 첫 번째 열: 1 + 5 = 6
# 두 번째 열: 2 + 6 = 8
# 세 번째 열: 3 + 7 = 10
# 네 번째 열: 4 + 8 = 12
print(t1.sum(dim=0)) # 결과: tensor([6, 8, 10, 12]) # 각 열의 합
# t1의 각 행(row)에 대한 합을 계산합니다.
# 첫 번째 행: 1 + 2 + 3 + 4 = 10
# 두 번째 행: 5 + 6 + 7 + 8 = 26
print(t1.sum(dim=1)) # 결과: tensor([10, 26]) # 각 행의 합
-->
예시 5)
# 2차원 텐서 t1을 생성합니다.
t1 = torch.Tensor([
[1, 2, 3, 4],
[5, 6, 7, 8]
])
# t1에서 전체 원소 중 최댓값의 인덱스를 반환합니다.
# PyTorch는 2차원 텐서를 1차원으로 평탄화(flatten)한 후, 최댓값의 인덱스를 계산합니다.
# 평탄화된 t1: [1, 2, 3, 4, 5, 6, 7, 8]
# 최댓값 8의 인덱스: 7
print(t1.argmax()) # 전체 원소 중 최댓값의 인덱스: 7
# t1의 각 열(column)에 대해 최댓값의 인덱스를 계산합니다.
# 첫 번째 열: [1, 5] -> 최댓값 5의 인덱스 1
# 두 번째 열: [2, 6] -> 최댓값 6의 인덱스 1
# 세 번째 열: [3, 7] -> 최댓값 7의 인덱스 1
# 네 번째 열: [4, 8] -> 최댓값 8의 인덱스 1
print(t1.argmax(dim=0)) # 각 열에 대한 최댓값의 인덱스 결과: tensor([1, 1, 1, 1])
print(t1.argmax(dim=1)) # 각 행에 대한 최댓값의 인덱스 결과: tensor([3, 3])
5. 텐서의 차원 조작
예시 1)
# 2차원 텐서 t1을 생성합니다.
t1 = torch.tensor([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
])
# torch.cat을 사용하여 t1을 세 번 이어붙입니다.
# dim=0: 행(row)을 기준으로 텐서를 이어붙입니다.
# 원래 t1은 3개의 행(row)을 가지고 있으므로,
# 결과는 3 * 3 = 9개의 행(row)을 가진 텐서가 됩니다.
result = torch.cat([t1, t1, t1], dim=0) # 행(row)을 기준으로 이어붙임
print(result)
# [[ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12],
# [ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12],
# [ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12]]
예시 2)
# 2차원 텐서 t1을 생성합니다.
# t1의 값은 다음과 같습니다:
# [[ 1, 2, 3, 4],
# [ 5, 6, 7, 8],
# [ 9, 10, 11, 12]]
# torch.cat을 사용하여 t1을 세 번 이어붙입니다.
# dim=1: 열(column)을 기준으로 텐서를 이어붙입니다.
# 원래 t1은 4개의 열(column)을 가지고 있으므로,
# 결과는 4 * 3 = 12개의 열(column)을 가진 텐서가 됩니다.
result = torch.cat([t1, t1, t1], dim=1) # 열(column)을 기준으로 이어붙임
print(result)
# 결과:
# [[ 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4],
# [ 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8],
# [ 9, 10, 11, 12, 9, 10, 11, 12, 9, 10, 11, 12]]
예시 3)
# 텐서 t1을 생성합니다.
# 값: [2], 데이터 타입: torch.int32
# t1 = torch.LongTensor([2])
# 또는 torch.LongTensor([2])로 동일하게 생성 가능 (long은 기본적으로 int64를 사용)
t1 = torch.tensor([2], dtype=torch.int)
# 텐서 t2를 생성합니다.
# 값: [5.0], 데이터 타입: torch.float32
t2 = torch.tensor([5.0])
# t1의 데이터 타입 출력
print(t1.dtype) # 결과: torch.int32 (32비트 정수형)
# t2의 데이터 타입 출력
print(t2.dtype) # 결과: torch.float32 (32비트 부동소수점형)
# PyTorch는 텐서 간 연산에서 데이터 타입을 자동으로 변환합니다.
# 여기서는 t1 (int32형)이 t2 (float32형)과 덧셈되므로,
# t1이 float32형으로 자동 형 변환되어 연산이 수행됩니다.
# 결과: [2.0 + 5.0] -> tensor([7.0], dtype=torch.float32)
print(t1 + t2) # tensor([7.0])
# t2를 int32형으로 강제 변환합니다.
# .type(torch.int32)는 t2를 int32형으로 변환한 후 덧셈 연산을 수행합니다.
# 결과: [2 + 5] -> tensor([7], dtype=torch.int32)
print(t1 + t2.type(torch.int32)) #tensor([7], dtype=torch.int32)
예시 4)
# view()는 텐서의 모양을 변경할 때 사용
# 1차원 텐서 t1을 생성합니다.
# t1은 1차원 텐서로 길이가 8이며, 이를 4행 2열의 2차원 텐서로 변환합니다.
# .view(4, 2)는 행(row)과 열(column)의 개수를 지정하여 새롭게 모양을 설정합니다.
t1 = torch.tensor([1, 2, 3, 4, 5, 6, 7, 8])
t2 = t1.view(4, 2)
# 변환된 텐서 t2를 출력합니다.
print(t2)
-->
tensor([[1, 2],
[3, 4],
[5, 6],
[7, 8]])
예시 5)
#t1의 값을 변경하면 t2도 변경
# 1차원 텐서 t1을 생성합니다.
# 값: [1, 2, 3, 4, 5, 6, 7, 8]
# .view()를 사용하여 텐서 t1의 모양(shape)을 변경합니다.
# t2는 t1의 데이터에 대한 다른 관점(view)로, 4행 2열의 2차원 텐서로 나타냅니다.
# t1과 t2는 동일한 메모리를 공유합니다.
t2 = t1.view(4, 2)
# t1의 첫 번째 원소를 변경합니다.
# t1[0] = 7으로 설정하면, t1의 데이터가 수정됩니다.
# t1과 t2는 동일한 데이터를 공유하므로, t2에서도 이 변경 사항이 반영됩니다.
t1[0] = 7
print(t1) # 결과: tensor([7, 2, 3, 4, 5, 6, 7, 8])
# t2를 출력합니다.
# t2는 t1과 동일한 메모리를 공유하기 때문에,
# 첫 번째 원소가 수정된 상태로 반영됩니다.
print(t2)
# 결과: tensor([[7, 2],
# [3, 4],
# [5, 6],
# [7, 8]])
예시 6)
# 1차원 텐서 t1을 생성합니다.
# 값: [7, 2, 3, 4, 5, 6, 7, 8]
# t1의 데이터를 복사하여 새로운 텐서 t3를 생성합니다.
# .clone()은 t1의 데이터를 복사하여 새로운 메모리 공간에 저장합니다.
# t3는 t1과 데이터를 공유하지 않습니다.
# .view(4, 2)를 사용하여 t3의 모양(shape)을 4행 2열로 변경합니다.
# 즉, t1의 값을 복사한 뒤에 모양 변경
t3 = t1.clone().view(4, 2)
print(t3) # 출력: tensor([[7, 2],
# [3, 4],
# [5, 6],
# [7, 8]])
# t1의 첫 번째 원소를 변경합니다.
# t1과 t3는 서로 독립적인 데이터를 가지므로, t1의 변경은 t3에 영향을 미치지 않는다.
t1[0] = 9
print(t1) # 출력: tensor([9, 2, 3, 4, 5, 6, 7, 8])
# t3는 t1의 복사본이므로 t1의 변경 사항은 반영되지 않습니다.
# t3의 값은 그대로 유지됩니다.
print(t3) # 출력: tensor([[7, 2],
# [3, 4],
# [5, 6],
# [7, 8]])
예시 7)
# 3차원 텐서 t1을 생성합니다.
# torch.rand((64, 32, 3))는 0과 1 사이의 난수로 채워진 3차원 텐서를 생성합니다.
# (64, 32, 3)은 텐서의 모양(shape)을 나타내며, 다음과 같은 의미를 가집니다:
# - 첫 번째 차원: 64개의 "블록" (32행 3열로 구성된 2차원 행렬)
# - 두 번째 차원: 각 블록에 32개의 행(row)이 있음
# - 세 번째 차원: 각 행에 3개의 열(column)이 있음
t1 = torch.rand((64, 32, 3)) #32행 3열이 64 개
# t1의 모양(shape)을 출력합니다.
print(t1.shape) #결과: torch.Size([64, 32, 3]) (64개의 32x3 행렬을 포함하는 3차원 텐서)
# 출력되는 값은 64개의 블록에 포함된 난수 행렬 중 일부만 표시될 수 있습니다.
print(t1)
-->
tensor([[[0.9283, 0.2544, 0.7173],
[0.3990, 0.7115, 0.2584],
[0.6251, 0.7940, 0.7772],
...,
[0.8421, 0.4966, 0.1066],
[0.2242, 0.3724, 0.9165],
[0.8341, 0.6935, 0.5021]],
[[0.7085, 0.7627, 0.2546],
[0.9232, 0.3989, 0.2418],
[0.2119, 0.5440, 0.6831],
...,
[0.1769, 0.0027, 0.7887],
[0.2597, 0.5745, 0.2615],
[0.7397, 0.4490, 0.9612]],
[[0.6656, 0.9879, 0.1693],
[0.4857, 0.1636, 0.8186],
[0.0518, 0.8938, 0.9734],
...,
[0.9238, 0.7500, 0.3138],
[0.6242, 0.0853, 0.6454],
[0.1325, 0.4319, 0.6533]],
...,
[[0.8220, 0.3140, 0.8395],
[0.0376, 0.0153, 0.6849],
[0.7339, 0.1322, 0.5956],
...,
[0.7963, 0.9466, 0.1467],
[0.2031, 0.6310, 0.3145],
[0.5232, 0.7926, 0.0172]],
[[0.4681, 0.0885, 0.8647],
[0.6537, 0.8776, 0.0261],
[0.9997, 0.8588, 0.1192],
...,
[0.6460, 0.6337, 0.1688],
[0.3588, 0.0840, 0.1842],
[0.6133, 0.8034, 0.7346]],
[[0.4105, 0.7676, 0.8077],
[0.2533, 0.1792, 0.4257],
[0.9966, 0.8492, 0.5531],
...,
[0.2918, 0.1969, 0.3926],
[0.3665, 0.5314, 0.2238],
[0.9114, 0.0854, 0.8065]]])
예시 8)
# 3차원 텐서 t1을 생성합니다.
# t1의 모양: torch.Size([64, 32, 3])
# torch.Size([64, 32, 3]) -> [3, 32, 64]
# .permute()를 사용하여 텐서의 차원 순서를 변경합니다.
# .permute(2, 1, 0)은 기존의 차원을 새로운 순서로 재배치합니다.
# - 기존 차원 (64, 32, 3)의 의미:
# (첫 번째 차원: 64, 두 번째 차원: 32, 세 번째 차원: 3)
# - 새 순서 (2, 1, 0):
# (세 번째 차원 -> 첫 번째 차원, 두 번째 차원 -> 두 번째 차원, 첫 번째 차원 -> 세 번째 차원)
t2 = t1.permute(2, 1, 0) # 결과: torch.Size([3, 32, 64])
# t2의 모양을 출력합니다.
print(t2.shape) # 결과: torch.Size([3, 32, 64])
예시 9)
# 3차원 텐서 t1을 생성합니다.
# 원래 t1의 모양: torch.Size([64, 32, 3])
t1 = torch.rand((64, 32, 3))
# .unsqueeze(0)을 사용하여 새로운 차원을 추가합니다.
# .unsqueeze(dim) 메서드는 지정한 위치(dim)에 새로운 차원을 추가합니다.
# 여기서 dim=0이므로, 기존의 텐서 앞에 새로운 차원을 추가합니다.
# 기존 모양: torch.Size([64, 32, 3])
# 새로운 모양: torch.Size([1, 64, 32, 3])
t1 = t1.unsqueeze(0)
# 변경된 t1을 출력합니다.
print(t1)
# 변경된 t1의 모양(shape)을 출력합니다.
print(t1.shape) # 결과: torch.Size([1, 64, 32, 3])
예시 10)
#크기가 1인 차원 제거
# squeeze(dim): 특정 차원이 1인 경우에만 차원을 제거
# 4차원 텐서 t1을 생성합니다.
# t1의 모양: torch.Size([1, 64, 32, 3])
# t1 = torch.rand((64, 32, 3)).unsqueeze(0)
# .squeeze()를 사용하여 크기가 1인 차원을 제거합니다.
# .squeeze(dim)는 지정한 차원이 크기가 1일 경우 해당 차원을 제거합니다.
# 여기서는 dim을 지정하지 않았으므로, 텐서에서 크기가 1인 모든 차원을 제거합니다.
# t1의 첫 번째 차원(크기 1)이 제거됩니다.
# 기존 모양: torch.Size([1, 64, 32, 3])
# 새로운 모양: torch.Size([64, 32, 3])
t1 = t1.squeeze()
# 변경된 t1을 출력합니다.
print(t1)
# 변경된 t1의 모양(shape)을 출력합니다.
print(t1.shape) # 결과: torch.Size([64, 32, 3])
6. 자동 미분과 기울기
예시 1)
# 1차원 텐서 x를 생성합니다.
# 값: [3.0, 4.0], requires_grad=True는 자동 미분(autograd)을 활성화하여
# x에 대한 그래디언트를 계산할 수 있도록 합니다.
# 1차원 텐서 y를 생성합니다.
# 값: [1.0, 2.0], requires_grad=True로 설정하여 y에 대한 그래디언트를 계산할 수 있도록 합니다.
x = torch.tensor([3.0, 4.0], requires_grad=True)
y = torch.tensor([1.0, 2.0], requires_grad=True)
# z는 x와 y의 요소별(element-wise) 합입니다.
# z = [3.0+1.0, 4.0+2.0] = [4.0, 6.0]
# z는 requires_grad=True 속성을 상속받아 그래디언트를 계산할 수 있습니다.
z = x + y
print(z) # 출력: tensor([4., 6.], grad_fn=<AddBackward0>)
# out은 z의 모든 요소의 평균입니다.
# out = (4.0 + 6.0) / 2 = 5.0
# out도 requires_grad=True 속성을 가지며, z와의 연산 기록을 통해 그래디언트 계산이 가능합니다.
out = z.mean()
print(out) # 출력: tensor(5., grad_fn=<MeanBackward0>)
예시 2)
# 1차원 텐서 x와 y를 생성합니다. (requires_grad=True로 자동 미분 활성화)
#x = torch.tensor([3.0, 4.0], requires_grad=True)
#y = torch.tensor([1.0, 2.0], requires_grad=True)
# z는 x와 y의 요소별 합입니다.
#z = x + y
# out은 z의 모든 요소의 평균입니다.
# out = (4.0 + 6.0) / 2 = 5.0
out = z.mean()
# out에 대해 자동 미분을 수행합니다.
# .backward()는 out을 기준으로 연산 기록(autograd graph)을 역전파하여
# x와 y의 그래디언트(∂out/∂x, ∂out/∂y)를 계산합니다.
out.backward()
# x에 대한 그래디언트를 출력합니다.
# out = z.mean() = (z[0] + z[1]) / 2 = ((x[0] + y[0]) + (x[1] + y[1])) / 2
# ∂out/∂x[i] = 1/2 (각 x[i]의 기여는 평균 연산에서 1/2)
print("x.grad: ", x.grad) # 결과: tensor([0.5, 0.5])
# y에 대한 그래디언트를 출력합니다.
# ∂out/∂y[i] = 1/2 (각 y[i]의 기여는 평균 연산에서 1/2)
print("y.grad: ", y.grad) # 결과: tensor([0.5, 0.5])
# z는 중간 연산 결과 텐서이며, `z.grad`는 계산되지 않습니다.
# `z`는 최종 스칼라 값이 아니므로 직접적인 `z.backward()` 호출이 없기 때문입니다.
print("z.grad: " , z.grad) # 결과: None
'LLM(Large Language Model)의 기초 > 머신러닝과 딥러닝' 카테고리의 다른 글
6. 서울 자전거 공유 수요 예측 데이터셋 (0) | 2025.01.08 |
---|---|
5. 주택 임대료 예측 데이터셋 (0) | 2025.01.07 |
4. 사이킷런 (0) | 2025.01.07 |
3. 파이토치로 구현한 선형 회귀 (2) | 2025.01.06 |
1. 머신러닝과 딥러닝 (2) | 2025.01.06 |