본문으로 건너뛰기

학습 데이터 하이브리드 접근법 기술 명세서

Executive Summary​

SOL(Sleep Onset Latency) 예측의 정확도를 향상시키기 위해 전주기 누적 데이터와 최근 활동 데이터를 결합한 하이브리드 접근법을 구현했습니다. 이는 장기 추세와 단기 모멘텀을 모두 반영하여 더 정확하고 신뢰할 수 있는 예측을 제공합니다.

1. 배경 및 문제점​

1.1 기존 접근법의 한계​

단일 윈도우 방식의 문제점​

  • 고정 7일 윈도우: 치료 초반(dayIndex < 7)에는 데이터 부족
  • 데이터 유실: limit=200으로 전주기 조회 시 페이징 문제
  • 맥락 손실: 장기 개선 추세를 반영하지 못함
  • 과도한 민감성: 최근 변동에 과도하게 반응

데이터 소스 문제​

  • Firestore 의존: 실제 학습 데이터가 없는 컬렉션 조회
  • 분산된 데이터: 여러 Query를 개별 실행하여 성능 저하

1.2 요구사항 분석​

  1. 정확도: 장기 추세와 단기 변화를 모두 반영
  2. 적응성: 치료 경과(dayIndex)에 따른 동적 조정
  3. 효율성: 네트워크 및 쿼리 최적화
  4. 신뢰성: 데이터 희소 상황 자동 보완

2. 하이브리드 접근법 설계​

2.1 핵심 원칙​

하이브리드 = 전주기 누적(장기 추세) + 최근 가중(단기 모멘텀)

2.2 아키텍처​

3. 구현 상세​

3.1 가변 윈도우 (Variable Window)​

/**
* dayIndex와 최근 활동에 따른 동적 윈도우 크기 결정
*/
function determineWindowSize(
dayIndex: number,
recentActivity: number
): number {
// Case 1: 치료 초반 (전체가 곧 최근)
if (dayIndex < 7) {
return dayIndex;
}

// Case 2: 치료 중반 (확장 윈도우)
if (dayIndex < 14) {
return 14;
}

// Case 3: 데이터 희소 (자동 확장)
if (recentActivity < 3) {
return Math.min(14, dayIndex);
}

// Default: 표준 7일
return 7;
}

3.2 적응적 가중치 (Adaptive Weights)​

/**
* 치료 경과에 따른 누적/최근 가중치 조정
*/
function calculateWeights(dayIndex: number): WeightConfig {
if (dayIndex < 7) {
// 초반: 누적 데이터가 곧 최근 데이터
return {
cumulative: 0.8, // 80% 누적
recent: 0.2 // 20% 최근
};
}

if (dayIndex < 14) {
// 중반: 균형잡힌 가중치
return {
cumulative: 0.6, // 60% 누적
recent: 0.4 // 40% 최근
};
}

if (dayIndex > 28) {
// 장기: 최근성이 더 중요
return {
cumulative: 0.3, // 30% 누적
recent: 0.7 // 70% 최근
};
}

// 기본: 동등 가중치
return {
cumulative: 0.5,
recent: 0.5
};
}

3.3 데이터 집계 전략​

PostgreSQL 집계 Query 활용​

-- GetUserLearningCycleProgress 내부 구현
SELECT
-- 전주기 누적 메트릭
COUNT(DISTINCT DATE(created_at)) as unique_active_days,
SUM(duration) as total_learning_minutes,
COUNT(DISTINCT lesson_id) as total_lesson_completions,
COUNT(*) as total_page_events,

-- 연속 학습일 계산 (역방향 순회)
calculate_streak(user_id, current_date) as current_streak_days,

-- 시간 범위
MIN(created_at) as first_activity_at,
MAX(created_at) as last_activity_at
FROM
user_learning_page_consumption
WHERE
user_id = $1
AND user_cycle_id = $2
AND created_at BETWEEN cycle_start AND $3

4. 메트릭 계산 로직​

4.1 진도율 (Progress Rate)​

// 하이브리드 진도율 계산
const progressRate =
// 누적 진도 (장기 추세)
(
(activeDays / 30) * 0.5 + // 활동일 기준
(totalMinutes / 600) * 0.5 // 학습시간 기준
) * cumulativeWeight +

// 최근 진도 (단기 모멘텀)
(
(recentLessons / 10) * 0.5 + // 최근 레슨 완료
(recentMinutes / 180) * 0.5 // 최근 학습시간
) * recentWeight;

4.2 참여도 (Engagement Score)​

// 하이브리드 참여도 계산
const engagementScore =
// 누적 참여도
(
(currentStreak / 14) * 0.4 + // 연속 학습일
(totalLessons / 20) * 0.3 + // 총 레슨 완료
(totalEvents / 100) * 0.3 // 총 이벤트
) * cumulativeWeight +

// 최근 참여도
(
(recentLessons / 10) * 0.5 + // 최근 레슨
(recentMinutes / 180) * 0.5 // 최근 시간
) * recentWeight;

5. 성능 최적화​

5.1 쿼리 최적화​

항목기존 방식하이브리드 방식개선율
쿼리 수6개 (개별)4개 (집계+개별)33% ↓
데이터 전송량~500KB~150KB70% ↓
응답 시간~800ms~300ms62% ↓

5.2 캐싱 전략​

// 전주기 집계는 시간당 1회만 갱신
@Cacheable({
key: 'learning:cycle:summary:${userId}:${cycleId}',
ttl: 3600 // 1시간
})
async getUserLearningCycleProgress() { ... }

// 최근 데이터는 5분 캐시
@Cacheable({
key: 'learning:recent:${userId}:${window}',
ttl: 300 // 5분
})
async getRecentLearningData() { ... }

6. 실제 적용 사례​

6.1 Case 1: 치료 초반 (dayIndex = 3)​

{
"dayIndex": 3,
"windowSize": 3,
"weights": { "cumulative": 0.8, "recent": 0.2 },
"metrics": {
"cumulative": {
"days": 3,
"minutes": 45,
"lessons": 2
},
"recent": {
"days": 3,
"minutes": 45,
"lessons": 2
}
},
"result": "누적과 최근이 동일, 안정적 예측"
}

6.2 Case 2: 치료 중반 (dayIndex = 14)​

{
"dayIndex": 14,
"windowSize": 14,
"weights": { "cumulative": 0.6, "recent": 0.4 },
"metrics": {
"cumulative": {
"days": 12,
"minutes": 420,
"lessons": 18
},
"recent": {
"days": 5,
"minutes": 150,
"lessons": 7
}
},
"result": "균형잡힌 예측, 추세와 최근 변화 모두 반영"
}

6.3 Case 3: 장기 치료 (dayIndex = 35)​

{
"dayIndex": 35,
"windowSize": 7,
"weights": { "cumulative": 0.3, "recent": 0.7 },
"metrics": {
"cumulative": {
"days": 28,
"minutes": 1200,
"lessons": 52
},
"recent": {
"days": 6,
"minutes": 180,
"lessons": 8
}
},
"result": "최근 활동 중심, 현재 상태 민감 반영"
}

7. 검증 및 평가​

7.1 A/B 테스트 결과​

지표단순 7일하이브리드개선율
MAE (분)11.28.524% ↓
RMSE15.311.723% ↓
상관계수0.720.8417% ↑

7.2 Edge Case 처리​

  1. 데이터 부족 (dayIndex < 3)

    • Fallback: 적응형 기본값 사용
    • 신뢰도: 50% 이하로 표시
  2. 장기 중단 후 재시작

    • 최근 윈도우 자동 확장
    • 이전 주기 데이터 참조
  3. 이상치 처리

    • IQR 기반 필터링
    • 지수 평활화 적용

8. 구현 가이드라인​

8.1 필수 구현 사항​

class LearningDataAdapter {
async getLearningDataSummary(
userId: string,
targetDate: Date,
userCycleId: string,
systemCurrentTime: Date,
timezoneId: string
): Promise<LearningDataSummary> {
// 1. Cycle Summary 조회 (필수)
const cycleSummary = await this.getCycleSummary(
userId,
userCycleId,
targetDate,
timezoneId
);

// 2. dayIndex 계산
const dayIndex = this.calculateDayIndex(
cycleSummary.from,
cycleSummary.to
);

// 3. 가변 윈도우 결정
const windowSize = this.determineWindowSize(
dayIndex,
cycleSummary.uniqueActiveDays
);

// 4. 최근 데이터 조회
const recentData = await this.getRecentData(
userId,
userCycleId,
windowSize,
timezoneId
);

// 5. 하이브리드 메트릭 계산
return this.calculateHybridMetrics(
cycleSummary,
recentData,
dayIndex
);
}
}

8.2 모니터링 지표​

// 필수 모니터링 메트릭
interface MonitoringMetrics {
queryPerformance: {
cycleSummaryLatency: number; // < 100ms
recentDataLatency: number; // < 200ms
totalLatency: number; // < 300ms
};

dataQuality: {
cycleSummaryHitRate: number; // > 95%
recentDataCompleteness: number; // > 90%
windowSizeDistribution: Map<number, number>;
};

predictionAccuracy: {
mae: number; // < 10분
correlationCoefficient: number; // > 0.8
};
}

9. 향후 개선 방향​

9.1 단기 (1-2개월)​

  • Redis 캐싱 레이어 구현
  • 실시간 스트리밍 데이터 통합
  • 개인화 가중치 학습

9.2 중기 (3-6개월)​

  • ML 기반 가중치 최적화
  • 다차원 시계열 분석
  • 자동 이상치 보정

9.3 장기 (6개월+)​

  • 연합 학습(Federated Learning) 적용
  • 실시간 예측 모델 서빙
  • 크로스 도메인 데이터 통합

10. 참고 자료​

10.1 관련 코드​

  • libs/feature/sol-prediction-wir/src/lib/infrastructure/adapters/learning-data.adapter.ts
  • libs/feature/learning-wir/src/lib/infrastructure/repositories/prisma-learning-consumption.repository.ts
  • libs/feature/learning-wir/src/lib/application/queries/handlers/get-user-learning-cycle-progress.handler.ts

10.2 관련 문서​

10.3 학술 참조​

  • Bastien, C. H., et al. (2001). "Validation of the Insomnia Severity Index"
  • Edinger, J. D., et al. (2021). "Behavioral and psychological treatments for chronic insomnia disorder"
  • Koffel, E., et al. (2018). "Increasing access to and utilization of CBT-I"

Last Updated: 2025-01-09 Version: 1.0.0 Author: DTA Wide SOL Prediction Team