안녕하세요!
오늘은 국비 과정이 끝나면서, 개인 공부를 계속 하면서 포트폴리오 정리를 하고 있습니다. 저번에 나간 데이콘 경진 대회가 재밌어서, 이번엔 어떤 걸 하려나~ 해서 찾아봤는데 휴먼 이해?라는 제목의 논문경진대회를 진행하더라고요! 그래서 알고리즘 공부도 할겸, 도전해볼까 합니다. 그래서 오늘은 초안 작성과 공부 기록을 남겨보려고 합니다.
1. 대회 개요
제 5회 ETRI 휴먼이해 인공지능 논문경진대회 - DACON
분석시각화 대회 코드 공유 게시물은 내용 확인 후 좋아요(투표) 가능합니다.
dacon.io
[배경]
5회를 맞이한 ETRI 휴먼이해 인공지능 논문경진대회는 한 걸음 더 나아갑니다.
일상 속에서 쌓이는 멀티모달 라이프로그 데이터에는 사람의 행동과 감정, 그리고 그 이면의 맥락이 고스란히 담겨 있으며,
지난 휴먼이해 논문경진대회를 통해 해당 데이터가 개인화 서비스, 감성 컴퓨팅, 인간-AI 상호작용 등 폭넓은 영역에서 실질적인 연구 성과로 이어질 수 있음을 확인했습니다.
이번 대회는 그간 축적된 연구 경험과 커뮤니티의 역량을 바탕으로, 사람을 더 깊이 이해하고 교감하는 AI 기술의 새로운 가능성을 함께 탐색하고자 합니다. 참신한 시각과 도전적인 연구로 인간 이해 AI의 다음 장을 열어 주세요.
[주제]
라이프로그 데이터를 활용한 수면, 감정, 스트레스 인식 및 추론
[설명]
라이프로그 데이터를 이용한 설문지표(취침 후 수면의 질(Q1), 취침 전 피로도(Q2) 및 스트레스(Q3))와 수면지표(총 수면시간(S1), 수면효율(S2), 수면 지연시간(S3), 수면 중 각성 시간(S4))의 예측
2. 대회 이해하기 및 계획 하기
일단 전 시작하기 전에 무조건 코드 공유를 먼저 본답니다. 이해도 못했는데 무작정 "AI한테 만들어줘!"하면 안 되겠죠?
데이터 구조
[배포용 데이터 구조]
data/
├── ch2025_data_items/
│ ├── ch2025_mACStatus.parquet
│ ├── ch2025_mActivity.parquet
│ ├── ch2025_mAmbience.parquet
│ ├── ch2025_mBle.parquet
│ ├── ch2025_mGps.parquet
│ ├── ch2025_mLight.parquet
│ ├── ch2025_mScreenStatus.parquet
│ ├── ch2025_mUsageStats.parquet
│ ├── ch2025_mWifi.parquet
│ ├── ch2025_wHr.parquet
│ ├── ch2025_wLight.parquet
│ └── ch2025_wPedo.parquet
│
├── ch2026_metrics_description.pdf
├── ch2026_metrics_train.csv
└── ch2026_submission_sample.csv
본 경진대회에서는 라이프로그 데이터(총 12개 항목 700일분)와 레이블(7개 지표 450일분)을 제공합니다.
- Data items (for 700 days): ch2025_data_items.zip (122.0MB)
- https://epretx.etri.re.kr/dataDetail?lang=ko&id=459
※ 본 데이터는 제공된 ch2025_data_items 폴더와 동일합니다.
- ch2026_metrics_train.csv : Seven metrics (for 450 days)
- ch2026_metrics_description.pdf : Seven metrics description
- ch2026_submission_sample.csv : sample submission
공유된 0.59 코드를 공유해주신 분이 있습니다. 블로그 길이상 내용은 첨부하지 않겠습니다. 그 분도 초안이지만 일단 분석을 해봤습니다.
공유 코드는 실제로는 골격정도 였습니다. parquet 센서 데이터를 실제로 연동하지 않고(윈도우 쌍 피처가 pass placeholder), "V152 앵커 OOF"라는 외부 산출물에 의존해서 그대로는 재현이 안 됩니다. 즉 0.59 코드는 참고용 설계도일 뿐이였습니다.
계획/데이터 확인
목표: 7개 이진 타깃(Q1Q3 설문, S1S4 수면지표)의 확률 예측, 평가 = 7개 타깃 평균 Log-Loss -> 낮을 수록 좋은 거임.
하루(한 사람의 하루)마다 7개 항목(Q1·Q2·Q3·S1·S2·S3·S4)을 0 또는 1로 분류하는 문제다.
단, 제출하는 건 0/1이 아니라 "그 항목이 1일 확률"(0~1 사이 숫자) 이다.
예: Q1이 1일 것 같으면 0.8, 잘 모르겠으면 0.5, 아닐 것 같으면 0.2.
데이터 구조 파악하기
라벨: train 450일, test 250일. 피험자는 id01~id10 단 10명, 그리고 train/test가 동일한 10명 + 기간도 겹침(2024-06~11).
즉 "처음 보는 사람"이 아니라 같은 사람의 다른 날을 맞추는 문제다.
센서 12종 parquet (700일분, ~122MB). 단순 스칼라(심박·조도·걸음·충전·화면·활동)와 중첩 구조(앰비언트 오디오 라벨, BLE/WiFi 주변기기, GPS, 앱사용)로 나뉨. GPS 위경도는 마스킹됨.
타깃 정의가 중요: Q1~Q3는 "그 사람 본인 평균 대비 그날이 위/아래냐" (5점 척도→이진). 그래서 피험자별로 대략 50:50이고, 개인 기준선 대비 그날의 편차가 핵심 신호. S1~S4는 NSF 수면 가이드라인 충족 여부(객관적).
이 대회는 "테이블러 + 피처엔지니어링" 문제이다.
- 학습 자체는 LightGBM/XGBoost/CatBoost로 450행 -> GPU 불필요하다.
- 수백만 행짜리 센서 시계열을 (피험자, 날짜) 단위 일일 피처로 집계하는 전처리.
- 딥러닝 문제가 아니라 피처 설계와 검증(CV) 설계가 중요한 문제다.
- 라벨: data/ch2026_metrics_train.csv (450일) / 제출 양식 data/ch2026_submission_sample.csv (250일).
- 피험자는 id01~id10 10명이며, train과 test에 동일한 10명이 등장하고 기간(2024-06~11)도 겹친다(인터리빙). 즉 "처음 보는 사람"이 아니라 "같은 사람의 다른 날"을 예측하는 문제다.
- 센서 12종(700일분, data/ch2025_data_items/*.parquet):
- 스칼라: mACStatus(충전), mActivity(활동코드), mLight/wLight(조도), mScreenStatus(화면사용), wPedo(걸음·거리·속도·칼로리), wHr(심박 배열).
- 중첩: mAmbience(오디오 장면 확률), mUsageStats(앱 사용시간), mWifi/mBle(주변 AP·기기), mGps(속도·고도; 위경도는 마스킹).
1. CV 재정의 (가장 큰 레버) — subject-out이 아니라 피험자 내부의 시간 기반 분할. 같은 사람의 미래/다른 날을 맞추는 게 진짜 문제.
2. 피험자별 정규화 — 각 피처를 그 사람 평균/분산 대비로 표준화 (Q1~Q3 정의와 정확히 일치).
3. 수면센서 피처 — 야간 심박/움직임/충전·화면 OFF 구간 → 총수면·효율·입면지연·각성(S1~S4) 직접 신호.
4. 확률 캘리브레이션 — log-loss는 잘 보정된 확률에 보상. 타깃별 보정 + 클리핑.
3. 뼈대 성능과 분석
각 행은 한 명의 피험자가 보낸 하루(`subject_id`, `sleep_date`, `lifelog_date`)이며, 그 하루에 대해 7개 이진 지표의 확률을 예측한다.
| 지표 | 의미 | | 1의 뜻 |
| Q1 | 기상 직후 주관적 수면의 질 | 개인 평균보다 좋음 |
| Q2 | 취침 직전 피로도 | 피로 낮음(좋음) |
| Q3 | 취침 직전 스트레스 | 스트레스 낮음(좋음) |
| S1 | 총수면시간(TST) 가이드라인 | 권장 충족 |
| S2 | 수면효율(SE) 가이드라인 | 권장 충족 |
| S3 | 입면지연(SOL) 가이드라인 | 권장 충족 |
| S4 | 수면 중 각성(WASO) 가이드라인 | 권장 충족 |
- Q1~Q3 (설문): 5점 척도 응답을 그 피험자 본인의 전체 기간 평균과 비교해 이진화한 값이다. 따라서 "그날이 그 사람 평소보다 좋았는가/나빴는가"가 본질이며, 피험자별 기준선이 매우 중요하다.
- S1~S4 (수면센서): Withings 수면 분석기 기반 지표가 NSF 가이드라인을 충족하는지 여부이다.
평가 산식
7개 지표 각각의 Log-Loss를 계산한 뒤 평균한다(낮을수록 좋음)
Score = (1/7) Σ_j [ -(1/N) Σ_i ( y_ij·log p_ij + (1-y_ij)·log(1-p_ij) ) ]
Log-Loss는 정답에 가까운 확률을 얼마나 자신 있게 맞혔는지를 평가한다.
정답을 맞히되 과도한 확신으로 틀리면 큰 벌점을 받으므로, 잘 보정된 확률을 내는 것이 핵심이다.
Public 점수는 테스트의 44% 표본, Private 점수는 100%로 산정된다.
3-1 핵심 통찰
피험자 기준선이 지배적 신호다.
- 타깃이 개인 평균 대비로 정의되므로, 피험자별 타깃 평균(prior)만으로도 강한 베이스라인이 된다.
- 모델은 이 기준선에서 출발해 센서 신호로 그날의 편차를 보정해야 한다.
- 본 코드는 누수 없는 OOF prior를 피처로 주입하고, 모델 예측과 prior를 타깃별 최적 가중으로 블렌드한다.
검증은 subject-out이 아니라 subject-stratified로 한다.
- 테스트에 같은 피험자가 있으므로, 각 피험자의 날들을 폴드에 분산시켜 "같은 사람의 다른 날"을 모사한다.
3-2 피처
- 일일 윈도우 통계(sensor_features.py): 각 센서를 하루의 시각 윈도우 (full/day/eve/night/morn)별 mean·std·min·max·sum·count로 요약.
- 수면구간 탐지(sleep_features.py): 화면 OFF·충전·정지·저심박·무걸음을 결합해 야간 수면 블록을 추정 → 입면시각·기상시각·총수면시간·수면효율·각성 프록시(S1~S4 직격).
- 중첩 센서(nested_features.py): 오디오 장면 확률(조용함/대화/음악), 앱 사용시간·앱 개수, 주변 WiFi/BLE 수, GPS 속도/고도.
- 피험자 z-score: 각 연속 피처를 피험자별 평균/표준편차로 표준화(개인 대비 편차).
- 캘린더: 요일·주말·월·순환(sin/cos) 특성.
3-3 모델
- 타깃별 LightGBM(이진), 작은 데이터에 맞춘 강한 정규화(num_leaves=15, min_child_samples=25, feature_fraction=0.6, L1/L2), 폴드 검증 Log-Loss로 early stopping.
- 폴드 시드 3종 × 5-fold 멀티시드 배깅으로 OOF·테스트 예측을 안정화.
3-4 코드 구조
src/
config.py # 경로(로컬/Colab 자동해석)·타깃·센서·윈도우 정의
sensor_features.py # parquet → (subject,date)×윈도우 통계 → cache/daily_features.parquet
sleep_features.py # 야간 수면구간 탐지 → cache/sleep_features.parquet
nested_features.py # 중첩 센서(오디오/앱/WiFi/BLE/GPS) → cache/nested_features.parquet
build_dataset.py # 라벨 행에 피처 결합 + z-score + 캘린더
cv.py # subject-stratified KFold
train.py # 정식 파이프라인(멀티시드 배깅+블렌드) → submissions/*.csv
train_v2.py # 단일 시드 실험본
train_baseline.py # 최소 베이스라인(prior 없음)
3-5 결과(검증 OOF 평균 Log-Loss)
| 단계 | OOF | 비고 |
| 단순 LGBM | 0.6095 | prior·수면·중첩 미사용 |
| + 피험자 prior 주입·블렌드 | 0.6008 | |
| + 수면구간 탐지 피처 | 0.5958 | S1·Q1 개선 |
| + 중첩 센서 피처 | 0.5887 | Q1 큰 개선 |
| + 멀티시드 배깅 | 0.5817 | 현재 |
참고 기준선: 누수 없는 피험자 prior 단독 ≈ 0.615.
4. 실제 제출 성능과 문제점 해결
일단 위 구조로 테스트를 해봤더니, 0.60782의 성능이 나왔습니다. 음...테스트 데이터를 가지고 성능을 확인했는데, 0.02의 차이는 꽤 크다고 생각했습니다. 그래서 검증 과정을 먼저 손봐야 될 거 같습니다.
두 가지 문제 발견:
- 검증(CV)이 낙관적이었다 (핵심): test 데이터는 각 피험자의 나중 날짜 블록에 몰려 있어 사실상 "과거로 미래를 예측"하는 문제인데, 우리가 날짜를 무작위로 섞어 검증해서 인접일이 새어들어갔습니다 → OOF 0.58은 가짜, 실제 LB 0.608.
- S2·S3·S4에선 모델이 prior보다 나쁜데, 낙관적 CV로 고른 블렌드 가중이 모델을 과신했습니다.
4-1 개선 사항 구현
아래는 이번 검증 기준을 다시 잡고, 5가지를 추가해 봤습니다.
- 3모델 앙상블(LGBM+XGB+CatBoost)
- isotonic 캘리브레이션(정직검증 통과시만)
- 약한 타깃 피처(수면 규칙성·HR 동역학·취침 전 부하)
- 수면구간 피처
- 시간 동역학(lag/rolling) 피처 — 수면빚·생활리듬
결과는 0.004...?의 일반화 성능이 내려가긴 했습니다. 일단 중요한 건 판정 기준을 잘 맞췄다는 것입니다.
last=0.6035가 실제 LB 0.6034와 정확히 일치했습니다.
그래서, 이젠 빠르게 반복 실험할 도구를 구현하고 이제 미세조정을 하면서 수정과 연구를 진행하면 될 거 같아요!!
뼈대가 잡혔으니, config로 학습 설정을 빠르게 조정하기 위해 파일을 나눠보고, 앙상블은 제출 하기 전에만 적용하면 될 거 같습니다.
config.py
from __future__ import annotations
from pathlib import Path
def resolve_project_root() -> Path:
here = Path(__file__).resolve()
for cand in [Path.cwd().resolve(), *here.parents]:
if (cand / "data").is_dir():
return cand
return Path.cwd().resolve()
PROJECT_ROOT = resolve_project_root()
DATA_DIR = PROJECT_ROOT / "data"
SENSOR_DIR = DATA_DIR / "ch2025_data_items"
CACHE_DIR = PROJECT_ROOT / "cache"
SUBMISSION_DIR = PROJECT_ROOT / "submissions"
CACHE_DIR.mkdir(exist_ok=True)
SUBMISSION_DIR.mkdir(exist_ok=True)
TRAIN_CSV = DATA_DIR / "ch2026_metrics_train.csv"
SAMPLE_CSV = DATA_DIR / "ch2026_submission_sample.csv"
TARGET_COLS = ["Q1", "Q2", "Q3", "S1", "S2", "S3", "S4"]
ID_COLS = ["subject_id", "sleep_date", "lifelog_date"]
EPS = 1e-15
PRIOR_SMOOTH = 8.0 # 피험자 prior 스무딩 강도(↑=전역평균 쪽으로 더 수축). 후보: 4/8/16/32
PROB_CLIP = 1e-6 # 확률 클리핑 하한(0/1 단정 방지). 후보: 1e-6 / 1e-3 / 1e-2
NUMERIC_SENSORS = {
"mACStatus": ["m_charging"],
"mLight": ["m_light"],
"wLight": ["w_light"],
"mScreenStatus": ["m_screen_use"],
"wPedo": [
"step", "step_frequency", "running_step", "walking_step",
"distance", "speed", "burned_calories",
],
}
WINDOWS = {
"full": (0, 24),
"day": (9, 18),
"eve": (18, 24),
"night": (0, 6),
"morn": (6, 9),
}
# Google Activity Recognition 코드
# 0 IN_VEHICLE, 1 ON_BICYCLE, 2 ON_FOOT, 3 STILL, 4 UNKNOWN, 5 TILTING, 7 WALKING, 8 RUNNING
ACTIVITY_STILL = {3}
ACTIVITY_MOVE = {2, 7, 8, 1}
ACTIVITY_VEHICLE = {0}
def sensor_path(name: str) -> Path:
return SENSOR_DIR / f"ch2025_{name}.parquet"
아직 시간이 많으니, 가설 계획을 정확히 세우고 진행하면 될 거 같습니다! 또 막상하니 욕심이 생기네요...😅
실험 레포지토리: https://github.com/gonida1010/dacon-etri-human-ai
GitHub - gonida1010/dacon-etri-human-ai: DACON - 라이프로그 데이터를 활용한 멀티모달 연구
DACON - 라이프로그 데이터를 활용한 멀티모달 연구. Contribute to gonida1010/dacon-etri-human-ai development by creating an account on GitHub.
github.com
'5. [개인] 프로젝트 및 공모전 > 4-4 공모전' 카테고리의 다른 글
| [개인 공모전] DACON - ETRI 휴먼이해 인공지능 논문경진대회 (2): 그래프 분석 시작 (0) | 2026.06.01 |
|---|---|
| [KDT 공모전] 멀티 에이전트 카카오톡 챗봇 플랫폼: 트러블 슈팅 (2) (0) | 2026.04.11 |
| [KDT 공모전] 멀티 에이전트 카카오톡 챗봇 플랫폼: Edu-Sync AI (1) (0) | 2026.04.10 |
