시험공부빅데이터분석

최소 코스 — 오늘 이것만

어려운 건 다 뺐어요. 개념 10개 + 코드 틀 3개 + 숫자 6개 + 서술형 4개만 보면 돼요. 약 1시간이에요. 위에서부터 차례로 읽으세요.

B 받는 법

① 코드 문제는 여백에 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로 남아요.

x = torch.arange(12).reshape(3, 4) x[:, 1] # shape (3,) 숫자로 뽑아서 열 차원이 사라짐 x[:, 1:2] # shape (3, 1) 범위로 뽑아서 열 차원이 남음
이렇게 고르세요

"숫자로 뽑으면 사라지고, 범위로 뽑으면 남는다"만 적용하세요. 남은 차원의 길이는 가져온 개수예요. 문제샘플 1번 정답은 (3,) → (3, 1)이에요.

확인 — x가 (3, 4)일 때 x[1:, :2]의 shape은?

(2, 2). 1번 행부터 끝까지 2개, 앞의 2열이에요.

3. 브로드캐스팅

모양이 다른 두 텐서를 더할 때, 작은 쪽을 복사해서 늘려 맞춰 주는 기능이에요. 판단은 두 모양을 오른쪽 끝에 맞추고 뒤에서부터 해요. 각 자리가 같거나, 하나가 1이거나, 한쪽에 없으면 돼요.

(2, 3) + (3,) # 가능 → (2, 3) (4, 1) + (1, 5) # 가능 → (4, 5) (2, 3) + (2,) # 불가 3과 2가 만나는데 같지도 1도 아니다
이렇게 고르세요

에러 나는 쌍은 "오른쪽 끝 숫자가 서로 다르고 1도 아닌 것"이에요.

확인 — (5, 1, 4)와 (3, 1)을 더하면 shape은?

(5, 3, 4). 오른쪽부터 4와 1, 1과 3이 모두 가능하고 5는 짝이 없어서 그대로예요.

4. CPU · GPU · device

CPU는 복잡한 일을 차례로 하는 똑똑한 한 명, GPU는 단순한 계산을 한꺼번에 하는 수천 명이에요. 딥러닝은 단순한 곱셈이 아주 많아서 GPU가 빨라요. 텐서와 모델은 같은 방(device)에 있어야 계산돼요.

device = "cuda" if torch.cuda.is_available() else "cpu" x = x.to(device) # 다시 저장해야 옮겨진다 model = model.to(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__(...)을 직접 불러요.

class Dog(Animal): def __init__(self, name, breed): super().__init__(name) # 부모가 name을 만들게 한다 self.breed = breed def speak(self): # 부모 speak를 덮어씀(오버라이딩) print(f"{self.name}가 멍멍")
이렇게 고르세요

"부모 __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는 부품을 어떤 순서로 쓸지 적는 곳이에요.

class MyModel(nn.Module): def __init__(self): super().__init__() # 맨 먼저, 꼭 self.fc = nn.Linear(3, 2) # 입력 3개 → 출력 2개 def forward(self, x): return self.fc(x) model = MyModel() y = model(torch.ones(4, 3)) # (4, 3) → (4, 2)

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이에요.

fc = nn.Linear(3, 2) fc.weight.shape # (2, 3) fc.bias.shape # (2,) # 파라미터 수 = 3×2 + 2 = 8

직접 만든 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는 덮어쓰지 않고 더해서 쌓여요.

w = torch.tensor(3.0, requires_grad=True) L = w ** 2 L.backward() print(w.grad) # tensor(6.) 미분 2w = 6 L2 = w ** 2 L2.backward() print(w.grad) # tensor(12.) 지우지 않아서 6 + 6
시험엔 이 한 문장

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()을 불러야 고쳐져요.

criterion = nn.MSELoss() optimizer = optim.SGD(model.parameters(), lr=0.1) optimizer.zero_grad() # 1 prediction = model(x) # 2 loss = criterion(prediction, target) # 3 loss.backward() # 4 grad 저장 (값은 그대로) optimizer.step() # 5 값이 바뀜
이렇게 고르세요

"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이에요.

loader = DataLoader(TensorDataset(x, y), batch_size=4, shuffle=False, drop_last=False) # 샘플 10개 → len(loader) = 3, batch 크기 [4, 4, 2] # drop_last=True → len(loader) = 2, batch 크기 [4, 4]
이렇게 고르세요

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개 (이대로 쓸 줄 알기)

# 틀 1. 모델 class MyModel(nn.Module): def __init__(self): super().__init__() self.fc = nn.Linear(입력수, 출력수) def forward(self, x): return self.fc(x) # 틀 2. 학습 한 step optimizer.zero_grad() prediction = model(batch_x) loss = criterion(prediction, batch_y) loss.backward() optimizer.step() # 틀 3. Dataset + DataLoader class MyDataset(Dataset): def __init__(self, x, y): super().__init__() self.x = x self.y = y def __len__(self): return len(self.x) def __getitem__(self, index): return self.x[index], self.y[index] loader = DataLoader(MyDataset(x, y), batch_size=4, shuffle=True)

숫자 6개

서술형 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__()이 필요한 이유를 설명하시오.

자식이 __init__()을 오버라이딩하면 부모의 __init__()은 자동으로 호출되지 않아 부모가 만드는 속성이 생기지 않는다. 따라서 super().__init__(...)을 직접 호출해야 하며, nn.Module을 상속한 모델에서는 layer 등록 준비를 위해 맨 먼저 호출한다.

시험장에서

① 여백에 학습 5단계(zero_grad → forward → loss → backward → step)부터 쓰기 ② 코드 문제는 shape과 숫자를 한 줄씩 적으며 따라가기 ③ 모르면 극단적인 말이 든 보기부터 지우고 두 개 중에 고르기. 화이팅!

시간이 남으면 B+ 코스나 핵심 문제를 풀어 보세요.