django Web 개발 - IRIS Prediction

Page content

개요

  • Python Django와 Sklearn을 활용하여 간단한 iris prediction 웹을 만들어본다.

사전준비

  • 머신러닝 기본 이론 및 원리는 어느정도 알고 있다고 가정한다.
  • Django 앱에 대해 어느정도 알고 있다고 가정한다.

무엇을 배우는가?

  • 머신러닝 모델을 활용하여 배포하는 과정을 배운다.

가상환경 설정

  • 가상환경을 생성한다.
$ virtualenv venv
created virtual environment CPython3.9.1.final.0-64 in 475ms
  creator CPython3Posix(dest=/Users/evan/Desktop/django-iris-tutorial/venv, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/Users/evan/Library/Application Support/virtualenv)
    added seed packages: pip==22.1.1, setuptools==62.3.2, wheel==0.37.1
  activators BashActivator,CShellActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator
  • 만들어진 가상환경에 접속한다.
$ source venv/bin/activate
(venv) $
  • 크게 3개의 라이브러리를 설치한다.
    • jupyterlab : 머신러닝 개발 과정을 진행할 에디터로 활용한다.
    • sklearn : 머신러닝 개발 관련 라이브러리이다.
    • django : django 웹 프레임워크 라이브러리이다.
(venv) $ pip install jupyterlab sklearn django

머신러닝 개발

  • iris 데이터를 불러오고 sklearn을 활용하여 모형 개발을 진행한다.
  • 모형 개발 시, 주요 Feature Engineering 과정은 생략한다.

(1) 모형 개발

  • jupyterlab을 실행한다.
(venv) $ python -m jupyterlab
  • 아래와 같이 코드를 입력한다.
# Module 불러오기
from pandas import read_csv
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
import pandas as pd

# 데이터셋 불러오기
df = pd.read_csv("data/iris.csv")

# 독립변수와 종속변수 분리
X = df[['sepal_length','sepal_width','petal_length','petal_width']]
y = df['classification']

# 훈련데이터와 종속데이터 분리
X_train, X_test, Y_train, Y_test = train_test_split(X, y, test_size=0.20, random_state=1)

# 모형 학습
model = SVC(gamma='auto')
model.fit(X_train, Y_train)

# 모형 예측
sepal_length = float(1.5)
sepal_width = float(5)
petal_length = float(4)
petal_width = float(3)

result = model.predict([[sepal_length,sepal_width,petal_length,petal_width]])  # input must be 2D array
print(result)
['Iris-virginica']
/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/sklearn/base.py:445: UserWarning: X does not have valid feature names, but SVC was fitted with feature names
  warnings.warn(

(2) 모형 저장

  • pickle을 통해 모형 저장을 할 수 있다.
pd.to_pickle(model, r'models/svc_model.pickle')

Django 시작

  • django 웹사이트 프로젝트를 시작한다.
(venv) $ django-admin startproject iris
(venv) $ cd iris
(venv) $ python manage.py startapp predict

settings.py

  • iris/settings.py를 열고 아래와 같이 수정한다.
.
.
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'predict', 
]
.
.

urls.py

  • iris/urls.py를 열고 아래와 같이 수정한다.
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('predict.urls', namespace='predict'))
]
  • predict/urls.py를 새로 생성하고 아래와 같이 코드를 추가한다.
from django.urls import path
from . import views

app_name = 'predict'

urlpatterns = [
    path('', views.predict, name='predict'),
]

views.py

  • 이제 predict/views.py에서 predict 함수를 만들어 백엔드 처리를 진행하고, 최종 결괏값을 predict.html로 돌려주는 함수를 구현할 것이다.
from django.shortcuts import render

# Create your views here.
def predict(request):
    return render(request, 'predict.html', {})

predict.html

  • predict/templates 폴더를 만들고, predict.html 파일을 새로 생성한다.
hello
  • predict.html 파일이 잘 열리는지 확인한다.
(venv) iris$ python manage.py runserver 

  • 현재 트리 구조는 아래와 같다.
$ tree -L 2
.
├── db.sqlite3
├── iris
│   ├── __init__.py
│   ├── __pycache__
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── predict
    ├── __init__.py
    ├── __pycache__
    ├── admin.py
    ├── apps.py
    ├── migrations
    ├── models.py
    ├── templates
    ├── tests.py
    ├── urls.py
    └── views.py

6 directories, 14 files

html 파일 추가

  • 크게 2개의 html 파일을 추가한다.

    • base.html, results.html, predict.html
  • base.html 코드 추가는 아래와 같이 한다.

<!doctype html>
<html lang="en">
    <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <title>Iris prediction</title>
    </head>
    <body>


    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="/">Prediction <span class="sr-only">(current)</span></a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/results">DB</a>
                </li>
            </ul>
        </div>
    </nav>

    {% block main %}
    {% endblock %}

    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    </body>
</html>
  • predict.html 파일에 다음과 같이 코드를 추가한다.
{% extends "base.html" %}

{% block main %}
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="exampleModalLabel">Prediction Results</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <h5>Prediction Input:</h5>
                <div>Sepal Length: <span id="sl"></span></div>
                <div>Sepal Width: <span id="sw"></span></div>
                <div>Petal Length: <span id="pl"></span></div>
                <div>Petal width: <span id="pw"></span></div>
                <h5 class="pt-3">Prediction Classification:</h5>
                <div id="prediction"></div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                <a class="btn btn-primary" href="/results" role="button">View DB</a>
            </div>
        </div>
    </div>
</div>

<div class="container pt-5">
    <div class="row justify-content-md-center">
        <div class="col-md-4">
            <h1>Iris Prediction</h1>
            <form action="" id="post-form">
                {% csrf_token %}
                <div class="form-group">
                    <label for="sepal_length">Sepal Length</label>
                    <input type="number" step="0.1" class="form-control" id="sepal_length" placeholder="" required>
                </div>
                <div class="form-group">
                    <label for="Sepal Width">Sepal Width</label>
                    <input type="number" step="0.1" class="form-control" id="sepal_width" placeholder="" required>
                </div>
                <div class="form-group">
                    <label for="petal_length">Petal Length</label>
                    <input type="number" step="0.1" class="form-control" id="petal_length" placeholder="" required>
                </div>
                <div class="form-group">
                    <label for="petal_width">Petal Width</label>
                    <input type="number" step="0.1" class="form-control" id="petal_width" placeholder="" required>
                </div>
                <button type="submit" value="Submit" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">Submit</button>
            </form>
        </div>
    </div>
</div>

<script>
    $(document).on('submit', '#post-form',function(e){
        e.preventDefault();
        $.ajax({
            type:'POST',
            url:'{% url "predict:submit_prediction" %}',
            data:{
                sepal_length:$('#sepal_length').val(),
                sepal_width:$('#sepal_width').val(),
                petal_length:$('#petal_length').val(),
                petal_width:$('#petal_width').val(),
                csrfmiddlewaretoken:$('input[name=csrfmiddlewaretoken]').val(),
                action: 'post'
            },
            success:function(json) {
                document.forms["post-form"].reset();
                document.getElementById("prediction").innerHTML = json['result']
                document.getElementById("sl").innerHTML = json['sepal_length']
                document.getElementById("sw").innerHTML = json['sepal_width']
                document.getElementById("pl").innerHTML = json['petal_length']
                document.getElementById("pw").innerHTML = json['petal_width']
            },
            error : function(xhr,errmsg,err) {

            }
        });
    })
</script>

{% endblock %}
  • predict/urls.py를 열고 아래와 같이 코드를 추가한다.
from django.urls import path
from . import views

app_name = "predict"

urlpatterns = [
    path('', views.predict, name='prediction_page'),
    path('predict/', views.predict_chances, name='submit_prediction'),
    path('results/', views.view_results, name='results'),
]
  • results.html 파일을 추가한다.
{% extends "base.html" %}

{% block main %}
    <div class="container pt-5">
        <div class="row">

        <h1>Prediction Results</h1>

            <table class="table">
                <thead>
                <tr>
                <th scope="col">#</th>
                <th scope="col">Sepal length</th>
                <th scope="col">Sepal width</th>
                <th scope="col">Petal length</th>
                <th scope="col">Petal width</th>
                <th scope="col">Prediction</th>
                </tr>
                </thead>
                <tbody>
                {% for data in dataset %}
                    <tr>
                    <th scope="row">{{ data.id }}</th>
                    <td>{{ data.sepal_length }}</td>
                    <td>{{ data.sepal_width }}</td>
                    <td>{{ data.petal_length }}</td>
                    <td>{{ data.petal_width }}</td>
                    <td>{{ data.classification }}</td>
                    </tr>
                {% endfor %}

                </tbody>
            </table>
        </div>
    </div>
{% endblock %}

predict/views.py

  • 위 파일을 열고 아래와 같이 함수를 추가한다.
from django.shortcuts import render
from django.http import JsonResponse
import pandas as pd 
from .models import PredResults

# Create your views here.
def predict(request):
    return render(request, 'predict.html', {})

def predict_chances(request):

    if request.POST.get('action') == 'post':

        # Receive data from client
        sepal_length = float(request.POST.get('sepal_length'))
        sepal_width = float(request.POST.get('sepal_width'))
        petal_length = float(request.POST.get('petal_length'))
        petal_width = float(request.POST.get('petal_width'))

        # Unpickle model
        model = pd.read_pickle(r"/Users/evan/Desktop/django-iris-tutorial/models/svc_model.pickle")
        # Make prediction
        result = model.predict([[sepal_length, sepal_width, petal_length, petal_width]])

        classification = result[0]

        PredResults.objects.create(sepal_length=sepal_length, sepal_width=sepal_width, petal_length=petal_length,
                                   petal_width=petal_width, classification=classification)

        return JsonResponse({'result': classification, 'sepal_length': sepal_length,
                             'sepal_width': sepal_width, 'petal_length': petal_length, 'petal_width': petal_width},
                            safe=False)

def view_results(request):
    # Submit prediction and show all
    data = {"dataset": PredResults.objects.all()}
    return render(request, "results.html", data)

predict/models.py

  • 위 파일을 열고 아래와 같이 코드를 추가한다.
from django.db import models

# Create your models here.
class PredResults(models.Model):

    sepal_length = models.FloatField()
    sepal_width = models.FloatField()
    petal_length = models.FloatField()
    petal_width = models.FloatField()
    classification = models.CharField(max_length=30)

    def __str__(self):
        return self.classification

predict.admin.py

  • 위 파일을 열고 아래와 같이 코드를 추가한다.
from django.contrib import admin
from .models import PredResults

# Register your models here.
admin.site.register(PredResults)

배포

  • 배포 전, 아래와 같이 터미널에서 명령어를 추가한다.
(venv) iris $ python manage.py makemigrations
(venv) iris $ python manage.py migrate
(venv) iris $ python manage.py createsuperuser
  • 배포 전, 아래와 같이 배포가 된 것을 확인한다.
(venv) iris $ python manage.py runserver

References