[Project] Seoul FP-Weather - (3) Deployment & Conclusion

Kyung Jae, Cheong·2022년 11월 7일
0

개인프로젝트

목록 보기
7/17
post-custom-banner

서울시 월별 기상정보로 서울시 식중독 발생 환자 수를 예측하는 프로그램 Seoul FP-Weather 개발 프로젝트 진행과정 정리 및 회고. (3) 대시보드 및 웹페이지 구현, 배포 과정

Seoul FP-Weather

  • App link = Seoul-FP-Weather (Koyeb 배포)
    • 예측 모델 정상 작동 확인 완료
    • Heroku를 통해 배포하였으나 2022년 11월 28일부터 Heroku 무료 요금제는 더 이상 제공되지 않으므로 Koyep을 통해 재배포 실시

(2023 수정사항)

  • 기존의 프로그램의 경우 Heroku를 통해서 배포를 진행했었으나, heroku 정책상 2022년 11월 28일부터 Heroku 무료 요금제는 더 이상 제공되지 않기 때문에 Application Error가 발생함.
  • 이에 Heroku와 동일하게 Gihub Repository 연동 방식이 가능한 Koyeb을 통해 재배포를 진행하였음.
    • 재배포한 웹사이트에서 예측 모델이 정상적으로 구동됨을 확인 완료함.
    • 대체 링크를 추가하여 수정사항을 반영하였음.

서울시 월별 날씨기반
식중독 환자수 예측
Seoul Food-Posisoning
Prediction by Weather

  • 서울시 월간 기상정보를 기반으로 서울시 월간 식중독 발생 환자 수를 예측하는 AI 머신 러닝 프로그램

  • Tech Stack

00. 프로젝트 개요

필수 포함 요소

  • 자유주제데이터 파이프라인 구축API 서비스 개발
  • 데이터베이스 구축 (Pull & Store)
  • API 서비스 개발 (Machine Learning & Frond-end)
  • 데이터 분석용 대시보드 개발 및 배포

프로젝트 목차

  • Introduction 서론
    • Food-Poisoning 식중독
    • Seoul FP-Weather 프로그램소개
  • Database 데이터베이스
    • Data Pipeline 파이프라인
    • Database DB구축
  • Modeling 모델링
    • Data Import 데이터 불러오기
    • Regression Modeling 회귀 모델
    • Object Encoding 객체 부호화
  • Deployment 배포
    • Prediction 평년값 예측
    • Dashboard 대시보드
    • Web Deployment 웹 배포
  • Conclusion 결론
    • Applications 활용방안
    • Limitations 한계점
    • Takeaway 핵심, 느낀점

Data Pipeline

04. Deployment 배포

  • 머신러닝에도 이용했던 서울 월별 기상요인 및 식중독 환자 수 데이터는 식중독 환자 발생 트렌드를 확인하기위한 대시보드를 제작하는데 이용하였음.

  • 서울 월별,계절별 기후평년값 데이터는 pickle 라이브러리를 통해 불러온 모델을 통해 예측을 실시한 후에 예측에 대한 대시보드를 제작하는데 이용하였음.

  • Bootstrap template을 기반으로 HTML5, CSS3를 통해 웹페이지를 구현하였고, 모바일 버젼에서도 편하게 볼 수 있게끔 웹페이지를 최적화하는 작업도 수행하였음.

4-1. Prediction 평년값 예측

STEP0. Library Import

import numpy as np
import pandas as pd
import pickle
import os
import sys
import psycopg2
from dotenv import load_dotenv

STEP1. DB - local 연결

#dotenv 로딩
load_dotenv(verbose=True)
'''
True
'''
#env 파일로부터 변수 추출
HOST = os.getenv('postgre_host')
PASSWORD = os.getenv('postgre_password')

DATABASE = 'postgredb'
USERNAME = 'kjcheong'
PORT = 5432
#연결
try:
    conn = psycopg2.connect(
        host=HOST,
        port=PORT,
        database=DATABASE,
        user=USERNAME,
        password = PASSWORD)
    cur = conn.cursor()
    print('connection success to DB')
except:
    print('connection failure to DB')
    sys.exit()
'''
connection success to DB
'''

STEP2. DB data를 DataFrame으로 저장

  • pandas.io.sql 모듈 이용
#DB를 dataframe으로 저장
import pandas.io.sql as psql
month_avg = psql.read_sql("SELECT * FROM w_avg_month", conn)
month_avg

season_avg = psql.read_sql("SELECT * FROM w_avg_season", conn)
season_avg

#연결 종료
conn.close()
  • 예측에 쓰일 변수만 남기기
#예측에 쓰일 변수만 남기기
month_var = month_avg.iloc[:,2:]
display(month_var)
season_var = season_avg.iloc[:,1:]
display(season_var)

STEP3. 모델 복호화 및 예측

#모델 복호화
model = pickle.load(open("model.pkl", "rb"))
#예측
predict_month = model.predict(month_var).round(0)
predict_season = model.predict(season_var).round(0)
print(predict_month)
print(predict_season)
'''
[ 94.  34.  16.  14. 142. 222. 162. 171. 124.  80.  68. 149.]
[ 72. 188.  54.  52.]
'''

STEP4. 기존 DataFrame에 예측 column 추가

#기존 dataframe에 column 추가
month_pred = month_avg.copy()
season_pred = season_avg.copy()
month_pred['predict_patient'] = predict_month.astype(int)
season_pred['predict_patient'] = predict_season.astype(int)
display(month_pred)
display(season_pred)

STEP5. 대시보드를 위한 csv파일 저장

#csv file로 저장
month_pred.to_csv('csv/pred-month.csv', index=False)
season_pred.to_csv('csv/pred-season.csv', index=False)

4-2. Dashboard 대시보드

  • 대시보드 제작시에 PostgreSQL로 연결하여 Data를 import를 해보긴 했으나, AWS free-tier의 월별 한도량이 초과되어 요금이 발생한 관계로... 이전 단계에서부터 대시보드용 csv 데이터를 생성하여 csv를 로딩하는 방식으로 대시보드의 data를 import하여 제작하고 배포하였음.
    • AWS free-tier 대신 무료로 데이터베이스를 사용할 수 있는 클라우드 서비스로 데이터베이스를 구축했다면 PostgreSQL에서 지속적으로 대시보드 업데이트가 이뤄지도록 구성해 볼 수도 있지 않을까.. 하는 생각을 해보았음..

연도별, 월별 트렌드 대시보드 - Google Looker Studio

  • 연도별, 월별 식중독 환자 수 대시보드는 삽입될 그래프의 형태가 복잡하지 않기 때문에 Google Looker Studio를 이용하여 제작하였음.
  • 검색 컨트롤을 통해 기간의 범위를 지정하여 볼 수도 있고, 모바일 버젼에서도 비율이 유지되도록 레이아웃을 설정하였음.

연도별 식중독 환자 수 (캡쳐)

월 평균 식중독 환자 수 (캡쳐)

계절별, 기상요인별 트렌드 대시보드 - Tableau Public

  • 계절별, 기상요인별 트렌드 대시보드는 Looker Studio로는 깔끔하게 구현하는게 쉽지 않아서 Tableau를 이용하여 제작하였음.
  • Tableau를 이용하며 느낀 장점들
    • 계절별로 그룹화하여 변수를 만들어내는 것이 더 수월했음
    • 변수의 표시 범위를 지정하기 수월했음
    • bar 와 line의 복합 그래프를 표시하는것이 더 수월했음
  • 단점들
    • 화면을 모든 기기에 최적화 시키는데에 다소 어려움을 느꼈음
    • 화면 구성하는 방식이나 대시보드를 꾸미는 것이 여러모로 불편했음
    • 웹페이지에 표시하기위해 HTML을 작성하는게 생각보다 까다로웠음

계절별 식중독 환자 수

기상요인별 식중독 환자 수

평년값 검색 및 예측 대시보드 - Google Looker Studio

  • 월별 평년값(1991~2020)을 통해 예상되는 식중독 환자수를 예측해볼 수 있도록 하기 위해 대시보드를 제작하게 되었음.
  • 월별 평년값을 검색하여 사용 할 수 있도록 컨트롤을 추가하는 기능은 Google Looker Studio가 더 편리하게 되어있어 Looker Studio로 대시보드를 제작하게 되었음.
    • 이를통해 굳이 기상청 홈페이지에 들어가서 검색하지 않아도 예상되는 식중독 환자수를 예측해보는데 활용할 수 있음.
  • 추가적으로 월별, 계절별로 모델이 예측한 값에 대한 Predicion 그래프도 구성에 포함시켰음.

월별 평년값 검색 table 및 계절별 평년값 평균값 table

월별, 계절별 평년값 예측 식중독 환자수

4-3. Web Deployment 웹 배포

  • 머신러닝 모델과 대시보드가 한 웹페이지에 포함되도록 HTML을 구성하여 웹페이지를 구현하였음.
    • 추가적으로 에러메세지(404에러)를 출력하는 HTML들도 추가적으로 구현하였음.
  • 웹서비스 구현은 Flask를 통해 구현하였고, Heroku를 통해 웹 서비스를 언제든 이용할 수 있도록 배포하였음.
    • 서버(Heroku)와 앱(Flask)을 연결하는 중간다리 역할을 수행하는 Web Server Gateway Interface (WSGI)는 gunicorn을 이용하였음.

FLASK 웹서비스 구현

앱 폴더 구성

  • Flask로 웹페이지를 구현하는 데 있어서 가장 중요한 것은 파일 구성이라서 파일 구성은 다음과 같이 구성하였음 (Flask 구현과 Heroku 작동에 이용된 파일 및 폴더만 표시)
Seoul-FP-Weather
├── app.py
├── data
│   └── model.pkl
├── templates
│   ├── index.html
│   ├── 404.html
│   ├── 404-1.html
│   └── 404-2.html
├── static
│   ├── css
│   ├── img
│   ├── js
│   └── favicon.ico
├── Procfile
└── requirements.txt

예측 모델을 웹페이지에 구현

  • app.py
#라이브러리 import
from flask import Flask, render_template, request
import numpy as np
import pickle

#플라스크 클래스명 지정 및 모델 불러오기
app = Flask(__name__)
model = pickle.load(open("./data/model.pkl", "rb"))

#에러 페이지 데코레이터
@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404
#웹 페이지 rendering
@app.route('/', methods=['GET', 'POST'])
def index():
    #웹페이지 (GET)
    if request.method == 'GET':
        return render_template('index.html'), 200
    #입력 변수를 서버에 보내 예측 실행 (POST)
    if request.method == 'POST':
        try:
            var1 = float(request.form['avgTa'])
            var2 = float(request.form['maxTa'])
            var3 = float(request.form['minTa'])
            var4 = float(request.form['sumRn'])
            var5 = float(request.form['avgWs'])
            var6 = float(request.form['avgRhm'])
            var7 = float(request.form['sumSsHr'])
            var8 = float(request.form['avgPs'])
            array = np.array([[var1, var2, var3, var4, var5, var6, var7, var8]])
            
            pred = int(model.predict(array).round(0))
            if pred < 0 :
                return render_template('404-1.html') #에러페이지(예상범위를 벗어남)
            else :
                return render_template('index.html',pred=pred)
        except:
            return render_template('404-2.html') #에러페이지(숫자만 입력하시오)
#앱 실행코드 (디버깅 모드)
if __name__ == "__main__":
    app.run(debug=True)
  • index.html (인풋박스 부분만)
...
<div class="cover-req" style="background-color: #444;">
  <br>
  <h1 class="cover-heading">
    <p>기상 정보를 입력해주세요<br>(숫자만 입력해주세요)</p>
  </h1>
  <div class="content-grid">
    <div class="heading-grid">
      <div class="grid-contents">
        <div class="work-request--info">
          <form action="/#predict-req" method="post">
            <div class="info-name" style="width: 150px; margin-bottom: 20px;">
              <label for="formGroupExampleInput">월 평균 기온 (℃)</label>
              <input type="text" name="avgTa" required>
            </div>
            <div class="info-name" style="width: 150px; margin-bottom: 20px;">
              <label for="formGroupExampleInput">월 최고 기온 (℃)</label>
              <input type="text" name="maxTa" required>
            </div>
            <div class="info-name" style="width: 150px; margin-bottom: 20px;">
              <label for="formGroupExampleInput">월 최저 기온 (℃)</label>
              <input type="text" name="minTa" required>
            </div>
            <div class="info-name" style="width: 150px; margin-bottom: 20px;">
              <label for="formGroupExampleInput">월 합 강수량 (mm)</label>
              <input type="text" name="sumRn" required>
            </div>
            <div class="info-name" style="width: 150px; margin-bottom: 20px;">
              <label for="formGroupExampleInput">월 평균 풍속 (m/s)</label>
              <input type="text" name="avgWs" required>
            </div>
            <div class="info-name" style="width: 150px; margin-bottom: 20px;">
              <label for="formGroupExampleInput">월 평균 상대습도 (%)</label>
              <input type="text" name="avgRhm" required>
            </div>
            <div class="info-name" style="width: 150px; margin-bottom: 20px;">
              <label for="formGroupExampleInput">월 합 일조시간 (hr)</label>
              <input type="text" name="sumSsHr" required>
            </div>
            <div class="info-name" style="width: 150px; margin-bottom: 20px;">
              <label for="formGroupExampleInput">월 평균 해면기압 (hpa)</label>
              <input type="text" name="avgPs" required>
            </div>
            <button type="submit" class="btn btn-lg btn-default">
              <img src="../static/img/icon.png" alt="FP-Weather"> Predict !
            </button>
          </form>
        </div>
      </div>
      <div class="grid-contents">
        <div class="predict-result">
          {% if pred %}
          <br><br>
          <img src="../static/img/icon-96.png">
          <h2>서울시<br></h2>
          <h3>월별 식중독<br><br>환자 수<br><br>예측 결과<br></h3>
          <h2>{{ pred }} 명</h2>
          <br>
          <img src="../static/img/icon-96.png">
          <br>
          {% endif %}
        </div>
      </div>
    </div>
  </div>
</div>
...

예측 테스트

  • 입력

  • 출력

  • 성공적으로 작동!

Heroku 앱 생성

  • Github Repository 생성

    • Github와 heroku를 연결하는 방식으로 진행하였음.
    • 앱 생성 이후엔 Github에 변경사항을 커밋 및 푸시하면 자동적으로 Heroku 업데이트가 이루어짐.
  • Heroku 회원가입 후 로그인 (Authenticator라는 휴대폰 어플을 통해 인증 후 로그인하였음)

  • 앱 생성

    • 블로그 글 작성 시점엔 이미 배포가 된 상태에서 작성한 것이라 같은 이름이 존재한다 뜨지만, 앱 생성 당시엔 문제 없었음
    • 주의 할 점은 알파벳 소문자로만 이뤄진 이름이어야한다는 점!
  • 깃허브 연결

    • Deployment method를 Github로 선택하고, 생성한 레포지토리와 branch를 지정
    • Automatic Deploys를 활성화 -> Github와 연동 완료!

Procfile & requirements.txt

  • 깃허브와 연결은 했으나, 아직은 웹페이지가 구동 되지 않음
  • Procfile을 통해 WSGI로 서로 연결시켜줘야하고, requirements.txt를 통해 구동에 필요한 패키지를 Heroku에서 설치하여 구동할 수 있도록 해줘야함
  • Procfile
    • WSGI로 gunicorn을 이용
web: gunicorn app:app
  • requirements.txt
    • pip freeze를 통해 requirements.txt 파일 생성
    • 앱 개발은 파이썬 3.8 버전으로 했었지만, Heroku에선 자동적으로 python 3.10 버전을 설치해서 실행이 되었음.
    • 그래도 구동에는 큰 문제는 없었음.
#python=3.8
#pip freeze > requirements.txt

certifi==2022.9.24
charset-normalizer==2.1.1
click==8.1.3
colorama==0.4.6
contourpy==1.0.6
cycler==0.11.0
Flask==2.2.2
fonttools==4.38.0
gunicorn==20.1.0
idna==3.4
importlib-metadata==5.0.0
itsdangerous==2.1.2
Jinja2==3.1.2
joblib==1.2.0
kiwisolver==1.4.4
lightgbm==3.3.3
MarkupSafe==2.1.1
matplotlib==3.6.0
numpy==1.23.4
packaging==21.3
pandas==1.5.1
Pillow==9.3.0
psycopg2-binary==2.9.5
pyparsing==3.0.9
python-dateutil==2.8.2
python-dotenv==0.21.0
pytz==2022.5
requests==2.28.1
scikit-learn==1.1.3
scipy==1.9.3
seaborn==0.12.1
six==1.16.0
threadpoolctl==3.1.0
urllib3==1.26.12
Werkzeug==2.2.2
wincertstore==0.2
zipp==3.10.0
  • 변경 사항을 Github에 푸시하여 앱 배포를 진행

최종 구현 확인

  • 문제없이 구동됨을 확인하였음!

(2023 수정사항)

  • 기존의 프로그램의 경우 Heroku를 통해서 배포를 진행했었으나, heroku 정책상 2022년 11월 28일부터 Heroku 무료 요금제는 더 이상 제공되지 않기 때문에 Application Error가 발생함.
  • 이에 Heroku와 동일하게 Gihub Repository 연동 방식이 가능한 Koyeb을 통해 재배포를 진행하였음.
    • 재배포한 웹사이트에서 예측 모델이 정상적으로 구동됨을 확인 완료함.
    • 대체 링크를 추가하여 수정사항을 반영하였음.
  • 파일 구성이나 방법은 Heroku와 동일한 방식이기 때문에 Koyep 배포 과정을 따로 추가하지는 않겠음.

05. Conclusion 결론

5-1. Applications 활용방안

  • 트렌드 대시보드 활용
    • 연도별, 월별 식중독 환자 발생 트렌드 파악 가능!
    • 계절별, 기상요인별 환자 발생 트렌드 파악 가능!
  • 머신러닝 예측 기능 활용
    • 월별 기상 정보로 식중독 환자 예측 가능!
  • 예측 대시보드 활용
    • 월별, 계절별 평년 값으로 예상 환자 수를 파악하는데 활용 가능!

5-2. Limitations 한계점

  • 적은 데이터 수
    • 월별데이터이다보니 아무래도 샘플수가 충분하지 않아 만족할만한 모델성능이 나오진 못했음
      • 수도권이나 전국으로 조사범위를 확대하여 극복해볼 수 있을 것으로 보임.
  • 한정된 기상요인
    • 8개의 기상주요요소를 통해 분석 및 예측을 진행하여 만족할만한 모델성능이 나오진 못했음
      • 식중독과 기상요소의 관계에 대한 지속적인 연구로 다른 기상요인들을 추가적으로 분석하고 예측하는 것으로 극복해볼 수 있을 것으로 보임.

5-3. Takeaway 핵심, 회고

Key Takeaway 핵심 요점

  • 프로젝트 핵심 요약 : 서울시의 월별 기상요인을 통해 식중독 발생 환자 수를 예측하는 프로그램구현하고 배포하는 것이 프로젝트의 핵심. -> 성공적인 구현 및 배포 완료.
  • 수행된 과정들 요약 : Data 수집, Cloud DB 구축, 머신러닝 API 서비스 개발, 대시보드 제작, 웹페이지 구현, 모바일 최적화 작업 등...!

My Takeaway 회고

  • 본 프로젝트는 코드스테이츠 AI 부트캠프 Section3 데이터엔지니어링 프로젝트로써 자유주제로 협업없이 진행한 개인프로젝트임.
  • 이번 프로젝트는 Section3를 마무리하고 Section4를 시작하기 전에 정리해 볼 수 있어 Section3를 마무리하는 느낌이 들어 마음 편히 다음 섹션을 준비할 수 있을 것 같아서 좋았음.
  • 이번 프로젝트는 그동안의 프로젝트들과는 달리 구성을 최대한 간단하게 구성하여 발표할 수 있어서 여유로운 발표진행이 가능하였음.
  • 주제 및 데이터 구축이 자유로웠던 만큼 관심도메인이었던 헬스케어 분야를 이번에도 다룰 수 있어서 비교적 즐겁게 프로젝트를 수행할 수 있었음.
  • 구현 과정에서 생각보다 큰 오류는 없이 순탄하게 진행되었고, 구현한 홈페이지가 정상적으로 작동한 모습을 보니 정말 뿌듯했음.
  • 프로젝트를 정리하면서 그동안 배웠던 내용들을 복습하는데에도 도움이 되어 유의미한 시간이었다 생각하고, 진행과정과 코드도 생각보다 깔끔하게 잘 정리가 된 느낌이라 만족스러웠음.
  • 코치진으로부터 피드백은 한달 뒤쯤 받을 예정이라 어떠한 피드백을 받게 될지는 모르겠지만, 어떠한 피드백을 받더라도 겸허히 받아들이려함.
  • 다음 섹션에선 딥러닝을 배우게 될텐데, 이번에 구현한 것처럼 다음 프로젝트에서도 앱을 구현하고 배포할 수 있을지는 막막한 느낌이 듦.. 다음 프로젝트는 어떤 주제로 할지도 고민이 많이 되는 상황이지만, 일단은 이번 섹션처럼 배우는 것에 집중하고 어느정도 감이 잡힐 때 프로젝트 준비를 하려고 함.
  • 아직도 배워야할 것은 많지만, 포기하지 않고 교육과정을 무사히 마칠수 있었으면 좋겠음..! 그래도 프로젝트 정리도 시간에 쫓기지 않고 잘 마쳐서 다음 섹션에 온전히 집중 할 수 있게되어 홀가분함.
  • 아자아자 파이팅 !!!
profile
Machine Learning (AI) Engineer & BackEnd Engineer (Entry)
post-custom-banner

0개의 댓글