목표
10.13에 1D-CNN을 LTSTDB 기록 몇 개로 학습해 워커에 연결까지 했지만, 임시 라벨로 만든 연결 테스트용이라 성능 수치는 의미가 없었다. 부하테스트는 진짜 모델 무게로 재야 의미가 있어서, 공개 데이터(PTB-XL)로 기준 모델을 직접 학습하고 여러 모델의 정확도와 판정 속도를 비교한다. (10.28)
- 서버 담당 관점 : 정확도가 오른 만큼 판정 속도를 내줘도 되는지를 숫자로 본다 (100명 실시간 기준)
- 흐름 : A 기존 AI 구조 이해 → B 학습 데이터 만들기 → C 시연·평가 데이터 분리 → D AI 흐름 공부 → E 기준 CNN 학습·채점과 모델 비교
- 결과 : 시험셋 민감도 0.929, 특이도 0.883, AUC 0.969. 모델 6종은 정확도가 비슷하고 속도는 16배 차이 → 경량 CNN 기준선, CNN+LSTM 최종 후보
A. 기존 AI 구조 이해
저장소에 있는 AI 코드가 어떻게 돌아가는지, 뭐가 있고 뭐가 비어 있는지 파악한다.
AI가 파이프라인 어디에 있나
생성기 → 수신부 → Redis 큐 → 워커
│
① 0.1초 묶음 50개를 모음 = 5초 창 [3, 1250]
② R파 찾기 (Pan-Tompkins, 3리드 중 2개 이상 동의)
③ 판정 predict() ← 여기가 AI 모델
↓
(정상 / 이상 의심 / 판정 불가, 확률)
- 워커는 모델이 뭔지 모름.
--model 옵션으로 ③에 끼울 걸 고름
--model |
정체 |
상태 |
| demo |
랜덤포레스트, R파로 숫자 10개를 뽑아 판정 (10.8 내가 만든 임시 모델) |
돌아감 (시연은 이걸로 함) |
| demo-nbeat |
위와 같고 R파 4개 구간만 판정 |
돌아감 |
| cnn |
1D-CNN 정식 모델 자리 |
10.13 LTSTDB로 연결 테스트 학습만 함 (model.pt는 저장소에 없음) |
1D-CNN 한 번 판정
5초 창 [3리드, 1250샘플]
① 리드 순서를 학습 때 순서로 맞춤 (이름이 다르면 에러)
② 정규화 : 리드마다 중앙값을 빼고 크기로 나눈 뒤 ±10으로 자름
③ 합성곱 블록 4개 : 특징 3→16→32→64→96개, 길이 1250→625→312→156→78
④ 평균 → 숫자 96개 → 점수 1개 → 0~1 확률
⑤ 확률 ≥ 임계값 → abnormal, 아니면 normal
⑥ 전극이 빠지면(lead off) 모델을 안 돌리고 unavailable
- 모델이 작음 (파라미터 약 5만 개) → GPU로 충분, CPU로도 돌아감
학습 설정 (train.py)
| 항목 |
설정 |
의미 |
| 입력 |
npz : X [N,3,1250], y [N], patient_ids [N] |
10.13 LTSTDB용 make_npz.py는 내가 만듦. 의사 판독 라벨이 있는 PTB-XL용은 없었음 → B |
| 학습/검증 |
환자 단위 80:20 |
같은 환자가 양쪽에 섞이면 성능이 부풀려짐 |
| 불균형 |
pos_weight = 정상 수 / 이상 수 |
이상이 적어도 무시당하지 않게 |
| 최적화 |
Adam, lr 0.001, 20 epoch, 배치 64 |
기본값 |
| 저장 |
검증 손실 가장 낮은 시점 |
과적합 방지 |
| 임계값 |
특이도 0.80 이상에서 민감도 최대 |
미탐이 더 치명적이라 민감도 우선 |
| 장치 |
기본 cpu |
GPU는 --device cuda |
피드백 반영 점검
| 피드백 |
반영 |
| 250Hz, 3리드 전부 사용 |
O |
| 미탐 우선 (민감도) |
O |
| 시간 기준 5초 고정 창 1순위 |
O |
| 전극 빠짐이면 판정 안 함 |
O |
| 박동 기준(N-Beat) 비교 |
X (CNN에는 없음) |
| 부정맥 2종(심방세동, 조기심실수축) |
X (정상/이상 이진 분류) |
| 노이즈 대응 |
X (노이즈 학습·평가 없음) |
| 판정 속도 측정 |
△ (10.13 맥북에서 창 하나 약 0.6ms, 테스트 서버에선 아직) |
정리
- 틀(모델, 학습, 예측, 워커 연결)은 있고, 10.13에 LTSTDB로 한 번 학습해서 워커 연결까지 확인함 (내 작업:
make_npz.py, cnn_model.py, eval_cnn.py)
- 비어 있는 건 의사 판독 라벨이 있는 데이터로 학습한 모델. LTSTDB 학습은 기록 몇 개에 임시 라벨이라 성능 수치에 의미가 없었음
- 테스트 서버에서 단위 테스트 8개 통과 확인 (틀은 멀쩡함)
B. 학습 데이터 만들기 (PTB-XL → NPZ)
공개 심전도 데이터 PTB-XL을 학습 코드가 먹는 모양(X [N, 3, 1250])으로 바꾼다. 파일 : ai/AI모델/make_ptbxl_npz.py
변환 흐름
PTB-XL 기록 1개 (12리드 · 500Hz · 10초 = [5000, 12])
① 리드 3개만 : V4, V3, II
② 500Hz → 250Hz [2500, 3]
③ [시간, 리드] → [리드, 시간] [3, 2500]
④ 10초 → 5초 창 2개 [3, 1250] × 2
▼
ptbxl_train.npz (fold 1~9) + ptbxl_test.npz (fold 10, 시험 전용)
정한 것과 이유
| 정한 것 |
이유 |
| 리드 V4·V3·II |
생성기 기본 기록이 V4·V3·II. 모델은 학습 때와 리드 이름이 다르면 거부함 |
| 250Hz |
우리 파이프라인·기기 속도 |
| 라벨 : 정상(NORM)=0, 심방세동(AFIB)·조기심실수축(PVC)=1, 그 외 제외 |
목표 부정맥 2종. 다른 질환을 0으로 넣으면 "정상이 아닌데 정상"이 생김 |
| fold 10 시험 전용 |
PTB-XL 권장 방식. 학습·임계값에 안 써야 진짜 성능 |
핵심 코드
def label_of(scp_codes):
codes = set(ast.literal_eval(scp_codes).keys())
if codes & ABNORMAL:
return 1
if "NORM" in codes:
return 0
return None
- 1줄 :
scp_codes는 "{'NORM': 100.0, 'SR': 0.0}" 같은 문자열이라 ast.literal_eval로 진짜 dict로 바꾸고 키(진단 코드)만 꺼냄
- 2줄 : 집합 교집합(&). AFIB나 PVC가 하나라도 있으면 이상
- 4줄 : 이상 코드 없이 NORM만 있으면 정상
- 6줄 : 둘 다 아니면 None → 학습에서 뺌
sig = rec.p_signal[:, idx].astype(np.float32)
sig = decimate(sig, SRC_FS // DST_FS, axis=0, zero_phase=True)
return np.ascontiguousarray(sig.T, dtype=np.float32)
- 1줄 : 12리드 중 V4·V3·II 열만 꺼냄 → [5000, 3]
- 2줄 : 500→250Hz. 한 칸씩 건너뛰면 높은 주파수가 낮은 주파수로 둔갑함(에일리어싱).
decimate는 저역통과 필터를 먼저 걸고 줄임. zero_phase로 파형이 시간축에서 밀리지 않게
- 3줄 :
.T로 [시간, 리드] → [리드, 시간]. 모델 입력 모양이 [3, 시간]
한계
- 라벨이 10초 단위라 5초 창 2개에 똑같이 붙음. 심방세동은 계속 이어져서 괜찮지만, 조기심실수축은 박동 하나라 한쪽 창에는 없을 수 있음 → 결과 볼 때 감안
- 생성기 기록 중에는 리드가 다른 것(V6·V5·aVF 등)도 있어서 그 환자는 모델이 거부함. 실제 기기가 보내는 3리드 확인 필요
확인
- 가짜 PTB-XL(12개 기록)로 돌려서 X (14, 3, 1250), 리드 순서 V4·V3·II 확인
- 실제 데이터 : 전체 21,799기록 중 정상 9,290 · 이상 2,452 사용, 10,057 제외. 학습용 창 약 2만 개(5초 간격) / 63,390개(1초 간격), 시험용 2,354개. 건너뛴 기록 0
옵션 추가 (10.28)
| 옵션 |
하는 일 |
이유 |
--leads |
학습할 3리드 바꾸기 (기본 V4 V3 II) |
실제 패치 리드가 정해지면 값만 바꿔 다시 학습 |
--stride 1 |
학습용 창을 1초씩 겹쳐 자르기 (10초 → 창 6개) |
데이터 늘리기. 시험셋은 같은 구간이 여러 번 채점되지 않게 늘 5초 간격 |
| 파일 이름 |
ptbxl_V4-V3-II_s1_train.npz, ptbxl_V4-V3-II_test.npz |
리드 조합마다 코드를 복사하지 않고 결과 파일로 구분 |
- 리드 조합 하나 = 모델 하나 (
models/model_V4-V3-II_s1.pt)
- 학습된 모델 파일(
models/, *.pt)은 저장소에 올리지 않음. GPU 장비에서 다시 만들 수 있음
C. 시연·평가 데이터 분리 (LTSTDB → INCART)
생성기가 보내는 파형을 용도별로 나눈다. 부하테스트는 지금처럼 LTSTDB, AI 시연과 평가는 INCART로 바꾼다.
문제
- LTSTDB 86개 기록 중 3리드는 18개뿐 (나머지는 2리드)
- 그 18개도 녹음할 때 전극을 다르게 붙여서 리드가 제각각
s30661 : V4 · V3 · II ← 모델과 맞음
s30691 : V6 · V5 · aVF ← 모델이 거부
s30742 : E-S · A-S · A-I ← 모델이 거부
- 처음부터 3개만 녹음해서, 없는 리드를 만들어 낼 수 없음
- 모델은 학습한 리드 조합만 받음 → LTSTDB로 시연하면 대부분 "판정 불가"
결정
| 쓰임 |
데이터 |
이유 |
| 서버 부하테스트 |
LTSTDB |
250Hz 연속 기록, 이전 측정과 비교 가능. 부하에는 리드 이름이 상관없음 |
| AI 시연·평가 |
INCART |
75개 기록 전부 표준 12리드 → 모델 리드를 골라 꺼낼 수 있음. 박동 주석이 있어 학습에 안 쓴 데이터로 평가 가능 |
- INCART는 원래 "정확도 평가용"으로 계획돼 있었는데(10.8) 아무도 쓰지 않았음. 원래 역할(평가) + 시연 스트림 역할을 더함
생성기 코드 (server/stream/stream_tcp.py)
idx = [names.index(l.upper()) for l in leads]
sig = np.asarray(rec.p_signal[:, idx], dtype=float)
sig = resample_poly(sig, FS, INCART_FS, axis=0)
- 1줄 : INCART 12리드 이름 목록에서 고른 3리드(기본 V4·V3·II)가 몇 번째 열인지 찾음
- 2줄 : 그 3개 열만 꺼냄
- 3줄 : 257Hz → 250Hz. 한 칸씩 건너뛰는 게 아니라 250/257 비율로 늘렸다 줄이면서 필터가 같이 걸려 파형이 안 깨짐
# 부하테스트 (기본값, 그대로)
python server/stream/stream_tcp.py 100 --records auto
# AI 시연·평가
python server/stream/stream_tcp.py 100 --source incart --records auto --incart-dir ~/data/incart
- 옵션 :
--source ltstdb|incart, --leads(모델 학습 리드와 같게), --incart-dir(받아둔 폴더)
- 캐시 파일 이름에 리드 조합을 넣음 → 조합을 바꾸면 따로 저장
- 확인 : 가짜 INCART 기록 2개로 V4·V3·II 열이 맞게 골라지고 1분 = 15,000샘플(250Hz), 4명 × 3초 120건 전송. 없는 리드를 주면 그 기록의 리드 목록을 보여주고 멈춤
D. AI 흐름 공부 정리 (10.28)
학습과 판정
- 학습 : 정답이 붙은 데이터를 보여주며 모델을 만드는 것. 미리 한 번, 결과물은 파일 하나(model.pt)
- 판정(추론) : 학습한 모델에 처음 보는 환자 파형을 넣어 정상인지 물어보는 것. 실시간, 환자마다 1초에 한 번
[학습] 저장된 파일 → npz → train.py → model.pt (오프라인, 한 번)
[판정] 생성기 → 서버 → 워커가 model.pt로 판정 (실시간)
- 워커 안에서 R파 검출과 CNN은 따로 돎. R파는 심박수·박동 위치, CNN은 5초 파형 통째로 보고 이상 확률
- CNN에 넣는 건 R파가 아니라 파형 그 자체 [3리드 × 1250샘플]
학습이 하는 일
① 파형 하나를 모델에 넣음
② 모델이 답함 (이상일 확률 0.30)
③ 정답 확인 (실제로는 이상 = 1)
④ 틀린 정도 계산 (손실)
⑤ 가중치를 다음엔 덜 틀리게 조금 고침
⑥ 다음 파형으로 반복
- 학습 데이터 전체를 한 바퀴 = 1 epoch. 우리 설정은 20바퀴
- 정상과 이상을 둘 다 보여줘야 둘을 가르는 기준을 배움
- 오래 걸리는 건 계산보다 데이터 준비와 여러 번 다시 학습하는 것. 우리 모델은 작아서 학습 자체는 몇 분~수십 분
npz
- 넘파이 배열 여러 개를 파일 하나에 묶어 압축 저장하는 형식
- 공개 데이터는 우리 형식과 다름 → 변환 코드가 세 가지를 한 번에 함
- 형식 변환 : 12리드 → 3리드, 500Hz → 250Hz, 10초 → 5초 창
- 정답 붙이기 : 진단 코드를 보고 0 / 1, 애매한 건 빼기
- 나누기 : 학습용과 시험용을 따로 저장
- 한 번 변환해두면 학습을 여러 번 돌려도 파일 하나만 읽으면 됨
데이터 세 가지의 역할
| 데이터 |
녹음 |
역할 |
| PTB-XL |
병원에서 누워서 10초, 12리드, 의사 판독 라벨, 약 1만 8천 명 |
학습 + 시험 |
| LTSTDB |
24시간 홀터, 250Hz, 3리드 18개 |
서버 부하테스트 스트림 |
| INCART |
30분, 12리드, 박동 주석, 75개 |
AI 시연 스트림 + 다른 데이터로 평가 |
| NSTDB |
노이즈만 녹음 |
시험셋과 생성기에 섞어 강건성 확인 |
- 학습은 저장된 녹음으로 미리 하고, 실시간은 생성기가 저장된 녹음을 0.1초씩 꺼내 흉내냄
- LTSTDB 학습은 안 함. 정답은 정확하지만 18명뿐이라 모델이 그 사람들만 외움
리드와 모델
- 모델 하나는 3리드를 한꺼번에 보고 판단함 → 리드 조합 하나 = 모델 하나
- 12리드 데이터(PTB-XL, INCART)는 어떤 조합이든 골라 꺼낼 수 있음. 3리드로만 녹음한 데이터(LTSTDB)는 안 됨
- 코드는 하나로 두고 설정값(--leads)만 바꿔 돌리며, 결과 파일 이름으로 구분. 코드를 복사하면 버그를 고칠 때 복사본마다 다 고쳐야 함
최종 모델까지 부족한 것
| 부족한 것 |
지금 데이터 |
실제 현장 |
| 측정 환경 |
병원 침대에 누워서 10초 |
패치를 붙이고 돌아다니는 환자 |
| 리드 |
표준 12리드 중 3개 |
패치 전극 위치는 다를 수 있음 |
| 노이즈 |
거의 깨끗함 |
움직임, 뒤척임, 샤워 |
- 진짜 최종 모델은 기기 데이터가 있어야 하는데, 이번에는 공개 데이터로 만들 수 있는 최선 + 기기 데이터가 오면 바로 다시 학습할 수 있는 파이프라인이 목표
- 정답이 없는 것(리드 구성, 평가 기준)은 기다리지 않고 근거를 갖고 우리가 정해 진행한 뒤, "이렇게 했는데 괜찮은지" 확인받음. 단 바꾸기 쉽게 해두고, 기준은 보고할 때 밝힐 것
E. 기준 CNN 학습·채점과 모델 비교
PTB-XL로 기준 CNN을 학습하고 학습에 안 쓴 시험셋으로 채점한다. 그다음 모델 구조·리드·학습 방식을 바꿔 가며 정확도와 판정 속도를 비교해, 서버에 올릴 모델을 고른다.
1. 기준 CNN 성적
GPU 장비에서 PTB-XL 전체로 학습하고, 학습에 한 번도 안 쓴 시험셋(fold 10)으로 채점 (eval_ptbxl.py)
| 구분 |
민감도 |
특이도 |
AUC |
미검출 |
오검출 |
| 검증셋 (참고, 임계값 고른 데이터) |
0.963 |
0.885 |
0.979 |
- |
- |
| 시험셋 5초 창 2,354개 |
0.929 |
0.883 |
0.969 |
34 / 482 |
219 / 1,872 |
| 시험셋 10초 기록 1,177개 |
0.967 |
0.842 |
0.984 |
8 / 241 |
148 / 936 |
- 판정 속도 : 창 1개 CPU 0.40ms / GPU 장비 0.41ms → 100명을 1초마다 판정해도 코어 1개의 4%. GPU가 필요 없음
- 검증 → 시험 하락이 작아 과적합은 크지 않음
- 약점 : 오검출이 정상 창의 12%. 누워서 잰 데이터라 실제 패치 환경에선 낮아질 것
2. 모델 6종 비교 (시드 3번 평균, 리드 V4·V3·II)
| 모델 |
창 AUC |
민감도 |
특이도 |
CPU 판정 |
파라미터 |
| CNN (기준선) |
0.969 ± 0.002 |
0.939 |
0.853 |
0.40ms |
4.5만 |
| MobileNet1D |
0.971 ± 0.002 |
0.946 |
0.877 |
0.95ms |
7.2만 |
| BiLSTM = CNN+LSTM (최종 후보) |
0.972 ± 0.001 |
0.947 |
0.892 |
1.59ms |
17.7만 |
| Inception1D |
0.973 ± 0.003 |
0.950 |
0.854 |
3.78ms |
47.8만 |
| FCN |
0.962 ± 0.001 |
0.934 |
0.822 |
5.99ms |
26.7만 |
| ResNet1D |
0.969 ± 0.001 |
0.945 |
0.874 |
6.55ms |
327만 |
- 정확도는 구조와 상관없이 비슷하고, 판정 속도는 최대 16배 차이
- 채택 기준(실험 전에 정함) : AUC가 최고와 0.005 이내인 모델 중 CPU 판정이 가장 빠른 것 → CNN을 기준선으로
- CNN+LSTM은 오검출이 가장 적음(특이도 0.892). 1.59ms라 100명도 감당 가능 → 최종 후보. PVC 구분·노이즈 평가·부하테스트에서 비교해 확정
3. 리드 조합·학습 방식 비교 (CNN)
| 조건 |
창 AUC |
| V4·V3·II (생성기 기본) |
0.969 |
| I·II·III (팔다리 전극) |
0.972 |
| II·V1·V5 (홀터) |
0.974 |
| I·II·V2 (노이즈 데이터셋과 같은 조합) |
0.975 |
| V4·V3·II, 학습 창 1초씩 겹쳐 자르기 (데이터 3배) |
0.969 |
- 리드 구성 차이는 0.006 안쪽. 실제 장비 리드가 정해지면 같은 방식으로 다시 학습하면 됨
- 1초 겹쳐 자르기는 비슷한 창만 늘어 AUC 그대로 → 기준은 5초 간격 유지. 조교 피드백대로 진폭 조절·가우시안 노이즈 같은 다른 증강을 다음에 비교
- fold : PTB-XL이 같은 환자가 섞이지 않게 미리 나눠 둔 10개 묶음. 1~9 학습, 10 시험 (K-Fold 교차검증은 다음에 5-Fold로)
4. 정리
- 데이터 3배, 모델 73배, 구조 6종, 시드 3번을 바꿔도 AUC는 0.97 근처 → 이 데이터로는 거의 다 뽑음. 처음 세운 "병목은 모델 크기" 가설은 틀렸고, 라벨(10초 기록 라벨을 5초 창에 그대로 붙임) 쪽 한계로 판단
- 다음은 학습보다 평가 : 노이즈 섞은 데이터, INCART 외부 평가, 100명 부하테스트
트러블슈팅
1. 학습 데이터 다운로드가 하루 넘게 걸림
- 증상:
wget으로 PTB-XL을 받는데 속도가 27KB/s, 남은 시간 18시간 이상
- 원인: physionet.org 원본 서버가 느림
- 해결: PhysioNet 공식 AWS 미러(
aws s3 sync --no-sign-request s3://physionet-open/...)로 바꿔 PTB-XL 2.6GB를 약 10분, INCART를 약 1분에 받음
- 배운 것: 공개 데이터는 원본 말고 공식 미러가 있는지 먼저 확인
2. "병목은 모델 크기"라는 가설이 틀림
- 증상: 학습 데이터를 3배로 늘려도(1초 겹쳐 자르기) AUC가 0.969 그대로
- 원인 추정: 모델이 작아서라고 보고 73배 큰 ResNet1D로 바꿨지만 AUC가 같았음 → 모델 크기 문제가 아니었음
- 결론: 병목은 라벨(10초 기록 라벨을 5초 창에 그대로 붙임) 쪽. 다음 단계를 학습에서 평가(노이즈·외부 데이터)로 바꿈
- 배운 것: 가설은 숫자로 확인하기 전까지 결론이 아님
남은 작업
목표
- 목적 : 임시 라벨로 만든 연결 테스트용 모델 대신, 의사 판독 라벨이 있는 공개 데이터(PTB-XL)로 기준 모델을 학습하고 정확도와 판정 속도를 서버 관점에서 비교
- 단계 흐름 : 6단계까지 = 서버 쪽 (전송·부하테스트) / 7단계 = 테스트 서버에 올릴 진짜 모델 만들기
- 흐름 : A 기존 AI 구조 이해 → B 학습 데이터 만들기 → C 시연·평가 데이터 분리 → E 학습·채점과 모델 비교
A. 기존 AI 구조 이해
- 목적 : 팀에 있던 AI 코드가 어디까지 됐고 뭐가 비어 있는지 파악
- 설명 : 모델·학습·판정·워커 연결 틀은 있었지만, 의사 판독 라벨이 있는 학습 데이터를 만드는 코드가 없어서 정식 학습이 된 적이 없었음
- 학습 : 정답이 붙은 데이터를 보여주며 모델을 만드는 것. 미리 한 번, 결과물은 파일 하나(
model.pt)
- 판정(추론) : 학습한 모델에 처음 보는 파형을 넣어 정상인지 물어보는 것. 실시간, 환자마다 1초에 한 번
- R파 검출과 AI는 따로 돎 : R파는 심박수·박동 위치, CNN은 5초 파형 [3, 1250]을 통째로 보고 이상 확률을 냄
B. 학습 데이터 만들기 (make_ptbxl_npz.py)
- 목적 : 공개 데이터 PTB-XL을 학습 코드가 읽는 모양으로 바꾸기
- 설명 : 12리드 중 3개만 고르고, 500Hz → 250Hz, 10초 → 5초 창 2개로 자른 뒤, 진단 코드로 정답(정상 0 / 이상 1)을 붙임
- PTB-XL : 병원에서 누워서 잰 10초 12리드 심전도 약 2만 건. 의사 판독 라벨이 있음
- npz : 넘파이 배열 여러 개를 파일 하나에 묶어 압축 저장하는 형식
- 다운샘플링(decimate) : 샘플 수를 줄이는 것. 그냥 건너뛰면 높은 주파수가 낮은 주파수로 둔갑해서(에일리어싱) 필터를 먼저 걸고 줄임
- fold 10 : PTB-XL이 같은 환자가 섞이지 않게 미리 나눠둔 10묶음 중 마지막. 시험용으로만 쓰고 학습에는 절대 안 씀
- 환자 단위 분리 : 같은 환자 기록이 학습과 시험에 같이 들어가면 외운 걸로 점수가 부풀려짐
C. 시연·평가 데이터 분리 (LTSTDB → INCART)
- 목적 : 부하테스트와 AI 시연에 쓸 데이터를 용도별로 나누기
- 설명 : LTSTDB는 기록마다 리드가 달라서 모델이 대부분 거부함 → 부하테스트는 LTSTDB 그대로, AI 시연·평가는 12리드인 INCART로
- 리드 : 전극 두 개 사이의 전압 차이. 같은 심장을 다른 방향에서 본 것
- 리드 조합 하나 = 모델 하나 : 모델은 학습한 3리드 순서로만 판정할 수 있음
- 리샘플링(resample_poly) : 257Hz → 250Hz처럼 비율로 늘렸다 줄이며 필터를 같이 걸어 파형이 안 깨지게 바꿈
E. 기준 CNN 학습·채점과 모델 비교 (train.py → eval_ptbxl.py)
- 목적 : GPU 장비에서 학습하고 학습에 안 쓴 시험셋으로 진짜 성적을 잰 뒤, 정확도가 오른 만큼 판정 속도를 내줘도 되는지 서버 관점에서 비교
- 설명 : 시험셋 5초 창 기준 민감도 0.929, 특이도 0.883, AUC 0.969, 창 1개 판정 CPU 0.40ms. 모델 6종 × 리드 2조합 × 시드 3번 비교에서 AUC는 0.962
0.976으로 비슷한데 CPU 판정은 0.40ms6.55ms로 16배 차이 → 경량 CNN은 기준선, CNN+LSTM(BiLSTM)은 최종 후보
- epoch : 학습 데이터 전체를 한 바퀴 다 본 것
- val_loss : 학습에 안 쓴 검증 데이터에서 틀린 정도. 내려가다 다시 오르면 외우기 시작한 것(과적합)
- 민감도 : 실제 이상 중에 이상이라고 잡은 비율 (미검출을 안 하는 능력)
- 특이도 : 실제 정상 중에 정상이라고 맞힌 비율 (오검출을 안 하는 능력)
- AUC : 임계값과 상관없이 정상과 이상을 얼마나 잘 가르는지. 1에 가까울수록 좋음
- 임계값 : 이상 확률이 몇 이상이면 이상으로 판정할지 정하는 기준. 미검출이 더 위험해서 민감도 우선으로 낮게 잡음 (0.1)
- 검증셋 vs 시험셋 : 검증셋은 임계값 고를 때 써서 점수가 후함. 시험셋은 한 번도 안 쓴 데이터라 진짜 성적
- 시드 : 학습 시작점을 정하는 난수. 바꿔서 여러 번 돌려야 차이가 우연인지 알 수 있음
- 채택 기준을 미리 정함 : 결과를 보고 기준을 바꾸면 원하는 결론에 끼워 맞추게 됨
- 파라미터 : 모델이 학습으로 조정하는 숫자 개수. 많을수록 무겁고 느림
- CNN+LSTM : 합성곱이 파형 모양(PVC)을, LSTM이 시간 순서·리듬(심방세동)을 봄
- 데이터 증강 : 데이터를 변형해 양을 늘리는 것. 1초 겹쳐 자르기는 비슷한 창만 늘어 효과가 없었고, 진폭 조절·가우시안 노이즈 같은 다른 방법이 필요 (조교 피드백)
트러블 슈팅
- 학습 데이터 다운로드가 하루 넘게 걸림
- 원인 :
physionet.org 직접 받기가 27KB/s
- 해결 : PhysioNet 공식 AWS 미러(
aws s3 sync --no-sign-request)로 바꿔 2.6GB를 약 10분에
- "병목은 모델 크기"라는 가설이 틀림
- 원인 : 데이터를 3배로 늘려도, 모델을 73배 키워도 AUC가 그대로였음
- 해결 : 병목은 라벨(10초 라벨을 5초 창에 그대로 붙임) 쪽으로 판단. 학습보다 노이즈·외부 평가로 다음 단계를 바꿈