2장. 신경망과 딥러닝의 기본 원리
실습 1 — 텐서 다루고 GPU 확인하기
PyTorch 의 텐서를 만들고 계산해 보고, 이 PC 의 GPU 를 확인한 뒤 CPU 와 GPU 의 행렬 곱 속도를 견줍니다. PyTorch 가 설치되어 있어야 합니다. GPU 가 없어도 실습은 끝까지 돌아가고, 속도 견주기만 건너뜁니다.
1단계. 텐서 만들기
import torch
print("PyTorch 버전:", torch.__version__)
# 파이썬 리스트로 텐서를 만든다. 실수(float)는 기본으로 float32 가 된다
t = torch.tensor([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
print(t)
print("모양:", t.shape, "자료형:", t.dtype)PyTorch 버전: 2.14.1+cu126
tensor([[1., 2., 3.],
[4., 5., 6.]])
모양: torch.Size([2, 3]) 자료형: torch.float32이 단계의 함수입니다.
| 코드 | 쉬운 뜻 |
|---|---|
torch.__version__ | 설치된 PyTorch 의 판 번호. +cu126 은 CUDA 12.6 용 GPU 판이라는 표시다 |
torch.tensor(리스트) | 리스트의 숫자를 텐서라는 상자에 담는다. 안쪽 리스트 하나가 한 줄(행)이 된다 |
t.shape | 상자의 모양. [2, 3] 은 2줄 3칸이다 |
t.dtype | 칸에 담긴 숫자의 종류. float32 는 소수를 32비트(4바이트)로 담는 형식이다 |
2단계. 계산하기
# NumPy 와 같은 모양의 계산이 그대로 된다
print(t + 10) # 모든 칸에 10 더하기
print(t * t) # 칸끼리 곱하기
print(t.sum(), t.mean()) # 합과 평균
# 행렬 곱: (2, 3) @ (3, 2) → (2, 2). t.T 는 행과 열을 뒤집은 것
print(t @ t.T)tensor([[11., 12., 13.],
[14., 15., 16.]])
tensor([[ 1., 4., 9.],
[16., 25., 36.]])
tensor(21.) tensor(3.5000)
tensor([[14., 32.],
[32., 77.]])이 단계의 함수입니다. NumPy 배열에 하던 계산을 텐서에도 그대로 쓸 수 있습니다.
| 코드 | 쉬운 뜻 | 결과 |
|---|---|---|
t + 10 | 모든 칸에 10 을 더한다 | 1 은 11, 6 은 16 |
t * t | 같은 자리의 칸끼리 곱한다(행렬 곱이 아니다) | 2 는 4, 6 은 36 |
t.sum(), t.mean() | 모든 칸의 합과 평균. 숫자 하나지만 0차원 텐서로 나온다 | tensor(21.), tensor(3.5000) |
t.T | 행과 열을 뒤집는다(전치). (2, 3) 이 (3, 2) 가 된다 | 1, 2, 3 이 한 열이 된다 |
t @ t.T | 행렬 곱. 앞의 줄과 뒤의 열을 짝지어 곱해 더한다("인공 뉴런" 절의 np.dot) | 첫 칸 14 = 1×1 + 2×2 + 3×3 |
t * t 와 t @ t.T 는 생김새가 비슷하지만 다릅니다. * 는 칸끼리 곱해 모양이 그대로(2, 3)이고, @ 는 줄과 열을 곱해 더해 모양이 (2, 2)로 바뀝니다.
3단계. NumPy, pandas 와 오가기
import numpy as np
import pandas as pd
df = pd.DataFrame({"기온": [3.5, 21.0, 28.4], "광고비만원": [50, 120, 180]})
# 표의 값을 NumPy 배열로 꺼낸 뒤 float32 텐서로 바꾼다(신경망은 보통 float32 를 쓴다)
x = torch.tensor(df.to_numpy(), dtype=torch.float32)
print(x)
# 텐서를 다시 NumPy 배열로
print(x.numpy().round(1))tensor([[ 3.5000, 50.0000],
[ 21.0000, 120.0000],
[ 28.4000, 180.0000]])
[[ 3.5 50. ]
[ 21. 120. ]
[ 28.4 180. ]]이 단계의 함수입니다. 표(pandas) → 배열(NumPy) → 텐서(PyTorch)로 옮겨 담고, 다시 배열로 되돌립니다.
| 코드 | 쉬운 뜻 |
|---|---|
df.to_numpy() | pandas 표에서 열 이름을 떼고 숫자만 NumPy 배열로 꺼낸다 |
torch.tensor(배열, dtype=torch.float32) | 배열을 텐서로 바꾸면서 숫자 종류를 float32 로 정한다. 정하지 않으면 NumPy 의 float64 가 그대로 따라온다 |
x.numpy() | 텐서를 NumPy 배열로 되돌린다. CPU 에 있는 텐서만 바로 된다(GPU 텐서는 .cpu() 를 먼저) |
출력의 3.5000 처럼 소수 넷째 자리까지 보이는 것은 텐서를 찍는 방식일 뿐, 값이 바뀐 것은 아닙니다.
4단계. GPU 확인하고 속도 견주기
import time
print("GPU 사용 가능:", torch.cuda.is_available())
if torch.cuda.is_available():
print("GPU 이름:", torch.cuda.get_device_name(0))
# 전체 메모리를 바이트에서 GB 로 바꿔 찍는다
print("GPU 메모리:", round(torch.cuda.get_device_properties(0).total_memory / 1024**3, 1), "GB")
# 2000×2000 행렬 둘을 곱하는 시간을 잰다(곱셈 80억 번 남짓)
a = torch.randn(2000, 2000)
b = torch.randn(2000, 2000)
start = time.perf_counter()
for _ in range(10):
c = a @ b
cpu_sec = (time.perf_counter() - start) / 10
print(f"CPU 한 번: {cpu_sec * 1000:.0f} ms")
if torch.cuda.is_available():
ag, bg = a.to("cuda"), b.to("cuda") # 두 행렬을 GPU 메모리로 옮긴다
c = ag @ bg # 첫 계산은 준비 시간이 섞여서 한 번 버린다
torch.cuda.synchronize() # GPU 가 일을 다 끝낼 때까지 기다린다
start = time.perf_counter()
for _ in range(10):
c = ag @ bg
torch.cuda.synchronize()
gpu_sec = (time.perf_counter() - start) / 10
print(f"GPU 한 번: {gpu_sec * 1000:.0f} ms, CPU 의 {cpu_sec / gpu_sec:.0f}배 빠르기")GPU 사용 가능: True
GPU 이름: NVIDIA GeForce GTX 1650
GPU 메모리: 4.0 GB
CPU 한 번: 79 ms
GPU 한 번: 8 ms, CPU 의 9배 빠르기이 단계의 함수입니다. 스톱워치를 들고 요리사(CPU)와 보조들(GPU)에게 같은 일을 시켜 보는 단계입니다.
| 코드 | 쉬운 뜻 | 비유로 치면 |
|---|---|---|
torch.cuda.is_available() | GPU 를 쓸 수 있으면 True | 보조들의 작업대가 있나 |
torch.cuda.get_device_name(0) | 0번 GPU 의 이름 | 작업대의 이름표 |
torch.cuda.get_device_properties(0).total_memory | 0번 GPU 의 전체 메모리(바이트). 1024**3 으로 나눠 GB 로 바꾼다 | 작업대의 넓이 |
torch.randn(2000, 2000) | 난수로 채운 2000×2000 텐서를 만든다. 속도를 재려는 연습용 재료다 | 깎을 감자 한 더미 |
time.perf_counter() | 지금 시각을 아주 잘게 잰다. 끝 시각에서 시작 시각을 빼면 걸린 시간이다 | 스톱워치 |
a.to("cuda") | 텐서를 GPU 메모리로 옮긴다 | 감자를 보조들의 작업대로 옮긴다 |
torch.cuda.synchronize() | GPU 가 맡은 일을 다 끝낼 때까지 기다린다 | 보조들이 다 깎았는지 확인하고 스톱워치를 누른다 |
시간은 10번 곱한 시간을 10 으로 나눈 평균입니다. GPU 쪽에서 첫 계산을 한 번 버리는 까닭은, 처음 한 번은 GPU 가 일할 준비를 하는 시간이 섞여 느리게 나오기 때문입니다.
이 실습이 뜻하는 것
이 실습은 주방에서 새 그릇과 일꾼을 시험해 보는 일과 같습니다. 숫자를 담을 그릇을 하나 만들어 모양과 재질을 보고(1단계), 그 그릇에 담긴 채로 더하고 곱해 보고(2단계), 다른 그릇(표, 배열)과 옮겨 담아 보고(3단계), 마지막으로 요리사 한 사람과 보조 여럿에게 같은 일을 시켜 스톱워치로 잽니다(4단계). 여기서 그릇이 텐서, 요리사가 CPU(Central Processing Unit, 중앙 처리 장치), 보조들이 GPU(Graphics Processing Unit, 그래픽 처리 장치)입니다. 비유와 다른 점은, GPU 는 사람처럼 판단하는 일꾼이 아니라 같은 단순 계산을 한꺼번에 많이 하는 장치라는 것입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 버전 | 2.14.1+cu126 | 설치된 PyTorch 가 GPU 를 쓸 수 있는 판입니다 |
| 1. 텐서 | torch.Size([2, 3]), torch.float32 | 2줄 3칸짜리 그릇이고, 칸마다 소수가 담겨 있습니다 |
| 2. 계산 | tensor(21.) tensor(3.5000) | 여섯 칸의 합이 21, 평균이 3.5 입니다. NumPy 와 같은 계산이 텐서에서도 됩니다 |
| 2. 행렬 곱 | [[14., 32.], [32., 77.]] | 줄과 줄을 짝지어 곱해 더한 결과입니다. 모양이 (2, 3) 에서 (2, 2) 로 바뀌었습니다 |
| 3. 오가기 | 같은 숫자 두 번 | 표 → 텐서 → 배열로 옮겨 담아도 값은 그대로입니다 |
| 4. GPU 확인 | True, GTX 1650, 4.0 GB | 이 PC 에는 메모리 4GB 짜리 GPU 가 있습니다 |
| 4. 속도 | CPU 79 ms, GPU 8 ms, 9배 | 이 PC 에서 큰 행렬 곱 한 번이 GPU 에서 9배쯤 빨랐습니다 |
한 줄로 줄이면, 텐서는 NumPy 배열처럼 다루는 숫자 그릇이고, 큰 행렬 계산은 GPU 가 훨씬 빠르다는 것을 확인한 실습입니다. 3단계의 표를 텐서로 바꾸는 줄은 실습 3부터 그대로 씁니다. 79ms, 8ms, 9배는 이 PC 에서 한 번 잰 값이라 수강생의 PC 에서는 다르게 나오고, GPU 가 없으면 속도 줄은 CPU 시간만 나옵니다.
코드 설명
torch.tensor에 소수를 넣으면 자료형이torch.float32가 됩니다. NumPy 의 기본(float64)보다 칸 하나의 크기가 절반이라, 신경망은 보통 float32 를 씁니다.t @ t.T처럼@가 행렬 곱입니다. (2, 3) 과 (3, 2) 를 곱해 (2, 2) 가 나옵니다. 순전파 절의 모양 규칙 그대로입니다.torch.tensor(df.to_numpy(), dtype=torch.float32)는 실습 3부터 표를 텐서로 바꿀 때 그대로 쓰는 줄입니다.- 이 PC 에서는 GPU 가 GeForce GTX 1650(4GB)으로 잡혔고, 2000×2000 행렬 곱 한 번이 CPU 79ms, GPU 8ms 로 9배쯤 빨랐습니다. 시간은 PC 와 그때의 상황에 따라 달라지므로, 수강생의 숫자는 이와 다르게 나옵니다. GPU 가 없으면 "GPU 사용 가능: False"와 CPU 시간만 나옵니다.
- GPU 는 일을 맡겨 두고 바로 다음 줄로 넘어가기 때문에, 시간을 잴 때는
torch.cuda.synchronize()로 끝날 때까지 기다려야 바르게 잴 수 있습니다.