연구 기록과 연구 노트
석 달 전에 돌린 실험의 결과 파일이 있다. 숫자는 남아 있지만, 그 실험을 왜 했는지, 그때 어떤 설정이 기본값이었는지, 비슷한 실험 세 개 중 왜 이것을 논문에 쓰기로 했는지는 기억나지 않는다. 리뷰어가 "학습률을 왜 이 값으로 정했는가"라고 물으면 대답할 근거가 없다. 연구에서 기억은 가장 먼저 사라지는 자료다.
이 장은 연구를 나중에 재구성할 수 있게 기록하는 방법을 다룬다. 실험 추적 도구가 숫자를 남기고(22.1장), 저장소 문서가 코드와 데이터를 설명한다면(6.7장), 연구 노트는 그 사이의 빈칸, 즉 무엇을 왜 했고 무엇을 배웠는가를 남긴다. 이 장의 형식은 예시다. 형식보다 중요한 것은 매일 같은 자리에 쓰고, 기록이 실행 결과와 연결되는 것이다.
학습 목표
- 매일의 연구를 재구성할 수 있게 기록한다
- 결정의 이유를 남긴다
연구 노트
왜 필요한가. 실험 하나는 질문, 가설, 설정, 실행, 결과, 해석, 다음 결정으로 이어진다. 실험 추적 도구와 git은 이 중 설정·실행·결과를 자동으로 남기지만, 질문·가설·해석·결정은 사람의 머릿속에만 있다. 이것이 사라지면 결과 파일은 맥락 없는 숫자가 된다. 연구 노트(research notebook, lab notebook)는 이 맥락을 날짜 순으로 남기는 기록이다.
정의. 연구 노트는 날짜별 항목의 모음이다. 각 항목에는 최소한 다음이 들어간다.
| 칸 | 내용 | 예 |
|---|---|---|
| 질문·가설 | 이 실험이 답하려는 것, 예상한 결과와 그 이유 | "저온 오차가 큰 것은 저온 학습 데이터가 적어서다. 저온 데이터를 2배로 늘리면 0 °C MAE가 0.1 %p 이상 줄 것이다" |
| 실행 | 무엇을 바꿨는가, 실행한 run ID, 커밋, 설정 hash | run-1107-a3, 커밋 39c3192, 설정 sha256:d710199e0a73 |
| 결과 | 숫자와 구간, 그림 경로. 예상과 달랐는가 | "0 °C MAE −0.04 [−0.11, +0.03]. 예상보다 작다" |
| 해석 | 결과가 가설에 대해 말하는 것. 사실과 추측을 구별한다 | "데이터 양만으로는 설명되지 않는 것 같다(추측)" |
| 다음 | 다음에 할 일과 그 이유 | "저온에서 내부 저항 특징을 추가해 본다" |
직관. 연구 노트는 나중의 자신에게 쓰는 편지다. 석 달 뒤의 자신은 지금의 맥락을 전혀 모르는 동료와 같다. 그 사람이 노트만 읽고 "이 실험을 왜 했고, 결과를 보고 무엇을 결정했는가"를 다시 말할 수 있으면 충분히 쓴 것이다.
형식 예시. 파일 하나에 하루를 쓰는 Markdown 형식이다.
# 2026-11-07 (금)
## 09:40 저온 데이터 증량 실험
- 질문: 0 °C 오차가 큰 원인이 학습 데이터 부족인가?
- 가설: 0 °C 학습 셀을 120 → 240개로 늘리면 0 °C MAE가 0.1 %p 이상 준다.
- 실행: run-1107-a3 (seed 5개), 커밋 39c3192, 설정 sha256:d710199e0a73
- 바꾼 것: data.cold_oversample = 2 (나머지는 run-1105-b1과 같음)
## 14:20 결과
- 0 °C MAE 변화 −0.04 %p, 95% 구간 [−0.11, +0.03] → 판정 보류
- 25 °C, 45 °C 변화 없음 (구간 ±0.02 안)
- 그림: outputs/run-1107-a3/per_temp_mae.svg
- 해석: 가설이 예상한 0.1 %p 감소는 구간 밖은 아니지만 점추정이 크게 못 미친다.
데이터 양만의 문제는 아닐 수 있다. (추측) 저온에서 내부 저항이 커지는 효과를 입력이 담지 못한다.
## 다음
- [ ] 내부 저항 추정값을 입력 특징으로 추가 (run-1108-*)
- [ ] seed를 10개로 늘려 오늘 결과의 구간을 좁힐지 결정 → 내부 저항 실험 결과를 보고 판단
원칙. 형식보다 지킬 것이 몇 가지 있다.
- 실행하면서 쓴다. 실험을 시작할 때 가설을, 결과를 볼 때 해석을 쓴다. 금요일에 한 주를 몰아서 쓰면 기억이 결과에 맞춰 다시 쓰인다. 실패한 시도는 빠지고, 결정의 이유는 결과를 알고 난 뒤의 논리로 바뀐다(사후 확증 편향, hindsight bias). 사후에 쓴 노트는 가장 중요한 정보, 즉 결과를 보기 전에 무엇을 예상했는가를 잃는다.
- 고치지 않고 덧붙인다. 틀린 해석을 발견하면 지우지 않고, 새 날짜로 "11-07의 해석은 틀렸다. 이유: …"를 쓴다. 과거의 판단과 그 근거가 남아야 판단이 언제 왜 바뀌었는지 재구성할 수 있다.
- 실패를 쓴다. 학습이 발산했다, 버그였다, 효과가 없었다. 실패 기록이 없으면 같은 실패를 반복하고, 성공한 결과가 몇 번의 시도 중 하나였는지(분모)를 잃는다(23.6장의 부정적 결과).
- 증거와 연결한다. "결과가 좋았다" 대신 run ID, 커밋, 설정 hash, 그림 경로를 적는다. 노트의 문장 하나에서 그 근거가 된 실행으로 돌아갈 수 있어야 한다.
- 사실과 추측을 구별한다. 측정한 것과 해석한 것을 다른 줄에 쓰고, 추측에는 "(추측)" 같은 표시를 붙인다. 석 달 뒤에는 둘이 구별되지 않는다.
연구에서의 의미. 연구 노트는 논문의 원재료다. 23.6장의 주장–근거 표는 노트의 결과 항목을 모은 것이고, 논문의 방법 절과 부록의 실험 설정은 노트의 실행 항목에서 나온다(23.8장). 노트가 없으면 논문을 쓸 때 실험을 다시 돌리거나, 기억으로 설정을 적다 틀린다.
결정 기록
왜 필요한가. 연구는 결정의 연속이다. 어떤 baseline을 쓸지, 어떤 metric을 주 지표로 삼을지, 어떤 실험을 중단할지, 어떤 결과를 논문에 넣을지. 결과는 남아도 결정의 이유는 남지 않는다. "왜 그렇게 했는가"라는 질문은 리뷰, 공동 연구자, 그리고 몇 달 뒤의 자신에게서 반드시 온다.
정의. 결정 기록(decision log)의 항목 하나는 다음을 담는다.
- 날짜와 결정: 무엇을 하기로(또는 하지 않기로) 했는가.
- 근거: 결정의 근거가 된 결과·문헌·제약. 노트 항목이나 run ID로 연결한다.
- 기각한 대안: 검토했지만 택하지 않은 선택지와 그 이유. 가장 자주 빠지고 가장 자주 필요하다. "왜 X를 안 했는가"라는 질문에 답하는 유일한 기록이기 때문이다.
- 되돌릴 조건: 어떤 증거가 나오면 이 결정을 다시 검토하는가.
6.7장의 결정 기록(ADR)은 저장소 차원의 큰 결정(데이터 분할 단위, 핵심 라이브러리)을 별도 파일로 남긴다. 연구 노트의 결정 기록은 더 가볍고 잦다. 하루에도 몇 번 내리는 실험 방향의 결정을 노트 안에 한 단락으로 쓴다. 그중 프로젝트 전체에 영향을 주는 것만 ADR로 승격한다.
## 결정 2026-11-10: 주 지표를 MAE에서 온도별 최대 MAE로 바꾼다
- 근거: 11-07, 11-08 노트. 평균 MAE는 B가 낫지만 0 °C에서 A보다 나쁘다(23.6장 표).
사용처(저온 지역 ESS)에서는 가장 나쁜 조건의 성능이 중요하다는 협력 기관의 의견(11-09 회의).
- 기각한 대안:
- 평균 MAE 유지: 저온 손해가 평균에 묻힌다.
- 0 °C MAE만 보기: 상온 성능 저하를 놓친다.
- 온도 가중 평균: 가중치를 정할 근거가 없다.
- 되돌릴 조건: 사용처가 상온 데이터센터로 바뀌면 평균 MAE로 돌아간다.
- 영향: 이미 돌린 run은 다시 집계만 하면 된다(예측 파일이 남아 있음). 논문 표 2의 주 열을 바꾼다.
직관. 결정 기록의 독자는 "그때 정보로 보면 합리적이었는가"를 판단하려는 사람이다. 나중에 결정이 틀린 것으로 드러나도, 그때 알던 것과 기각한 대안이 남아 있으면 무엇을 몰랐는지가 분명해진다. 반대로 결정만 남고 이유가 없으면, 그 결정이 근거 있는 선택이었는지 우연한 기본값이었는지 구별할 수 없다.
무엇을 기록하는가. 모든 선택을 기록할 수는 없다. 다음 질문 중 하나에 "그렇다"면 기록한다.
- 나중에 누군가 "왜?"라고 물을 것인가? (리뷰어, 공동 저자)
- 되돌리는 데 비용이 큰가? (데이터 재처리, 대규모 재학습)
- 결과의 해석을 바꾸는가? (주 지표, 분할 방식, 이상치 처리 기준)
- 무엇인가를 하지 않기로 했는가? (실험 중단, baseline 제외, 데이터 일부 제외)
마지막 항목이 특히 중요하다. 중단한 실험과 제외한 데이터는 결과 파일에 흔적이 남지 않는다. 기록이 없으면 "결과를 보고 불리한 것을 뺐다"는 의심에 답할 수 없다.
주간 보고
왜 필요한가. 연구 노트는 자신을 위한 상세 기록이고, 주간 보고는 지도교수·팀·공동 연구자를 위한 요약이다. 보고의 목적은 진행 상황을 알리는 것보다 도움을 받는 것이다. 막힌 점이 일찍 드러나야 한 달을 낭비하지 않는다.
구성. 주간 보고는 노트에서 뽑아 만든다. 따로 기억을 더듬어 쓰지 않는다.
# 주간 보고 2026-11-03 ~ 11-09
## 요약 (세 줄)
- 저온 오차의 원인이 데이터 양이 아님을 확인했다(증량해도 0 °C MAE 변화 −0.04 %p, 판정 보류).
- 내부 저항 특징을 넣자 0 °C MAE가 0.15 %p 줄었다(seed 5개, 구간 [−0.22, −0.08]).
- 주 지표를 온도별 최대 MAE로 바꾸기로 했다(결정 11-10).
## 진행
- 실험 3건: run-1107-a3, run-1108-b*, run-1109-c* (노트 11-07 ~ 11-09)
- 핵심 그림 1개: outputs/run-1108-b2/per_temp_mae.svg
## 막힌 점
- 내부 저항 추정이 셀 제조사마다 달라, 제조사 2곳의 셀에서만 추정값이 있다.
## 도움 요청
- 제조사 C 셀의 임피던스 측정 자료를 얻을 수 있는지 (협력 기관 문의 필요)
- 주 지표 변경에 대한 의견 (다음 미팅에서 5분)
## 다음 주 계획
- 내부 저항 특징의 효과를 seed 10개로 확인
- 제조사별로 결과를 나눠 보고
원칙.
- 결론을 먼저 쓴다. 읽는 사람은 첫 세 줄만 읽을 수 있다. "이번 주에 X를 했다"보다 "X를 해서 Y를 알았다"가 낫다.
- 숫자에 구간과 판정을 붙인다. "좋아졌다" 대신 "0.15 %p 줄었다, 구간 [−0.22, −0.08]".
- 막힌 점을 숨기지 않는다. 진행이 없던 주에 보고를 미루면 문제가 커진다. "막혔고, 이것이 필요하다"는 보고가 가장 가치 있는 보고다.
- 도움 요청은 구체적으로 쓴다. "조언 부탁드립니다"가 아니라 누구에게 무엇을 언제까지.
도구
기록 도구. 무엇을 쓰든 세 가지를 확인한다. 날짜로 정렬되는가, 검색되는가, 실행 결과로 링크할 수 있는가. 집필 시점 예시로 흔히 쓰는 선택지는 다음과 같다.
| 도구 | 장점 | 주의 |
|---|---|---|
저장소 안의 Markdown 파일(notes/2026-11-07.md) |
코드와 함께 버전 관리, grep으로 검색, 커밋 hash와 자연스럽게 연결 |
공동 편집이 불편. 공개 저장소면 노트도 공개된다 |
| 연구실 위키 | 공유와 공동 편집, 페이지 링크 | 개인 노트로 쓰기엔 무겁다. 내보내기 형식 확인 |
| 노트 앱(Obsidian·Notion 등) | 빠른 입력, 링크, 태그 | 데이터가 어디에 저장되는지, 서비스가 사라지면 내보낼 수 있는지 확인 |
| 전자 연구 노트(ELN) | 실험실 기록 규정(서명·시각 기록) 지원 | 기관이 지정한 경우가 많다. 기관 규정 확인 |
어느 도구든 기관의 연구 기록 규정이 우선한다. 연구비 과제나 기관에 따라 연구 노트의 형식·보존 기간·서명 방식이 정해져 있을 수 있으므로, 소속 기관의 규정을 먼저 확인한다. 노트에 비밀번호·API 키·개인정보를 쓰지 않는다.
실험 추적과 연결하기. 노트와 실험 추적이 연결되려면 노트의 각 실행 항목에 네 가지가 있어야 한다.
- run ID: 실험 추적 도구에서 그 실행을 찾는 키(22.1장).
- 커밋 hash와 작업 트리 상태: 코드 버전. 커밋하지 않은 변경이 있었다면 커밋 hash는 실제로 돌린 코드를 가리키지 않는다. 그 사실을 적거나, 실행 전에 커밋한다.
- 설정 hash: 설정 파일의 내용으로 만든 짧은 hash. 같은 설정 파일 이름이 시간이 지나며 다른 내용을 가질 수 있으므로, 이름이 아니라 내용을 가리키는 값이 필요하다.
- 데이터 버전: 데이터 파일의 hash나 버전 태그(15부, 22.2장).
이 값들을 손으로 옮기면 틀린다. 다음은 노트에 붙일 "도장"을 출력하는 작은 스크립트의 핵심이다(전체 코드는 labs/research-practice/research-records/note_stamp.py).
import hashlib, json, subprocess
def git(*args):
out = subprocess.run(["git", *args], capture_output=True, text=True)
return out.stdout.strip() if out.returncode == 0 else None
def config_hash(cfg: dict) -> str:
"""키 순서와 공백에 무관한 설정 hash(앞 12자리)."""
canon = json.dumps(cfg, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
return hashlib.sha256(canon.encode("utf-8")).hexdigest()[:12]
commit = git("rev-parse", "--short", "HEAD")
dirty = git("status", "--porcelain", "--untracked-files=no") # 비어 있지 않으면 커밋 안 된 변경
설정 hash는 파일의 바이트가 아니라 정규화한 내용(키를 정렬하고 공백을 없앤 JSON)으로 만든다. 그래야 키 순서나 들여쓰기만 바뀐 파일은 같은 hash를, 값이 하나라도 바뀐 파일은 다른 hash를 갖는다. 실습 폴더에는 같은 설정을 키 순서만 바꿔 쓴 TOML 파일 두 개가 있다. 집필 중 이 장의 원고를 고치던 상태에서 실행한 결과는 다음과 같다(집필 시점 예시, 저장소 루트에서).
uv run --project labs python labs/research-practice/research-records/note_stamp.py \
labs/research-practice/research-records/example_train.toml \
labs/research-practice/research-records/example_train_reordered.toml
# - 시각: 2026-11-07T11:03+00:00
# - 커밋: 39c3192 (커밋하지 않은 변경 있음!)
# - 설정: labs/research-practice/research-records/example_train.toml sha256:d710199e0a73
# - 설정: labs/research-practice/research-records/example_train_reordered.toml sha256:d710199e0a73
두 설정 파일의 hash가 같다. 그리고 커밋 줄에 경고가 붙었다. 원고를 고치던 중이라 작업 트리에 커밋하지 않은 변경이 있었기 때문이다. 이 상태에서 학습을 돌렸다면 39c3192는 실제로 실행한 코드를 가리키지 않는다.
더 나아가 학습 스크립트가 시작할 때 이 값들을 실험 추적 도구와 결과 폴더(outputs/<run-id>/)에 자동으로 남기게 하면(6.3장·6.5장), 노트에는 run ID 하나만 적어도 나머지를 찾을 수 있다.
README와 노트의 경계. 저장소 README(6.7장)는 "지금 이 코드로 무엇을 어떻게 재현하는가"를 쓰고, 노트는 "그동안 무엇을 왜 했는가"를 쓴다. README는 최신 상태로 고쳐 쓰고, 노트는 고치지 않고 덧붙인다. 노트에서 확정된 절차(결과 재현 명령, 데이터 준비 단계)는 README로 옮긴다.
실습
2주 동안 연구 노트를 쓰고, 노트만으로 한 실험의 의사결정 과정을 재구성한다.
- 노트 위치와 형식을 정한다. 저장소 안이라면
notes/YYYY-MM-DD.md로 하루 한 파일. 이 장의 다섯 칸(질문·가설, 실행, 결과, 해석, 다음)을 템플릿으로 만든다. - 실험을 시작할 때마다 가설과 예상 결과를 먼저 쓴다.
note_stamp.py(또는 같은 일을 하는 자신의 스크립트)로 커밋·설정 hash를 붙인다. - 결정을 내릴 때마다 결정 기록을 쓴다. 기각한 대안과 되돌릴 조건을 빠뜨리지 않는다. 2주 동안 최소 세 개.
- 매주 노트에서 주간 보고를 만든다. 보고를 쓰는 데 노트 밖의 기억이 필요했다면, 그 내용이 노트에 빠진 것이다. 무엇이 빠졌는지 적고 템플릿을 고친다.
- 2주 뒤, 실험 하나를 골라 노트만 보고 "무엇을 왜 했고, 결과를 보고 무엇을 결정했는가"를 한 쪽으로 쓴다. 가능하면 동료에게 노트를 주고 같은 일을 하게 한 뒤 두 결과를 비교한다. 동료가 재구성하지 못한 부분이 노트의 빈칸이다.
실패 사례
- 결과만 기록하고 실패와 이유를 남기지 않는 것. 성공한 run의 숫자만 남으면 같은 실패를 반복하고, 결과가 몇 번의 시도 중 하나였는지 알 수 없다.
- 노트를 나중에 몰아서 쓰는 것. 결과를 알고 난 뒤에 쓴 가설은 가설이 아니다. 실패는 빠지고 결정의 이유는 사후 논리로 바뀐다.
- "설정 파일 이름"만 적는 것.
configs/base.yaml은 시간이 지나며 내용이 바뀐다. 커밋 hash나 설정 hash처럼 내용을 가리키는 값을 적는다. - 커밋하지 않은 코드로 실행하고 커밋 hash를 적는 것. 그 hash는 실제로 돌린 코드가 아니다. 실행 전에 커밋하거나, 작업 트리가 깨끗하지 않았다는 사실을 함께 적는다.
- 결정만 쓰고 기각한 대안을 쓰지 않는 것. "왜 X를 하지 않았는가"에 답할 수 없다.
통과 기준
- 실습 기간의 실험 하나(3개월 이상 된 기록이 있으면 그것)를 골라, 기록만으로 실험의 가설·설정(커밋·설정 hash)·결과·그 뒤의 결정과 이유를 설명한다. 동료가 같은 노트로 재구성한 결과와 비교해 빈칸을 찾는다.
- 2주 동안의 노트에서 모든 실행 항목이 run ID·커밋·설정 hash로 실행 결과에 연결되어 있다.
- 결정 기록 세 개 이상이 기각한 대안과 되돌릴 조건을 포함한다.