행동 분류 추론 파이프라인 — E2E 3.3배
바딧 · 2026.02 · 재작업 전담 · 프로덕션 운영 중
- Python
- NumPy
- LightGBM
- MLflow
6축 IMU raw 신호에서 903개 특징을 뽑아 행동을 분류하는 프로덕션 코어 패키지를 재작업해 E2E 처리 시간을 3.3배 줄였다.
- IMU raw6축 25Hz
- 윈도잉24h → 8,640
- 특징 추출 903개병목이던 구간
최대 병목(cough 특징, 전체의 48.6%) 벡터화 9.8× · 외부 라이브러리 → NumPy 재구현
- 분류LightGBM
- 모델 로딩MLflow 레지스트리
로컬 파일 폴백 제거 — 서빙 중인 모델을 추적 가능하게
입력 검증 게이트를 앞단에 추가했고, 동작을 바꾸지 않는 범위에서 중복 코드를 정리했다.
같은 시간, 같은 입력
win/sec · E2E · 실개체 24h · 8,640 윈도우 · 3.3×
문제
센서에서 올라온 6축 IMU raw 신호를 윈도우로 자르고 903개 특징을 추출해 행동을 분류하는 코어 패키지가 있었다. 이게 파이프라인 전체의 병목이었다. 실개체 하루치를 처리하는 데 E2E 65.8초가 걸렸고, 개체 수가 늘수록 재학습·재처리 주기가 그만큼 밀렸다.
문제는 이걸 건드리면 모델 출력이 바뀔 수 있다는 점이었다. 특징 하나의 수치가 미세하게 달라져도 학습된 분류기의 판정이 흔들린다. 속도를 얻자고 결과를 몰래 바꿀 수는 없었다.
한 일
병목의 정체부터 측정했다. 기침 관련 특징 세트 하나가 파이프라인 시간의 48.6%(43.9초)를 먹고 있었다. 피크 주변 윈도우 약 1만 개를 per-row 파이썬 루프로 돌리고 있었다.
- 그 특징 세트를 선별적으로 벡터화했다. 63개 중 61개를 NumPy 배치 연산으로 바꾸고 2개(엔트로피 계열)는 per-row로 남겼다 — 전부 바꾸는 게 목표가 아니라 이득이 있는 곳만 바꾸는 게 목표였다. 이 구간만 43.9초 → 4.5초(9.8×).
- 외부 라이브러리(tsfresh) 호출 6개를 NumPy 재구현으로 대체했다. 해당 구간이 37% 빨라졌고 의존성 하나가 빠졌다.
- 교체할 때마다 새 배치 함수와 기존 per-row 함수를 수치로 대조했다(오차 1e-6 이내, 단위 테스트 36개).
- 모델 로딩을 로컬 파일 폴백 구조에서 레지스트리 단일 경로로 정리했다. 어떤 모델이 실제로 서빙되고 있는지 추적 가능해졌다.
- 입력 검증 게이트를 추가하고, 동작을 바꾸지 않는 범위에서 코드 중복을 제거했다.
결과
속도를 얻자고 결과를 몰래 바꿀 수는 없었다. 교체마다 수치를 대조하고, 달라진 값은 숨기지 않고 기록했다.
실개체 24시간 raw 216만 행 / 8,640 윈도우 기준. 전체 테스트 208 passed / 0 failed. 특징 값은 모두 오차 1e-6 이내로 일치했지만, 그 미세한 차이가 경계값에 걸려 기침 통계 하나가 53 → 40으로 바뀌었다. 이건 숨기지 않고 변경 기록에 남겼다.
측정 조건
- 대상
- 실개체 24시간 IMU raw 신호 (6축 25Hz)
- 표본
- 216만 행 → 8,640 윈도우
- 비교 기준
- 개선 전 동일 입력 · E2E 65.8초 (131 win/sec)
- 검증
- 배치 함수 vs 기존 per-row 함수 수치 대조(atol 1e-6) · 전체 테스트 208 passed / 0 failed
모든 수치는 실제 호출 경로 그대로 잰 E2E(65.8 → 19.9초) 기준이다. 단계별 실행 시간을 합산하면 96 → 423 win/sec(4.4×)로 더 크게 나오지만, 파이프라인 밖 단계가 섞여 있어 대표 수치로 쓰지 않는다.