최소 코스 — 오늘 이것만
어려운 건 다 뺐어요. 개념 10개 + 코드 틀 3개 + 숫자 6개 + 서술형 4개만 보면 돼요. 약 1시간이에요. 위에서부터 차례로 읽으세요.
① 코드 문제는 여백에 shape과 숫자를 한 줄씩 적으며 따라간다 ② 보기에서 "항상 · 자동으로 · 아무것도 안 한다"처럼 극단적인 말이 든 것은 일단 의심한다. 이 둘이면 B는 충분해요. 교수님이 낼 유형은 개념 설명 · 코드 빈칸 · 결과 예측 세 가지예요.
먼저 큰 그림 한 줄
이 과목은 결국 이거예요. 컴퓨터가 숫자로 답을 예측하고, 틀린 만큼 조금씩 고쳐 가면서 정답에 가까워지게 하는 방법을 코드(PyTorch)로 배운다.
zero_grad → forward → loss → backward → step
외우는 요령 — 지우고(zero_grad) → 풀어 보고(forward) → 채점하고(loss) → 틀린 방향 알아내고(backward) → 고친다(step)
꼭 알아야 할 10개
1. 딥러닝은 어떻게 배우나
시험 문제를 푸는 학생을 떠올려 보세요. 처음엔 아무렇게나 찍어서 많이 틀려요. 얼마나 틀렸는지(loss)를 보고, 어느 쪽으로 고치면 덜 틀릴지(gradient)를 알아낸 다음, 조금만(학습률 lr) 고쳐요. 이걸 계속 반복해요.
고치는 식은 w ← w − lr × gradient 하나예요. gradient가 음수면 w를 키워야 loss가 줄어서 마이너스 마이너스로 더하기가 돼요.
딥러닝은 weight를 랜덤으로 시작해, 예측 → loss 계산 → gradient 계산 → w ← w − lr × gradient 업데이트를 데이터로 반복해 loss를 줄인다.
확인 — w=0.7, gradient=−2, lr=1이면 새 w는?
0.7 − (1 × −2) = 2.7.
2. shape 읽기 (슬라이싱)
shape은 텐서의 가로세로 크기표예요. (3, 4)는 3행 4열이에요. 일부를 잘라 낼 때 숫자 하나로 뽑으면 그 차원이 사라지고, 범위로 뽑으면 크기 1로 남아요.
"숫자로 뽑으면 사라지고, 범위로 뽑으면 남는다"만 적용하세요. 남은 차원의 길이는 가져온 개수예요. 문제샘플 1번 정답은 (3,) → (3, 1)이에요.
확인 — x가 (3, 4)일 때 x[1:, :2]의 shape은?
(2, 2). 1번 행부터 끝까지 2개, 앞의 2열이에요.
3. 브로드캐스팅
모양이 다른 두 텐서를 더할 때, 작은 쪽을 복사해서 늘려 맞춰 주는 기능이에요. 판단은 두 모양을 오른쪽 끝에 맞추고 뒤에서부터 해요. 각 자리가 같거나, 하나가 1이거나, 한쪽에 없으면 돼요.
에러 나는 쌍은 "오른쪽 끝 숫자가 서로 다르고 1도 아닌 것"이에요.
확인 — (5, 1, 4)와 (3, 1)을 더하면 shape은?
(5, 3, 4). 오른쪽부터 4와 1, 1과 3이 모두 가능하고 5는 짝이 없어서 그대로예요.
4. CPU · GPU · device
CPU는 복잡한 일을 차례로 하는 똑똑한 한 명, GPU는 단순한 계산을 한꺼번에 하는 수천 명이에요. 딥러닝은 단순한 곱셈이 아주 많아서 GPU가 빨라요. 텐서와 모델은 같은 방(device)에 있어야 계산돼요.
GPU가 있는지는 실행 환경마다 다르므로 device를 "cuda"로 고정하지 않고 torch.cuda.is_available()로 정하며, 텐서와 모델은 반드시 같은 device에 있어야 하고 .to(device)의 결과는 변수에 다시 저장해야 한다.
확인 — x.to(device)만 쓰고 저장하지 않으면 x는 어디에 있나?
원래 있던 곳(CPU)에 그대로 있어요.
5. 클래스 · 상속 · super()
클래스는 붕어빵 틀, 인스턴스는 구워진 붕어빵이에요. 같은 틀에서 만든 붕어빵은 서로 따로예요(s1.score를 바꿔도 s2는 그대로). 자식 클래스는 부모의 기능을 물려받아요.
가장 중요한 함정은 하나예요. 자식이 __init__을 다시 쓰면 부모 __init__은 자동으로 안 불려요. 그래서 super().__init__(...)을 직접 불러요.
"부모 __init__이 항상 자동 실행"은 틀린 보기, "자식 speak만 실행되고 부모 speak는 실행되지 않는다"는 맞는 보기예요. 문제샘플 2번 정답은 "super().__init__(name)은 부모의 초기화를 실행하며, dog.speak()은 자식이 재정의한 메서드를 실행한다"예요.
확인 — 위 Dog에서 super().__init__(name) 줄을 빼고 dog.name을 읽으면?
AttributeError. name을 만드는 부모 초기화가 실행되지 않았기 때문이에요.
6. nn.Module 틀과 model(x)
PyTorch 모델은 nn.Module을 상속해서 만들어요. 우리가 쓰는 곳은 두 군데뿐이에요. __init__은 부품(층)을 사서 등록하는 곳, forward는 부품을 어떤 순서로 쓸지 적는 곳이에요.
model(x)라고만 썼는데 forward가 실행되는 이유는, 부모 nn.Module에 있는 __call__이 대신 불려서 준비를 한 뒤 forward를 실행하기 때문이에요.
model(x)를 호출하면 상속받은 nn.Module의 __call__()이 호출 처리를 거친 뒤 forward()를 실행한다. 그래서 forward를 직접 부르지 않는다.
확인 — 위 코드에서 nn.Linear(3, 2) 대신 nn.Linear(2, 3)을 쓰면?
입력의 마지막 차원 3과 Linear의 입력 2가 안 맞아서 에러가 나요.
7. nn.Linear의 모양과 파라미터 수, nn.Parameter
nn.Linear(in, out)은 weight (out, in)와 bias (out,)를 가져요. 입력 (N, in)이 들어오면 (N, out)이 나와요. 학습되는 숫자의 개수는 in × out + out이에요.
직접 만든 weight는 nn.Parameter로 감싸야 모델에 등록되어 optimizer가 고쳐 줘요. 그냥 텐서로 두면 기울기는 계산돼도 업데이트가 안 돼요.
Linear(in, out)에서 weight는 (out, in)이에요. 헷갈리면 "곱할 때 뒤집어서(.T) (in, out)이 되는 모양"이라고 기억하세요. 파라미터 수에 bias(+out)를 빼먹지 마세요. TinyMLP(4, 8, 2)는 (4×8+8) + (8×2+2) = 58이에요.
확인 — nn.Linear(5, 3)의 파라미터는 모두 몇 개?
5×3 + 3 = 18개.
8. gradient · backward · 누적
gradient는 "w를 조금 키우면 loss가 어느 쪽으로 얼마나 변하나"예요. loss.backward()가 이걸 계산해서 .grad에 적어 두기만 해요. w 값은 그대로예요. 또 grad는 덮어쓰지 않고 더해서 쌓여요.
loss.backward()는 chain rule로 gradient를 계산해 .grad에 저장(누적)할 뿐 파라미터 값은 바꾸지 않으며, gradient는 누적되므로 매 step 시작에 zero_grad()로 초기화한다.
확인 — w=2, 손실 (w×3 − 10)²일 때 w.grad는?
2×(6−10)×3 = −24.
9. loss · optimizer · 학습 5단계
loss는 틀린 정도를 숫자 하나로 만든 점수예요. 회귀에서는 MSE(차이를 제곱해 평균)를 써요. optimizer는 그 기울기로 weight를 실제로 고치는 도구예요. 만들기만 해서는 안 고쳐지고, step()을 불러야 고쳐져요.
"step은 저장된 기울기로 갱신한다"가 맞고, "step이 기울기를 계산한다" · "backward가 값을 갱신한다" · "zero_grad가 파라미터를 0으로 만든다"는 모두 틀려요(문제샘플 5번). prediction과 target의 shape은 꼭 맞춰야 해요.
확인 — weight 값이 실제로 바뀌는 시점은?
optimizer.step()이 실행될 때예요. backward 직후에는 grad만 생겨요.
10. Dataset · DataLoader · batch · epoch
데이터가 많으면 한 번에 다 넣지 않고 카드 덱처럼 몇 장씩 나눠서(batch) 써요. Dataset은 샘플을 번호로 꺼낼 수 있는 상자(__len__과 __getitem__), DataLoader는 그 상자에서 batch로 묶어 차례로 꺼내 주는 기계예요. batch 하나로 weight를 한 번 고치고, 전부 한 번 쓰면 1 epoch이에요.
len(dataset) = 샘플 수, len(loader) = 올림(샘플÷batch). 10개를 4개씩이면 3개예요. for 문이 끝난 뒤 batch_y는 마지막 묶음 값이에요(문제샘플 4번 정답은 10 3 / [4, 4, 2] / [53.0, 59.0]). 학습은 shuffle=True, 검증은 False예요.
확인 — 샘플 90개, batch 20, 30 epoch이면 step은 모두 몇 번?
batch가 20·20·20·20·10으로 5개라서 5 × 30 = 150번이에요.
코드 틀 3개 (이대로 쓸 줄 알기)
숫자 6개
- Linear(in, out) 파라미터 = in×out + out (Linear(3,2) = 8, Linear(5,3) = 18, TinyMLP(4,8,2) = 58)
- w=0.7, grad=−2, lr=1 → 2.7
- grad 값 — w=3, L=w² → 6 · (wx−t)², w=2, x=3, t=10 → −24 · 누적 4 → 16 → 32
- 샘플 10개, batch 4 → batch 3개 [4, 4, 2] (drop_last=True면 2개)
- 전체 step = epoch 수 × batch 개수 (90개, batch 20, 30 epoch → 150)
- 브로드캐스팅 — (2,3)+(3,) 가능, (2,3)+(2,) 불가, (4,1)+(1,5) = (4,5)
서술형 4개 — 이것만 연습
가리고 종이에 한 번씩 써 보세요. 위 "시험엔 이 한 문장"을 이어 붙이면 답이 돼요.
1. model(x)로 호출하면 forward가 실행되는 과정을 설명하시오.
MyModel에는 __call__이 없으므로 부모 nn.Module의 __call__()이 상속되어 실행된다. 이 __call__()이 hook 등 필요한 처리를 한 뒤 자식 클래스의 forward()를 호출한다. 그래서 forward를 직접 부르지 않고 model(x)로 호출한다.
2. loss.backward()와 optimizer.step()의 차이를 설명하시오.
loss.backward()는 chain rule로 각 파라미터의 gradient를 계산해 .grad에 저장(누적)할 뿐 값은 바꾸지 않는다. optimizer.step()은 저장된 gradient를 이용해 파라미터 값을 갱신한다. gradient는 누적되므로 매 step 시작에 zero_grad()로 초기화한다.
3. Dataset, DataLoader, batch, epoch의 관계를 설명하시오.
Dataset은 인덱스로 샘플을 꺼낼 수 있는 데이터 모음이고, DataLoader는 이를 batch_size개씩 묶어 내준다. batch 하나로 weight를 한 번 업데이트(step)하며, 모든 batch를 한 번 사용하면 학습 데이터 전체를 한 번 쓴 것이므로 1 epoch이다.
4. 자식 클래스에서 super().__init__()이 필요한 이유를 설명하시오.
① 여백에 학습 5단계(zero_grad → forward → loss → backward → step)부터 쓰기 ② 코드 문제는 shape과 숫자를 한 줄씩 적으며 따라가기 ③ 모르면 극단적인 말이 든 보기부터 지우고 두 개 중에 고르기. 화이팅!