이 글은 제가 작업했던 내용을 정리하기 위해 수기 형식으로 작성 된 글입니다.
2022.12.09 - [[신.만.추]] - 신입이 만드는 추천시스템-1(개요)
2022.12.09 - [[신.만.추]] - 신입이 만드는 추천시스템-2(데이터 수집, 스크래핑)
2022.12.09 - [[신.만.추]] - 신입이 만드는 추천시스템-3(셀레니움 최소화)
2022.12.09 - [[신.만.추]] - 신입이 만드는 추천시스템-4(한국어 전처리 및 워드임베딩)
2022.12.10 - [[신.만.추]] - 신입이 만드는 추천시스템-5(아이템 벡터화)
- 플라스크로 단일서버 구축하기
- 검색성능 향상
- 포트포워딩으로 외부 접속 허용하기
- uWSGI와 gunicorn
저번에 작성했던 글에서 이제 기본적인 추천시스템을 만들만한 조건들은 충족시키면서 개략적인 추천로직을 완성했다.
이번 글에서는 개략적으로 완성된 추천로직을 플라스크 서버에 적용시켜서 활용하는 과정들에 대해 전반적인 내용을 적고 다음 글부터 해당 내용을 다시 자세히 적을 것이다.
1. 플라스크로 단일서버 구축하기
플라스크만 가지고서 웹서버를 구축하는건 생각보다 쉬운일이다.
단, 프론트엔드나 백엔드를 생각하지 않고 단순히 데이터를 요청받았을 때 넘겨주는 형태의 서버만을 말한다.
내가 구축한 추천시스템의 경우, 여러 조건들(필터)과 검색하고 싶은 키워드만을 서버에 넘겨주면 해당 키워드와 조건에 맞는 크리에이터를 추천로직을 통해 리스트로 만들어 넘겨주는 형태였다.
즉 그림으로 보면 아래와 같다.
서비스 운영서버 → (데이터 요청-검색 키워드, 필터 포함) → 추천 시스템 서버 → 키워드 및 필터에 맞는 크리에이터 리스트 리턴 → 서비스 운영서버에서 해당 리스트 처리 → 노출
즉, 내가 플라스크로 구축한 영역은 아래 정도 밖에 되지 않는다.
(데이터 요청-검색 키워드, 필터 포함) → 추천 시스템 서버 → 키워드 및 필터에 맞는 크리에이터 리스트 리턴
프론트엔드 부분이나 백엔드 부분을 신경쓰지 않아도 되었기 때문에 생각보다 수월하게 서버를 구축할 수 있었다.
플라스크의 아주 기초적인 내용은 참조링크를 참고하면 좋을 것 같다.
물론 내가 짠 웹서버 코드 또한 기초적인 내용이라 사실 아래 코드만 보아도 상관은 없을 것 같기도 하다.
플라스크 서버 코드 기본코드
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "hello flask"
if __name__ == "__main__":
app.run()
위의 코드대로 app.py를 만들어 터미널에서 app.py가 위치한 곳으로 이동하여 python app.py를 실행시키면 아래와 같은 문구가 출력된다.
이후, localhost:5000으로 접속했을 때, ‘hello flask’가 나오게 된다.
이제 여기에 하나하나 추가하여 추천시스템 서버를 만든다.
우선 데이터를 가져오는 부분이 필요하다.
실제적으로 추천리스트를 만들어서 리턴해주어야 하기 때문에, 서비스 DB서버에서 데이터를 가져와 데이터프레임 형식으로 변수에 저장해 놓는다.
해당 데이터프레임은 사용자가 필터조건을 추가하거나, 키워드를 입력하고 검색을 진행할 때 활용된다.
사용된 데이터는 이전 글에서 크리에이터 벡터를 만들 때 사용했던 데이터로 생각하면 된다.
<데이터 로드>
플라스크 서버에서 데이터 로드코드
from flask import Flask
import os
import re
import pandas as pd
import numpy as np
import pymysql # mysql에서 데이터를 가져오기 위한 핵심라이브러리
app = Flask(__name__)
# 데이터 로드 부분
host = '127.0.0.1' #로컬 호스트
port = 3306 # mysql의 기본 port
user = 'localhost' # 사용자 이름
passwd = 'password' # DB 비밀번호
db = 'recomService'
charset = 'utf8mb4'
print('데이터 로딩중')
# tb_search_keyword에서 전체 데이터 가져오기
sevice_con = pymysql.connect(host = host,
user = user,
password = passwd,
db = db,
charset = charset,
cursorclass = pymysql.cursors.DictCursor
)
sevice_cur = sevice_con .cursor()
sevice_sql = 'SELECT * FROM tb_creator'
sevice_cur.execute(sevice_sql)
creator_db = sevice_cur.fetchall()
creator_df = pd.DataFrame(creator_db)
# vector 파일 및 모델 데이터 불러오기
with open('y_idx2name.pkl','rb') as f:
y_idx2name=pickle.load(f)
with open('vec_stack.pkl','rb') as f:
vec_stack=pickle.load(f)
vec_stack = np.nan_to_num(vec_stack, copy=False)
model = FastText.load('trained_model')
# 서버 라우팅 분리
@app.route("/")
def hello():
return "hello flask"
if __name__ == "__main__":
app.run()
웹서버는 파이썬의 Flask로 구동되기 때문에, 데이터 또한 파이썬으로 불러와서 처리 후 리턴 해 주어야 한다.
따라서 PyMySQL이라는 라이브러리를 활용하여 MySQL과 파이썬을 연동시켜준 후 해당 db에서 데이터를 가져온다.
위의 코드에 적혀있는 내용은 단순히 구성을 보여주기 위한 예시 코드이다.
host나 user, db 등의 내용은 상황에 맞게 수정하여 사용해야 한다.
이전에 크리에이터 데이터로 학습이 진행 된 모델도 마찬가지로 불러오고, vec_stack이라는 변수에 각 크리에이터의 벡터를 저장했던 pickle파일도 로드해야 한다.
해당 파일과 데이터프레임이 핵심 데이터라고 할수 있다.
플라스크는 페이지에 따라 route를 따로 분리 할 수도 있다.
이를 활용하여 추천 리스트를 만들어주는 route를 분리해 사용한다.
추천 로직에 활용되었던 코드를 이제 route에 붙여넣고, 서비스 서버쪽에서 데이터를 put의 형태로 넘겨주면 해당 데이터에 따라 추천리스트를 만들어 리턴해주기만 하면 되는 시스템이다.
<추천 route분리>
전체 추천 route 코드 코드
# 서버 라우팅 분리
@app.route("/")
def hello():
return "hello flask"
@app.route('/rec', methods = ['POST'])
def rec():
if request.method == 'POST':
res = { "resultCode" : "200",
"resultMessage": "성공",
"data" : []}
keyword_dict = request.json
keyword = keyword_dict["keyword"]
keyword = keyword .replace('\\','')
keyword = keyword .replace('‘','')
keyword = keyword .replace('’','')
keyword = re.sub(r"[^a-zA-Z0-9가-힣]",' ',keyword)
keyword = ' '.join(re.split(r"\\s+", keyword))
keyword = keyword.strip()
keyword = keyword.split(' ')
min_range = keyword_dict["min_range"]
max_range = keyword_dict["max_range"]
if max_range==0:
temp_df = creator_df[(creator_df['구독자수']>=min_range)]
else:
temp_df = creator_df[(creator_df['구독자수']>=min_range) & (creator_df['구독자수']<=max_range)]
# 일반적으로 중요한 키워드는 앞쪽에 적기때문에 앞쪽에 가중치 부여
keyword_cnt = len(keyword)
weight_list = [6,3]
if keyword_cnt>=3:
for i in range(keyword_cnt-len(weight_list)):
weight_list.append(1)
# 키워드 벡터화
word_vecs = np.zeros(200)
sum_weight = 0
for word_idx in range(len(keyword)):
word_vec = model.wv[keyword[word_idx]]
weight = weight_list[word_idx]
sum_weight += weight
word_vecs+=word_vec.dot(weight)
# keyword 가중평균 구하기
target_vector = word_vecs/sum_weight
cosine_matrix = cosine_similarity([target_vector],vec_stack)
cos_idx = np.where(cosine_matrix[0])[0]
cos_val = cosine_matrix[0][np.where(cosine_matrix[0])]
creator_name = [y_idx2name[idx] for idx in cos_idx]
zip_seq_val = list(zip(creator_name,cos_val))
sim_df = pd.DataFrame(zip_seq_val,columns=['채널명','유사도'])
sim_df = pd.merge(left = sim_df , right = temp_df, how = "inner", on = '채널명')
zip_seq_val = sim_df[['채널명','구독자수','유사도']].values.tolist()
zip_seq_val.sort(key = lambda x : (x[2]), reverse = True)
for v in zip_seq_vlal:
res['data'].append({'creatorName':v[0], 'subscribers':v[1], 'simVal':v[2]})
return jsonify(res)
if __name__ == "__main__":
app.run()
지금까지 진행된 코드는 localhost로만 운용이 가능한 상태이다.
외부에서도 접속 가능하도록 만들기 위해서는 host를 변경하고, 포트포워딩을 해주어야 한다.
참조링크
'[신.만.추]' 카테고리의 다른 글
신입이 만드는 추천시스템-8(포트포워딩) (0) | 2022.12.10 |
---|---|
신입이 만드는 추천시스템-7(검색성능향상) (0) | 2022.12.10 |
신입이 만드는 추천시스템-5(아이템 벡터화) (0) | 2022.12.10 |
신입이 만드는 추천시스템-4(한국어 전처리 및 워드임베딩) (0) | 2022.12.09 |
신입이 만드는 추천시스템-3(셀레니움 최소화) (0) | 2022.12.09 |