Django로 딥러닝 모델 서빙
Source: Dev.to
번역하려는 전체 텍스트를 제공해 주시면, 원본 형식과 마크다운을 그대로 유지하면서 한국어로 번역해 드리겠습니다.
Introduction
딥러닝은 대형 언어 모델과 함께 갑자기 등장한 것이 아닙니다. 이 분야는 20세기 후반의 초기 신경망 연구에서 시작해 수십 년에 걸쳐 발전해 왔으며, 컴퓨팅 파워, 데이터, 알고리즘이 향상됨에 따라 점진적으로 개선되었습니다. 오늘날 딥러닝 시스템은 이미지 인식, 자연어 처리, 추천 시스템 및 기타 다양한 실제 응용 분야에서 사용됩니다. 그러나 모델을 학습하는 것만이 워크플로우의 일부에 불과합니다. 모델을 유용하게 만들려면 서비스를 제공해야 합니다. 입력을 받아 신뢰할 수 있게 예측을 반환해야 합니다. 이 기사에서는 Django와 PyTorch를 사용해 딥러닝 모델을 서비스하는 실용적인 접근 방식을 단계별로 살펴보겠습니다.
Note: 여기서 중점은 생산 규모 최적화보다 명확성과 정확성에 있습니다 (다음 기사에서 다룰 예정입니다).
Part 1: Django 프로젝트 설정
-
가상 환경을 만들고 활성화한 뒤 Django를 설치합니다
python -m venv venv source venv/bin/activate pip install django -
새 Django 프로젝트를 생성합니다
django-admin startproject sample_project cd sample_project -
모델 서빙 로직을 포함할 애플리케이션을 생성합니다
python manage.py startapp my_app -
프로젝트 디렉터리 구조
sample_project/ ├── sample_project/ │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── my_app/ │ ├── views.py │ ├── apps.py │ └── templates/ └── manage.py -
sample_project/settings.py에서my_app을INSTALLED_APPS에 추가합니다
이 단계에서는 아직 딥러닝 코드는 포함되지 않습니다.
Source:
Part 2: 딥러닝 모델 학습
모델 학습은 의도적으로 Django 외부에서 수행됩니다. 학습은 보통 연산량이 많아 별도의 스크립트나 노트북에서 처리하는 것이 좋습니다. Django의 역할은 나중에 추론이며, 최적화는 아닙니다.
아래는 검증을 포함한 지도 학습을 수행하고 일반적인 메트릭을 추적하는 학습 함수 예시입니다. 여기서는 PyTorch 모델을 사용합니다.
def train_model(
model: nn.Module,
loss_fn,
optim,
lr: float,
train_dl,
val_dl,
num_classes: int,
epochs: int = 20,
device: str = "cuda",
) -> dict[str, list[float]]:
"""
Train a PyTorch model.
- Moves data and the model to the chosen device (CPU or GPU)
- Computes loss using cross‑entropy (appropriate for multi‑class classification)
- Tracks accuracy and macro‑averaged F1 score using torchmetrics
- Separates training and validation phases
"""
# implementation goes here …
핵심 포인트
- 이 함수는 Django와 완전히 독립적이며, 학습과 서빙 사이의 명확한 분리를 보장합니다.
- 작업에 따라 직접 모델을 학습하거나 사전 학습된 모델을 적용할 수 있습니다.
Part 3: 모델 가중치 저장 및 로드
학습이 끝난 후에는 모델이 학습한 파라미터를 저장하여 Django가 추론 시 로드할 수 있도록 합니다.
torch.save(
{"model_state_dict": model.state_dict()},
"resnet18_model.pth"
)
모델을 서비스할 때는 학습 중에 사용했던 아키텍처를 정확히 재구성한 뒤, 저장된 가중치를 로드합니다:
model = ResNet18_CustomHead(num_classes=5).to(device)
ckpt = torch.load("resnet18_model.pth", map_location=device)
model.load_state_dict(ckpt["model_state_dict"])
model.eval() # dropout, batch‑norm 업데이트 등을 비활성화
이와 같은 명시적인 방법은 다양한 환경에서 오류 발생 가능성을 줄여줍니다.
Part 4: Django 뷰를 사용한 추론 만들기
이제 모든 것을 연결합니다. 아래 뷰는 업로드된 이미지를 받아서 학습된 모델을 로드하고, 추론을 수행한 뒤 예측 결과를 JSON 형태로 반환합니다.
import os
import uuid
import shutil
from pathlib import Path
from django.http import JsonResponse
from django.shortcuts import render
from django.conf import settings
import torch
from torchvision import transforms
# Assume these utilities are defined elsewhere in the project
# from .utils import load_images_from_path, get_default_test_transforms, infer_on_unknown_data
# from .models import ResNet18_CustomHead
TEMP_DIR = Path(settings.BASE_DIR) / "temp"
MODEL_WEIGHTS_PATH = Path(settings.BASE_DIR) / "model_weights"
def analysisPageView(request):
if request.method == "POST":
# ---- Prepare a clean temporary directory ----
os.makedirs(TEMP_DIR, exist_ok=True)
# Remove any leftover sub‑directories
for p in TEMP_DIR.iterdir():
if p.is_dir():
shutil.rmtree(p)
req_dir = TEMP_DIR / uuid.uuid4().hex
req_dir.mkdir(parents=True, exist_ok=True)
# ---- Save uploaded images to the temp folder ----
uploaded_files = request.FILES.getlist("images")
for f in uploaded_files:
file_name = Path(f.name).name
ext = Path(file_name).suffix.lower()
final_file_name = f"image-{uuid.uuid4().hex[:8]}{ext}"
with open(req_dir / final_file_name, "wb") as dest:
for chunk in f.chunks():
dest.write(chunk)
# ---- Load images and preprocessing transforms ----
images = load_images_from_path(req_dir)
test_tfms = get_default_test_transforms()
device = "cuda" if torch.cuda.is_available() else "cpu"
# ---- Load the model (once per request – OK for demos) ----
model = ResNet18_CustomHead(num_classes=5).to(device)
ckpt = torch.load(
MODEL_WEIGHTS_PATH / "resnet18_model.pth",
map_location=device,
)
model.load_state_dict(ckpt["model_state_dict"])
model.eval()
# ---- Run inference ----
pred_labels = infer_on_unknown_data(
images, model, device, test_tfms
)
return JsonResponse({"labels": pred_labels})
# GET request – render the upload page
return render(request, "app/analysis.html")
중요한 참고 사항
- 요청 핸들러 내부에서 모델을 로드하는 것은 데모에는 괜찮지만 트래픽이 많은 시스템에서는 비효율적입니다. 실제 서비스에서는 서버 시작 시 한 번만 모델을 로드하고 요청마다 재사용하도록 해야 합니다.
model.eval()은 드롭아웃이나 배치 정규화와 같이 학습 시에만 사용되는 레이어들을 비활성화합니다.- 전처리는 학습 시 사용한 변환과 정확히 동일해야 하며, 그렇지 않으면 예측 결과가 신뢰할 수 없게 됩니다.
위 단계들을 따라 하면 PyTorch 모델을 학습하고 가중치를 저장한 뒤, Django 웹 애플리케이션을 통해 모델을 서비스할 수 있는 최소하지만 실용적인 파이프라인을 구축할 수 있습니다.
Part 5: API 테스트
**urls.py**에 뷰를 연결한 후에는 다음 방법으로 테스트할 수 있습니다:
- HTML 폼을 통해 이미지 제출
- Postman 또는 curl을 사용해 POST 요청 전송
- 프론트엔드 애플리케이션에서 엔드포인트 호출
모든 설정이 올바르게 되어 있으면 Django가 예측 결과를 구조화된 JSON 형태로 반환하므로 다른 서비스와 쉽게 통합할 수 있습니다.
모델 추론을 위한 예시 뷰
(이전 뷰가 이미 이 기능을 포함하고 있습니다; 아래 코드는 참고용으로 제공됩니다.)
def ModelInferenceView():
...
images = load_images_from_path(req_dir)
# ⚠️ Warning: not recommended for production
model = ResNet18_CustomHead(num_classes=5).to(device)
ckpt = torch.load(MODEL_WEIGHTS_PATH / "resnet18_model.pth",
map_location=device)
state_dict = ckpt["model_state_dict"]
model.load_state_dict(state_dict)
...
pred_labels = infer_on_unknown_data(images, model, device, test_tfms)
return JsonResponse({'labels': pred_labels})
Recap
이 기사에서는 Django를 사용하여 딥러닝 모델을 서비스하는 전체 라이프사이클을 살펴보았습니다:
- Django 프로젝트와 애플리케이션을 생성했습니다
- 웹 레이어 외부에서 PyTorch 모델을 학습했습니다
- 모델 가중치를 올바르게 저장하고 로드했습니다
- 추론을 위한 Django 뷰를 노출했습니다
- HTTP 요청을 통해 모델을 테스트했습니다
Django는 대규모 모델 서빙을 위해 설계된 것은 아니지만, 프로토타입, 내부 도구, 그리고 단순성과 유연성이 중요한 워크로드에 실용적인 선택입니다.
오늘은 여기까지입니다. 참고 코드(관련은 있지만 다른 프로젝트용)는 여기에서 확인할 수 있습니다. 이 글이 도움이 되었다면 LinkedIn에서 저를 팔로우하고 저장소에 별표를 달아 주세요.