Django로 머신러닝 애플리케이션 구축하기
모델 훈련부터 웹 양식 및 API 생성까지, 단일 튜토리얼로 Django를 활용한 자체 엔드투엔드 머신러닝 앱을 구축하고 서비스하세요.

머신 러닝은 다양한 분야에서 강력한 응용 가능성을 지니지만, 실제 환경에서 머신 러닝 모델을 효과적으로 배포하려면 웹 프레임워크의 사용이 필수적인 경우가 많습니다.
Django는 Python용 고급 웹 프레임워크로, 확장 가능하고 안전한 웹 애플리케이션 구축에 특히 널리 사용됩니다. scikit-learn과 같은 라이브러리와 결합하면 Django를 통해 개발자는 API를 통해 머신 러닝 모델 추론을 제공할 수 있으며, 사용자가 이러한 모델과 상호작용할 수 있는 직관적인 웹 인터페이스를 구축할 수도 있습니다.
이 튜토리얼에서는 머신러닝 모델의 예측 결과를 제공하는 간단한 Django 애플리케이션을 구축하는 방법을 배웁니다. 이 단계별 가이드에서는 초기 모델 훈련부터 추론 및 테스트 API 구현까지 전체 과정을 안내합니다.
1. 프로젝트 설정
기본 프로젝트 구조를 생성하고 필요한 종속성을 설치하는 것으로 시작하겠습니다.
새 프로젝트 디렉터리를 생성하고 해당 디렉터리로 이동합니다:
mkdir django-ml-app && cd django-ml-app
필요한 Python 패키지를 설치합니다:
pip install Django scikit-learn joblib
mlapp이라는 새 Django 프로젝트를 초기화하고 predictor라는 새 앱을 생성합니다:
django-admin startproject mlapp .
python manage.py startapp predictor
앱의 HTML 파일을 위한 템플릿 디렉터리를 설정합니다:
mkdir -p templates/predictor
위 명령어를 실행한 후 프로젝트 폴더 구조는 다음과 같아야 합니다:
django-ml-app/
├─ .venv/
├─ mlapp/
│ ├─ __init__.py
│ ├─ asgi.py
│ ├─ settings.py
│ ├─ urls.py
│ └─ wsgi.py
├─ predictor/
│ ├─ migrations/
│ ├─ __init__.py
│ ├─ apps.py
│ ├─ forms.py <-- 나중에 추가 할 예정
│ ├─ services.py <-- 나중에 추가 할 예정 (model load/predict)
│ ├─ views.py <-- 업데이트 예정
│ ├─ urls.py <-- 나중에 추가 할 예정
│ └─ tests.py <-- 나중에 추가 할 예정
├─ templates/
│ └─ predictor/
│ └─ predict_form.html
├─ manage.py
├─ requirements.txt
└─ train.py <-- 머신 러닝 훈련 스크립트
2. 머신 러닝 모델 훈련
다음으로, Django 앱이 예측에 사용할 모델을 생성하겠습니다. 이 튜토리얼에서는 scikit-learn에 포함된 고전적인 아이리스(Iris) 데이터셋을 사용하겠습니다.
프로젝트 루트 디렉터리에서 train.py라는 스크립트를 생성하세요. 이 스크립트는 아이리스 데이터셋을 로드하고 훈련 세트와 테스트 세트로 분할합니다.
다음으로, 훈련 데이터에 대해 랜덤 포레스트 분류기를 훈련시킵니다. 훈련이 완료되면, 훈련된 모델과 함께 특징 이름 및 목표 레이블을 포함한 메타데이터를 predictor/model/ 디렉터리에 joblib를 사용하여 저장합니다.
from pathlib import Path
import joblib
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
MODEL_DIR = Path("predictor") / "model"
MODEL_DIR.mkdir(parents=True, exist_ok=True)
MODEL_PATH = MODEL_DIR / "iris_rf.joblib"
def main():
data = load_iris()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
clf = RandomForestClassifier(n_estimators=200, random_state=42)
clf.fit(X_train, y_train)
joblib.dump(
{
"estimator": clf,
"target_names": data.target_names,
"feature_names": data.feature_names,
},
MODEL_PATH,
)
print(f"Saved model to {MODEL_PATH.resolve()}")
if __name__ == "__main__":
main()
훈련 스크립트 실행:
python train.py
모든 것이 성공적으로 실행되면 모델이 저장되었음을 확인하는 메시지가 표시됩니다.
3. Django 설정 구성
이제 앱과 훈련 스크립트가 준비되었으므로, Django가 새로운 애플리케이션과 템플릿 위치를 인식할 수 있도록 구성해야 합니다.
mlapp/settings.py를 열고 다음과 같이 업데이트하세요:
- predictor 앱을 INSTALLED_APPS에 등록하세요. 이렇게 하면 Django가 프로젝트 라이프사이클(모델, 뷰, 폼 등)에 사용자 정의 앱을 포함시킵니다.
- 템플릿 경로 templates/ 디렉터리를 TEMPLATES 구성에 추가합니다. 이는 Django가 특정 앱에 직접 연결되지 않은 HTML 템플릿(예: 나중에 구축할 양식)을 로드할 수 있도록 보장합니다.
- 개발 중 모든 호스트를 허용하도록 ALLOWED_HOSTS를 설정합니다. 이렇게 하면 호스트 관련 오류 없이 로컬에서 프로젝트를 더 쉽게 실행할 수 있습니다.
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"predictor", # <-- add
]
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"], # <-- add
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
# For dev
ALLOWED_HOSTS = ["*"]
4. URL 추가
앱 등록이 완료되었으므로, 다음 단계는 사용자가 페이지와 API 엔드포인트에 접근할 수 있도록 URL 라우팅을 설정하는 것입니다. Django는 urls.py 파일을 통해 들어오는 HTTP 요청을 라우팅합니다.
두 가지 라우팅 세트를 구성할 것입니다:
- 프로젝트 수준 URL (mlapp/urls.py) – 관리자 패널과 같은 글로벌 라우팅 및 predictor 앱의 라우팅을 포함합니다.
- 앱 수준 URL (predictor/urls.py) – 웹 양식 및 API에 대한 특정 경로를 정의합니다.
mlapp/urls.py를 열고 다음과 같이 업데이트하세요.
# mlapp/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("predictor.urls")), # web & API routes
]
이제 새 파일 predictor/urls.py를 생성하고 앱 전용 경로를 정의하세요:
# predictor/urls.py
from django.urls import path
from .views import home, predict_view, predict_api
urlpatterns = [
path("", home, name="home"),
path("predict/", predict_view, name="predict"),
path("api/predict/", predict_api, name="predict_api"),
]
5. 양식 구축
사용자가 웹 인터페이스를 통해 모델과 상호작용할 수 있도록, 꽃 측정값(꽃받침과 꽃잎 크기)을 입력할 수 있는 입력 양식이 필요합니다. Django는 내장된 forms 모듈로 이를 쉽게 구현할 수 있습니다.
아이리스 분류기에 필요한 네 개의 숫자 입력값을 수집하기 위해 간단한 양식 클래스를 만들겠습니다.
predictor/ 앱에 forms.py라는 새 파일을 생성하고 다음 코드를 추가하세요:
# predictor/forms.py
from django import forms
class IrisForm(forms.Form):
sepal_length = forms.FloatField(min_value=0, label="Sepal length (cm)")
sepal_width = forms.FloatField(min_value=0, label="Sepal width (cm)")
petal_length = forms.FloatField(min_value=0, label="Petal length (cm)")
petal_width = forms.FloatField(min_value=0, label="Petal width (cm)")
6. 모델 로드 및 예측
이제 아이리스 분류기를 훈련하고 저장했으므로, Django 앱이 모델을 로드하고 예측에 사용할 방법이 필요합니다. 체계성을 유지하기 위해, 모든 예측 관련 로직을 predictor 앱 내의 전용 services.py 파일에 배치하겠습니다.
이렇게 하면 뷰는 요청/응답 처리에만 집중할 수 있고, 예측 로직은 재사용 가능한 서비스 모듈에 분리됩니다.
predictor/services.py에 다음 코드를 추가하세요:
# predictor/services.py
from __future__ import annotations
from pathlib import Path
from typing import Dict, Any
import joblib
import numpy as np
_MODEL_CACHE: Dict[str, Any] = {}
def get_model_bundle():
"""
Loads and caches the trained model bundle:
{
"estimator": RandomForestClassifier,
"target_names": ndarray[str],
"feature_names": list[str],
}
"""
global _MODEL_CACHE
if "bundle" not in _MODEL_CACHE:
model_path = Path(__file__).resolve().parent / "model" / "iris_rf.joblib"
_MODEL_CACHE["bundle"] = joblib.load(model_path)
return _MODEL_CACHE["bundle"]
def predict_iris(features):
"""
features: list[float] of length 4 (sepal_length, sepal_width, petal_length, petal_width)
Returns dict with class_name and probabilities.
"""
bundle = get_model_bundle()
clf = bundle["estimator"]
target_names = bundle["target_names"]
X = np.array([features], dtype=float)
proba = clf.predict_proba(X)[0]
idx = int(np.argmax(proba))
return {
"class_index": idx,
"class_name": str(target_names[idx]),
"probabilities": {str(name): float(p) for name, p in zip(target_names, proba)},
}
7. 뷰
뷰는 사용자 입력, 모델, 최종 응답(HTML 또는 JSON) 사이의 연결 고리 역할을 합니다. 이 단계에서는 세 가지 뷰를 구축할 것입니다:
- home – 예측 양식을 렌더링합니다.
- predict_view – 웹 인터페이스에서 양식 제출을 처리합니다.
- predict_api – 프로그래매틱 예측을 위한 JSON API 엔드포인트를 제공합니다.
predictor/views.py에 다음 코드를 추가하세요:
from django.http import JsonResponse
from django.shortcuts import render
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt # <-- add
from .forms import IrisForm
from .services import predict_iris
import json
def home(request):
return render(request, "predictor/predict_form.html", {"form": IrisForm()})
@require_http_methods(["POST"])
def predict_view(request):
form = IrisForm(request.POST)
if not form.is_valid():
return render(request, "predictor/predict_form.html", {"form": form})
data = form.cleaned_data
features = [
data["sepal_length"],
data["sepal_width"],
data["petal_length"],
data["petal_width"],
]
result = predict_iris(features)
return render(
request,
"predictor/predict_form.html",
{"form": IrisForm(), "result": result, "submitted": True},
)
@csrf_exempt # <-- add this line
@require_http_methods(["POST"])
def predict_api(request):
# Accept JSON only (optional but recommended)
if request.META.get("CONTENT_TYPE", "").startswith("application/json"):
try:
payload = json.loads(request.body or "{}")
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON."}, status=400)
else:
# fall back to form-encoded if you want to keep supporting it:
payload = request.POST.dict()
required = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
missing = [k for k in required if k not in payload]
if missing:
return JsonResponse({"error": f"Missing: {', '.join(missing)}"}, status=400)
try:
features = [float(payload[k]) for k in required]
except ValueError:
return JsonResponse({"error": "All features must be numeric."}, status=400)
return JsonResponse(predict_iris(features))
8. 템플릿
마지막으로 아이리스 예측기의 사용자 인터페이스로 사용할 HTML 템플릿을 생성합니다.
이 템플릿은 다음과 같은 역할을 합니다:
- 앞서 정의한 Django 폼 필드를 렌더링합니다.
- 반응형 폼 입력란을 갖춘 깔끔하고 스타일링된 레이아웃을 제공합니다.
- 예측 결과가 있을 경우 이를 표시합니다.
- 프로그래밍 방식으로 접근을 선호하는 개발자를 위한 API 엔드포인트를 언급합니다.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Iris Predictor</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body { font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 2rem; }
.card { max-width: 540px; padding: 1.25rem; border: 1px solid #e5e7eb; border-radius: 12px; }
.row { display: grid; gap: 0.75rem; grid-template-columns: 1fr 1fr; }
label { font-size: 0.9rem; color: #111827; }
input { width: 100%; padding: 0.5rem; border: 1px solid #d1d5db; border-radius: 8px; }
button { margin-top: 1rem; padding: 0.6rem 0.9rem; border: 0; border-radius: 8px; background: #111827; color: white; cursor: pointer; }
.result { margin-top: 1rem; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 0.75rem; }
.muted { color: #6b7280; }
</style>
</head>
<body>
<h1>Iris Predictor</h1>
<p class="muted">Enter Iris flower measurements to get a prediction.</p>
<div class="card">
<form action="/predict/" method="post">
{% csrf_token %}
<div class="row">
<div>
<label for="id_sepal_length">Sepal length (cm)</label>
{{ form.sepal_length }}
</div>
<div>
<label for="id_sepal_width">Sepal width (cm)</label>
{{ form.sepal_width }}
</div>
<div>
<label for="id_petal_length">Petal length (cm)</label>
{{ form.petal_length }}
</div>
<div>
<label for="id_petal_width">Petal width (cm)</label>
{{ form.petal_width }}
</div>
</div>
<button type="submit">Predict</button>
</form>
{% if submitted and result %}
<div class="result">
Predicted class: {{ result.class_name }}<br />
<div class="muted">
Probabilities:
<ul>
{% for name, p in result.probabilities.items %}
<li>{{ name }}: {{ p|floatformat:3 }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
<p class="muted" style="margin-top:1rem;">
API available at <code>POST /api/predict/</code>
</p>
</body>
</html>
9. 애플리케이션 실행
모든 준비가 완료되었으니, 이제 Django 프로젝트를 실행하고 웹 양식과 API 엔드포인트를 모두 테스트해 보겠습니다.
기본 Django 데이터베이스(관리자, 세션 등)를 설정하려면 다음 명령을 실행하세요:
python manage.py migrate
Django 개발 서버를 시작하세요:
python manage.py runserver
모든 설정이 올바르게 완료되면 다음과 유사한 출력이 표시됩니다:
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
September 09, 2025 - 02:01:27
Django version 5.2.6, using settings 'mlapp.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
웹 양식 인터페이스를 사용하려면 브라우저를 열고 다음 주소로 이동하세요: http://127.0.0.1:8000/.

curl을 사용하여 API에 POST 요청을 보낼 수도 있습니다:
curl -X POST http://127.0.0.1:8000/api/predict/ \
-H "Content-Type: application/json" \
-d '{"sepal_length":5.1,"sepal_width":3.5,"petal_length":1.4,"petal_width":0.2}'
예상 응답:
{
"class_index": 0,
"class_name": "setosa",
"probabilities": {
"setosa": 1.0,
"versicolor": 0.0,
"virginica": 0.0
}
}
10. 테스트
마무리하기 전에 애플리케이션이 예상대로 작동하는지 확인하는 것이 좋습니다. Django는 Python의 unittest 모듈과 통합되는 내장 테스트 프레임워크를 제공합니다.
다음과 같은 간단한 테스트를 몇 가지 만들어 확인해 보겠습니다:
- 홈페이지가 올바르게 렌더링되고 제목이 포함되는지.
- API 엔드포인트가 유효한 예측 응답을 반환하는지.
predictor/tests.py에 다음 코드를 추가하세요:
from django.test import TestCase
from django.urls import reverse
class PredictorTests(TestCase):
def test_home_renders(self):
resp = self.client.get(reverse("home"))
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, "Iris Predictor")
def test_api_predict(self):
url = reverse("predict_api")
payload = {
"sepal_length": 5.0,
"sepal_width": 3.6,
"petal_length": 1.4,
"petal_width": 0.2,
}
resp = self.client.post(url, payload)
self.assertEqual(resp.status_code, 200)
data = resp.json()
self.assertIn("class_name", data)
self.assertIn("probabilities", data)
터미널에서 다음 명령어를 실행하세요:
python manage.py test
다음과 유사한 출력이 표시되어야 합니다:
Found 2 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.758s
OK
Destroying test database for alias 'default'...
이 테스트들이 통과되었다면, Django + 머신러닝 애플리케이션이 엔드투엔드(end-to-end)로 올바르게 작동하고 있다고 확신할 수 있습니다.
요약
Django 프레임워크를 사용하여 머신러닝 애플리케이션을 완성했으며, 모든 구성 요소를 하나의 기능적인 시스템으로 통합했습니다.
모델 훈련 및 저장부터 시작하여 예측을 위한 Django 서비스에 통합했습니다. 또한 사용자 입력을 위한 깔끔한 웹 양식을 구축하고 프로그래밍 방식 접근을 위한 JSON API를 노출했습니다. 더불어 애플리케이션이 안정적으로 실행되도록 자동화된 테스트를 구현했습니다.
이 프로젝트는 아이리스 데이터셋에 초점을 맞췄지만, 동일한 구조를 확장하여 더 복잡한 모델, 더 큰 데이터셋 또는 생산 환경에 바로 적용 가능한 API까지 수용할 수 있어 실제 머신러닝 애플리케이션을 위한 견고한 기반이 됩니다.
'AI Tutorials (AI 학습 자료)' 카테고리의 다른 글
| 생성형 AI 개발자가 되는 4단계 (0) | 2025.11.24 |
|---|---|
| KubeVirt: Kubernetes 환경에서 가상머신을 관리하는 차세대 가상화 플랫폼 (0) | 2025.11.13 |
| 강화학습의 혁명: 320억 파라미터 QwQ-32B가 열어가는 AI 신세계 (0) | 2025.03.07 |
| 오픈 소스 이미지 생성 모델 가이드 (4) | 2025.03.01 |
| Streamlit으로 AI 만들기: 초보자를 위한 쉬운 가이드 (10) | 2025.01.21 |