💓

[인턴] EEG 신호로 수면 단계 분류하기: Chronos와 RAG를 활용한 실험

Tags
Intern
AI
Published
January 6, 2026
Author
SSUM

들어가며

"과연 시계열 Foundation Model로 의료 신호를 분석할 수 있을까?"
최근 몇 주간 이 질문을 가지고 흥미로운 실험을 진행했습니다. 바로 Amazon의 Chronos 모델RAG(Retrieval-Augmented Generation) 기법을 결합해서 뇌파(EEG) 신호로부터 수면 단계를 자동으로 분류하는 시스템을 만드는 것이었죠.
솔직히 처음에는 "이게 될까?" 싶었습니다. 😅 하지만 막상 해보니 NLP에서 쓰던 기법들이 생리학적 신호 분석에도 놀라울 정도로 잘 작동하더라고요. 이번 글에서는 그 실험 과정과 배운 점들을 공유해보려 합니다.

문제 정의: 수면 단계 분류가 왜 어려운가?

수면 단계란?

우리가 잠을 잘 때, 뇌는 다음과 같은 단계를 순환합니다:
  • Wake (깨어있음): 의식이 있는 상태
  • N1 (얕은 수면 1단계): 잠이 들기 시작
  • N2 (얕은 수면 2단계): 본격적인 수면
  • N3 (깊은 수면): 서파수면, 회복에 중요
  • REM (렘수면): 꿈을 꾸는 단계
임상에서는 뇌파(EEG)를 보고 의사가 직접 30초 단위로 이 단계들을 분류합니다. 이걸 Sleep Staging이라고 하죠.

기술적 도전 과제

그런데 이게 생각보다 까다롭습니다:
  1. 개인차가 엄청납니다: 두개골 두께, 전도도, 개인의 생리학적 특성 때문에 같은 수면 단계여도 신호 모양이 사람마다 다릅니다.
  1. 데이터가 복잡합니다: 100Hz 샘플링 레이트로 30초면 3,000개의 데이터 포인트입니다.
  1. 라벨링 비용이 높습니다: 전문가가 일일이 판독해야 하죠.
그래서 저는 생각했습니다. "Foundation Model의 힘을 빌리면 어떨까?" 🤔

핵심 아이디어: Time-Aware RAG 시스템

전체 아키텍처

설계한 시스템은 다음과 같습니다:
EEG 신호 ↓ Chronos Encoder (시계열 임베딩 추출) ↓ 512차원 벡터 ↓ Two-Stage Retrieval (유사한 수면 패턴 검색) ↓ LLM In-Context Learning (검색된 예시로 분류) ↓ 수면 단계 예측
간단히 말하면:
  1. Chronos로 EEG 신호를 dense vector로 변환
  1. 유사도 검색으로 비슷한 과거 사례를 찾고
  1. LLM이 그 사례를 참고해서 최종 판단

왜 Chronos인가?

Chronos는 Amazon이 만든 시계열 Foundation Model입니다. T5(텍스트 모델)의 아키텍처를 시계열 데이터에 적용한 건데요, 핵심은 이겁니다:
  • 사전학습: 엄청나게 많은 시계열 데이터로 학습됨
  • Zero-shot Transfer: 의료 신호를 따로 학습 안 해도 됨
  • 고정된 임베딩: 512차원 벡터로 시계열을 표현
저는 amazon/chronos-t5-small (20M 파라미터) 모델을 썼습니다. 더 큰 모델(Base, Large)도 있지만, 속도와 품질의 균형을 고려했죠.

실험 과정: 단계별 구현

1단계: 데이터 준비

Sleep-EDF 데이터셋을 사용했습니다:
  • 형식: Parquet (CSV보다 빠르고 압축률 좋음)
  • 샘플링: 100Hz (1초에 100개 샘플)
  • 전처리: Z-Score 정규화 (평균 0, 표준편차 1)
정규화가 중요한 이유는, 사람마다 신호 크기가 다르기 때문입니다. 정규화를 안 하면 모델이 "누가 잤는지"만 배우고 "어떻게 잤는지"는 못 배웁니다.
# 30초 에폭 = 100Hz × 30s = 3000 샘플 context_tensor_cpu = torch.tensor( segment['target'].values, # Z-score normalized EEG dtype=torch.float32 ).unsqueeze(0)

2단계: Chronos 임베딩 추출

여기서 중요한 함정이 하나 있습니다. Chronos의 토크나이저는 CPU에서만 작동합니다! 😱
# ❌ 이렇게 하면 에러! input_ids = pipeline.tokenizer.context_input_transform(tensor_gpu) # ✅ 이렇게 해야 함 input_ids, attention_mask, scale = pipeline.tokenizer.context_input_transform( context_tensor_cpu # CPU 텐서로! ) # 그 다음에 GPU로 이동 input_ids = input_ids.to(device) attention_mask = attention_mask.to(device)
이거 몰라서 처음에 한참 헤맸습니다... 😅 공식 문서에도 잘 안 나와있더라고요.
임베딩을 얻고 나면 Mean Pooling으로 고정 크기 벡터를 만듭니다:
# (batch, seq_len, 512) → (batch, 512) pooled = torch.mean(embeddings, dim=1)

3단계: Two-Stage Retrieval (핵심!)

개인차 문제를 해결하기 위해 2단계 검색을 설계했습니다:

Stage 1: User Selection (사용자 선택)

# 각 사용자의 "프로필" = 모든 에폭의 평균 벡터 user_profile = np.mean(user_embeddings, axis=0) # 현재 사용자와 가장 비슷한 사용자 찾기 similarity = cosine_similarity(target_profile, source_profiles) best_user = argmax(similarity)
먼저 **"생리학적으로 비슷한 사람"**을 찾습니다. 이게 일종의 도메인 적응(Domain Adaptation) 역할을 하죠.

Stage 2: Instance Selection (에폭 선택)

# 선택된 사용자의 에폭들 중 가장 유사한 것 찾기 similar_epochs = find_top_k_similar(current_epoch, best_user_epochs, k=3)
그 사람의 데이터 중에서 **"현재 입력과 가장 비슷한 30초 구간"**을 찾습니다.
이 2단계 접근이 핵심입니다. 처음에 전체 데이터베이스에서 바로 검색했더니 노이즈가 너무 많았거든요.

4단계: LLM In-Context Learning

검색된 예시들로 프롬프트를 구성합니다:
당신은 수면 단계 분류 전문가입니다. 현재 사용자(SC4001)와 매우 유사한 뇌파 패턴을 가진 사용자(SC4002, 유사도=0.95)의 데이터를 검색했습니다. 검색된 예시: 1. [유사도=0.92] → N2 (얕은 수면) 2. [유사도=0.89] → N2 (얕은 수면) 3. [유사도=0.87] → N3 (깊은 수면) 현재 입력의 수면 단계를 예측하세요.
LLM은 이 예시들의 패턴을 보고 판단합니다. 마치 의사가 비슷한 과거 케이스를 참고하듯이요.
여기서 temperature=0.1로 설정했습니다. 분류 태스크니까 결정론적(deterministic)이어야 하거든요.

검증 & 시각화

t-SNE로 임베딩 품질 확인

정말 신기했던 건, 모델이 수면 단계를 학습한 적이 없는데도 임베딩 공간에서 자연스럽게 클러스터링이 되더라는 거예요! 😲
# 512차원 → 2차원 투영 tsne = TSNE(n_components=2, perplexity=30, random_state=42) X_embedded = tsne.fit_transform(embeddings)
시각화해보니:
  • 왼쪽 플롯 (수면 단계별): 같은 단계끼리 뭉쳐있음 → 생리학적 특성을 캡처
  • 오른쪽 플롯 (사용자별): 사용자별로도 뭉쳐있음 → 개인차도 보존
이게 의미하는 건, Chronos가 범용 시계열 모델로 학습됐지만 도메인 특화 패턴도 잡아낸다는 겁니다. Transfer Learning의 힘이죠!

Forecasting으로 파이프라인 검증

혹시 몰라서 Chronos의 본래 용도인 **예측(forecasting)**도 테스트해봤습니다:
forecast = pipeline.predict( context_tensor_cpu, prediction_length=100, # 다음 1초 예측 num_samples=20 # 확률적 예측 )
결과적으로 미래 신호를 꽤 그럴듯하게 예측하더라고요. 파이프라인이 제대로 작동한다는 증거였죠.

배운 교훈

1. CPU/GPU 경계를 명확히 하라

Chronos 토크나이저의 CPU 제약은 문서에 명시되지 않았습니다. 이런 함정을 피하려면:
  • 에러 메시지를 잘 읽자: "CUDA operation not supported" 같은 힌트
  • 단계별로 디바이스 체크: print(tensor.device) 습관화

2. Foundation Model은 Zero-shot으로도 강력하다

Chronos를 의료 데이터로 따로 Fine-tuning하지 않았는데도 임베딩 품질이 좋았습니다. 이유는:
  • 사전학습 데이터의 다양성: 다양한 시계열 패턴 학습
  • 아키텍처의 범용성: T5의 Attention 메커니즘
"모든 걸 처음부터 학습"하는 시대는 지났습니다. 이제는 **"어떤 Foundation Model을 어떻게 활용할까"**가 핵심이죠.

3. 검색 전략이 성능을 좌우한다

처음엔 단순히 "가장 비슷한 에폭 3개"를 찾았습니다. 문제는 다른 사람의 다른 생리학적 패턴이 검색되는 거였죠.
Two-Stage Retrieval로 바꾸니:
  • 노이즈 감소
  • 검색 속도 향상 (후보군 축소)
  • 해석 가능성 증가 ("이 사람과 비슷해서 참고했습니다")
이게 Inter-Subject Variability 문제를 우회하는 영리한 방법이었습니다.

4. 정규화는 선택이 아니라 필수

Z-Score 정규화를 빼고 돌려봤더니 임베딩이 완전히 무너졌습니다. 왜냐면:
  • Chronos는 정규화된 데이터로 사전학습됨
  • 원본 스케일의 EEG는 분포가 완전히 다름
Transfer Learning할 때는 사전학습 데이터의 전처리 방식을 똑같이 따라야 합니다.

한계와 개선 방향

현재 시스템의 한계

부끄러운 이야기지만, 아직 정량적 평가(Accuracy, F1-score)를 제대로 못 했습니다. 😅 다음 단계로:
  1. 정량 평가: 테스트 셋에서 Accuracy 측정
  1. 베이스라인 비교: CNN, LSTM 같은 전통적 방법과 비교
  1. 클래스 불균형 처리: N2가 압도적으로 많음

개선 아이디어

프롬프트 강화: 현재는 라벨만 전달하는데, 통계적 특징도 추가하면?
검색된 예시: 1. [유사도=0.92] → N2 (주파수 파워: 델타 70%, 세타 20%)
앙상블: 여러 유사 사용자의 예시를 모두 활용
SC4002 (유사도 0.95): N2, N2, N3 → 66% N2 SC4005 (유사도 0.91): N2, N2, N2 → 100% N2 → 최종 예측: N2 (신뢰도 높음)
Fine-tuning: Chronos를 Sleep-EDF로 LoRA Fine-tuning하면 더 좋아질까? 실험해볼 가치가 있습니다.

마치며

이번 실험을 통해 **"NLP 기법이 생리학적 신호에도 통한다"**는 걸 확인했습니다.
핵심은:
  • Foundation Model의 범용 표현력
  • RAG의 예시 기반 학습
  • Two-Stage Retrieval의 도메인 적응
그리고 무엇보다, 실패와 시행착오 속에서 배운 게 많았습니다. CPU/GPU 이슈로 삽질할 때, t-SNE 돌리다가 메모리 터질 때, 검색 결과가 엉망일 때... 😭 하지만 그 과정이 결국 더 깊은 이해로 이어지더라고요.
아직 부족한 점이 많지만, 이 실험은 Medical AI에서 Foundation Model + RAG 조합의 가능성을 보여준 좋은 사례라고 생각합니다.
다음에는 정량 평가 결과와 베이스라인 비교를 가지고 다시 찾아오겠습니다! 😊

References & Resources

  • Chronos 논문: Ansari et al. (2024), "Chronos: Learning the Language of Time Series"
  • AASM 매뉴얼: Berry et al. (2012), 수면 단계 분류 표준
  • RAG 개념: Lewis et al. (2020), "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks"

코드 저장소

(실험 코드는 정리해서 GitHub에 올릴 예정입니다)

여러분은 시계열 Foundation Model을 어떻게 활용하고 계신가요? 댓글로 공유해주시면 함께 배울 수 있을 것 같습니다! 🚀