본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 1. · 17 Views
MLOps End-to-End 프로젝트 - 전체 파이프라인 구축
머신러닝 모델을 프로덕션 환경에 배포하고 운영하기 위한 전체 MLOps 파이프라인을 구축합니다. 데이터 수집부터 모델 배포, 모니터링까지 실무에서 필요한 모든 과정을 다룹니다.
목차
- 데이터_수집_자동화
- DVC로_데이터_버전_관리
- MLflow로_실험_추적
- 모델_학습_파이프라인
- 모델_배포_자동화
- 실시간_모니터링_구축
- 드리프트_감지_및_재학습
- CICD_통합_및_프로덕션_운영
1. 데이터 수집 자동화
김개발 씨는 머신러닝 팀에 배치된 지 한 달이 되었습니다. 매일 아침 수동으로 데이터를 다운로드하고 정리하는 작업이 반복되자, 선배 박시니어 씨가 말했습니다.
"이 작업, 자동화하면 하루에 2시간은 아낄 수 있어요."
데이터 수집 자동화는 원천 데이터를 정해진 주기에 따라 자동으로 가져오고 저장하는 시스템입니다. 마치 신문 배달부가 매일 아침 정해진 시간에 신문을 배달해주는 것처럼, 데이터도 자동으로 수집되어야 합니다.
이를 통해 휴먼 에러를 줄이고 일관된 데이터 품질을 유지할 수 있습니다.
다음 코드를 살펴봅시다.
# data_collector.py - 데이터 수집 자동화 파이프라인
import schedule
import pandas as pd
from datetime import datetime
import boto3
class DataCollector:
def __init__(self, source_config):
self.config = source_config
self.s3_client = boto3.client('s3')
def fetch_data(self):
# API에서 데이터를 가져옵니다
raw_data = self._call_api(self.config['api_endpoint'])
# 데이터 검증 및 정제
validated_data = self._validate(raw_data)
# S3에 저장
self._save_to_s3(validated_data)
print(f"[{datetime.now()}] 데이터 수집 완료: {len(validated_data)}건")
# 매일 오전 6시에 자동 실행
schedule.every().day.at("06:00").do(collector.fetch_data)
김개발 씨는 입사 첫 달, 매일 아침 같은 일을 반복했습니다. 데이터베이스에 접속하고, SQL 쿼리를 실행하고, 결과를 CSV로 저장하고, 그것을 다시 정해진 폴더에 업로드하는 일이었습니다.
어느 날은 실수로 어제 데이터를 덮어쓰기도 했고, 또 어느 날은 휴가 중이라 아무도 데이터를 수집하지 못한 적도 있었습니다. 박시니어 씨가 김개발 씨의 책상으로 다가왔습니다.
"매일 똑같은 일을 반복하고 있네요. 이거, 컴퓨터가 더 잘할 수 있는 일이에요." 그렇다면 데이터 수집 자동화란 정확히 무엇일까요?
쉽게 비유하자면, 데이터 수집 자동화는 마치 아파트 경비실에서 택배를 대신 받아주는 것과 같습니다. 우리가 집에 없어도 택배는 안전하게 보관되고, 우리는 필요할 때 가져가기만 하면 됩니다.
마찬가지로 데이터 수집 자동화 시스템은 우리가 자고 있는 새벽에도 묵묵히 데이터를 수집하고 정리해둡니다. 자동화가 없던 시절에는 어땠을까요?
담당자가 출장을 가면 데이터가 비어버렸습니다. 실수로 잘못된 날짜의 데이터를 가져오기도 했습니다.
더 큰 문제는 이런 실수를 나중에야 발견한다는 것이었습니다. 모델 학습이 끝난 뒤에야 "어?
왜 성능이 이상하지?" 하고 들여다보면, 수집 단계에서 문제가 있었던 것입니다. 바로 이런 문제를 해결하기 위해 데이터 수집 자동화가 필요합니다.
코드를 한 번 작성해두면, 시스템이 정해진 시간에 자동으로 데이터를 가져옵니다. schedule 라이브러리를 사용하면 파이썬에서 쉽게 스케줄링을 구현할 수 있습니다.
위 코드의 핵심은 DataCollector 클래스입니다. 이 클래스는 세 가지 주요 역할을 수행합니다.
첫째, API나 데이터베이스에서 원시 데이터를 가져옵니다. 둘째, 가져온 데이터가 올바른지 검증합니다.
셋째, 검증된 데이터를 S3 같은 저장소에 안전하게 보관합니다. 실제 현업에서는 이보다 더 복잡한 요구사항이 있습니다.
데이터 소스가 여러 개일 수도 있고, 수집 실패 시 재시도 로직이 필요할 수도 있습니다. 하지만 기본 구조는 동일합니다.
정해진 시간에, 정해진 소스에서, 정해진 방식으로 데이터를 가져오는 것입니다. 주의할 점도 있습니다.
자동화 시스템은 한 번 설정하면 잊어버리기 쉽습니다. 하지만 API가 변경되거나 데이터 형식이 바뀌면 수집이 실패할 수 있습니다.
따라서 반드시 알림 시스템을 함께 구축해야 합니다. 수집 실패 시 Slack이나 이메일로 알림을 받을 수 있도록 말입니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 자동화 시스템을 구축한 뒤, 김개발 씨는 매일 아침 2시간을 절약하게 되었습니다.
그 시간에 더 중요한 일, 바로 모델 개선에 집중할 수 있게 된 것입니다.
실전 팁
💡 - 수집 실패 시 재시도 로직과 알림 시스템을 반드시 구축하세요
- 수집된 데이터의 row 수나 용량을 로깅하여 이상 징후를 조기에 발견하세요
2. DVC로 데이터 버전 관리
"이 모델, 지난주에 학습시킨 건데 그때 데이터가 뭐였지?" 김개발 씨가 머리를 긁적였습니다. 분명히 좋은 성능을 냈던 모델인데, 어떤 데이터로 학습했는지 기억이 나지 않았습니다.
박시니어 씨가 한숨을 쉬며 말했습니다. "그래서 DVC가 필요한 거예요."
**DVC(Data Version Control)**는 Git으로 코드를 관리하듯, 데이터와 모델 파일을 버전 관리하는 도구입니다. 마치 타임머신처럼 과거 어느 시점의 데이터로도 돌아갈 수 있게 해줍니다.
대용량 파일을 직접 Git에 올리지 않고, 메타 정보만 관리하여 효율적으로 버전을 추적합니다.
다음 코드를 살펴봅시다.
# DVC 초기화 및 데이터 버전 관리
# 터미널에서 실행
dvc init
dvc remote add -d storage s3://my-bucket/dvc-storage
# 데이터 파일을 DVC로 추적
dvc add data/training_data.csv
git add data/training_data.csv.dvc data/.gitignore
git commit -m "Add training data v1.0"
# 데이터 업데이트 후 새 버전 저장
dvc add data/training_data.csv
git add data/training_data.csv.dvc
git commit -m "Update training data v1.1 - added 10000 new samples"
dvc push
# 이전 버전으로 롤백
git checkout HEAD~1 -- data/training_data.csv.dvc
dvc checkout
김개발 씨는 곤란한 상황에 빠졌습니다. 지난주에 학습시킨 모델이 현재 운영 중인 모델보다 성능이 좋았는데, 그때 사용한 데이터가 무엇이었는지 알 수 없었습니다.
data_v2_final_진짜최종.csv 같은 파일명만이 폴더에 남아 있을 뿐이었습니다. 박시니어 씨가 화면을 보더니 고개를 저었습니다.
"코드는 Git으로 관리하면서, 데이터는 왜 그렇게 관리하고 있어요? 코드와 데이터는 항상 함께 움직여야 해요." 그렇다면 DVC란 정확히 무엇일까요?
쉽게 비유하자면, DVC는 마치 도서관의 서가 번호 시스템과 같습니다. 도서관에서는 책 자체를 옮기지 않고도, 서가 번호만 알면 어떤 책이든 찾을 수 있습니다.
DVC도 마찬가지입니다. 실제 데이터 파일은 S3 같은 저장소에 보관하고, Git에는 그 데이터를 찾아갈 수 있는 메타 정보만 저장합니다.
기존에는 어떤 문제가 있었을까요? 대용량 데이터 파일을 Git에 직접 올리면 저장소가 금방 터져버립니다.
그렇다고 데이터를 Git 밖에 두면 코드와 데이터의 연결고리가 끊어집니다. "이 코드가 어떤 데이터로 학습되었는지" 추적할 방법이 없어지는 것입니다.
DVC는 이 문제를 우아하게 해결합니다. dvc add 명령어를 실행하면, 데이터 파일 대신 .dvc 확장자를 가진 작은 메타 파일이 생성됩니다.
이 메타 파일에는 원본 데이터의 해시값과 저장 위치 정보가 담겨 있습니다. 위 코드를 단계별로 살펴보겠습니다.
먼저 dvc init으로 DVC를 초기화합니다. 그 다음 dvc remote add로 실제 데이터가 저장될 원격 저장소를 설정합니다.
S3, GCS, Azure Blob 등 다양한 클라우드 스토리지를 지원합니다. dvc add 명령어가 핵심입니다.
이 명령어를 실행하면 데이터 파일이 DVC 캐시로 이동하고, 대신 .dvc 파일이 생성됩니다. 이 .dvc 파일을 Git에 커밋하면, 코드와 데이터가 함께 버전 관리됩니다.
실무에서 가장 빛나는 순간은 롤백이 필요할 때입니다. git checkout으로 이전 커밋의 .dvc 파일을 가져오고, dvc checkout을 실행하면 해당 시점의 데이터가 복원됩니다.
마치 타임머신을 타고 과거로 돌아가는 것처럼요. 주의할 점이 있습니다.
DVC는 데이터 파일 자체가 아니라 메타 정보를 추적합니다. 따라서 dvc push를 잊으면 다른 팀원이 데이터를 받을 수 없습니다.
git push 할 때 항상 dvc push도 함께 하는 습관을 들이세요. 다시 김개발 씨의 이야기로 돌아가 봅시다.
DVC를 도입한 뒤, 김개발 씨는 어떤 커밋에서든 그 시점의 데이터를 정확히 복원할 수 있게 되었습니다. "v1.0.0 태그로 가서 dvc checkout 하면 돼요"라고 자신 있게 말할 수 있게 된 것입니다.
실전 팁
💡 - git commit 후에는 반드시 dvc push도 함께 실행하세요
- .dvc 파일과 dvc.lock 파일은 반드시 Git에 커밋해야 합니다
3. MLflow로 실험 추적
"learning_rate 0.01로 해봤고, 0.001로도 해봤고... 근데 어느 게 더 좋았더라?" 김개발 씨가 수십 개의 터미널 창 사이에서 헤매고 있었습니다.
박시니어 씨가 다가와 물었습니다. "실험 기록은 어디에 하고 있어요?" 김개발 씨는 멋쩍게 웃으며 답했습니다.
"음... 머릿속이요."
MLflow는 머신러닝 실험의 하이퍼파라미터, 메트릭, 모델 아티팩트를 체계적으로 추적하고 관리하는 플랫폼입니다. 마치 과학자가 실험 노트에 모든 것을 기록하듯, MLflow는 모든 실험 과정을 자동으로 기록해줍니다.
나중에 최고 성능의 모델이 어떤 설정으로 학습되었는지 쉽게 찾아볼 수 있습니다.
다음 코드를 살펴봅시다.
# experiment_tracker.py - MLflow 실험 추적
import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score
# MLflow 실험 설정
mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("fraud-detection-model")
with mlflow.start_run(run_name="rf_baseline"):
# 하이퍼파라미터 로깅
params = {"n_estimators": 100, "max_depth": 10, "random_state": 42}
mlflow.log_params(params)
# 모델 학습
model = RandomForestClassifier(**params)
model.fit(X_train, y_train)
# 메트릭 로깅
predictions = model.predict(X_test)
mlflow.log_metric("accuracy", accuracy_score(y_test, predictions))
mlflow.log_metric("f1_score", f1_score(y_test, predictions))
# 모델 저장
mlflow.sklearn.log_model(model, "model")
김개발 씨는 지난 일주일간 수십 번의 실험을 진행했습니다. learning_rate를 바꿔보고, layer 수를 늘려보고, dropout 비율을 조정해보고...
그런데 문제가 생겼습니다. 분명히 어제 F1 score가 0.95까지 나왔던 것 같은데, 오늘 다시 해보니 0.90밖에 안 나옵니다.
어제 어떤 설정을 썼는지 기억이 나지 않습니다. 박시니어 씨가 김개발 씨의 화면을 보더니 한숨을 쉬었습니다.
"머신러닝에서 재현성은 생명이에요. 같은 결과를 다시 낼 수 없다면, 그건 실험이 아니라 요행이에요." 그렇다면 MLflow란 정확히 무엇일까요?
쉽게 비유하자면, MLflow는 마치 요리사의 레시피 북과 같습니다. 훌륭한 요리사는 새로운 요리를 만들 때마다 재료의 양, 조리 시간, 불의 세기를 꼼꼼히 기록합니다.
그래야 나중에 그 맛있는 요리를 다시 만들 수 있기 때문입니다. MLflow도 마찬가지로 모든 실험의 "레시피"를 기록해둡니다.
MLflow 없이 실험을 관리하려면 어떻게 해야 할까요? 엑셀 시트에 하이퍼파라미터를 적어두거나, 노션에 실험 결과를 기록해야 합니다.
하지만 사람이 직접 기록하면 빼먹기도 하고, 실수하기도 합니다. 무엇보다 모델 파일과 기록이 따로 관리되어 연결이 끊어지기 쉽습니다.
MLflow는 이 모든 것을 자동화합니다. **mlflow.log_params()**로 하이퍼파라미터를, **mlflow.log_metric()**으로 성능 지표를, **mlflow.log_model()**로 학습된 모델 자체를 저장합니다.
이 모든 것이 하나의 "run" 아래 묶여서 관리됩니다. 위 코드의 핵심은 with mlflow.start_run() 블록입니다.
이 블록 안에서 일어나는 모든 로깅은 하나의 실험 run으로 기록됩니다. run_name을 지정하면 나중에 찾기 쉽습니다.
예를 들어 "rf_baseline", "rf_tuned_v2" 같은 이름을 붙일 수 있습니다. MLflow UI에서 실험 결과를 시각적으로 비교할 수 있다는 것도 큰 장점입니다.
여러 run의 메트릭을 차트로 비교하고, 최고 성능의 모델을 한눈에 찾을 수 있습니다. 하이퍼파라미터와 성능 간의 관계도 쉽게 파악할 수 있습니다.
실무에서는 팀 전체가 하나의 MLflow 서버를 공유합니다. 다른 팀원의 실험 결과를 참고하고, 이미 시도된 설정을 다시 시도하는 낭비를 줄일 수 있습니다.
누군가 좋은 결과를 냈다면, 그 run을 그대로 가져와서 이어서 실험할 수도 있습니다. 주의할 점도 있습니다.
MLflow는 기본적으로 로컬 파일시스템에 데이터를 저장합니다. 팀 협업을 위해서는 반드시 중앙 서버를 구축해야 합니다.
또한 모델 아티팩트가 커지면 저장소 용량 관리에도 신경 써야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
MLflow를 도입한 뒤, 김개발 씨는 자신 있게 말할 수 있게 되었습니다. "어제 최고 성능 낸 모델이요?
run_id abc123이고, 여기서 바로 배포할 수 있어요."
실전 팁
💡 - 실험 이름은 프로젝트별로, run 이름은 실험 목적별로 명확하게 짓세요
- autolog 기능을 활용하면 프레임워크별로 자동 로깅이 가능합니다
4. 모델 학습 파이프라인
"김개발 씨, 이 모델 다시 학습시켜줄 수 있어요?" 매니저의 요청에 김개발 씨는 고개를 끄덕였습니다. 하지만 속으로는 걱정이 앞섰습니다.
데이터 전처리부터 학습, 평가까지... 수동으로 하려면 반나절은 걸릴 것입니다.
그때 박시니어 씨가 말했습니다. "파이프라인 만들어뒀잖아요.
버튼 하나로 끝나요."
모델 학습 파이프라인은 데이터 로드부터 전처리, 학습, 평가, 저장까지 전체 과정을 자동화된 워크플로우로 구성한 것입니다. 마치 공장의 조립 라인처럼 각 단계가 순차적으로 실행되며, 한 번 구축해두면 반복 실행이 쉬워집니다.
재현 가능하고 일관된 모델 학습이 가능해집니다.
다음 코드를 살펴봅시다.
# training_pipeline.py - 모델 학습 파이프라인
from prefect import flow, task
import pandas as pd
from sklearn.model_selection import train_test_split
import mlflow
@task
def load_data(path: str) -> pd.DataFrame:
"""데이터 로드 태스크"""
return pd.read_parquet(path)
@task
def preprocess(df: pd.DataFrame) -> tuple:
"""전처리 태스크 - 결측치 처리, 인코딩"""
df = df.dropna()
X = df.drop('target', axis=1)
y = df['target']
return train_test_split(X, y, test_size=0.2, random_state=42)
@task
def train_model(X_train, y_train, params: dict):
"""모델 학습 태스크"""
from sklearn.ensemble import GradientBoostingClassifier
model = GradientBoostingClassifier(**params)
model.fit(X_train, y_train)
return model
@flow(name="training-pipeline")
def training_pipeline(data_path: str, params: dict):
"""전체 학습 파이프라인 플로우"""
df = load_data(data_path)
X_train, X_test, y_train, y_test = preprocess(df)
model = train_model(X_train, y_train, params)
return model
김개발 씨는 처음 입사했을 때 모델 학습을 주피터 노트북에서 진행했습니다. 셀을 하나씩 실행하며 결과를 확인하고, 필요하면 다시 위로 돌아가 수정하곤 했습니다.
하지만 문제가 있었습니다. 매번 실행할 때마다 결과가 조금씩 달랐고, 어떤 셀을 어떤 순서로 실행했는지 헷갈리기 일쑤였습니다.
박시니어 씨가 김개발 씨의 노트북을 보더니 말했습니다. "노트북은 탐색에는 좋지만, 프로덕션에는 적합하지 않아요.
파이프라인으로 만들어야 해요." 그렇다면 모델 학습 파이프라인이란 정확히 무엇일까요? 쉽게 비유하자면, 파이프라인은 마치 자동차 공장의 조립 라인과 같습니다.
자동차 공장에서는 원자재가 들어오면 정해진 순서대로 가공되고, 조립되고, 검수를 거쳐 완성차가 나옵니다. 중간에 사람이 개입하지 않아도 일관된 품질의 자동차가 생산됩니다.
ML 파이프라인도 마찬가지로 데이터가 들어오면 정해진 과정을 거쳐 모델이 나옵니다. 파이프라인이 없으면 어떤 문제가 생길까요?
우선 재현성이 떨어집니다. "지난번에는 됐는데 이번에는 왜 안 되지?" 같은 상황이 빈번해집니다.
또한 자동화가 어렵습니다. 매번 사람이 직접 스크립트를 실행해야 합니다.
그리고 협업이 힘들어집니다. 다른 사람이 내 노트북을 이해하기 어렵기 때문입니다.
위 코드에서는 Prefect라는 워크플로우 오케스트레이션 도구를 사용했습니다. @task 데코레이터로 각 단계를 독립적인 태스크로 정의하고, @flow 데코레이터로 태스크들을 연결합니다.
Airflow, Kubeflow, Dagster 등 다른 도구를 사용해도 개념은 동일합니다. 코드를 자세히 살펴보겠습니다.
load_data 태스크는 저장소에서 데이터를 읽어옵니다. preprocess 태스크는 결측치 처리와 데이터 분할을 담당합니다.
train_model 태스크는 실제 모델 학습을 수행합니다. 각 태스크는 독립적으로 실행되고, 입력과 출력이 명확합니다.
파이프라인의 큰 장점 중 하나는 캐싱입니다. 데이터 로드와 전처리가 동일하다면, 해당 태스크의 결과를 재사용할 수 있습니다.
하이퍼파라미터만 바꿔서 여러 번 실험할 때 시간을 크게 절약할 수 있습니다. 실무에서는 파이프라인에 알림과 리트라이 로직을 추가합니다.
태스크가 실패하면 Slack으로 알림을 보내고, 일시적인 오류라면 자동으로 재시도합니다. 이렇게 하면 새벽에 파이프라인이 돌아가다 실패해도 다음 날 출근해서 바로 대응할 수 있습니다.
주의할 점도 있습니다. 파이프라인을 너무 잘게 쪼개면 오히려 관리가 어려워집니다.
논리적으로 의미 있는 단위로 태스크를 나누세요. 또한 태스크 간 데이터 전달 시 직렬화 비용을 고려해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 이제 매니저가 "모델 다시 학습시켜주세요"라고 요청하면, 김개발 씨는 파이프라인 실행 버튼 하나만 누르면 됩니다.
점심 먹고 돌아오면 새 모델이 준비되어 있습니다.
실전 팁
💡 - 태스크는 단일 책임 원칙을 따르되, 너무 잘게 쪼개지 않도록 균형을 유지하세요
- 실패 시 재시도 로직과 알림을 반드시 설정하세요
5. 모델 배포 자동화
김개발 씨가 드디어 만족스러운 모델을 완성했습니다. 정확도 95%, F1 score 0.93.
이제 서비스에 적용할 차례입니다. 하지만 막상 배포하려니 막막했습니다.
"이 모델 파일을 어떻게 서비스에 올리지?" 박시니어 씨가 웃으며 말했습니다. "모델 레지스트리에 등록하고 CI/CD 파이프라인 태우면 돼요."
모델 배포 자동화는 학습된 모델을 프로덕션 환경에 안전하고 일관되게 배포하는 시스템입니다. 마치 앱 스토어에 앱을 등록하듯, 모델을 레지스트리에 등록하고 승인 과정을 거쳐 서비스에 배포합니다.
수동 배포의 실수를 줄이고, 롤백도 쉽게 할 수 있습니다.
다음 코드를 살펴봅시다.
# model_deployment.py - 모델 배포 자동화
import mlflow
from mlflow.tracking import MlflowClient
client = MlflowClient()
# 모델을 레지스트리에 등록
model_uri = "runs:/abc123/model"
model_name = "fraud-detection-model"
mlflow.register_model(model_uri, model_name)
# 모델 버전을 Production 스테이지로 승격
client.transition_model_version_stage(
name=model_name,
version="3",
stage="Production",
archive_existing_versions=True # 기존 Production 모델은 Archived로
)
# FastAPI 서빙 엔드포인트
from fastapi import FastAPI
import mlflow.pyfunc
app = FastAPI()
model = mlflow.pyfunc.load_model(f"models:/{model_name}/Production")
@app.post("/predict")
async def predict(features: dict):
prediction = model.predict([list(features.values())])
return {"prediction": int(prediction[0])}
김개발 씨는 처음 모델을 배포할 때 많이 당황했습니다. 주피터 노트북에서 학습시킨 모델을 pickle 파일로 저장했는데, 이걸 어떻게 서비스에 올려야 할지 몰랐습니다.
scp로 서버에 파일을 옮기고, 수동으로 서비스를 재시작했습니다. 그런데 다음 날 서비스가 다운되었습니다.
라이브러리 버전이 달라서 모델을 로드할 수 없었던 것입니다. 박시니어 씨가 복구 작업을 도우며 말했습니다.
"모델 배포도 코드 배포처럼 자동화해야 해요. 그래야 이런 사고를 막을 수 있어요." 그렇다면 모델 배포 자동화란 정확히 무엇일까요?
쉽게 비유하자면, 모델 배포 자동화는 마치 아파트 경비 시스템과 같습니다. 아무나 아파트에 들어올 수 없듯이, 아무 모델이나 프로덕션에 올라가서는 안 됩니다.
입주민(검증된 모델)만이 정해진 절차를 거쳐 입장(배포)할 수 있습니다. 그리고 문제가 생기면 즉시 퇴거(롤백)시킬 수 있어야 합니다.
수동 배포의 문제점은 무엇일까요? 첫째, 일관성이 없습니다.
배포할 때마다 약간씩 다른 방법을 사용하게 됩니다. 둘째, 추적이 어렵습니다.
현재 서비스에 어떤 모델이 올라가 있는지, 언제 누가 배포했는지 알기 어렵습니다. 셋째, 롤백이 복잡합니다.
문제가 생겼을 때 이전 버전으로 빠르게 돌아갈 수 없습니다. 위 코드의 핵심은 모델 레지스트리입니다.
MLflow 모델 레지스트리는 모델의 생명주기를 관리합니다. 모델은 None, Staging, Production, Archived 네 가지 스테이지를 거칩니다.
새 모델은 먼저 Staging에서 테스트를 거치고, 검증이 완료되면 Production으로 승격됩니다. transition_model_version_stage 함수를 자세히 보겠습니다.
archive_existing_versions=True 옵션이 중요합니다. 이 옵션을 켜면 기존 Production 모델이 자동으로 Archived로 이동합니다.
만약 새 모델에 문제가 생기면, Archived된 이전 모델을 다시 Production으로 올릴 수 있습니다. FastAPI를 사용한 서빙 엔드포인트도 살펴보겠습니다.
mlflow.pyfunc.load_model은 레지스트리에서 Production 스테이지의 모델을 자동으로 가져옵니다. 모델이 업데이트되면 서비스만 재시작하면 새 모델이 로드됩니다.
실무에서는 Blue-Green 배포나 Canary 배포 전략을 사용합니다. 새 모델을 일부 트래픽에만 먼저 적용해보고, 문제가 없으면 전체로 확대합니다.
이렇게 하면 잘못된 모델이 전체 서비스에 영향을 미치는 것을 방지할 수 있습니다. 주의할 점도 있습니다.
모델과 함께 의존성 버전도 관리해야 합니다. MLflow는 conda.yaml이나 requirements.txt를 모델과 함께 저장합니다.
서빙 환경에서 이 의존성을 정확히 재현해야 모델이 제대로 동작합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
이제 김개발 씨는 모델을 배포할 때 레지스트리에 등록하고, PR을 올리고, 코드 리뷰를 받습니다. 승인이 나면 CI/CD 파이프라인이 자동으로 배포합니다.
더 이상 새벽에 scp를 치다가 실수할 일이 없습니다.
실전 팁
💡 - Staging 환경에서 충분히 테스트한 후 Production으로 승격하세요
- 모델과 함께 의존성 정보도 반드시 버전 관리하세요
6. 실시간 모니터링 구축
새 모델을 배포한 지 일주일이 지났습니다. 김개발 씨는 뿌듯했습니다.
배포할 때 정확도 95%였으니까요. 그런데 어느 날 CS팀에서 연락이 왔습니다.
"요즘 예측이 자꾸 틀려요. 확인 좀 해주세요." 김개발 씨는 깜짝 놀랐습니다.
언제부터 성능이 떨어졌는지 전혀 몰랐기 때문입니다.
실시간 모니터링은 배포된 모델의 성능과 상태를 지속적으로 관찰하고 이상 징후를 감지하는 시스템입니다. 마치 환자의 심전도를 모니터링하는 것처럼, 모델의 건강 상태를 실시간으로 확인합니다.
성능 저하를 조기에 발견하여 빠르게 대응할 수 있습니다.
다음 코드를 살펴봅시다.
# model_monitoring.py - 실시간 모니터링 시스템
from prometheus_client import Counter, Histogram, Gauge, start_http_server
import time
# 메트릭 정의
prediction_counter = Counter('model_predictions_total', 'Total predictions', ['result'])
latency_histogram = Histogram('prediction_latency_seconds', 'Prediction latency')
accuracy_gauge = Gauge('model_accuracy', 'Real-time model accuracy')
class ModelMonitor:
def __init__(self, model):
self.model = model
self.predictions = []
self.actuals = []
def predict_with_monitoring(self, features):
start_time = time.time()
# 예측 수행
prediction = self.model.predict([features])[0]
# 메트릭 기록
latency_histogram.observe(time.time() - start_time)
prediction_counter.labels(result=str(prediction)).inc()
return prediction
def update_accuracy(self, actual):
"""실제 결과가 확인되면 정확도 업데이트"""
self.actuals.append(actual)
if len(self.actuals) >= 100:
accuracy = sum(p == a for p, a in zip(self.predictions[-100:], self.actuals[-100:])) / 100
accuracy_gauge.set(accuracy)
김개발 씨는 충격을 받았습니다. 배포할 때 95%였던 정확도가 어느새 70%까지 떨어져 있었습니다.
더 충격적인 것은 이 사실을 CS팀에서 알려주기 전까지 전혀 몰랐다는 것입니다. 만약 일찍 알았더라면 피해를 줄일 수 있었을 텐데요.
박시니어 씨가 커피를 건네며 말했습니다. "모델은 배포하고 끝이 아니에요.
아기처럼 계속 돌봐줘야 해요. 모니터링이 없으면 모델이 아프다는 걸 알 수가 없어요." 그렇다면 실시간 모니터링이란 정확히 무엇일까요?
쉽게 비유하자면, 실시간 모니터링은 마치 자동차 계기판과 같습니다. 운전할 때 속도계, 연료계, 엔진 경고등을 계속 확인하듯이, 모델 운영에서도 다양한 지표를 실시간으로 확인해야 합니다.
연료가 떨어지기 전에 주유하고, 엔진에 문제가 생기면 바로 정비소에 가듯이 말입니다. 모니터링 없이 모델을 운영하면 어떤 문제가 생길까요?
우선 성능 저하를 늦게 발견합니다. 사용자가 불만을 제기할 때쯤이면 이미 많은 피해가 발생한 후입니다.
또한 원인 분석이 어렵습니다. 기록이 없으니 언제부터, 왜 성능이 떨어졌는지 알 수 없습니다.
위 코드에서는 Prometheus를 사용한 메트릭 수집 시스템을 구현했습니다. Prometheus는 시계열 데이터베이스로, 시간에 따른 메트릭 변화를 저장하고 쿼리할 수 있습니다.
Grafana와 연동하면 멋진 대시보드도 만들 수 있습니다. 세 가지 종류의 메트릭을 정의했습니다.
Counter는 누적 값으로, 총 예측 횟수를 셉니다. Histogram은 분포를 측정하는데, 예측 지연 시간의 분포를 파악할 수 있습니다.
Gauge는 현재 값을 나타내며, 실시간 정확도를 표시합니다. predict_with_monitoring 메서드가 핵심입니다.
예측을 수행하면서 동시에 지연 시간과 예측 결과를 기록합니다. 이렇게 하면 모든 예측에 대한 메트릭이 자동으로 수집됩니다.
실무에서는 어떤 지표를 모니터링할까요? 첫째, 예측 지연 시간입니다.
갑자기 느려지면 인프라에 문제가 있을 수 있습니다. 둘째, 예측 분포입니다.
평소에는 0과 1이 반반이었는데 갑자기 1만 나온다면 이상한 것입니다. 셋째, 정확도입니다.
실제 결과가 확인되면 예측과 비교합니다. 주의할 점도 있습니다.
모니터링 자체가 시스템에 부하를 주어서는 안 됩니다. 메트릭 수집은 비동기로 처리하고, 모든 요청이 아니라 샘플링을 통해 일부만 상세 기록하는 전략도 고려해보세요.
알림 설정도 중요합니다. 지연 시간이 임계치를 넘거나, 정확도가 일정 수준 아래로 떨어지면 Slack이나 PagerDuty로 알림을 보냅니다.
새벽에도 대응할 수 있도록 온콜 시스템을 구축하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
모니터링 시스템을 구축한 후, 김개발 씨는 매일 아침 대시보드를 확인합니다. 정확도가 조금이라도 떨어지면 바로 알림이 옵니다.
이제 더 이상 CS팀에서 먼저 문제를 발견하는 일은 없습니다.
실전 팁
💡 - 지연 시간, 예측 분포, 정확도를 최소한으로 모니터링하세요
- 임계치를 넘으면 즉시 알림이 오도록 설정하세요
7. 드리프트 감지 및 재학습
김개발 씨는 모니터링 대시보드를 보며 고민에 빠졌습니다. 정확도가 서서히 떨어지고 있었습니다.
모델 코드를 바꾼 적도 없는데 왜 그럴까요? 박시니어 씨가 설명했습니다.
"세상이 변했거든요. 모델을 학습시킬 때 세상과 지금 세상이 달라요.
이걸 드리프트라고 해요."
드리프트 감지 및 재학습은 시간이 지남에 따라 데이터 분포가 변하는 현상을 탐지하고, 모델을 최신 데이터로 재학습시키는 프로세스입니다. 마치 지도가 오래되면 새로 제작해야 하듯, 모델도 현실 세계의 변화에 맞춰 업데이트되어야 합니다.
이를 통해 모델 성능을 지속적으로 유지할 수 있습니다.
다음 코드를 살펴봅시다.
# drift_detection.py - 드리프트 감지 및 재학습
from scipy import stats
import numpy as np
from datetime import datetime
class DriftDetector:
def __init__(self, reference_data, threshold=0.05):
self.reference = reference_data
self.threshold = threshold
def detect_drift(self, current_data):
"""KS 테스트로 데이터 드리프트 감지"""
drift_detected = {}
for column in self.reference.columns:
statistic, p_value = stats.ks_2samp(
self.reference[column],
current_data[column]
)
drift_detected[column] = p_value < self.threshold
return drift_detected
def should_retrain(self, drift_results, min_drift_ratio=0.3):
"""재학습 필요 여부 판단"""
drift_count = sum(drift_results.values())
total_features = len(drift_results)
return drift_count / total_features >= min_drift_ratio
class AutoRetrainer:
def trigger_retraining(self, reason: str):
"""재학습 파이프라인 트리거"""
print(f"[{datetime.now()}] 재학습 시작 - 사유: {reason}")
# Prefect/Airflow 파이프라인 트리거
# training_pipeline.run(data_path="s3://data/latest/")
김개발 씨가 만든 사기 탐지 모델은 출시 당시 정확도가 95%였습니다. 하지만 6개월이 지나자 85%로 떨어졌습니다.
코드를 전혀 건드리지 않았는데 말입니다. 도대체 무슨 일이 일어난 걸까요?
박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "6개월 전과 지금, 사기 패턴이 똑같을까요?
사기꾼들도 진화해요. 새로운 수법이 등장하고, 기존 수법은 사라지고.
모델은 과거 데이터로 학습했으니, 새로운 패턴을 모르는 거예요." 그렇다면 드리프트란 정확히 무엇일까요? 쉽게 비유하자면, 드리프트는 마치 유행의 변화와 같습니다.
10년 전 패션 트렌드로 오늘 옷을 고르면 촌스러워 보이듯, 오래된 데이터로 학습한 모델은 현재 상황에 맞지 않게 됩니다. 데이터 드리프트는 입력 데이터의 분포가 변하는 것이고, 컨셉 드리프트는 입력과 출력 사이의 관계가 변하는 것입니다.
드리프트를 어떻게 감지할까요? 위 코드에서는 Kolmogorov-Smirnov(KS) 테스트를 사용했습니다.
이 통계적 검정은 두 분포가 같은 분포에서 왔는지 확인합니다. p-value가 임계치보다 낮으면 분포가 유의미하게 다르다고 판단합니다.
DriftDetector 클래스를 자세히 보겠습니다. reference_data는 모델을 학습할 때 사용한 데이터입니다.
새로운 데이터가 들어오면 각 피처별로 KS 테스트를 수행합니다. 여러 피처에서 동시에 드리프트가 감지되면 재학습을 트리거합니다.
재학습 시점을 결정하는 것은 중요한 비즈니스 결정입니다. 너무 자주 재학습하면 리소스 낭비이고, 너무 늦게 하면 서비스 품질이 떨어집니다.
위 코드에서는 전체 피처의 30% 이상에서 드리프트가 감지되면 재학습을 트리거하도록 설정했습니다. 실무에서는 스케줄 기반 재학습과 트리거 기반 재학습을 병행합니다.
매주 정해진 시간에 자동으로 재학습하면서, 드리프트가 심하게 감지되면 즉시 재학습을 트리거합니다. 두 전략을 조합하면 안정성과 민첩성을 모두 확보할 수 있습니다.
주의할 점도 있습니다. 재학습된 모델이 반드시 더 좋다는 보장은 없습니다.
따라서 챔피언-챌린저 패턴을 사용하세요. 새 모델은 일단 Staging에 배포하고, 기존 모델과 성능을 비교한 후 더 좋을 때만 Production으로 승격합니다.
또 하나 중요한 점은 데이터 라벨링입니다. 재학습하려면 새 데이터에 대한 라벨이 필요합니다.
사기 탐지의 경우, 실제로 사기였는지 확인되기까지 시간이 걸릴 수 있습니다. 이런 피드백 루프도 설계 시 고려해야 합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 드리프트 감지 시스템을 구축한 후, 모델은 자동으로 현실에 적응합니다.
사기꾼의 새로운 수법이 등장해도 2주 안에 모델이 업데이트됩니다. 정확도는 항상 90% 이상을 유지합니다.
실전 팁
💡 - 데이터 드리프트와 컨셉 드리프트를 구분하여 모니터링하세요
- 재학습된 모델은 반드시 기존 모델과 비교 테스트 후 배포하세요
8. CICD 통합 및 프로덕션 운영
"이제 모든 조각이 준비됐어요." 박시니어 씨가 화이트보드에 그린 그림을 가리켰습니다. 데이터 수집, DVC, MLflow, 파이프라인, 배포, 모니터링, 드리프트 감지...
김개발 씨가 지난 몇 달간 구축한 모든 것이 그려져 있었습니다. "이제 이것들을 하나로 연결할 차례예요.
그게 바로 CI/CD입니다."
CI/CD 통합 및 프로덕션 운영은 MLOps의 모든 구성 요소를 하나의 자동화된 파이프라인으로 연결하는 것입니다. 마치 오케스트라 지휘자가 각 악기를 조율하여 아름다운 교향곡을 만들듯, CI/CD는 코드 변경부터 모델 배포까지 전체 흐름을 조율합니다.
이를 통해 안전하고 빠른 모델 업데이트가 가능해집니다.
다음 코드를 살펴봅시다.
# .github/workflows/mlops-pipeline.yml
name: MLOps CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run unit tests
run: pytest tests/ -v
train:
needs: test
runs-on: ubuntu-latest
steps:
- name: Pull data with DVC
run: dvc pull
- name: Run training pipeline
run: python -m pipelines.training --config configs/prod.yaml
- name: Log to MLflow
run: python -m scripts.log_experiment
deploy:
needs: train
if: github.ref == 'refs/heads/main'
steps:
- name: Promote model to Production
run: |
python -m scripts.promote_model --stage production
- name: Deploy to Kubernetes
run: |
kubectl apply -f k8s/model-serving.yaml
kubectl rollout status deployment/model-api
김개발 씨는 지난 몇 달간 많은 것을 배웠습니다. 데이터를 자동으로 수집하고, DVC로 버전 관리하고, MLflow로 실험을 추적하고, 파이프라인으로 학습을 자동화하고, 모델을 배포하고, 모니터링하고, 드리프트를 감지하는 법까지.
하지만 아직 한 가지가 빠져 있었습니다. "이 모든 게 따로따로 움직이고 있어요." 박시니어 씨가 지적했습니다.
"코드를 푸시하면 자동으로 테스트가 돌고, 학습이 시작되고, 성능이 좋으면 배포되는... 그런 통합된 파이프라인이 필요해요." 그렇다면 CI/CD 통합이란 정확히 무엇일까요?
쉽게 비유하자면, CI/CD는 마치 자동 세차장과 같습니다. 차를 입구에 넣으면 세정, 헹굼, 왁스, 건조가 자동으로 진행됩니다.
운전자는 출구에서 깨끗해진 차를 받기만 하면 됩니다. MLOps CI/CD도 마찬가지로, 코드를 푸시하면 테스트, 학습, 평가, 배포가 자동으로 진행됩니다.
**CI(Continuous Integration)**는 코드 변경이 있을 때마다 자동으로 빌드하고 테스트하는 것입니다. 문법 오류, 단위 테스트 실패 등을 조기에 발견합니다.
**CD(Continuous Deployment)**는 테스트를 통과한 코드를 자동으로 프로덕션에 배포하는 것입니다. 위 코드는 GitHub Actions를 사용한 MLOps 파이프라인입니다.
세 개의 job이 순차적으로 실행됩니다. test job은 단위 테스트를 실행합니다.
train job은 DVC로 데이터를 가져오고 학습 파이프라인을 실행합니다. deploy job은 main 브랜치에 머지될 때만 실행되며, 모델을 프로덕션에 배포합니다.
needs 키워드가 중요합니다. train은 test가 성공해야 실행되고, deploy는 train이 성공해야 실행됩니다.
어느 단계에서든 실패하면 파이프라인이 중단되고 알림이 갑니다. 실무에서는 몇 가지 추가 단계가 있습니다.
모델 검증을 통해 새 모델의 성능이 기존 모델보다 나은지 확인합니다. 스모크 테스트로 배포된 모델이 실제로 예측을 반환하는지 확인합니다.
롤백 전략도 마련해두어야 합니다. 프로덕션 운영에서 중요한 것은 **관찰가능성(Observability)**입니다.
로그, 메트릭, 트레이스를 통해 시스템 상태를 한눈에 파악할 수 있어야 합니다. 문제가 발생했을 때 "언제, 어디서, 왜" 발생했는지 빠르게 파악할 수 있어야 합니다.
주의할 점도 있습니다. CI/CD 파이프라인이 복잡해지면 유지보수가 어려워집니다.
파이프라인 코드도 코드입니다. 모듈화하고, 주석을 달고, 버전 관리하세요.
또한 시크릿 관리에 신경 써야 합니다. API 키, 데이터베이스 비밀번호 같은 민감 정보는 GitHub Secrets 같은 보안 저장소에 보관하세요.
다시 김개발 씨의 이야기로 돌아가 봅시다. 이제 김개발 씨가 코드를 푸시하면 마법처럼 일이 진행됩니다.
테스트가 돌고, 모델이 학습되고, 성능이 좋으면 자동으로 배포됩니다. 김개발 씨는 더 중요한 일에 집중할 수 있게 되었습니다.
바로 더 좋은 모델을 만드는 일 말입니다. 그리고 몇 달 후, 김개발 씨는 더 이상 "김개발 씨"가 아니었습니다.
팀에서 MLOps 전문가로 인정받게 되었고, 새로 입사하는 후배들에게 "박시니어 씨"가 그랬던 것처럼, 이 모든 것을 가르쳐주고 있습니다.
실전 팁
💡 - 파이프라인 코드도 버전 관리하고 코드 리뷰를 받으세요
- 배포 후 스모크 테스트로 기본 동작을 반드시 확인하세요
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.