이미지 로딩 중...
AI Generated
2025. 11. 23. · 0 Views
PyTorch 기초 및 Tensor 연산 완벽 가이드
PyTorch의 핵심인 Tensor 연산을 처음부터 실무까지 단계별로 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 일상 비유와 함께 설명하며, 실제 딥러닝 프로젝트에서 바로 활용할 수 있는 실전 예제를 제공합니다.
목차
- Tensor 생성 및 초기화
- Tensor 연산 및 Broadcasting
- Tensor Indexing 및 Slicing
- Tensor Shape 변환 (Reshape, View, Transpose)
- GPU 가속 및 Device 관리
- Autograd 및 자동 미분
- Tensor와 NumPy 상호 변환
- Tensor 연결 및 분할 (Cat, Stack, Chunk, Split)
- 수학 함수 및 통계 연산
- 데이터 타입 변환 및 타입 캐스팅
1. Tensor 생성 및 초기화
시작하며
여러분이 딥러닝 모델을 처음 만들 때 "데이터를 어떻게 표현해야 하지?"라는 고민을 해본 적 있나요? 예를 들어, 고양이와 강아지를 구별하는 모델을 만들 때 이미지 데이터를 어떤 형태로 저장하고 처리해야 할까요?
이런 문제는 모든 딥러닝 초보자가 겪는 첫 번째 관문입니다. 이미지는 픽셀의 집합이고, 각 픽셀은 RGB 값을 가지고 있죠.
이걸 그냥 리스트로 저장하면 계산이 너무 느리고, NumPy 배열로 하자니 GPU를 제대로 활용할 수 없습니다. 바로 이럴 때 필요한 것이 PyTorch의 Tensor입니다.
Tensor는 마치 엑셀 시트처럼 데이터를 표 형태로 정리하되, GPU를 활용해서 번개처럼 빠르게 계산할 수 있는 특별한 상자라고 생각하시면 됩니다.
개요
간단히 말해서, Tensor는 PyTorch에서 데이터를 담는 기본 단위입니다. 숫자 하나만 담을 수도 있고, 수백만 개의 데이터를 담을 수도 있는 똑똑한 그릇이에요.
왜 Tensor가 필요할까요? 딥러닝에서는 수백만 번의 계산을 반복해야 합니다.
만약 일반 Python 리스트를 사용하면 커피 한 잔 마실 시간에 끝날 계산이 하루 종일 걸릴 수 있어요. Tensor는 GPU라는 초고속 계산기를 활용할 수 있어서 같은 계산을 수백 배 빠르게 할 수 있습니다.
기존에는 NumPy 배열을 사용했다면, 이제는 PyTorch Tensor로 GPU 가속까지 받을 수 있습니다. NumPy는 CPU에서만 작동하지만, Tensor는 CPU와 GPU를 자유롭게 오갈 수 있죠.
Tensor의 핵심 특징은 세 가지입니다: 첫째, 자동 미분을 지원해서 딥러닝 학습이 가능하고, 둘째, GPU 연산이 가능하며, 셋째, NumPy와 쉽게 변환됩니다. 이러한 특징들이 PyTorch를 딥러닝 연구자들의 첫 번째 선택으로 만들었습니다.
코드 예제
import torch
# 주석: 0부터 9까지의 숫자로 채워진 1차원 Tensor 생성
tensor_1d = torch.arange(10)
# 주석: 0과 1 사이의 랜덤 값으로 채워진 2x3 행렬 생성
tensor_2d = torch.rand(2, 3)
# 주석: 모든 값이 0인 3x3 행렬 생성
zeros = torch.zeros(3, 3)
# 주석: 모든 값이 1인 2x2 행렬 생성
ones = torch.ones(2, 2)
# 주석: Python 리스트로부터 Tensor 생성
from_list = torch.tensor([[1, 2], [3, 4]])
# 주석: GPU로 이동 (CUDA가 사용 가능한 경우)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
gpu_tensor = tensor_2d.to(device)
print(f"1D Tensor: {tensor_1d}")
print(f"Random 2D Tensor:\n{tensor_2d}")
print(f"GPU Tensor device: {gpu_tensor.device}")
설명
이것이 하는 일: 위 코드는 다양한 방법으로 Tensor를 생성하고, GPU로 옮기는 전체 과정을 보여줍니다. 실무에서 가장 자주 사용하는 Tensor 초기화 패턴들을 모두 포함하고 있어요.
첫 번째로, torch.arange(10)는 0부터 9까지의 연속된 숫자를 담은 1차원 Tensor를 만듭니다. 이건 마치 1부터 10까지 번호표를 순서대로 나열한 것과 같아요.
torch.rand(2, 3)는 0과 1 사이의 랜덤 값으로 채워진 2행 3열 행렬을 만드는데, 가중치를 랜덤하게 초기화할 때 정말 많이 사용합니다. 그 다음으로, torch.zeros()와 torch.ones()가 실행되면서 특정 값으로 채워진 행렬을 만듭니다.
zeros는 딥러닝 모델의 편향(bias)을 초기화할 때, ones는 마스킹 행렬을 만들 때 자주 사용되죠. torch.tensor()로 Python 리스트를 직접 Tensor로 변환할 수도 있는데, 이건 작은 데이터를 테스트할 때 편리합니다.
마지막으로, .to(device) 메서드가 Tensor를 GPU로 옮깁니다. 이 한 줄이 실행 속도를 수백 배 빠르게 만들 수 있어요.
torch.cuda.is_available()로 GPU가 있는지 먼저 확인한 다음, 있으면 'cuda'를 사용하고 없으면 'cpu'를 사용하는 게 안전합니다. 여러분이 이 코드를 사용하면 어떤 크기의 데이터든 빠르게 Tensor로 변환하고, GPU 가속을 받을 수 있습니다.
실무에서는 이미지 데이터를 Tensor로 변환해서 CNN 모델에 입력하거나, 텍스트 데이터를 임베딩 벡터로 만들 때 이 패턴을 매일 사용하게 될 거예요. 또한 NumPy 배열이 있다면 torch.from_numpy()로 즉시 변환 가능하고, 반대로 .numpy() 메서드로 NumPy로 되돌릴 수도 있습니다.
실전 팁
💡 Tensor를 생성할 때 dtype을 명시하세요. torch.zeros(3, 3, dtype=torch.float32)처럼 데이터 타입을 지정하면 메모리를 절약하고 계산 속도도 빨라집니다.
💡 GPU 메모리는 한정적입니다. 큰 Tensor를 GPU로 옮기기 전에 print(torch.cuda.memory_allocated())로 현재 메모리 사용량을 확인하는 습관을 들이세요.
💡 requires_grad=True 옵션을 사용하면 해당 Tensor의 그래디언트를 자동으로 계산합니다. 학습 가능한 파라미터를 만들 때 필수입니다.
💡 torch.empty()는 초기화하지 않은 Tensor를 만들어서 zeros()보다 빠르지만, 쓰레기 값이 들어있을 수 있으니 주의하세요.
💡 같은 모양의 Tensor를 여러 개 만들 때는 tensor_like() 함수들을 활용하세요. torch.zeros_like(existing_tensor)는 기존 Tensor와 똑같은 shape의 0 행렬을 만듭니다.
2. Tensor 연산 및 Broadcasting
시작하며
여러분이 두 개의 이미지를 합성하거나, 모델의 예측값과 실제값을 비교할 때 "크기가 다른 데이터끼리 계산하면 어떻게 되지?"라는 의문을 가져본 적 있나요? 예를 들어, 100개의 데이터 포인트에 같은 값 5를 더하고 싶을 때 100번 반복문을 돌려야 할까요?
이런 상황은 딥러닝에서 정말 자주 발생합니다. 배치로 들어온 128개의 이미지에 같은 정규화 값을 빼거나, 모든 픽셀에 같은 밝기 보정을 적용할 때마다 반복문을 쓴다면 코드가 지저분해지고 속도도 느려집니다.
바로 이럴 때 필요한 것이 PyTorch의 Broadcasting 기능입니다. Broadcasting은 마치 복사-붙여넣기를 자동으로 해주는 똑똑한 비서처럼, 크기가 다른 Tensor끼리도 알아서 모양을 맞춰서 연산해줍니다.
개요
간단히 말해서, Broadcasting은 크기가 다른 Tensor끼리 연산할 때 자동으로 모양을 맞춰주는 기능입니다. 5 + [1, 2, 3]을 계산하면 자동으로 [5, 5, 5] + [1, 2, 3] = [6, 7, 8]로 계산해주는 거죠.
왜 Broadcasting이 필요할까요? 딥러닝에서는 배치 단위로 데이터를 처리하는데, 모든 데이터에 같은 연산을 적용해야 할 때가 많습니다.
예를 들어, 128개 이미지의 평균을 0으로 만들고 싶을 때, Broadcasting 없이는 for 루프로 128번 반복해야 하지만, Broadcasting을 사용하면 한 줄로 끝납니다. 기존에는 NumPy의 broadcasting을 사용했다면, PyTorch도 동일한 규칙을 따릅니다.
하지만 PyTorch는 GPU에서도 작동하기 때문에 수천 배 빠른 속도로 같은 작업을 수행할 수 있습니다. Broadcasting의 핵심 특징은 두 가지입니다: 첫째, 자동으로 차원을 확장해서 모양을 맞춰주고, 둘째, 실제로 메모리를 복사하지 않아서 매우 효율적입니다.
이러한 특징들이 대용량 데이터 처리에서 필수적입니다. 또한 element-wise 연산(덧셈, 곱셈 등)뿐만 아니라 행렬 곱셈, 차원 축소 등 다양한 연산을 직관적으로 수행할 수 있습니다.
코드 예제
import torch
# 주석: 기본 Tensor 연산 - element-wise 덧셈, 곱셈
a = torch.tensor([1, 2, 3])
b = torch.tensor([4, 5, 6])
print(f"덧셈: {a + b}") # [5, 7, 9]
print(f"곱셈: {a * b}") # [4, 10, 18]
# 주석: Broadcasting - 스칼라와 Tensor 연산
tensor = torch.tensor([[1, 2], [3, 4]])
result = tensor + 10 # 모든 원소에 10을 더함
print(f"Broadcasting 결과:\n{result}")
# 주석: Broadcasting - 다른 shape의 Tensor 연산
x = torch.ones(3, 4) # 3x4 행렬
y = torch.tensor([1, 2, 3, 4]) # 1차원 (4,) 벡터
z = x + y # y가 자동으로 (1, 4) -> (3, 4)로 확장됨
print(f"Shape 확장 결과: {z.shape}")
# 주석: 행렬 곱셈 - @ 연산자 또는 torch.matmul() 사용
mat1 = torch.randn(2, 3)
mat2 = torch.randn(3, 2)
product = mat1 @ mat2 # 또는 torch.matmul(mat1, mat2)
print(f"행렬 곱셈 결과 shape: {product.shape}") # (2, 2)
설명
이것이 하는 일: 위 코드는 PyTorch의 다양한 Tensor 연산 방법을 보여줍니다. 같은 크기의 Tensor 연산부터 Broadcasting을 활용한 효율적인 연산까지 실무에서 가장 많이 사용하는 패턴들을 담고 있어요.
첫 번째로, a + b와 a * b는 같은 위치의 원소끼리 더하거나 곱합니다. 이걸 element-wise 연산이라고 부르는데, [1, 2, 3] + [4, 5, 6] = [1+4, 2+5, 3+6] = [5, 7, 9]처럼 계산됩니다.
이건 이미지 필터를 적용하거나 활성화 함수를 계산할 때 핵심적으로 사용됩니다. 그 다음으로, tensor + 10처럼 스칼라(숫자 하나)와 Tensor를 연산하면 Broadcasting이 자동 발동합니다.
PyTorch가 내부적으로 10을 [[10, 10], [10, 10]]으로 확장해서 계산하는데, 실제로 메모리를 복사하지 않아서 엄청 빠릅니다. 이미지 정규화할 때 평균값을 빼는 작업이 바로 이 원리입니다.
세 번째 예제에서 x + y는 더 복잡한 Broadcasting을 보여줍니다. x는 (3, 4) 모양이고 y는 (4,) 모양인데, PyTorch가 y를 (1, 4)로 보고 이걸 3번 복사해서 (3, 4)로 만든 다음 더합니다.
배치 정규화(Batch Normalization)에서 각 채널마다 다른 평균과 분산을 적용할 때 이 패턴을 사용합니다. 마지막으로, @ 연산자는 행렬 곱셈을 수행합니다.
mat1의 (2, 3)과 mat2의 (3, 2)가 곱해져서 (2, 2) 결과가 나오는데, 이건 딥러닝의 가장 핵심 연산입니다. 모든 신경망의 층(layer)이 본질적으로 행렬 곱셈이거든요.
여러분이 이 코드를 사용하면 반복문 없이 깔끔하고 빠르게 배치 연산을 처리할 수 있습니다. 실무에서는 128개 이미지를 한 번에 정규화하거나, 1000개의 예측값에 softmax를 적용하거나, 배치 전체에 dropout을 적용할 때 이 패턴들을 조합해서 사용합니다.
GPU에서 실행하면 수만 개의 데이터도 순식간에 처리할 수 있어요.
실전 팁
💡 Broadcasting 규칙을 외우세요: 뒤에서부터 차원을 비교해서 같거나, 하나가 1이거나, 하나가 없으면 Broadcasting 가능합니다. (3, 1, 5)와 (2, 5)는 (3, 2, 5)로 확장됩니다.
💡 unsqueeze()로 명시적으로 차원을 추가하면 코드 가독성이 좋아집니다. y.unsqueeze(0)는 (4,) -> (1, 4)로 만들어서 Broadcasting 의도를 명확히 보여줍니다.
💡 inplace 연산(+=, *=)을 사용하면 메모리를 절약할 수 있지만, autograd 그래프가 망가질 수 있으니 학습 중에는 피하세요.
💡 torch.einsum()을 배우면 복잡한 Tensor 연산을 한 줄로 표현할 수 있습니다. 'ij,jk->ik'는 행렬 곱셈을 의미합니다.
💡 shape이 예상과 다르면 .shape으로 확인하고, 필요하면 reshape()이나 view()로 모양을 바꾸세요. view(-1, 784)는 배치 크기를 자동으로 맞춰줍니다.
3. Tensor Indexing 및 Slicing
시작하며
여러분이 이미지의 특정 영역만 잘라내거나, 배치 데이터 중 일부만 선택해서 처리하고 싶을 때 막막하셨나요? 예를 들어, 128개 이미지 중에서 잘못 분류된 샘플들만 골라내서 다시 확인하고 싶다면 어떻게 해야 할까요?
이런 상황은 데이터 전처리와 디버깅에서 매일 발생합니다. 특정 조건을 만족하는 데이터만 필터링하거나, Tensor의 일부분만 수정하거나, 차원을 재배열해야 할 때 효율적인 방법이 없다면 코드가 복잡해지고 속도도 느려집니다.
바로 이럴 때 필요한 것이 Tensor의 Indexing과 Slicing 기능입니다. 이건 마치 엑셀에서 특정 행과 열만 선택하는 것처럼, Tensor의 원하는 부분만 쏙쏙 뽑아낼 수 있는 강력한 도구입니다.
개요
간단히 말해서, Indexing은 Tensor의 특정 위치에 있는 값을 가져오거나 수정하는 것이고, Slicing은 Tensor의 일부 범위를 잘라내는 것입니다. Python 리스트의 인덱싱과 비슷하지만 훨씬 강력합니다.
왜 Indexing과 Slicing이 필요할까요? 딥러닝에서는 데이터의 일부만 선택해서 처리하는 경우가 정말 많습니다.
예를 들어, 이미지의 중앙 부분만 크롭(crop)하거나, 시계열 데이터의 최근 10개 타임스텝만 사용하거나, 배치에서 특정 클래스의 샘플만 필터링할 때 Indexing이 필수입니다. 기존에는 NumPy 스타일의 인덱싱을 사용했다면, PyTorch는 거기에 더해서 Boolean Masking, Advanced Indexing, Gather/Scatter 같은 고급 기능을 제공합니다.
이걸로 조건부 선택, 중복 선택, 차원 재배열까지 자유자재로 할 수 있습니다. Indexing의 핵심 특징은 세 가지입니다: 첫째, 기본 인덱싱은 원본 Tensor의 뷰(view)를 반환해서 메모리 효율적이고, 둘째, Boolean Masking으로 조건을 만족하는 원소만 선택 가능하며, 셋째, Advanced Indexing으로 불연속적인 위치의 원소들을 한 번에 선택할 수 있습니다.
이러한 기능들이 복잡한 데이터 조작을 간단하게 만들어줍니다.
코드 예제
import torch
# 주석: 기본 인덱싱 - 특정 위치의 값 접근
tensor = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"[0, 0] 위치: {tensor[0, 0]}") # 1
print(f"첫 번째 행: {tensor[0]}") # [1, 2, 3]
# 주석: Slicing - 범위로 선택 (시작:끝:간격)
print(f"처음 2행: \n{tensor[:2]}") # 0번, 1번 행
print(f"마지막 2열: \n{tensor[:, -2:]}") # 모든 행의 뒤 2개 열
# 주석: Boolean Masking - 조건을 만족하는 원소만 선택
mask = tensor > 5 # 5보다 큰 값만 True
filtered = tensor[mask] # [6, 7, 8, 9]
print(f"5보다 큰 값: {filtered}")
# 주석: Advanced Indexing - 원하는 위치들만 선택
indices = torch.tensor([0, 2]) # 0번째, 2번째 행 선택
selected_rows = tensor[indices]
print(f"선택된 행:\n{selected_rows}")
# 주석: Inplace 수정 - 특정 부분만 값 변경
tensor[0, :] = 0 # 첫 번째 행을 모두 0으로
print(f"수정 후:\n{tensor}")
설명
이것이 하는 일: 위 코드는 Tensor의 데이터를 선택하고 조작하는 다양한 방법을 보여줍니다. 단순한 위치 접근부터 조건부 필터링까지, 실전에서 정말 많이 쓰는 패턴들을 담았어요.
첫 번째로, tensor[0, 0]은 0번 행, 0번 열의 값(1)을 가져옵니다. tensor[0]은 0번 행 전체를 가져오는데, 이건 배치에서 첫 번째 샘플만 확인할 때 유용합니다.
2차원이든 4차원이든 같은 방식으로 접근할 수 있어요. 그 다음으로, tensor[:2]는 처음 2개 행을 슬라이싱합니다.
:는 "전체"를 의미하고, :2는 "0번부터 2번 전까지(0, 1)"를 의미합니다. tensor[:, -2:]는 모든 행(:)의 뒤에서 2개 열(-2:)을 선택하는데, 이미지의 오른쪽 절반만 자르거나 시계열의 최근 데이터만 가져올 때 이 패턴을 씁니다.
세 번째로, Boolean Masking이 정말 강력합니다. tensor > 5는 각 원소가 5보다 큰지 True/False로 판단한 마스크를 만들고, tensor[mask]는 True인 위치의 값만 1차원으로 모아서 반환합니다.
이건 이상치(outlier)를 제거하거나, 특정 클래스의 샘플만 필터링하거나, 손실값이 큰 샘플을 찾을 때 필수적입니다. Advanced Indexing은 Tensor를 인덱스로 사용해서 원하는 행들만 선택합니다.
indices = [0, 2]로 0번째와 2번째 행만 뽑아내는데, 순서를 바꾸거나 중복 선택도 가능합니다. 마지막으로 tensor[0, :] = 0처럼 슬라이싱한 부분에 직접 값을 할당하면 원본 Tensor가 수정됩니다.
이건 데이터 증강(augmentation)에서 특정 영역을 마스킹할 때 사용합니다. 여러분이 이 코드를 사용하면 복잡한 반복문 없이 원하는 데이터만 깔끔하게 선택할 수 있습니다.
실무에서는 잘못 분류된 샘플만 골라내서 재학습하거나, 이미지의 특정 영역에만 필터를 적용하거나, 배치에서 hard negative mining을 할 때 이런 기술들을 조합합니다. 특히 Boolean Masking과 Advanced Indexing을 함께 쓰면 정말 복잡한 조건도 한 줄로 처리할 수 있어요.
실전 팁
💡 기본 인덱싱은 뷰를 반환하지만, Boolean Masking은 새로운 Tensor를 만듭니다. 메모리가 부족하면 주의하세요.
💡 torch.where(condition, x, y)를 사용하면 조건에 따라 다른 값을 선택할 수 있습니다. if-else를 Tensor 단위로 처리하는 거죠.
💡 ellipsis(...)를 사용하면 중간 차원을 생략할 수 있습니다. tensor[..., -1]은 마지막 차원의 마지막 원소를 의미합니다.
💡 gather()와 scatter()를 배우면 복잡한 인덱싱 작업을 효율적으로 할 수 있습니다. NLP의 임베딩 레이어가 gather()의 대표 예시입니다.
💡 nonzero()로 0이 아닌 원소의 인덱스를 찾을 수 있습니다. (mask == True).nonzero()는 조건을 만족하는 위치를 좌표로 반환합니다.
4. Tensor Shape 변환 (Reshape, View, Transpose)
시작하며
여러분이 CNN으로 학습한 이미지 특징을 완전연결층(FC Layer)에 넣으려고 할 때 "shape 에러"를 본 적 있나요? 예를 들어, (32, 64, 7, 7) 모양의 특징 맵을 (32, 3136) 모양으로 펼쳐야 하는데 어떻게 해야 할까요?
이런 상황은 딥러닝 모델을 만들 때 거의 항상 발생합니다. 합성곱층의 출력을 분류층에 연결하거나, 배치 차원을 추가/제거하거나, 이미지의 높이와 너비를 바꾸는 등 Tensor의 모양을 변경해야 하는 경우가 정말 많습니다.
바로 이럴 때 필요한 것이 Reshape, View, Transpose 같은 Shape 변환 연산입니다. 이건 마치 레고 블록을 다시 조립하는 것처럼, 같은 데이터를 다른 모양으로 재배열하는 기술입니다.
개요
간단히 말해서, Shape 변환은 Tensor의 데이터는 그대로 두고 모양만 바꾸는 연산입니다. 12개 원소를 (3, 4), (4, 3), (2, 6) 등 다양한 모양으로 재배열할 수 있죠.
왜 Shape 변환이 필요할까요? 딥러닝의 각 층(layer)은 특정 모양의 입력을 요구합니다.
CNN은 4차원 (배치, 채널, 높이, 너비)를 원하고, RNN은 3차원 (배치, 시퀀스, 특징)을 원하며, FC Layer는 2차원 (배치, 특징)을 원합니다. 한 층에서 다음 층으로 데이터를 전달할 때마다 Shape을 맞춰줘야 해요.
기존에는 NumPy의 reshape()를 사용했다면, PyTorch는 view(), reshape(), transpose(), permute() 등 더 다양한 옵션을 제공합니다. view()는 메모리 효율적이지만 연속된 메모리가 필요하고, reshape()는 항상 작동하지만 때로 복사본을 만듭니다.
Shape 변환의 핵심 특징은 세 가지입니다: 첫째, view()는 원본 메모리를 공유하는 뷰를 만들어서 초고속이고, 둘째, transpose()는 차원의 순서를 바꿔서 (높이, 너비)를 (너비, 높이)로 전환할 수 있으며, 셋째, -1을 사용하면 자동으로 크기를 계산해줍니다. 이러한 기능들이 복잡한 모델 구조를 유연하게 만들어줍니다.
코드 예제
import torch
# 주석: View - 메모리를 공유하는 모양 변경 (연속된 메모리 필요)
x = torch.arange(12) # [0, 1, 2, ..., 11]
x_2d = x.view(3, 4) # 3x4 행렬로 변환
print(f"View 결과:\n{x_2d}")
# 주석: Reshape - 항상 작동하는 모양 변경 (필요시 복사)
x_reshaped = x.reshape(2, 6) # 2x6 행렬로 변환
print(f"Reshape 결과:\n{x_reshaped}")
# 주석: -1로 자동 크기 계산 (나머지 차원에서 추론)
batch_imgs = torch.randn(32, 3, 28, 28) # 32개의 28x28 RGB 이미지
flattened = batch_imgs.view(32, -1) # (32, 3*28*28) = (32, 2352)
print(f"Flatten 결과 shape: {flattened.shape}")
# 주석: Transpose - 두 차원을 맞바꿈
matrix = torch.tensor([[1, 2, 3], [4, 5, 6]]) # 2x3
transposed = matrix.t() # 3x2로 전치
print(f"Transpose 결과:\n{transposed}")
# 주석: Permute - 모든 차원의 순서를 재배열
imgs = torch.randn(10, 3, 224, 224) # (배치, 채널, 높이, 너비)
imgs_hwc = imgs.permute(0, 2, 3, 1) # (배치, 높이, 너비, 채널)
print(f"Permute 결과 shape: {imgs_hwc.shape}")
설명
이것이 하는 일: 위 코드는 Tensor의 모양을 바꾸는 모든 주요 방법을 보여줍니다. 딥러닝 모델에서 층과 층 사이를 연결할 때 반드시 알아야 하는 필수 연산들입니다.
첫 번째로, view(3, 4)는 12개 원소를 3행 4열로 재배열합니다. [0, 1, 2, ..., 11]이 [[0,1,2,3], [4,5,6,7], [8,9,10,11]]로 바뀌는데, 중요한 건 메모리를 복사하지 않는다는 거예요.
원본 x와 x_2d가 같은 데이터를 가리키기 때문에 엄청 빠르고 메모리 효율적입니다. 하지만 Tensor가 메모리에 연속적으로 저장돼 있어야만 작동합니다.
그 다음으로, reshape(2, 6)는 view()와 비슷하지만 항상 작동한다는 차이가 있습니다. 만약 메모리가 연속적이지 않으면 자동으로 복사본을 만들어서라도 모양을 바꿔줍니다.
transpose() 후에 view()를 쓰면 에러가 나는데, reshape()를 쓰면 해결됩니다. 세 번째로, view(32, -1)의 -1은 "나머지 차원으로 자동 계산"을 의미합니다.
총 원소 수가 32 × 3 × 28 × 28 = 75,264개니까, 32로 나누면 2,352가 나옵니다. 그래서 자동으로 (32, 2352)가 되는 거죠.
이건 CNN의 출력을 FC Layer로 연결할 때 매번 사용하는 필수 패턴입니다. 네 번째로, .t()는 2차원 행렬의 행과 열을 바꿉니다.
(2, 3) → (3, 2)로 전치되는데, 선형대수의 전치 행렬과 똑같습니다. 마지막으로 permute(0, 2, 3, 1)은 차원 순서를 완전히 재배열합니다.
0번 차원은 그대로, 1번은 맨 뒤로, 2번은 1번 자리로, 3번은 2번 자리로 이동시킵니다. 이건 이미지 데이터를 저장할 때 PyTorch 형식(CHW)에서 NumPy/OpenCV 형식(HWC)으로 바꾸는 데 필수입니다.
여러분이 이 코드를 사용하면 모델의 어떤 층이든 자유롭게 연결할 수 있습니다. 실무에서는 ResNet의 GAP(Global Average Pooling) 출력을 flatten해서 분류기에 넣거나, Transformer의 (배치, 시퀀스, 임베딩) 출력을 재배열해서 다음 층으로 보내거나, 이미지를 패치로 나누기 위해 복잡한 reshape을 수행하는 등 Shape 변환을 정말 자주 씁니다.
특히 -1을 잘 활용하면 배치 크기가 바뀌어도 코드를 수정할 필요가 없어요.
실전 팁
💡 view() 에러가 나면 contiguous()를 먼저 호출하세요. tensor.transpose(0, 1).contiguous().view(...)처럼 연속 메모리로 만든 후 view()를 사용하면 됩니다.
💡 squeeze()로 크기가 1인 차원을 제거하고, unsqueeze()로 차원을 추가할 수 있습니다. (32, 1, 28, 28) → squeeze(1) → (32, 28, 28)처럼요.
💡 flatten(start_dim, end_dim)을 사용하면 특정 차원 범위만 펼칠 수 있습니다. flatten(1)은 배치 차원을 제외하고 나머지를 모두 펼칩니다.
💡 einstein summation인 torch.einsum()을 배우면 복잡한 transpose와 reshape을 한 번에 할 수 있습니다. 'bchw->bhwc'는 permute(0,2,3,1)과 같습니다.
💡 nn.Flatten() 모듈을 사용하면 모델 안에서 자동으로 flatten이 적용됩니다. Sequential 안에 넣기 편리합니다.
5. GPU 가속 및 Device 관리
시작하며
여러분이 딥러닝 모델을 학습시킬 때 "왜 이렇게 느리지?"라는 생각을 해본 적 있나요? 예를 들어, MNIST 데이터셋 학습이 10분 걸리는데 ImageNet은 며칠이 걸린다면 뭔가 문제가 있는 거죠.
이런 속도 문제는 CPU에서만 학습할 때 발생합니다. CPU는 범용 프로세서라서 다양한 작업을 할 수 있지만, 딥러닝처럼 같은 계산을 수백만 번 반복하는 작업에는 비효율적입니다.
GPU는 단순한 계산을 수천 개 동시에 처리할 수 있어서 딥러닝에 최적화돼 있어요. 바로 이럴 때 필요한 것이 PyTorch의 GPU 가속입니다.
코드 몇 줄만 추가하면 같은 모델을 수십 배에서 수백 배 빠르게 학습시킬 수 있습니다. 마치 자전거에서 스포츠카로 갈아타는 것과 같죠.
개요
간단히 말해서, GPU 가속은 Tensor와 모델을 GPU 메모리로 옮겨서 초고속 병렬 계산을 수행하는 기능입니다. .to('cuda')라는 간단한 메서드로 CPU에서 GPU로 이동할 수 있어요.
왜 GPU 가속이 필요할까요? 현대 딥러닝 모델은 수백만에서 수십억 개의 파라미터를 가지고 있습니다.
이런 모델을 CPU로 학습하면 몇 주에서 몇 달이 걸리지만, GPU를 사용하면 몇 시간에서 며칠로 줄어듭니다. 연구와 실무 둘 다에서 GPU는 선택이 아니라 필수입니다.
기존에는 TensorFlow의 GPU 설정이 복잡했다면, PyTorch는 훨씬 간단합니다. CUDA가 설치돼 있다면 .to('cuda') 한 줄로 끝납니다.
여러 GPU를 사용하는 분산 학습도 DataParallel이나 DistributedDataParallel로 쉽게 구현할 수 있어요. GPU 가속의 핵심 특징은 세 가지입니다: 첫째, 같은 device에 있는 Tensor끼리만 연산 가능하므로 일관성 있게 관리해야 하고, 둘째, CPU ↔ GPU 데이터 전송에 시간이 걸리므로 최소화해야 하며, 셋째, GPU 메모리는 제한적이라서 배치 크기를 적절히 조절해야 합니다.
이러한 특성을 이해하면 최적의 성능을 뽑아낼 수 있습니다.
코드 예제
import torch
# 주석: CUDA(GPU) 사용 가능 여부 확인
print(f"CUDA 사용 가능: {torch.cuda.is_available()}")
print(f"사용 가능한 GPU 개수: {torch.cuda.device_count()}")
# 주석: Device 객체 생성 - GPU 있으면 cuda, 없으면 cpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"사용할 Device: {device}")
# 주석: Tensor를 GPU로 이동
x = torch.randn(1000, 1000) # CPU에 생성
x_gpu = x.to(device) # GPU로 이동
print(f"Tensor 위치: {x_gpu.device}")
# 주석: 처음부터 GPU에 Tensor 생성
y = torch.ones(1000, 1000, device=device)
# 주석: GPU 연산 수행 (같은 device에 있어야 함)
result = x_gpu @ y # 행렬 곱셈을 GPU에서 수행
print(f"연산 결과 device: {result.device}")
# 주석: GPU에서 CPU로 이동 (NumPy 변환이나 출력을 위해)
result_cpu = result.cpu()
print(f"CPU로 이동 완료: {result_cpu.device}")
# 주석: GPU 메모리 확인 및 정리
if torch.cuda.is_available():
print(f"할당된 GPU 메모리: {torch.cuda.memory_allocated() / 1024**2:.2f} MB")
torch.cuda.empty_cache() # 캐시 정리
설명
이것이 하는 일: 위 코드는 GPU를 활용하기 위한 전체 워크플로우를 보여줍니다. GPU 확인부터 데이터 이동, 연산, 메모리 관리까지 실무에서 필수적인 패턴들을 담았어요.
첫 번째로, torch.cuda.is_available()로 현재 시스템에 CUDA가 설치돼 있고 GPU를 사용할 수 있는지 확인합니다. GPU가 없는 환경에서 .to('cuda')를 바로 쓰면 에러가 나기 때문에 항상 먼저 확인해야 합니다.
torch.cuda.device_count()는 사용 가능한 GPU 개수를 알려주는데, 여러 GPU가 있다면 분산 학습을 고려할 수 있습니다. 그 다음으로, device 변수를 만들어서 GPU가 있으면 'cuda', 없으면 'cpu'를 담습니다.
이렇게 하면 나중에 코드를 수정할 필요 없이 device만 바꿔주면 되니까 훨씬 유연합니다. 회사 서버(GPU 있음)와 노트북(GPU 없음)에서 같은 코드를 돌릴 수 있어요.
세 번째로, x.to(device)가 Tensor를 지정한 device로 이동시킵니다. GPU 메모리로 데이터를 복사하는 과정이라서 큰 Tensor는 시간이 좀 걸립니다.
대신 torch.ones(1000, 1000, device=device)처럼 처음부터 GPU에 만들면 복사 과정이 없어서 더 빠릅니다. 특히 학습 루프 안에서는 데이터를 미리 GPU로 옮겨두는 게 효율적입니다.
x_gpu @ y는 두 Tensor의 행렬 곱셈을 GPU에서 수행합니다. 중요한 건 두 Tensor가 같은 device에 있어야 한다는 거예요.
하나는 CPU, 하나는 GPU에 있으면 "expected all tensors to be on the same device" 에러가 발생합니다. 마지막으로 result.cpu()는 결과를 다시 CPU로 가져오는데, 이건 화면에 출력하거나 NumPy 배열로 변환하거나 파일로 저장할 때 필요합니다.
여러분이 이 코드를 사용하면 학습 속도를 극적으로 향상시킬 수 있습니다. 실무에서는 모델과 데이터를 모두 GPU로 옮긴 후 학습하고, 주기적으로 torch.cuda.memory_allocated()로 메모리를 확인하며, 배치 크기를 조절해서 out-of-memory 에러를 방지합니다.
특히 대규모 모델(BERT, GPT 등)을 학습할 때는 mixed precision training(torch.cuda.amp)을 사용해서 메모리를 절반으로 줄이고 속도도 높일 수 있어요.
실전 팁
💡 모델을 GPU로 옮길 때는 model.to(device)를 한 번만 호출하고, 데이터는 매 배치마다 inputs.to(device)로 옮기세요.
💡 torch.cuda.empty_cache()는 캐시만 비우고 실제 메모리는 해제하지 않습니다. del 변수명으로 먼저 삭제한 후 호출해야 효과가 있습니다.
💡 pin_memory=True 옵션을 DataLoader에 사용하면 CPU→GPU 전송 속도가 빨라집니다. num_workers와 함께 쓰면 더 효과적입니다.
💡 여러 GPU를 사용하려면 nn.DataParallel(model)로 감싸거나, 더 효율적인 nn.parallel.DistributedDataParallel을 사용하세요.
💡 CUDA out of memory 에러가 나면 배치 크기를 절반으로 줄이거나, gradient accumulation을 사용해서 작은 배치를 여러 번 누적하세요.
6. Autograd 및 자동 미분
시작하며
여러분이 딥러닝 모델을 학습시킬 때 "손실 함수의 미분값을 어떻게 계산하지?"라는 고민을 해본 적 있나요? 예를 들어, 수백만 개 파라미터의 그래디언트를 하나하나 손으로 계산한다면 평생 걸릴 거예요.
이런 문제는 딥러닝의 핵심인 역전파(Backpropagation) 알고리즘을 구현할 때 발생합니다. 복잡한 신경망일수록 미분 계산이 어려워지고, 한 줄만 틀려도 모델이 전혀 학습하지 못합니다.
손으로 미분 공식을 유도하고 코드로 구현하는 건 너무 힘들고 에러가 발생하기 쉽습니다. 바로 이럴 때 필요한 것이 PyTorch의 Autograd(자동 미분) 기능입니다.
이건 마치 계산기가 자동으로 계산해주듯이, 여러분이 정의한 연산을 기록했다가 자동으로 미분값을 계산해주는 마법 같은 시스템입니다.
개요
간단히 말해서, Autograd는 Tensor에 대한 모든 연산을 추적해서 자동으로 그래디언트(미분값)를 계산해주는 엔진입니다. requires_grad=True만 설정하면 모든 계산이 자동으로 기록됩니다.
왜 Autograd가 필요할까요? 딥러닝 학습의 핵심은 "손실을 줄이는 방향으로 파라미터를 업데이트"하는 건데, 그 방향을 알려면 손실 함수를 파라미터로 미분해야 합니다.
ResNet 같은 복잡한 모델의 미분을 손으로 계산하는 건 사실상 불가능하지만, Autograd는 어떤 모델이든 자동으로 처리합니다. 기존에는 TensorFlow의 GradientTape를 사용했다면, PyTorch는 더 직관적인 방식으로 작동합니다.
연산을 수행하면 자동으로 계산 그래프가 만들어지고, .backward()만 호출하면 모든 그래디언트가 계산됩니다. 동적 그래프 방식이라서 디버깅도 쉽고 유연합니다.
Autograd의 핵심 특징은 세 가지입니다: 첫째, requires_grad=True인 Tensor의 모든 연산을 자동 추적하고, 둘째, .backward()로 연쇄 법칙(chain rule)을 자동 적용해서 그래디언트를 계산하며, 셋째, 계산 그래프를 메모리에 저장했다가 backward 후 자동으로 삭제합니다. 이러한 기능들이 딥러닝 연구를 획기적으로 쉽게 만들었습니다.
코드 예제
import torch
# 주석: requires_grad=True로 그래디언트 추적 시작
x = torch.tensor([2.0], requires_grad=True)
w = torch.tensor([3.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)
# 주석: 순전파 - 연산 그래프가 자동으로 만들어짐
y = w * x + b # y = 3*2 + 1 = 7
loss = (y - 5) ** 2 # loss = (7-5)^2 = 4
print(f"순전파 결과 - y: {y.item()}, loss: {loss.item()}")
# 주석: 역전파 - 모든 그래디언트 자동 계산
loss.backward() # dloss/dx, dloss/dw, dloss/db 계산
# 주석: 계산된 그래디언트 확인
print(f"dx (∂loss/∂x): {x.grad}") # 12.0
print(f"dw (∂loss/∂w): {w.grad}") # 8.0
print(f"db (∂loss/∂b): {b.grad}") # 4.0
# 주석: 그래디언트 초기화 (누적 방지)
x.grad.zero_()
w.grad.zero_()
b.grad.zero_()
# 주석: 그래디언트 추적 비활성화 (추론 시 사용)
with torch.no_grad():
prediction = w * x + b # 계산 그래프 생성 안 함
print(f"추론 결과: {prediction.item()}")
설명
이것이 하는 일: 위 코드는 PyTorch의 자동 미분 시스템이 어떻게 작동하는지 처음부터 끝까지 보여줍니다. 간단한 선형 함수지만, 수백 층짜리 신경망도 같은 원리로 작동해요.
첫 번째로, requires_grad=True를 설정한 Tensor는 "이 변수는 학습 가능한 파라미터니까 미분값을 계산해줘"라고 PyTorch에게 알려줍니다. x, w, b 모두 그래디언트를 추적하기 시작하고, 이들을 사용한 모든 연산(곱셈, 덧셈 등)이 계산 그래프에 기록됩니다.
실제 신경망에서는 nn.Parameter로 감싸면 자동으로 requires_grad=True가 됩니다. 그 다음으로, y = w * x + b와 loss = (y - 5) ** 2를 계산하면 PyTorch가 내부적으로 "y는 w와 x를 곱한 다음 b를 더해서 만들어졌고, loss는 y에서 5를 뺀 다음 제곱했다"는 정보를 저장합니다.
이게 바로 계산 그래프인데, 나중에 미분할 때 이 정보를 역순으로 따라가면서 연쇄 법칙을 적용합니다. 세 번째로, loss.backward()가 실행되면 마법이 일어납니다.
계산 그래프를 거꾸로 따라가면서 dloss/dy, dy/dw, dy/dx, dy/db를 차례대로 계산하고, 연쇄 법칙으로 조합해서 최종적으로 dloss/dw, dloss/dx, dloss/db를 구합니다. 수학적으로는 복잡하지만 PyTorch가 자동으로 처리하니까 우리는 .backward() 한 줄만 쓰면 됩니다.
네 번째로, .grad 속성에 계산된 그래디언트가 저장됩니다. w.grad는 "loss를 줄이려면 w를 어느 방향으로 얼마나 바꿔야 하는가"를 알려주는 값입니다.
실제 학습에서는 optimizer가 이 값을 사용해서 w -= learning_rate * w.grad처럼 파라미터를 업데이트합니다. 중요한 건 .backward()를 여러 번 호출하면 그래디언트가 누적되기 때문에, 매번 .zero_()로 초기화해야 한다는 거예요.
여러분이 이 코드를 사용하면 어떤 복잡한 모델이든 자동으로 학습시킬 수 있습니다. 실무에서는 CNN, RNN, Transformer 같은 복잡한 구조를 정의하고, loss.backward()로 그래디언트를 계산한 다음, optimizer.step()으로 파라미터를 업데이트하는 패턴을 반복합니다.
with torch.no_grad()는 추론할 때 사용하는데, 메모리를 절약하고 속도를 높이기 위해 계산 그래프를 만들지 않습니다.
실전 팁
💡 optimizer.zero_grad()를 사용하면 모델의 모든 파라미터 그래디언트를 한 번에 초기화할 수 있습니다. 수동으로 .zero_()할 필요 없어요.
💡 .detach()로 계산 그래프에서 Tensor를 분리하면 그 이후 연산은 추적되지 않습니다. 메모리 누수를 방지할 때 유용합니다.
💡 torch.autograd.grad()를 사용하면 .backward() 없이 직접 그래디언트를 계산할 수 있습니다. 고차 미분이나 커스텀 학습 루프에 유용합니다.
💡 gradient clipping으로 그래디언트 폭발을 방지하세요. torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)처럼 사용합니다.
💡 retain_graph=True 옵션을 .backward()에 주면 계산 그래프를 유지해서 여러 번 backward할 수 있습니다. 하지만 메모리를 많이 쓰니 조심하세요.
7. Tensor와 NumPy 상호 변환
시작하며
여러분이 PyTorch로 모델을 학습시킨 후 결과를 Matplotlib으로 시각화하거나, NumPy 기반의 기존 코드와 통합하려고 할 때 막막하셨나요? 예를 들어, 이미지 처리 라이브러리들은 대부분 NumPy 배열을 요구하는데 PyTorch Tensor를 어떻게 변환해야 할까요?
이런 상황은 실무에서 정말 자주 발생합니다. 데이터 전처리는 Pandas와 NumPy로 하고, 학습은 PyTorch로 하고, 시각화는 Matplotlib으로 하는 게 일반적인 워크플로우니까요.
각 라이브러리가 요구하는 데이터 형식이 다르기 때문에 변환이 필수입니다. 바로 이럴 때 필요한 것이 Tensor와 NumPy 간의 상호 변환입니다.
PyTorch는 .numpy()와 torch.from_numpy()라는 간단한 메서드로 두 형식을 자유롭게 오갈 수 있게 해줍니다.
개요
간단히 말해서, Tensor와 NumPy 배열은 서로 쉽게 변환할 수 있습니다. Tensor를 NumPy로 바꿀 때는 .numpy()를 쓰고, NumPy를 Tensor로 바꿀 때는 torch.from_numpy()를 쓰면 됩니다.
왜 상호 변환이 필요할까요? 데이터 과학 생태계는 대부분 NumPy를 기반으로 만들어졌습니다.
Scikit-learn, Pandas, OpenCV, Matplotlib 모두 NumPy 배열을 사용하죠. PyTorch로 딥러닝을 하더라도 전처리나 후처리에서는 이런 도구들을 함께 써야 하기 때문에 변환 능력이 필수입니다.
기존에는 리스트로 변환한 다음 다시 NumPy나 Tensor로 만드는 비효율적인 방법을 사용했다면, PyTorch는 직접 변환을 지원합니다. 더 중요한 건 CPU Tensor와 NumPy 배열이 메모리를 공유한다는 거예요.
복사 없이 변환되니까 대용량 데이터도 빠르게 처리할 수 있습니다. 상호 변환의 핵심 특징은 세 가지입니다: 첫째, CPU Tensor와 NumPy는 메모리를 공유해서 한쪽을 수정하면 다른 쪽도 바뀌고, 둘째, GPU Tensor는 먼저 CPU로 옮긴 후 변환해야 하며, 셋째, 데이터 타입(dtype)이 자동으로 매핑됩니다.
이러한 특성을 이해하면 효율적으로 데이터를 다룰 수 있습니다.
코드 예제
import torch
import numpy as np
# 주석: NumPy 배열을 PyTorch Tensor로 변환
np_array = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)
tensor = torch.from_numpy(np_array)
print(f"NumPy -> Tensor:\n{tensor}")
# 주석: PyTorch Tensor를 NumPy 배열로 변환 (CPU만 가능)
tensor_data = torch.randn(2, 3)
np_data = tensor_data.numpy()
print(f"Tensor -> NumPy:\n{np_data}")
# 주석: 메모리 공유 확인 - 한쪽을 수정하면 다른 쪽도 변경됨
shared_np = np.array([1, 2, 3], dtype=np.float32)
shared_tensor = torch.from_numpy(shared_np)
shared_np[0] = 999 # NumPy 수정
print(f"메모리 공유 확인: {shared_tensor}") # Tensor도 999로 변경됨
# 주석: 메모리 공유 방지 - clone()으로 복사본 생성
copied_tensor = torch.from_numpy(shared_np).clone()
shared_np[1] = 888
print(f"복사본은 영향 없음: {copied_tensor}") # 원본 유지
# 주석: GPU Tensor는 먼저 CPU로 이동 후 변환
if torch.cuda.is_available():
gpu_tensor = torch.randn(2, 3, device='cuda')
np_from_gpu = gpu_tensor.cpu().numpy() # .cpu() 먼저 호출
print(f"GPU -> NumPy 변환 완료")
# 주석: dtype 매핑 - PyTorch와 NumPy의 데이터 타입 자동 변환
float_tensor = torch.tensor([1.5, 2.5], dtype=torch.float64)
float_np = float_tensor.numpy()
print(f"dtype 확인: Tensor {float_tensor.dtype} -> NumPy {float_np.dtype}")
설명
이것이 하는 일: 위 코드는 PyTorch Tensor와 NumPy 배열을 양방향으로 변환하는 모든 주요 패턴을 보여줍니다. 실무에서 데이터 파이프라인을 구축할 때 반드시 알아야 하는 필수 기술들입니다.
첫 번째로, torch.from_numpy(np_array)는 NumPy 배열을 받아서 PyTorch Tensor로 변환합니다. 이때 중요한 건 실제로 데이터를 복사하지 않는다는 거예요.
원본 NumPy 배열과 새로운 Tensor가 같은 메모리 주소를 가리키기 때문에 대용량 데이터도 순식간에 변환됩니다. CSV 파일을 Pandas로 읽고 NumPy로 변환한 다음 Tensor로 만들 때 이 패턴을 사용합니다.
그 다음으로, tensor_data.numpy()는 반대 방향 변환을 수행합니다. Tensor를 NumPy 배열로 바꾸는데, 이것도 메모리를 공유합니다.
주의할 점은 GPU Tensor는 직접 NumPy로 변환할 수 없다는 거예요. NumPy는 CPU에서만 작동하기 때문에 먼저 .cpu()로 CPU로 옮긴 다음 .numpy()를 호출해야 합니다.
세 번째로, 메모리 공유의 양날의 검을 보여줍니다. shared_np[0] = 999로 NumPy 배열을 수정하면 shared_tensor도 자동으로 999로 바뀝니다.
같은 메모리를 보고 있으니까요. 이게 편리할 때도 있지만, 의도치 않은 버그를 만들 수도 있습니다.
만약 독립적인 복사본이 필요하다면 .clone()을 사용해서 새로운 메모리에 데이터를 복사해야 합니다. GPU에서 CPU로, 다시 NumPy로 변환하는 과정은 gpu_tensor.cpu().numpy()처럼 메서드를 체이닝합니다.
GPU 메모리에서 CPU 메모리로 데이터를 전송하는 과정이라서 큰 Tensor는 시간이 좀 걸립니다. 마지막으로 dtype은 자동으로 매핑되는데, torch.float32는 np.float32가 되고, torch.int64는 np.int64가 됩니다.
대부분 직관적이지만, 가끔 예상과 다를 수 있으니 확인하는 게 좋습니다. 여러분이 이 코드를 사용하면 PyTorch와 NumPy 생태계를 자유롭게 오갈 수 있습니다.
실무에서는 이미지를 PIL이나 OpenCV로 불러온 다음 NumPy로 변환하고, Tensor로 바꿔서 모델에 입력하고, 결과를 다시 NumPy로 변환해서 Matplotlib으로 그리는 식의 파이프라인을 만듭니다. 특히 전처리(augmentation, normalization)는 NumPy/PIL로, 학습은 PyTorch로, 평가 지표 계산은 Scikit-learn으로 하는 하이브리드 워크플로우가 일반적입니다.
실전 팁
💡 메모리 공유가 싫다면 torch.tensor(np_array)를 사용하세요. from_numpy()와 달리 항상 새로운 복사본을 만듭니다.
💡 requires_grad=True인 Tensor는 .detach().numpy()로 변환해야 합니다. 그래디언트 추적을 먼저 끊어야 NumPy로 변환됩니다.
💡 PIL Image는 먼저 np.array(img)로 NumPy로 바꾼 다음 torch.from_numpy()를 사용하세요. 또는 torchvision.transforms.ToTensor()가 더 편리합니다.
💡 Pandas DataFrame은 .values 또는 .to_numpy()로 NumPy로 만든 후 Tensor로 변환하세요. torch.tensor(df.values)처럼요.
💡 대용량 데이터는 torch.from_numpy()를 사용해서 복사를 피하고, 메모리를 절약하세요. 단, 원본을 수정하지 않도록 주의하세요.
8. Tensor 연결 및 분할 (Cat, Stack, Chunk, Split)
시작하며
여러분이 여러 개의 이미지를 하나의 배치로 묶거나, 큰 Tensor를 작은 조각으로 나누어 처리하고 싶을 때 어떻게 해야 할지 고민해본 적 있나요? 예를 들어, 3개의 (32, 3, 224, 224) Tensor를 (96, 3, 224, 224) 하나로 합치고 싶다면요?
이런 상황은 데이터 로딩과 배치 처리에서 매일 발생합니다. 여러 개의 샘플을 배치로 묶어서 한 번에 학습시키거나, 반대로 배치를 개별 샘플로 나누어 후처리하거나, 모델의 중간 출력을 여러 개로 분할해서 다른 경로로 보내야 할 때가 많습니다.
바로 이럴 때 필요한 것이 Tensor의 연결(concatenate)과 분할(split) 연산입니다. 이건 마치 레고 블록을 이어 붙이거나 분해하는 것처럼, 여러 Tensor를 하나로 합치거나 하나를 여러 개로 나눌 수 있습니다.
개요
간단히 말해서, Tensor 연결은 여러 개의 Tensor를 하나로 합치는 것이고, 분할은 하나의 Tensor를 여러 개로 나누는 것입니다. torch.cat()과 torch.stack()으로 합치고, torch.chunk()와 torch.split()으로 나눕니다.
왜 연결과 분할이 필요할까요? 딥러닝에서는 배치 단위로 데이터를 처리하는데, 여러 샘플을 하나로 묶어야 GPU를 효율적으로 활용할 수 있습니다.
또한 Skip Connection(ResNet)이나 Multi-head Attention(Transformer)처럼 중간 특징을 합치거나 나누는 아키텍처가 많아서, 이런 연산이 모델 구조의 핵심이 됩니다. 기존에는 NumPy의 np.concatenate()와 np.split()을 사용했다면, PyTorch는 cat, stack, chunk, split 등 더 다양한 옵션을 제공합니다.
cat은 기존 차원에서 합치고, stack은 새로운 차원을 만들어서 합치는 차이가 있어서 상황에 맞게 선택할 수 있습니다. 연결과 분할의 핵심 특징은 세 가지입니다: 첫째, cat()은 지정한 차원에서 Tensor들을 이어붙이고, 둘째, stack()은 새로운 차원을 추가해서 쌓으며, 셋째, chunk()와 split()은 Tensor를 균등 또는 원하는 크기로 나눕니다.
이러한 연산들이 복잡한 모델 구조를 구현하는 데 필수적입니다.
코드 예제
import torch
# 주석: torch.cat() - 기존 차원에서 이어붙이기
x = torch.tensor([[1, 2], [3, 4]]) # 2x2
y = torch.tensor([[5, 6], [7, 8]]) # 2x2
cat_dim0 = torch.cat([x, y], dim=0) # dim=0(행)으로 합침 -> 4x2
cat_dim1 = torch.cat([x, y], dim=1) # dim=1(열)으로 합침 -> 2x4
print(f"Cat dim=0:\n{cat_dim0}")
print(f"Cat dim=1:\n{cat_dim1}")
# 주석: torch.stack() - 새로운 차원 추가해서 쌓기
stacked = torch.stack([x, y], dim=0) # 새로운 0번 차원 생성 -> 2x2x2
print(f"Stack dim=0 shape: {stacked.shape}")
# 주석: torch.chunk() - 균등하게 N개로 분할
data = torch.arange(12).view(3, 4) # 3x4 행렬
chunks = torch.chunk(data, chunks=3, dim=0) # 3개 행으로 분할
print(f"Chunk 결과: {len(chunks)}개, 첫 번째:\n{chunks[0]}")
# 주석: torch.split() - 원하는 크기로 분할
splits = torch.split(data, split_size_or_sections=[1, 2], dim=0) # 1행, 2행으로 분할
print(f"Split 결과: {len(splits)}개, 크기: {[s.shape for s in splits]}")
# 주석: 실전 예제 - 배치 합치기
batch1 = torch.randn(32, 3, 224, 224) # 32개 이미지
batch2 = torch.randn(32, 3, 224, 224)
combined_batch = torch.cat([batch1, batch2], dim=0) # 64개로 합침
print(f"배치 합치기: {batch1.shape} + {batch2.shape} = {combined_batch.shape}")
설명
이것이 하는 일: 위 코드는 여러 Tensor를 합치고 나누는 모든 주요 방법을 보여줍니다. 배치 처리, Skip Connection, Multi-head 구조 등 실무에서 정말 자주 쓰는 핵심 연산들입니다.
첫 번째로, torch.cat([x, y], dim=0)는 두 Tensor를 0번 차원(행)에서 이어붙입니다. 2x2 두 개가 4x2 하나가 되는데, 마치 위아래로 붙이는 거죠.
dim=1로 하면 옆으로 붙여서 2x4가 됩니다. 중요한 건 합치려는 차원을 제외한 나머지 차원의 크기가 모두 같아야 한다는 거예요.
dim=0로 합칠 때는 열의 개수(2)가 같아야 하고, dim=1로 합칠 때는 행의 개수(2)가 같아야 합니다. 그 다음으로, torch.stack([x, y], dim=0)는 새로운 차원을 만들어서 쌓습니다.
2x2 두 개가 2x2x2가 되는데, 첫 번째 차원이 "몇 번째 Tensor인가"를 나타냅니다. 이건 여러 샘플을 배치로 묶을 때 사용하는 전형적인 패턴입니다.
cat과 달리 모든 Tensor의 shape이 완전히 같아야 합니다. 세 번째로, torch.chunk(data, chunks=3, dim=0)는 Tensor를 균등하게 3개로 나눕니다.
3x4 행렬을 행 방향으로 3개로 나누면 각각 1x4가 되는 거죠. 만약 딱 나누어떨어지지 않으면 마지막 조각이 더 작아집니다.
이건 배치를 여러 GPU에 분산시킬 때 유용합니다. 네 번째로, torch.split(data, [1, 2], dim=0)는 원하는 크기로 분할합니다.
3x4를 1행과 2행으로 나누면 1x4와 2x4 두 조각이 됩니다. chunk보다 유연해서 Multi-head Attention처럼 불균등하게 나눠야 할 때 씁니다.
마지막 예제는 실전 상황인데, 두 개의 배치를 하나로 합쳐서 더 큰 배치를 만듭니다. 데이터 증강으로 생성한 샘플들을 원본과 합쳐서 한 번에 학습시킬 때 이 패턴을 사용합니다.
여러분이 이 코드를 사용하면 복잡한 데이터 조작을 간단하게 처리할 수 있습니다. 실무에서는 ResNet의 Skip Connection에서 두 특징 맵을 cat으로 합치거나, Transformer의 Query/Key/Value를 split으로 나누어 Multi-head로 처리하거나, DataLoader에서 가변 길이 시퀀스들을 패딩 후 stack으로 배치로 만드는 등 이런 연산을 매일 사용합니다.
특히 dim 매개변수를 잘 이해하면 어떤 복잡한 구조도 자유자재로 만들 수 있어요.
실전 팁
💡 cat과 stack의 차이를 명확히 하세요. cat은 차원 개수 유지, stack은 차원 하나 증가합니다. [(2,3), (2,3)] → cat은 (4,3), stack은 (2,2,3)입니다.
💡 torch.unbind()로 Tensor를 차원을 따라 분해할 수 있습니다. stack의 반대 연산으로, (3,2,4)를 unbind(dim=0)하면 (2,4) 3개가 됩니다.
💡 einops 라이브러리의 rearrange()를 배우면 복잡한 reshape/cat/split을 한 줄로 표현할 수 있습니다. 'b h w c -> b (h w) c'처럼요.
💡 리스트 내포 대신 cat을 사용하세요. [tensor for _ in range(10)]보다 torch.cat([tensor]*10)이 훨씬 빠릅니다.
💡 동적 배치 크기를 다룰 때는 torch.nn.utils.rnn.pad_sequence()를 사용하면 가변 길이 시퀀스를 자동으로 패딩해서 배치로 만들어줍니다.
9. 수학 함수 및 통계 연산
시작하며
여러분이 모델 출력의 평균을 구하거나, 손실 함수에 지수/로그를 적용하거나, 데이터를 정규화하고 싶을 때 어떤 함수를 써야 할지 고민해본 적 있나요? 예를 들어, 배치 128개 이미지의 픽셀 평균과 표준편차를 한 번에 계산하려면 어떻게 해야 할까요?
이런 상황은 데이터 전처리와 모델 학습에서 필수적입니다. 정규화하지 않은 데이터는 학습이 불안정하고, 통계 정보 없이는 데이터 분포를 이해할 수 없으며, 활성화 함수나 손실 함수는 대부분 수학 함수(exp, log, sin 등)를 사용합니다.
바로 이럴 때 필요한 것이 PyTorch의 수학 함수와 통계 연산입니다. 이건 마치 과학용 계산기처럼, 기본 사칙연산부터 복잡한 통계 계산까지 모두 Tensor 단위로 빠르게 처리할 수 있습니다.
개요
간단히 말해서, PyTorch는 수백 가지의 수학 함수와 통계 함수를 제공합니다. mean(), std(), sum(), max(), min() 같은 기본 통계부터 sin(), cos(), exp(), log() 같은 수학 함수까지 모두 GPU에서 작동합니다.
왜 이런 함수들이 필요할까요? 딥러닝의 모든 과정이 수학 연산의 연속입니다.
데이터 정규화는 평균과 표준편차를 빼고 나누고, Softmax는 지수 함수를 사용하며, Cross Entropy는 로그를 사용합니다. Batch Normalization, Layer Normalization, Weight Initialization 모두 통계 함수에 의존합니다.
기존에는 NumPy의 수학 함수를 사용했다면, PyTorch는 같은 API에 GPU 가속을 더했습니다. np.mean()과 torch.mean()은 사용법이 거의 같지만, PyTorch는 GPU에서 수백 배 빠르게 계산할 수 있습니다.
또한 Autograd와 통합돼 있어서 미분도 자동으로 됩니다. 수학/통계 연산의 핵심 특징은 세 가지입니다: 첫째, 차원을 지정해서 축소(reduction) 연산을 할 수 있고, 둘째, keepdim=True로 차원을 유지하면서 계산 가능하며, 셋째, element-wise 함수는 Broadcasting과 결합해서 강력합니다.
이러한 기능들이 복잡한 데이터 처리를 간결하게 만들어줍니다.
코드 예제
import torch
# 주석: 기본 통계 함수 - 평균, 합계, 최대/최소
data = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
print(f"전체 평균: {data.mean()}") # 3.5
print(f"행별 평균: {data.mean(dim=1)}") # [2.0, 5.0]
print(f"열별 합계: {data.sum(dim=0)}") # [5.0, 7.0, 9.0]
print(f"최댓값: {data.max()}, 최솟값: {data.min()}")
# 주석: 표준편차와 분산 - 정규화에 필수
print(f"표준편차: {data.std()}") # 전체 표준편차
print(f"분산: {data.var()}") # 전체 분산
# 주석: 차원 유지 옵션 - Broadcasting에 유용
row_mean = data.mean(dim=1, keepdim=True) # (2, 1) 모양 유지
normalized = data - row_mean # Broadcasting으로 각 행에서 평균 뺌
print(f"행별 정규화:\n{normalized}")
# 주석: 수학 함수 - exp, log, sqrt, sin, cos 등
x = torch.tensor([1.0, 2.0, 3.0])
print(f"지수: {torch.exp(x)}") # e^x
print(f"자연로그: {torch.log(x)}") # ln(x)
print(f"제곱근: {torch.sqrt(x)}") # √x
print(f"절댓값: {torch.abs(torch.tensor([-1, 2, -3]))}")
# 주석: argmax/argmin - 최댓값/최솟값의 인덱스 찾기
logits = torch.tensor([[0.1, 0.3, 0.6], [0.8, 0.1, 0.1]])
predictions = torch.argmax(logits, dim=1) # 각 행의 최댓값 인덱스
print(f"예측 클래스: {predictions}") # [2, 0]
# 주석: clamp - 값을 특정 범위로 제한
clamped = torch.clamp(data, min=2.0, max=5.0) # 2~5 범위로 제한
print(f"Clamp 결과:\n{clamped}")
설명
이것이 하는 일: 위 코드는 데이터 분석과 모델 학습에서 가장 자주 쓰는 수학/통계 연산들을 보여줍니다. 정규화, 활성화 함수, 손실 계산 등 거의 모든 곳에서 이런 함수들을 사용합니다.
첫 번째로, data.mean()은 전체 원소의 평균을 계산합니다. (1+2+3+4+5+6)/6 = 3.5가 나오죠.
data.mean(dim=1)은 1번 차원(열)을 따라 평균을 내서 각 행의 평균을 계산합니다. [1,2,3]의 평균 2.0과 [4,5,6]의 평균 5.0이 나옵니다.
이건 배치의 각 샘플마다 통계를 계산할 때 필수입니다. sum(), max(), min()도 같은 방식으로 dim을 지정할 수 있습니다.
그 다음으로, std()와 var()는 표준편차와 분산을 계산하는데, 데이터 정규화의 핵심입니다. ImageNet 데이터셋의 RGB 채널별 평균과 표준편차를 계산해서 정규화하는 게 전형적인 패턴입니다.
keepdim=True는 정말 중요한 옵션인데, 차원을 유지해서 Broadcasting이 가능하게 만듭니다. (2, 3) Tensor의 mean(dim=1)은 기본적으로 (2,)가 되지만, keepdim=True를 주면 (2, 1)이 돼서 원본 (2, 3)에서 바로 뺄 수 있습니다.
세 번째로, torch.exp(), torch.log(), torch.sqrt() 같은 수학 함수는 각 원소에 함수를 적용합니다. exp(x)는 Softmax의 핵심이고, log(x)는 Cross Entropy의 핵심이며, sqrt(x)는 Xavier/He Initialization에서 사용됩니다.
이런 함수들은 모두 미분 가능해서 역전파에도 문제없이 작동합니다. 네 번째로, torch.argmax()는 최댓값의 인덱스를 반환합니다.
분류 모델의 출력 logits에서 가장 높은 점수를 받은 클래스를 찾을 때 사용하는 필수 함수입니다. [0.1, 0.3, 0.6]에서 최댓값은 2번 인덱스의 0.6이니까 2를 반환합니다.
마지막으로 torch.clamp()는 값을 특정 범위로 제한하는데, Gradient Clipping이나 픽셀 값을 0-255로 제한할 때 유용합니다. 여러분이 이 코드를 사용하면 복잡한 통계 처리와 수학 연산을 한 줄로 끝낼 수 있습니다.
실무에서는 배치 정규화를 구현할 때 mean()과 var()를 사용하고, Softmax를 직접 만들 때 exp()와 sum()을 조합하며, 모델 예측을 실제 클래스로 변환할 때 argmax()를 사용하고, 이미지 픽셀을 정규화할 때 mean()과 std()로 표준화합니다. 특히 dim과 keepdim을 잘 활용하면 Broadcasting과 결합해서 정말 강력한 원라이너를 만들 수 있어요.
실전 팁
💡 torch.nanmean()과 torch.nansum()을 사용하면 NaN 값을 무시하고 통계를 계산할 수 있습니다. 결측치가 있는 데이터 처리에 유용합니다.
💡 torch.median()은 이상치에 강건한 중앙값을 계산합니다. 평균 대신 중앙값으로 정규화하면 outlier의 영향을 줄일 수 있습니다.
💡 torch.cumsum()과 torch.cumprod()로 누적 합과 누적 곱을 계산할 수 있습니다. 시계열 데이터의 누적 수익률 계산 등에 유용합니다.
💡 torch.topk(k)로 상위 k개 값과 인덱스를 한 번에 얻을 수 있습니다. Top-5 accuracy를 계산할 때 필수입니다.
💡 torch.histc()로 히스토그램을 계산하고, torch.bincount()로 빈도를 셀 수 있습니다. 데이터 분포를 이해하는 데 도움됩니다.
10. 데이터 타입 변환 및 타입 캐스팅
시작하며
여러분이 모델을 학습시킬 때 "RuntimeError: expected scalar type Float but found Double"이나 "expected scalar type Long but found Float" 같은 에러를 본 적 있나요? 예를 들어, 손실 함수에 정수형 레이블을 넣었는데 실수형을 기대한다는 에러가 나면 당황스럽죠.
이런 타입 에러는 딥러닝 초보자가 가장 자주 겪는 문제 중 하나입니다. 이미지는 보통 0-255 정수(uint8)인데 모델은 0-1 실수(float32)를 원하고, 레이블은 정수(int64)여야 하는데 실수로 넣으면 에러가 나고, GPU는 float16을 쓰면 빠른데 기본은 float32라서 수동 변환이 필요합니다.
바로 이럴 때 필요한 것이 데이터 타입 변환(Type Casting)입니다. 이건 마치 환전하듯이, 같은 데이터를 다른 타입으로 바꿔서 상황에 맞게 사용할 수 있게 해줍니다.
개요
간단히 말해서, 타입 변환은 Tensor의 데이터 타입(dtype)을 바꾸는 연산입니다. .float()로 실수형으로, .long()으로 정수형으로, .half()로 반정밀도로 변환할 수 있습니다.
왜 타입 변환이 필요할까요? 각 연산과 함수는 특정 타입을 요구합니다.
nn.CrossEntropyLoss는 레이블이 Long(int64)이어야 하고, 대부분의 신경망 연산은 Float(float32)를 요구하며, Mixed Precision Training은 Half(float16)를 사용합니다. 또한 메모리를 절약하거나 계산 속도를 높이기 위해 타입을 바꾸는 경우도 많습니다.
기존에는 NumPy의 astype()을 사용했다면, PyTorch는 .to(dtype), .float(), .long(), .int() 등 더 직관적인 메서드를 제공합니다. 또한 자동 타입 변환(type promotion)도 지원해서 float와 int를 더하면 자동으로 float가 됩니다.
타입 변환의 핵심 특징은 세 가지입니다: 첫째, .to(dtype)로 모든 타입 변환이 가능하고, 둘째, 축약형(.float(), .long() 등)으로 자주 쓰는 타입을 빠르게 변환하며, 셋째, half precision(float16)으로 메모리를 절반으로 줄이고 속도를 높일 수 있습니다. 이러한 기능들이 효율적인 학습과 추론을 가능하게 합니다.
코드 예제
import torch
# 주석: 기본 타입 확인 - dtype 속성으로 확인
x = torch.tensor([1, 2, 3])
print(f"기본 타입: {x.dtype}") # torch.int64 (Long)
# 주석: 실수형 변환 - float(), double(), half()
x_float = x.float() # torch.float32
x_double = x.double() # torch.float64
x_half = x.half() # torch.float16 (반정밀도, GPU 가속)
print(f"Float32: {x_float.dtype}, Float64: {x_double.dtype}, Float16: {x_half.dtype}")
# 주석: 정수형 변환 - long(), int(), short(), byte()
y = torch.tensor([1.7, 2.3, 3.9])
y_long = y.long() # torch.int64 (소수점 버림)
y_int = y.int() # torch.int32
print(f"실수 -> 정수 변환: {y} -> {y_long}")
# 주석: .to() 메서드 - 더 유연한 타입 변환
z = torch.randn(3, 3)
z_fp16 = z.to(torch.float16)
z_int = z.to(dtype=torch.int32)
print(f".to() 변환: {z.dtype} -> {z_fp16.dtype}")
# 주석: 타입 맞춤 - 다른 Tensor의 타입 따라하기
template = torch.tensor([1.0, 2.0])
data = torch.tensor([3, 4, 5])
data_matched = data.to(template.dtype) # template과 같은 타입으로
print(f"타입 맞춤: {data.dtype} -> {data_matched.dtype}")
# 주석: 실전 예제 - 레이블 타입 변환 (분류 문제)
labels_float = torch.tensor([0.0, 1.0, 2.0]) # 잘못된 타입
labels_correct = labels_float.long() # CrossEntropyLoss용으로 변환
print(f"레이블 변환: {labels_float.dtype} -> {labels_correct.dtype}")
# 주석: 이미지 정규화 - uint8 -> float32 및 0-1 범위
image_uint8 = torch.randint(0, 256, (3, 224, 224), dtype=torch.uint8)
image_normalized = image_uint8.float() / 255.0 # [0, 255] -> [0.0, 1.0]
print(f"이미지 정규화: {image_uint8.dtype} -> {image_normalized.dtype}, 범위: {image_normalized.max()}")
설명
이것이 하는 일: 위 코드는 Tensor의 데이터 타입을 변환하는 모든 주요 방법과 실전 패턴을 보여줍니다. 타입 에러를 해결하고, 메모리를 절약하고, 계산을 최적화하는 필수 기술들입니다.
첫 번째로, x.dtype으로 현재 타입을 확인할 수 있습니다. Python 정수로 Tensor를 만들면 기본적으로 torch.int64(Long)가 되고, 실수로 만들면 torch.float32(Float)가 됩니다.
이 기본값을 알아야 에러를 예방할 수 있어요. .float()는 가장 많이 쓰는 float32로 변환하고, .double()은 더 높은 정밀도가 필요할 때 float64로 변환하며, .half()는 메모리를 절반으로 줄이는 float16으로 변환합니다.
그 다음으로, y.long()은 실수를 정수로 변환하는데, 소수점 이하를 버립니다. 1.7 -> 1, 2.3 -> 2, 3.9 -> 3처럼요.
이건 분류 모델의 레이블을 변환할 때 필수입니다. nn.CrossEntropyLoss는 반드시 Long 타입 레이블을 요구하기 때문에, 실수로 들어온 레이블은 .long()으로 변환해야 합니다.
.int()는 int32로 변환하는데, Long보다 메모리를 적게 쓰지만 PyTorch는 대부분 Long을 선호합니다. 세 번째로, .to(torch.float16)은 더 유연한 변환 방법입니다.
축약형이 없는 타입(uint8, bool 등)으로 변환할 때 필수이고, device와 dtype을 동시에 변환할 때도 편리합니다. tensor.to(device='cuda', dtype=torch.float16)처럼 한 번에 GPU로 옮기면서 반정밀도로 바꿀 수 있어요.
data.to(template.dtype)은 다른 Tensor의 타입을 따라하는 패턴인데, 두 Tensor의 타입을 맞춰야 할 때 유용합니다. 특히 함수 인자로 받은 Tensor의 타입을 모를 때, 기준이 되는 Tensor와 같은 타입으로 맞추면 안전합니다.
마지막 두 예제는 실전 상황인데, labels_float.long()은 분류 손실 함수에 넣기 위한 레이블 변환이고, image_uint8.float() / 255.0은 이미지를 0-255 정수에서 0-1 실수로 정규화하는 필수 전처리입니다. 여러분이 이 코드를 사용하면 타입 에러를 쉽게 해결하고, 메모리를 절약하며, 계산 속도를 높일 수 있습니다.
실무에서는 데이터 로딩 시 이미지를 uint8에서 float32로 변환하고, 레이블을 long으로 확실히 변환하며, 대규모 모델 학습 시 float16(AMP)을 사용해서 GPU 메모리를 절반으로 줄입니다. 특히 .to()를 잘 활용하면 device와 dtype를 한 번에 변환해서 코드가 간결해지고, template.dtype 패턴으로 타입 불일치 에러를 예방할 수 있어요.
실전 팁
💡 torch.set_default_dtype(torch.float64)로 기본 실수형 타입을 바꿀 수 있습니다. 하지만 대부분의 경우 float32가 최적입니다.
💡 Mixed Precision Training(AMP)을 사용하면 자동으로 적절한 연산만 float16으로 바꿔서 속도와 정확도를 모두 잡을 수 있습니다.
💡 .bool()로 Boolean 타입으로 변환하면 메모리를 최소화할 수 있습니다. 마스크는 bool로 저장하는 게 효율적입니다.
💡 타입 변환은 복사본을 만들지 않고 뷰를 반환할 수 있습니다. 원본을 보존하려면 .clone()을 먼저 호출하세요.
💡 torch.finfo(dtype)와 torch.iinfo(dtype)로 각 타입의 최댓값/최솟값/정밀도를 확인할 수 있습니다. Overflow를 방지하는 데 유용합니다.
댓글 (0)
함께 보면 좋은 카드 뉴스
데이터 증강과 정규화 완벽 가이드
머신러닝 모델의 성능을 극대화하는 핵심 기법인 데이터 증강과 정규화에 대해 알아봅니다. 실무에서 바로 활용할 수 있는 다양한 기법과 실전 예제를 통해 과적합을 방지하고 모델 성능을 향상시키는 방법을 배웁니다.
ResNet과 Skip Connection 완벽 가이드
딥러닝 모델이 깊어질수록 성능이 떨어지는 문제를 해결한 혁신적인 기법, ResNet과 Skip Connection을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실제 구현 코드와 함께 배워보세요.
CNN 아키텍처 완벽 가이드 LeNet AlexNet VGGNet
컴퓨터 비전의 기초가 되는 세 가지 핵심 CNN 아키텍처를 배웁니다. 손글씨 인식부터 이미지 분류까지, 딥러닝의 발전 과정을 따라가며 각 모델의 구조와 특징을 실습 코드와 함께 이해합니다.
CNN 기초 Convolution과 Pooling 완벽 가이드
CNN의 핵심인 Convolution과 Pooling을 초급자도 쉽게 이해할 수 있도록 설명합니다. 이미지 인식의 원리부터 실제 코드 구현까지, 실무에서 바로 활용 가능한 내용을 담았습니다.
TensorFlow와 Keras 완벽 입문 가이드
머신러닝과 딥러닝의 세계로 들어가는 첫걸음! TensorFlow와 Keras 프레임워크를 처음 접하는 분들을 위한 친절한 가이드입니다. 실무에서 바로 활용할 수 있는 핵심 개념과 예제를 통해 AI 모델 개발의 기초를 탄탄히 다져보세요.