데이터사이언스/추천시스템

맥주 추천시스템 구현 - 7. 웹 구현하기(Django)

ghtis1798 2021. 3. 1. 01:35

 

🔨URL 설정하기

우선 프로젝트 폴더의 urls.py를 수정합니다.

현재 프로젝트 이름은 myproject입니다.

''로 경로가 없으면 beer 앱의 경로로 넘어갑니다.

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('beer.urls')),
]

이번엔 beer 앱의 urls.py를 수정합니다.

'' 경로로 접근 시 views.py의 index 함수를 실행합니다.

ver1, ver2 역시 마찬가지로 views.py의 ver1, ver2 함수를 실행합니다.

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index ,name = 'index'),
    path('ver1', views.ver1, name = 'ver1'),
    path('ver2', views.ver2, name = 'ver2'),
]

views.py에 index(), ver1(), ver2()함수를 작성하기 전에 우선 templates 폴더와 렌더링 할 html 파일을 생성했습니다.

📝templates 작성하기

1. index.html 작성

우선 홈 화면에 해당하는 index.html 파일을 작성하겠습니다.

홈 화면에 들어갈 이미지는 beer > static > img 폴더 안에 저장합니다.

index.html 코드입니다.

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>

    <meta charset="UTF-8">
    <title>🍺Prost!</title>
    <style>
        body{
        }
        div{
            text-align: center;
        }
    </style>
</head>
<body>
    <div><img src= "{% static 'img/home.png' %}"</div>
    <h1>🍺Prost!</h1>
    <div id = "top">추천 유형을 선택해주세요.</div><br>
    <div id = "recommendation">
        <a href="ver1">1. 맥주간 유사도 기반 추천</a><br><br>
        <a href="ver2">2. 내 평점이 반영된 맥주 추천</a>
    </div>
</body>
</html>

index.html 파일을 작성했으니 views.py에서 html 파일을 렌더링해주는 함수를 작성하겠습니다.

from django.shortcuts import render

def index(request):
    return render(request, 'beer/index.html')

def ver1(request):
    return render(request, 'beer/ver1.html')

def ver2(request):
    return render(request, 'beer/ver2.html')

python manage.py runserver로 실행합니다.

127.0.0.8000 에 접속하면 index.html로 연결되고 다음과 같은 페이지가 뜨게됩니다.

2. ver1.html 작성

ver1은 맥주간 유사도에 기반한 추천입니다.

따라서 input으로 받을 값은 맥주 종류와 맥주 선택시 중요시하는 취향입니다.

둘 다 Select box로 구현했으며 선택한 취향에 따라 가중치를 부여하여 계산했습니다.

선택할 취향의 종류는 향(Aroma), 맛(Flavor), 목넘김(Mouthfell)입니다.

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>

    <meta charset="UTF-8">
    <title>🍺Ver1)Recommendation</title>
    <style>
        h1 { text-align: center; color: white;}
        img {
            display: block; margin: 0px auto;
            width:40%;
        }
        select {
            width: 200px;
            padding: .5em .5em;
            border: 1px solid #999;
            font-family: inherit;
            background: url('arrow.jpg') no-repeat 95% 50%;
            border-radius: 0px;
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
        }
        input[type=text] {
            width : 190px;
            height : 30px;
            font-size : 20px;
        }
        body{
            background-image:url("{% static 'img/맥주배경.jpg' %}");
            background-size:100% 200%;
        }
    </style>
</head>
<body>
<div style="background-color:#0d47a1;">
    <h1>좋아하는 맥주와 중요시 하는 취향을 선택해주세요.</h1>
</div>
    <div id = 'contents' style="text-align:center;">
        <img id="img1" src="static/img/Beer.jfif">
        <div>
            <form method="POST">
                <br>
                {% csrf_token %}
                아이디 :
                <input type="text" name="name"><br><br>

                맥주 :
                <select name="beer">
                    {% for tmp in beer_list %}
                        <option value='{{tmp}}'>{{tmp}}</option>
                    {% endfor %}
                </select><br><br>
                취향 :
                <select name="detail">
                    <option value="Flavor">맛</option>
                    <option value="Aroma">향</option>
                    <option value="Mouthfeel">목넘김</option>
                </select><br><br>
                <input class="btn btn-success" type="submit" value="Pick!">
            </form>
        </div>
    </div>
</body>
</html>

3. ver1_result.html 작성

ver1_result.html은 ver1.html에서 요청한 값에 대해 추천된 맥주들을 표시하는 페이지입니다.

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
<!--    <link rel= "stylesheet" type="text/css" href="{% static 'css/ver1_result.css' %}">-->

    <meta charset="UTF-8">
    <title>🍺Ver1)Recommendation</title>
    <style>
        img {
        display: block; margin: 0px auto;
        max-width:10%; max-height:10%;
        }
        h1 { text-align: center; color: white;}
        h2 { text-align: center; }
        h3 { text-align: center; }
    </style>
</head>
<body>

<div style="background-color:#0d47a1;">
    <h1>첫 번째 추천 맥주는 {{ result.0 }} 입니다.</h1>
</div>
<img src="static/img/{{ result.0 }}.jfif" alt="사진은 수집중입니다😢">

<h2>추천 맥주 유형 </h2>
<h3>{{ category.0 }}<h3>
<h2>어울리는 요리</h2>
<h3>{{ food.0 }}</h3>
<div id='myDiv'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")
    data = [
      {
      type: 'scatterpolar',
      r: data['cluster3'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Good',
      marker : { color : 'salmon'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster2'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'SoSo',
      marker : { color : 'blue'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster1'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Sad',
      marker : { color : 'skyblue'}
      },
      {
      type: 'scatterpolar',
      r: data['beer_cluster1'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: data['beer_name'][0],
      marker : { color : 'green'}
      },
    ]

    layout = {
      polar: {
        radialaxis: {
          visible: true,
          range: [0, 1]
        }
      },
      title: {
        text:'추천 맥주 클러스터 유형',
        font: {
          size: 24
        },
        x : 0.47
      }
    }

Plotly.newPlot("myDiv", data, layout)
</script>

<div id='line1'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    var trace1 = {
      x: data['tmp_year'][0],
      y: data['tmp_ratings'][0],
      type: 'scatter'
    }

    var result = [trace1];

    layout = {
      title: {
        text:'추천 맥주의 연도별 평점 트렌드',
        font: {
          size: 24
        },
        x : 0.5
      }
    }
    Plotly.newPlot('line1', result, layout);
</script>

<div style="background-color:#0d47a1;">
    <h1>두 번째 추천 맥주는 {{ result.1 }} 입니다.</h1>
</div>
<img src="static/img/{{ result.1 }}.jfif" alt="사진은 수집중입니다😢">

<h2>추천 맥주 유형 </h2>
<h3>{{ category.1 }}<h3>
<h2>어울리는 요리</h2>
<h3>{{ food.1 }}</h3>
<div id='myDiv2'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    data = [
      {
      type: 'scatterpolar',
      r: data['cluster3'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Good',
      marker : { color : 'salmon'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster2'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'SoSo',
      marker : { color : 'blue'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster1'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Sad',
      marker : { color : 'skyblue'}
      },
      {
      type: 'scatterpolar',
      r: data['beer_cluster2'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: data['beer_name'][1],
      marker : { color : 'green'}
      }
    ]

    layout = {
      polar: {
        radialaxis: {
          visible: true,
          range: [0, 1]
        }
      },
      title: {
        text:'추천 맥주 클러스터 유형',
        font: {
          size: 24
        },
        x : 0.47
      }
    }

Plotly.newPlot("myDiv2", data, layout)
</script>

<div id='line2'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    var trace1 = {
      x: data['tmp_year'][1],
      y: data['tmp_ratings'][1],
      type: 'scatter'
    }

    var result = [trace1];

    layout = {
        title: {
        text:'추천 맥주의 연도별 평점 트렌드',
        font: {
          size: 24
        },
        x : 0.5
        }
    }
    Plotly.newPlot('line2', result, layout);
</script>


<div style="background-color:#0d47a1;">
    <h1>세 번째 추천 맥주는 {{ result.2 }} 입니다.</h1>
</div>
<img src="static/img/{{ result.2 }}.jfif" alt="사진은 수집중입니다😢">
<h2>추천 맥주 유형 </h2>
<h3>{{ category.2 }}<h3>
<h2>어울리는 요리</h2>
<h3>{{ food.2 }}</h3>
<div id='myDiv3'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    data = [
      {
      type: 'scatterpolar',
      r: data['cluster3'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Good',
      marker : { color : 'salmon'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster1'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Sad',
      marker : { color : 'skyblue'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster2'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'SoSo',
      marker : { color : 'blue'}
      },
      {
      type: 'scatterpolar',
      r: data['beer_cluster3'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: data['beer_name'][2],
      marker : { color : 'green'}
      },
    ]

    layout = {
      polar: {
        radialaxis: {
          visible: true,
          range: [0, 1]
        }
      },
      title: {
        text:'추천 맥주 클러스터 유형',
        font: {
          size: 24
        },
        x : 0.47
      }
    }

Plotly.newPlot("myDiv3", data, layout)
</script>

<div id='line3'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    var trace1 = {
      x: data['tmp_year'][2],
      y: data['tmp_ratings'][2],
      type: 'scatter'
    }

    var result = [trace1];

    layout = {
      title: {
        text:'추천 맥주의 연도별 평점 트렌드',
        font: {
          size: 24
        },
        x : 0.5
      }
    }
    Plotly.newPlot('line3', result, layout);
</script>
</body>
</html>

4. ver2.html 작성

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>

    <meta charset="UTF-8">
    <title>🍺Ver2)Recommendation</title>
    <style>
        h1 { text-align: center; color: white;}
        img {
            display: block; margin: 0px auto;
            width:40%;
        }
        select {
        width: 200px;
        padding: .8em .5em;
        border: 1px solid #999;
        font-family: inherit;
        background: url('arrow.jpg') no-repeat 95% 50%;
        border-radius: 0px;
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;
        }
        input[type=text] {
            width : 190px;
            height : 30px;
            font-size : 20px;
        }
        #user {
        }
        body{
            background-image:url("{% static 'img/맥주배경.jpg' %}");
            background-size:100% 200%;
        }
    </style>
</head>
<body>

<div style="background-color:#0d47a1;">
    <h1>좋아하는 맥주와 평점을 선택해주세요.</h1>
</div>

<div style="text-align:center;">
    아이디 : <input type="text" name="name" text-align="center"><br><br>
</div>

<img src="static/img/Beer.jfif" alt="추천맥주">
<div id="user">
    <div style="text-align:center; margin:10px;">
    <form method="POST">
        {% csrf_token %}
        <div style="float:left; margin:5px;">
        맥주 :
        <select name="beer1">
            {% for tmp in beer_list %}
                <option value='{{tmp}}'>{{tmp}}</option>
            {% endfor %}
        </select><br>
        평점 :
        <select name="rating1">
            <option value=1>1</option>
            <option value=2>2</option>
            <option value=3 selected>3</option>
            <option value=4>4</option>
            <option value=5>5</option>
        </select><br><br>
        </div>

        <div style="float:left; margin:5px;">
        맥주 :
        <select name="beer2">
            {% for tmp in beer_list %}
                <option value='{{tmp}}'>{{tmp}}</option>
            {% endfor %}
        </select><br>
        평점 :
        <select name="rating2">
            <option value=1>1</option>
            <option value=2>2</option>
            <option value=3 selected>3</option>
            <option value=4>4</option>
            <option value=5>5</option>
        </select><br><br>
        </div>

        <div style="float:left; margin:5px;">
        맥주 :
        <select name="beer3">
            {% for tmp in beer_list %}
                <option value='{{tmp}}'>{{tmp}}</option>
            {% endfor %}
        </select><br>
        평점 :
        <select name="rating3">
            <option value=1>1</option>
            <option value=2>2</option>
            <option value=3 selected>3</option>
            <option value=4>4</option>
            <option value=5>5</option>
        </select><br><br>
        </div>

        <div style="float:left; margin:5px;">
        맥주 :
        <select name="beer4">
            {% for tmp in beer_list %}
                <option value='{{tmp}}'>{{tmp}}</option>
            {% endfor %}
        </select><br>
        평점 :
        <select name="rating4">
            <option value=1>1</option>
            <option value=2>2</option>
            <option value=3 selected>3</option>
            <option value=4>4</option>
            <option value=5>5</option>
        </select><br><br>
        </div>

        <div style="float:left; margin:5px;">
        맥주 :
        <select name="beer5">
            {% for tmp in beer_list %}
                <option value='{{tmp}}'>{{tmp}}</option>
            {% endfor %}
        </select><br>
        평점 :
        <select name="rating5">
            <option value=1>1</option>
            <option value=2>2</option>
            <option value=3 selected>3</option>
            <option value=4>4</option>
            <option value=5>5</option>
        </select>
        </div>
            <br><br><br>
        <input class="btn btn-success" type="submit" value="Pick!">
    </form>
    </div>
</div>
</div>
</body>
</html>

5. ver2_result.html 작성

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
    <link rel= "stylesheet" type="text/css" href="{% static 'css/ver2_result.css' %}">
    <meta charset="UTF-8">
    <title>🍺Ver2)Recommendation</title>
    <style>
        img {
        display: block; margin: 0px auto;
        max-width:10%; max-height:10%;
        }
        h1 { text-align: center; color: white;}
        h2 { text-align: center;}
        h3 { text-align: center;}
        #header {
            background-color:lightgrey;
            height:100px;
        }
    </style>
</head>
<body>

<div style="background-color:#0d47a1;">
    <h1>첫 번째 추천 맥주는 {{ result.0 }} 입니다.</h1>
</div>
<img src="/static/img/{{ result.0 }}.jfif" alt="사진은 수집중입니다😢">
<h2>추천 맥주 유형 </h2>
<h3>{{ category.0 }}<h3>
<h2>어울리는 요리</h2>
<h3>{{ food.0 }}</h3>
<div id='myDiv'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    data = [
      {
      type: 'scatterpolar',
      r: data['cluster3'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Good',
      marker : { color : 'salmon'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster2'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'SoSo',
      marker : { color : 'blue'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster1'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Sad',
      marker : { color : 'skyblue'}
      },
      {
      type: 'scatterpolar',
      r: data['beer_cluster1'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: data['beer_name'][0],
      marker : { color : 'green'}
      },
    ]

    layout = {
      polar: {
        radialaxis: {
          visible: true,
          range: [0, 1]
        }
      },
      title: {
        text:'추천 맥주 클러스터 유형',
        font: {
          size: 24
        },
        x : 0.47
      }
    }


Plotly.newPlot("myDiv", data, layout)
</script>
<div id='line1'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    var trace1 = {
      x: data['tmp_year'][0],
      y: data['tmp_ratings'][0],
      type: 'scatter'
    }

    var result = [trace1];
    layout = {
      title: {
        text:'추천 맥주의 연도별 평점 트렌드',
        font: {
          size: 24
        },
        x : 0.5
      }
    }
    Plotly.newPlot('line1', result, layout);
</script>


<div style="background-color:#0d47a1;">
    <h1>두 번째 추천 맥주는 {{ result.1 }} 입니다.</h1>
</div>
<img src="/static/img/{{ result.1 }}.jfif" alt="사진은 수집중입니다😢">
<h2>추천 맥주 유형 </h2>
<h3>{{ category.1 }}<h3>
<h2>어울리는 요리</h2>
<h3>{{ food.1 }}</h3>
<div id='myDiv2'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    data = [
      {
      type: 'scatterpolar',
      r: data['cluster3'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Good',
      marker : { color : 'salmon'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster2'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'SoSo',
      marker : { color : 'blue'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster1'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Sad',
      marker : { color : 'skyblue'}
      },
      {
      type: 'scatterpolar',
      r: data['beer_cluster2'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: data['beer_name'][1],
      marker : { color : 'green'}
      }
    ]

    layout = {
      polar: {
        radialaxis: {
          visible: true,
          range: [0, 1]
        }
      },
      title: {
        text:'추천 맥주 클러스터 유형',
        font: {
          size: 24
        },
        x : 0.47
      }
    }

Plotly.newPlot("myDiv2", data, layout)
</script>
<div id='line2'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    var trace1 = {
      x: data['tmp_year'][1],
      y: data['tmp_ratings'][1],
      type: 'scatter'
    }

    var result = [trace1];

    layout = {
      title: {
        text:'추천 맥주의 연도별 평점 트렌드',
        font: {
          size: 24
        },
        x : 0.5
      }
    }
    Plotly.newPlot('line2', result, layout);
</script>

<div style="background-color:#0d47a1;">
    <h1>세 번째 추천 맥주는 {{ result.2 }} 입니다.</h1>
</div>
<img src="/static/img/{{ result.2 }}.jfif" alt="사진은 수집중입니다😢">
<h2>추천 맥주 유형 </h2>
<h3>{{ category.2 }}<h3>
<h2>어울리는 요리</h2>
<h3>{{ food.2 }}</h3>
<div id='myDiv3'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    data = [
      {
      type: 'scatterpolar',
      r: data['cluster3'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Good',
      marker : { color : 'salmon'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster1'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'Sad',
      marker : { color : 'skyblue'}
      },
      {
      type: 'scatterpolar',
      r: data['cluster2'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: 'SoSo',
      marker : { color : 'blue'}
      },
      {
      type: 'scatterpolar',
      r: data['beer_cluster3'],
      theta: ['Aroma', 'Appearance', 'Flavor','Mouthfeel', 'Overall'],
      fill: 'toself',
      name: data['beer_name'][2],
      marker : { color : 'green'}
      },
    ]

    layout = {
      polar: {
        radialaxis: {
          visible: true,
          range: [0, 1]
        }
      },
      title: {
        text:'추천 맥주 클러스터 유형',
        font: {
          size: 24
        },
        x : 0.47
      }
    }

Plotly.newPlot("myDiv3", data, layout)
</script>
<div id='line3'></div>
<script>
    var data = JSON.parse("{{ targetJson|escapejs }}")

    var trace1 = {
      x: data['tmp_year'][2],
      y: data['tmp_ratings'][2],
      type: 'scatter'
    }

    var result = [trace1];

    layout = {
      title: {
        text:'추천 맥주의 연도별 평점 트렌드',
        font: {
          size: 24
        },
        x : 0.5
      }
    }
    Plotly.newPlot('line3', result, layout);
</script>
</body>
</html>

📝views.py 작성하기

views.py에는 로직을 담당하는 코드들이 들어갑니다.

실질적인 추천시스템을 구현한 내용이 들어가므로 필요한 파일들을 포함시키겠습니다.

추가로 수집한 맥주 이미지들도 static > img 폴더안에 넣어두었습니다.

구현했던 함수들과 필요한 라이브러리도 추가했습니다.

from django.shortcuts import render

import pandas as pd
import numpy as np
import json
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics import mean_squared_error

# 우리가 예측한 평점과 실제 평점간의 차이를 MSE로 계산
def get_mse(pred, actual):
    # 평점이 있는 실제 영화만 추출
    pred = pred[actual.nonzero()].flatten()
    actual = actual[actual.nonzero()].flatten()
    return mean_squared_error(pred, actual)


# 특정 맥주와 비슷한 유사도를 가지는 맥주 Top_N에 대해서만 적용 -> 시간오래걸림
def predict_rating_topsim(ratings_arr, item_sim_arr, n=20):
    # 사용자-아이템 평점 행렬 크기만큼 0으로 채운 예측 행렬 초기화
    pred = np.zeros(ratings_arr.shape)

    # 사용자-아이템 평점 행렬의 맥주 개수만큼 루프
    for col in range(ratings_arr.shape[1]):
        # 유사도 행렬에서 유사도가 큰 순으로 n개의 데이터 행렬의 인덱스 반환
        top_n_items = [np.argsort(item_sim_arr[:, col])[:-n - 1:-1]]
        # 개인화된 예측 평점 계산 : 각 col 맥주별(1개), 2496 사용자들의 예측평점
        for row in range(ratings_arr.shape[0]):
            pred[row, col] = item_sim_arr[col, :][top_n_items].dot(
                ratings_arr[row, :][top_n_items].T)
            pred[row, col] /= np.sum(item_sim_arr[col, :][top_n_items])

    return pred

# 사용자가 안 먹어본 맥주를 추천하자.
def get_not_tried_beer(ratings_matrix, userId):
    # userId로 입력받은 사용자의 모든 맥주 정보를 추출해 Series로 반환
    # 반환된 user_rating은 영화명(title)을 인덱스로 가지는 Series 객체
    user_rating = ratings_matrix.loc[userId, :]

    # user_rating이 0보다 크면 기존에 관란함 영화.
    # 대상 인덱스를 추출해 list 객체로 만듦
    tried = user_rating[user_rating > 0].index.tolist()

    # 모든 맥주명을 list 객체로 만듦
    beer_list = ratings_matrix.columns.tolist()

    # list comprehension으로 tried에 해당하는 영화는 beer_list에서 제외
    not_tried = [beer for beer in beer_list if beer not in tried]

    return not_tried

# 예측 평점 DataFrame에서 사용자 id 인덱스와 not_tried로 들어온 맥주명 추출 후
# 가장 예측 평점이 높은 순으로 정렬
def recomm_beer_by_userid(pred_df, userId, not_tried, top_n):
    recomm_beer = pred_df.loc[userId, not_tried].sort_values(ascending=False)[:top_n]
    return recomm_beer

# 평점, Aroma, Flavor, Mouthfeel 중 피처 선택 후 유사도 계산
def recomm_feature(df, col):
    feature = col
    ratings = df[['아이디','맥주', feature]]

    # 피벗 테이블을 이용해 유저-아이디 매트릭스 구성
    ratings_matrix = ratings.pivot_table(feature, index='아이디', columns='맥주')
    ratings_matrix.head(3)
    # fillna함수를 이용해 Nan처리
    ratings_matrix = ratings_matrix.fillna(0)

    # 유사도 계산을 위해 트랜스포즈
    ratings_matrix_T = ratings_matrix.transpose()

    # 아이템-유저 매트릭스로부터 코사인 유사도 구하기
    item_sim = cosine_similarity(ratings_matrix_T, ratings_matrix_T)

    # cosine_similarity()로 반환된 넘파이 행렬에 영화명을 매핑해 DataFrame으로 변환
    item_sim_df = pd.DataFrame(data=item_sim, index=ratings_matrix.columns,
                              columns=ratings_matrix.columns)

    return item_sim_df

# 해당 맥주와 유사한 유사도 5개 추천
def recomm_beer(item_sim_df, beer_name):
    # 해당 맥주와 유사도가 높은 맥주 5개만 추천
    return item_sim_df[beer_name].sort_values(ascending=False)[1:4

1. ver1 함수 작성하기

ver1에서는 클러스터링 및 EDA과정에서 얻었던 결과값을 불러옵니다.

그리고 추천시스템에 해당하는 로직만 계산하도록 구현했습니다.

POST로부터 프론트에서 입력을 받아오고,

추천된 결과를 ver1_result.html로 return하도록 작성했습니다.

    beer_list = pd.read_csv('맥주이름.csv', encoding='utf-8', index_col=0)
    beer_year = pd.read_csv('맥주_연도별평점.csv', encoding='utf-8', index_col=0)
    ratings = pd.read_csv('정제된데이터.csv', encoding='utf-8', index_col=0)
    cluster_3 = pd.read_csv('대표군집클러스터링.csv', encoding='utf-8', index_col=0)
    cluster_all = pd.read_csv('전체맥주클러스터링.csv', encoding='utf-8', index_col=0)
    beer_data = pd.read_csv('맥주_cbf_data.csv', encoding='utf-8', index_col=0)
    beer_list = beer_list['맥주']
    cluster_3 = cluster_3.values

    if request.method == 'POST':
        beer_name = request.POST.get('beer', '')
        detail = request.POST.get('detail', '')
        df_aroma = recomm_feature(ratings, 'Aroma')
        df_flavor = recomm_feature(ratings, 'Flavor')
        df_mouthfeel = recomm_feature(ratings, 'Mouthfeel')

        if detail=='Aroma':
            df = df_aroma*0.8 + df_flavor*0.1 + df_mouthfeel*0.1
        if detail=='Flavor':
            df = df_aroma*0.1 + df_flavor*0.8 + df_mouthfeel*0.1
        if detail=='Mouthfeel':
            df = df_aroma*0.1 + df_flavor*0.1 + df_mouthfeel*0.8

        result = recomm_beer(df, beer_name)
        result = result.index.tolist()

        # 클러스터링 결과
        tmp_cluster=[]
        category=[]
        food=[]
        for i in range(3):
            target = cluster_all[cluster_all['맥주']==result[i]]
            target = target[['Aroma', 'Appearance', 'Flavor', 'Mouthfeel', 'Overall']]
            target = target.values[0]
            tmp_cluster.append(target)

            try :
                category.append(beer_data[beer_data['맥주이름']==result[i]]['Main Category'].values[0])
                food.append(beer_data[beer_data['맥주이름']==result[i]]['Paring Food'].values[0])
            except :
                category.append('수집되지 않았습니다.')
                food.append('수집되지 않았습니다.')
        # 연도별 평점 결과
        tmp_year = []
        tmp_ratings = []
        for i in range(3):
            target = beer_year[beer_year['맥주']==result[i]]
            target_year = target['년'].tolist()
            target_rating = target['평점'].tolist()
            tmp_year.append(target_year)
            tmp_ratings.append(target_rating)

        # 넘겨줄 데이터 Json 변환
        targetdict = {
            'beer_name' : result,
            'beer_cluster1' : tmp_cluster[0].tolist(),
            'beer_cluster2' : tmp_cluster[1].tolist(),
            'beer_cluster3' : tmp_cluster[2].tolist(),
            'cluster1' : cluster_3[0].tolist(),
            'cluster2' : cluster_3[1].tolist(),
            'cluster3' : cluster_3[2].tolist(),
            'tmp_year' : tmp_year,
            'tmp_ratings' : tmp_ratings
        }

        targetJson = json.dumps(targetdict)

        return render(request, 'beer/ver1_result.html',
                   {'result':result, 'beer_list':beer_list,'targetJson':targetJson,
                    'category':category, 'food':food})
    else:
        return render(request, 'beer/ver1.html', {'beer_list':beer_list})

2. ver2 함수 작성하기

def ver2(request):
    beer_list = pd.read_csv('맥주이름.csv', encoding='utf-8', index_col=0)
    beer_year = pd.read_csv('맥주_연도별평점.csv', encoding='utf-8', index_col=0)
    ratings = pd.read_csv('정제된데이터.csv', encoding='utf-8', index_col=0)
    cluster_3 = pd.read_csv('대표군집클러스터링.csv', encoding='utf-8', index_col=0)
    cluster_all = pd.read_csv('전체맥주클러스터링.csv', encoding='utf-8', index_col=0)
    beer_data = pd.read_csv('맥주_cbf_data.csv', encoding='utf-8', index_col=0)
    beer_list = beer_list['맥주']
    cluster_3 = cluster_3.values

    if request.method == 'POST':
        name = request.POST.get('name', '')
        beer = []
        rating = []
        for i in range(1,6):
            beer.append(request.POST.get('beer'+str(i), ''))
            rating.append((request.POST.get('rating'+str(i), '')))

        for i in range(len(beer)):
            tmp = []
            tmp.append(name)
            tmp.append(beer[i])
            tmp.append(float(rating[i]))
            tmp = pd.DataFrame(data=[tmp], columns=['아이디','맥주','평점'])
            ratings = pd.concat([ratings, tmp])

        uname = name
        ratings_matrix = ratings.pivot_table('평점', index='아이디', columns='맥주')
        ratings_matrix = ratings_matrix.fillna(0)
        ratings_matrix_T = ratings_matrix.transpose()

        item_sim = cosine_similarity(ratings_matrix_T, ratings_matrix_T)
        item_sim_df = pd.DataFrame(data=item_sim, index=ratings_matrix.columns,
                                   columns=ratings_matrix.columns)

        # top_n과 비슷한 유저들만 추천에 사용
        ratings_pred = predict_rating_topsim(ratings_matrix.values, item_sim_df.values, n=5)
        # 계산된 예측 평점 데이터는 DataFrame으로 재생성
        ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index=ratings_matrix.index,
                                           columns=ratings_matrix.columns)

        # 유저가 먹지 않은 맥주이름 추출
        not_tried = get_not_tried_beer(ratings_matrix, uname)
        # 아이템 기반의 최근접 이웃 CF로 맥주 추천
        recomm_beer = recomm_beer_by_userid(ratings_pred_matrix, uname, not_tried, top_n=3)
        recomm_beer = pd.DataFrame(data=recomm_beer.values, index=recomm_beer.index,
                                   columns=['예측평점'])
        # 추천 결과로 나온 맥주이름만 추출
        result = recomm_beer.index.tolist()

        # 클러스터링 결과
        tmp_cluster = []
        category = []
        food = []
        for i in range(3):
            target = cluster_all[cluster_all['맥주'] == result[i]]
            target = target[['Aroma', 'Appearance', 'Flavor', 'Mouthfeel', 'Overall']]
            target = target.values[0]
            tmp_cluster.append(target)

            try :
                category.append(beer_data[beer_data['맥주이름']==result[i]]['Main Category'].values[0])
                food.append(beer_data[beer_data['맥주이름']==result[i]]['Paring Food'].values[0])
            except :
                category.append('수집되지 않았습니다.')
                food.append('수집되지 않았습니다.')

        tmp_year = []
        tmp_ratings = []
        for i in range(3):
            target = beer_year[beer_year['맥주'] == result[i]]
            target_year = target['년'].tolist()
            target_rating = target['평점'].tolist()
            tmp_year.append(target_year)
            tmp_ratings.append(target_rating)

        targetdict = {
            'beer_name': result,
            'beer_cluster1': tmp_cluster[0].tolist(),
            'beer_cluster2': tmp_cluster[1].tolist(),
            'beer_cluster3': tmp_cluster[2].tolist(),
            'cluster1': cluster_3[0].tolist(),
            'cluster2': cluster_3[1].tolist(),
            'cluster3': cluster_3[2].tolist(),
            'tmp_year': tmp_year,
            'tmp_ratings': tmp_ratings
        }

        targetJson = json.dumps(targetdict)

        return render(request, 'beer/ver2_result.html',
                      {'result': result, 'beer_list': beer_list,'targetJson': targetJson,
                       'category':category, 'food':food})

    else:
        return render(request, 'beer/ver2.html', {'beer_list': beer_list})

✨구현 결과

홈 화면

Ver1) 맥주간 유사도 기반 추천 화면

Ver1_result) 추천 결과 화면

 

Ver2) 개인의 평점이 반영된 맥주 추천 화면

Ver2_result) 추천 결과 화면

 

다음 포스팅에서는 웹을 Pythonanywhere에 배포해보려고 합니다.