디버깅에 대한 고찰
디버깅에 대한 고찰
🔵 Intro
예전 면접에서 받았던 질문 중에 기억에 남는 질문이 ‘디버깅 하는 방식을 저에게 알려주시겠어요?’ 라는 질문이었다.
아니.. 디버깅하는데 무슨 방식이 있어? 과거 C/C++ 면접에서 디버깅을 하는 방식은 비주얼스튜디오의 기능인 중단점(break point)였다.
그런데 이 기능은 어느 정도 숙달도 필요할 뿐더러 수많은 변수의 값을 step-by-step으로 관찰하는 건 힘들 것이다.
그래도 면접에서 뭐라도 있어보이게 얘기해야해서 break point를 설명했지만 정작 제대로 써본 적은 몇 번 없었다.
그리고 시간이 흐르고 C/C++이 아닌 파이썬을 사용하는 환경으로 바뀌었고 비주얼 스튜디오라는 앱도 VScode로 더 가벼워지고 강력한 앱으로 변경되었다.
그래서 이번 기회에 어떤 디버깅 방식이 있는지, 혹시 내가 모르는 체계적인 디버깅 이론이 있는지 한 번 알아보았다.
⚪ 디버깅의 기본 자세
디버깅은 범인을 찾는 과정과 똑같다. 코드는 용의자고, 에러는 범죄 현장, 로그는 증거물이다. 무작정 코드를 고치기 전에 탐정처럼 생각하는 습관이 중요함.
- 버그 재현하기 (Reproduce the bug): 어떤 조건에서 에러가 나는지 100% 재현할 수 있어야 한다. ‘어쩔 땐 되고 어쩔 땐 안 돼요’가 가장 잡기 힘든 범인임.
- 범위 좁히기 (Isolate the problem): 에러가 발생한 코드 블록, 함수, 모듈 단위로 범위를 좁혀나가야 함. “이 함수에 데이터를 넣기 전엔 괜찮았는데, 나온 뒤엔 이상해졌다”처럼.
- 가설 세우고 검증하기 (Hypothesize and Test): “아마 데이터의 shape이 문제일 거야”라고 가설을 세우고,
print()
나 디버거로 가설이 맞는지 확인하는 과정을 반복함.
⚪ 고전적이지만 가장 강력한 방법들
print() 디버깅
- 가장 원시적이지만, 가장 강력하고 직관적인 방법. “이 코드가 실행은 되는 건가?” 싶을 때
print("여기는 실행됨")
한 줄이면 바로 알 수 있음. - 무엇을 찍어봐야 할까?
- Shape 확인: 텐서나 numpy 배열의
.shape
은 무조건 찍어보는 습관을 들이면 좋다. 딥러닝 에러의 80%는 shape 불일치에서 오니깐. - Type 확인:
type()
함수로 데이터의 자료형(e.g.,torch.Tensor
,numpy.ndarray
,PIL.Image
)을 확인. 함수가 기대하는 타입과 다른 경우가 많음. - 값 확인: 반복문 안에서 특정 변수의 값이 어떻게 변하는지, 혹은
NaN
이나inf
같은 이상한 값이 섞여있는지 확인.
- Shape 확인: 텐서나 numpy 배열의
고무 오리 디버깅 (Rubber Duck Debugging)
- 이거 농담 같지만, 구글 같은 회사에서도 쓰는 실제 방법론이라고 함. 옆에 오리 인형(이나 사람, 혹은 그냥 허공)에게 “나는 지금 이 함수에 이런 모양의 데이터를 넣고 있는데, 여기서 이런 계산을 거치면 이런 결과가 나와야 하거든? 그런데…” 라고 말로 설명해보면 순간적으로 버그 지점을 생각해낼 수 있다.
- 왜 효과가 있을까? 남에게 설명하려면 자기 생각을 논리적으로 정리해야 함. 이 과정에서 스스로 코드의 모순점을 발견하는 경우가 정말 많음.
⚪ 2. 주피터 노트북 환경에서의 꿀팁
“일단 커널 재시작부터”
- 주피터 노트북의 가장 큰 함정은 ‘숨겨진 상태(hidden state)’임. 셀 실행 순서가 꼬이거나, 지웠던 셀의 변수가 메모리에 남아있는 등 눈에 보이지 않는 상태 때문에 에러가 나기도 함.
- 코드가 이상하게 동작한다 싶으면, 일단
Kernel > Restart & Clear Output
이나Restart & Run All
을 눌러서 깨끗한 상태에서 다시 실행해보는 것이 중요.
매직 커맨드: %debug
- 셀을 실행하다 에러가 발생했을 때, 바로 다음 셀에
%debug
라고 입력하고 실행해. - 그러면 에러가 발생한 바로 그 시점의 대화형 디버거(pdb)가 실행돼. 여기서 변수 값을 직접 확인하거나, 코드를 한 줄씩 실행해보는 등 훨씬 깊이 있는 분석이 가능함.
print()
를 미리 넣지 않아도 돼서 아주 유용함.
⚪ 3. 딥러닝 모델 디버깅: 가장 어려운 과제
코드는 돌아가는데 학습이 안 되거나 결과가 이상할 때 쓰는 방법들.
작게 시작해서 검증하기 (Start Small)
- Overfit on a small batch: 전체 데이터셋으로 학습시키기 전에, 아주 작은 데이터셋(예: 1~2개 배치)만으로 모델을 학습을 진행. 모델이 정상이라면 이 작은 데이터에 대해서는 Loss가 0에 가깝게 떨어지면서 완벽하게 과적합(overfitting)이 일어나야 함. 만약 이것조차 안된다면, 모델 구조나 데이터 파이프라인에 심각한 버그가 있다는 뜻.
학습 과정에서 흔히 만나는 문제들
- Loss가 줄어들지 않을 때:
- 의심: Learning rate가 너무 낮거나 높다. 데이터 정규화(Normalization)가 잘못되었음.
- 검증: Learning rate를 10배 높이거나 낮춰본다. 데이터가 실제로
[0, 1]
이나 평균 0, 표준편차 1 사이로 정규화되었는지print
로 확인.
- Loss가 NaN으로 폭발할 때:
- 의심: Learning rate가 너무 높다.
log(0)
처럼 불안정한 연산이 있다. - 검증: Learning rate를 확 낮춘다.
torch.autograd.set_detect_anomaly(True)
코드를 추가해서 어디서 그래디언트가 터지는지 확인한다.
- 의심: Learning rate가 너무 높다.
⚪ 4. 전문 디버깅 툴
print()
만으로는 한계가 올 때, VS Code나 PyCharm 같은 IDE에 내장된 디버거를 사용하는 게 좋아.
- Breakpoint (중단점): 코드의 특정 줄에 ‘멈춤’ 표시를 해두고, 코드가 거기까지 실행되면 잠시 멈추게 할 수 있음.
- Step-by-step 실행: 멈춘 상태에서 코드를 한 줄씩(step over), 또는 함수 안으로 들어가며(step into) 실행할 수 있음.
- 변수 검사: 실행되는 모든 시점의 변수 값을 실시간으로 확인할 수 있어서
print
를 일일이 넣을 필요가 없음.
🔵 결론적으로
에러 로그만 보는 수동적인 디버깅에서 벗어나서, ‘가설을 세우고 -> print
나 디버거로 검증’하는 능동적인 자세를 갖는 것이 핵심.
특히 딥러닝에서는 ‘작은 데이터로 먼저 검증하는 습관’이 수많은 시간을 아껴줄 수 있다.
This post is licensed under CC BY 4.0 by the author.