시험공부빅데이터분석

B+ 최단 코스

시간이 없을 때 이것만 하세요. 교수님이 말한 시험 유형(개념 설명 · 코드 빈칸 채우기 · 실행 결과 예측)에 나오는 것만 골랐어요. 전부 해도 약 2시간이고, 위에서부터 순서대로 하면 돼요.

B+ 전략 한 줄

시험은 코드를 주고 결과를 고르는 5지선다가 중심이에요. 외우기보다 코드를 한 줄씩 손으로 따라가서 shape과 숫자를 직접 계산하는 연습이 점수를 좌우해요. 아래 "시험 단골 코드 유형 8개"를 눈으로만 읽지 말고 가리고 풀어 보세요.

① 큰 그림 한 줄 외우기 10분

시험지를 받으면 여백에 이 두 줄부터 써 두세요. 문제 절반이 여기서 나와요.

그대로 외우기 1 — 학습 한 번(step)

zero_grad → forward(예측) → loss → backward → step

그대로 외우기 2 — 모델 클래스의 틀

nn.Module 상속 → super().__init__() → self에 layer 등록 → forward → model(x)로 호출

단계하는 일 (한 줄)
optimizer.zero_grad()지난번에 쌓인 기울기(grad)를 지운다
prediction = model(x)입력으로 예측값을 계산한다 (forward)
loss = criterion(prediction, target)예측과 정답의 차이를 숫자 하나로 만든다
loss.backward()각 파라미터의 기울기를 계산해 .grad에 저장만 한다 (값은 안 바뀜)
optimizer.step()저장된 기울기로 파라미터 값을 실제로 고친다

교수님 설명을 요약하면 — 데이터를 넣어 예측하고, 정답과의 차이(loss)를 보고, weight를 loss가 줄어드는 쪽으로 고치는 일을 계속 반복한다.

② 핵심 카드만 읽기 40분

장마다 첫 카드부터 읽으세요. 각 카드의 "시험엔 이렇게 써요" 칸을 꼭 소리 내어 읽고, 코드 아래의 "한 줄씩 읽기" 표를 눈으로 따라가세요.

  1. 왕초보 코드 읽기 — 리스트 번호 · 슬라이싱 · 함수 · f-string · range 코드가 낯설면 먼저
  2. 딥러닝 큰 그림 — 학습 한 바퀴 · weight 개수 · 업데이트 식 · epoch 1순위
  3. Tensor — shape 읽기 · 슬라이싱 · 브로드캐스팅 · reshape/squeeze/cat/stack 단골
  4. GPU와 Colab — device 규칙 두 가지 · is_available
  5. 클래스와 상속 — self · __init__ · super() · 오버라이딩 · __call__ 단골
  6. nn.Module — 모델 틀 · model(x)가 forward를 부르는 과정 · Linear의 shape 1순위
  7. Autograd — backward · gradient 값 · 누적과 zero_grad · leaf · no_grad 단골
  8. 손실·옵티마이저 — MSE · shape 함정 · SGD · step 단골
  9. Dataset·DataLoader — __len__/__getitem__ · batch 수 · drop_last · shuffle 단골
  10. 학습 루프·검증 — 5단계 반복 · epoch 평균 loss · 검증은 업데이트 안 함

시간이 모자라면 표시가 있는 1순위와 단골 장(2·3·5·6·7·8·9번)부터 읽으세요.

③ 꼭 외울 것 15분

숫자와 문장은 이 표대로만 알면 돼요. 코드 문제의 답을 계산할 때 그대로 써요.

항목외울 내용
nn.Linear(in, out)weight는 (out, in), bias는 (out,). 파라미터 수 = in×out + out. 계산은 x @ W.T + b. 입력 (N, in) → 출력 (N, out)
파라미터 수 예Linear(3,2) = 8 · Linear(5,3) = 18 · TinyMLP(4,8,2) = 58 (ReLU는 0개)
인덱스0부터 센다. a[1:4]는 1~3번(끝 번호 제외)
브로드캐스팅오른쪽 끝부터 비교해 같거나 · 하나가 1이거나 · 한쪽에 없으면 가능. (2,3)+(3,) 가능 / (2,3)+(2,) 불가 / (4,1)+(1,5) = (4,5)
cat · stackcat은 기존 차원에 이어 붙임(차원 수 그대로), stack은 새 차원을 만듦(차원 수 +1)
device모델과 데이터는 같은 device. x = x.to(device)처럼 다시 저장. device는 "cuda" if torch.cuda.is_available() else "cpu"
상속자식이 __init__을 다시 쓰면 부모 __init__은 자동으로 안 불린다 → super().__init__(...)
model(x)부모 nn.Module의 __call__이 처리한 뒤 forward 실행. forward를 직접 부르지 않는다
backward · stepbackward = grad 계산·저장(누적), step = 값 갱신. grad는 누적되므로 매번 zero_grad
grad 규칙backward는 숫자 하나(scalar)에만 · .grad는 leaf에만 · 한 번 쓴 그래프는 재사용 불가 · 값 수정은 torch.no_grad() 안에서
SGD 식w ← w − lr × grad. 예: w=0.7, grad=−2, lr=1 → 2.7. 학습률은 보통 아주 작게
외울 grad 값w=3, L=w² → 6 · w=2, (wx−t)², x=3, t=10 → −24 · w=2 누적 4 → 16 → 32 · W=[1,2,3], X=[2,1,4], L=(W@X)² → W.grad=[64,32,128]
batch 수len(loader) = 올림(샘플 수 ÷ batch). 10개, batch 4 → 3개 [4,4,2] / drop_last=True면 [4,4]
epoch · stepepoch = 데이터 전체 한 번. 전체 step = epoch 수 × batch 개수 (90개, batch 20, 30 epoch → 150)
Dataset__len__(개수)과 __getitem__(번호로 꺼내기)을 만든다
shuffle학습 True, 검증 False

④ 시험 단골 코드 유형 8개 40분

각 유형을 코드 → 결과 → 왜 순서로 풀어 놨어요. 코드만 보고 먼저 결과를 예상한 다음에 아래를 보세요.

유형 1. 슬라이싱과 shape (문제샘플 1번)

import torch x = torch.arange(12).reshape(3, 4) a = x[:, 1] b = x[:, 1:2] print(tuple(a.shape)) print(tuple(b.shape))

결과 — (3,) 그리고 (3, 1) (선택지 "(3,) → (3, 1)")

왜 — x는 3행 4열이에요. 숫자 하나(1)로 뽑은 차원은 사라지고, 범위(1:2)로 뽑은 차원은 크기 1로 남아요. 행은 :로 다 가져왔으니 3이 그대로예요. 열 개수 4를 고르면 틀려요.

이렇게 고르세요

"숫자로 뽑으면 차원이 사라지고, 범위로 뽑으면 차원이 남는다"만 적용하면 돼요. 남은 차원의 길이는 가져온 개수예요.

유형 2. 브로드캐스팅과 합 (sum의 dim)

import torch a = torch.zeros(4, 1) b = torch.zeros(1, 5) x = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(tuple((a + b).shape)) print(x.sum(dim=0).tolist(), x.sum(dim=1).tolist())

결과 — (4, 5) 그리고 [5, 7, 9] [6, 15]

왜 — (4,1)과 (1,5)는 1인 쪽이 늘어나 (4,5)가 돼요. dim=0은 위아래로 합쳐 열마다(1+4, 2+5, 3+6), dim=1은 좌우로 합쳐 행마다(6, 15) 합해요. (2,3)+(2,)처럼 오른쪽 끝 3과 2가 다르고 1도 아니면 에러예요.

이렇게 고르세요

shape은 맨 오른쪽부터 짝을 지어 보고, dim은 "그 번호의 차원이 사라진다"고 생각하세요. dim=0이면 앞 차원이 사라져요.

유형 3. 상속·super·오버라이딩 출력 (문제샘플 2번)

class Animal: def __init__(self, name): self.name = name def speak(self): print("동물이 소리를 냅니다.") class Dog(Animal): def __init__(self, name, breed): super().__init__(name) self.breed = breed def speak(self): print(f"{self.name}가 멍멍 짖습니다.") dog = Dog("Bori", "Poodle") dog.speak()

결과 — Bori가 멍멍 짖습니다. 한 줄만 나와요.

왜 — super().__init__(name)이 부모 초기화를 직접 불러 name이 생겨요. speak는 Dog가 다시 써서(오버라이딩) 자식 것만 실행되고 부모 것은 같이 실행되지 않아요. 만약 super().__init__(name) 줄이 없으면 dog.name을 읽을 때 AttributeError가 나요.

이렇게 고르세요

"자식이 __init__을 쓰면 부모 __init__은 자동 실행"이라는 보기는 항상 틀려요. "부모와 자식이 차례로 실행"도 틀려요.

유형 4. 모델 만들기와 호출 과정 (문제샘플 3번)

class MyModel(nn.Module): def __init__(self): super().__init__() self.fc = __________ # (A) 입력 3개 → 출력 2개 def forward(self, x): return __________ # (B) model = MyModel() x = torch.ones(4, 3) y = model(x) # y의 shape은 (4, 2)

결과 — (A) nn.Linear(3, 2), (B) self.fc(x). 호출 과정은 "상속받은 __call__()의 호출 처리를 거쳐 forward() 실행"이에요.

왜 — Linear는 (입력 개수, 출력 개수) 순서로 써요. (B)에 self.fc만 쓰면 층 자체를 돌려줄 뿐 계산이 안 돼요. model(x)가 __init__을 다시 실행한다는 보기는 틀려요. __init__은 모델을 만들 때 한 번만 돌아요. 참고로 Linear(3,2)의 파라미터는 3×2+2 = 8개예요.

이렇게 고르세요

① Linear(입력, 출력) 순서 확인 ② 호출은 self.fc(x)처럼 괄호가 있는 보기 ③ 호출 과정은 "__call__ → forward"인 보기를 고르세요.

유형 5. gradient 값 손계산

w = torch.tensor(2.0, requires_grad=True) x = torch.tensor(3.0) target = torch.tensor(10.0) pred = w * x loss = (pred - target) ** 2 loss.backward() print(pred.item(), loss.item(), w.grad.item()) # 누적 v = torch.tensor(2.0, requires_grad=True) (v ** 2).backward() print(v.grad.item()) # 4.0 (v ** 3).backward() print(v.grad.item()) # 16.0 (4 + 12) v.grad.zero_() (v ** 4).backward() print(v.grad.item()) # 32.0

결과 — 6.0 16.0 -24.0 그리고 4.0 → 16.0 → 32.0

왜 — pred = 2×3 = 6, loss = (6−10)² = 16이에요. loss를 w로 미분하면 2(wx−t)·x = 2×(−4)×3 = −24예요. 방법은 chain rule든 한 번에 미분이든 값만 맞으면 돼요. 누적은 "덮어쓰기"가 아니라 "더하기"라서 4 + 12 = 16이고, zero_()로 지운 뒤 w⁴의 미분 4w³ = 32만 남아요.

이렇게 고르세요

미분은 x²→2x, x³→3x², x⁴→4x³만 알면 돼요. zero_grad가 없으면 "더해진다"가 정답이에요. backward 직후에 w 값은 그대로예요.

유형 6. DataLoader의 배치 수와 크기 (문제샘플 4번)

x = torch.arange(20, dtype=torch.float32).reshape(10, 2) y = x[:, 0:1] + 2 * x[:, 1:2] + 3 dataset = TensorDataset(x, y) loader = DataLoader(dataset, batch_size=4, shuffle=False, drop_last=False) batch_sizes = [] for batch_x, batch_y in loader: batch_sizes.append(len(batch_x)) print(len(dataset), len(loader)) print(batch_sizes) print(batch_y.flatten().tolist())

결과 — 10 3 / [4, 4, 2] / [53.0, 59.0]

왜 — 샘플 10개를 4개씩 자르면 4, 4, 2라서 batch가 3개예요. for가 끝난 뒤의 batch_y는 마지막 batch의 정답이에요. 마지막은 8번·9번 샘플이고 y = x₀ + 2x₁ + 3이니 (16,17) → 53, (18,19) → 59예요.

이렇게 고르세요

① len(dataset)은 샘플 수 ② len(loader)는 올림(샘플÷batch) ③ batch_x·batch_y가 마지막 값이면 마지막 묶음의 값, 이 세 가지만 확인하면 보기가 거의 한 개로 줄어요.

유형 7. 학습 함수의 역할 (문제샘플 5번)

optimizer.zero_grad() prediction = model(batch_x) loss = criterion(prediction, batch_y) loss.backward() optimizer.step()

결과 — "optimizer.step()은 저장된 기울기를 이용해 optimizer에 전달된 파라미터를 갱신한다"가 정답이에요.

왜 — 역할이 서로 섞이지 않게 기억하세요. zero_grad는 기울기를 지우고(파라미터 값은 그대로), criterion은 loss만 계산하고, backward는 기울기를 만들어 저장만 하고, step은 그 기울기로 값을 고쳐요. 순서가 backward → zero_grad → step이면 기울기가 지워져서 값이 안 바뀌어요.

이렇게 고르세요

"~까지 한다"가 붙은 보기(backward가 값까지 갱신, step이 기울기까지 계산)는 역할을 섞은 오답이에요.

유형 8. loss의 shape 함정과 epoch 평균

prediction = torch.tensor([[2.0], [4.0], [6.0]]) # (3, 1) target = torch.tensor([1.0, 5.0, 4.0]) # (3,) wrong = nn.MSELoss()(prediction, target) right = nn.MSELoss()(prediction, target.reshape(-1, 1)) print(wrong.item(), right.item()) # batch 3개(크기 4, 4, 2)의 loss가 3.0, 3.0, 6.0일 때 epoch 평균 print((3.0*4 + 3.0*4 + 6.0*2) / 10)

결과 — 6.0 2.0 그리고 3.6

왜 — (3,1)과 (3,)을 그대로 빼면 에러가 아니라 3×3으로 브로드캐스팅되어 틀린 값(6.0)이 나와요. 맞춰서 (3,1)끼리 빼면 (1+1+4)/3 = 2예요. epoch 평균은 batch loss에 샘플 수를 곱해 더한 뒤 전체 샘플 수(10)로 나눠요. 그냥 평균 4.0은 작은 마지막 batch를 부풀려요.

이렇게 고르세요

prediction과 target의 shape이 다르면 "경고만 나오고 값이 틀려진다"가 정답이에요. 에러가 난다는 보기는 함정이에요.

⑤ 서술형처럼 묻는다면 — 3줄 답 10분

객관식이 중심이지만 개념 설명 문제는 이 문장들을 고르면 돼요.

1. model(x)가 forward를 실행하는 이유는?

MyModel에는 __call__이 없어서 부모 nn.Module의 __call__이 상속되어 실행되고, 그 안에서 hook 등 처리를 한 뒤 자식에 정의된 forward()를 호출한다. 그래서 forward를 직접 부르지 않고 model(x)로 호출한다.

2. backward()와 step()의 차이, zero_grad가 필요한 이유는?

backward()는 gradient를 계산해 .grad에 저장(누적)할 뿐 값은 바꾸지 않고, step()은 저장된 gradient로 파라미터를 갱신한다. gradient는 더해지며 쌓이므로 매 step 시작에 zero_grad()로 초기화한다.

3. nn.Parameter와 일반 텐서의 차이는?

nn.Parameter는 모델에 자동 등록되어 parameters()에 나오고 optimizer로 업데이트된다. 일반 텐서는 requires_grad=True여도 등록되지 않아 gradient는 계산되어도 업데이트되지 않는다.

4. epoch와 batch, DataLoader의 관계는?

DataLoader는 Dataset의 샘플을 batch_size개씩 묶어 내주고, batch 하나로 weight를 한 번 업데이트(step)한다. 모든 batch를 한 번 쓰면 학습 데이터 전체를 한 번 사용한 것이므로 1 epoch이다.

안 봐도 되는 것 (시간 아끼기)

시험 날 아침 30분

  1. 퀴즈 → "틀린 문제만 다시" (10분)
  2. 위 ① 두 줄(학습 5단계, 모델 틀)을 종이에 한 번 써 보기 (5분)
  3. ③ "꼭 외울 것" 표를 훑기 (10분)
  4. ④ 코드 유형 8개의 "이렇게 고르세요" 상자만 읽기 (5분)
시험장에서

코드 문제는 여백에 shape과 숫자를 한 줄씩 적으며 따라가세요. 눈으로만 풀면 틀려요. 보기 중 "항상 · 자동으로 · 아무것도 안 한다" 같은 극단적인 말이 들어간 건 대부분 오답이에요. 모르면 소거법으로 두 개까지 줄여 찍어요.

위 내용을 다 봤으면 마지막으로 핵심 문제 47개를 풀어요. 틀린 건 해설만 읽고 넘어가면 돼요.

핵심 문제 풀기