본문 바로가기

Flask

[Flask] 유클리드 거리를 이용한 영양제 추천 서버

안녕하세요!

 

졸업 작품 Nutriguide가 어느덧 마무리 단계를 거쳐

 

전시회를 준비하고 있습니다.

 

페이지의 프론트엔드 코드를 리뷰하기전에

 

서버 코드를 리뷰해보도록 하겠습니다.

 

 

 

Pill Good

 

 

저는 저희가 구사하고자 하는 추천 알고리즘과 가장 비슷한

 

PillGood 이라는 프로젝트의 오픈 소스를 깃허브에서 얻어서

 

모델을 조금 변형했습니다.

 

PillGood의 추천 결과 페이지

 

기존 PillGood은 설문 조사를 통해

 

사용자의 부족한 영양소를 구해서

 

부족한 영양소와 영양제 성분과의 유클리드 거리를 구해서

 

거리가 가장 가까운 영양제를 추천해주는 방식입니다.

 

저희는 식단을 입력받아서 식단에 부족한 영양소를 구하고,

 

결과를 기반으로 영양소 추천을 진행하고 싶었기에

 

인풋값만 식단으로 변경했습니다.

 

# 데이터 로드
pill_data = pd.read_csv('Final_Pill_Standardization_Content_Dataset.csv', header=0, encoding='cp949') #영양제 영양소 함량 데이터셋
food_data = pd.read_csv('food_dataset.csv', encoding='cp949') #평균 섭취량을 구하기 위한 음식 데이터셋
Final_Pill_Dataset = pd.read_csv('Final_Pill_Dataset.csv',header=0, encoding='cp949')
Final_Pill_Dataset_KIDS = pd.read_csv('Final_Pill_Dataset+KIDS.csv',header=0, encoding='cp949')
food_data_recommand = pd.read_csv('MinMax_food_data.csv', encoding='cp949') #평균 섭취량을 구하기 위한 음식 데이터셋

 

데이터셋은 PillGood의 영양제 데이터셋과 음식 데이터셋을 사용했습니다.

 

음식 데이터셋에 부족한 음식들은 

 

농식품올바로 (rda.go.kr)

 

농식품올바로

 

koreanfood.rda.go.kr

 

에서 영양 성분 정보를 얻어와서 전처리 했습니다.

 

NutriGuide는 사용자에게 키, 몸무게, 성별, 나이, 식단을 입력받습니다.

 

입력받은 키, 몸무게로 BMI 정보를 표시합니다.

# BMI 계산 메소드
def calc(vJson):
    height = vJson['height']
    height = int(height) / 100    
    weight = vJson['weight']  
    weight = int(weight)
    age = vJson['age']  
    age = int(age)
    gender = vJson['gender'] # 추가: 성별 정보
   
    # BMI 계산
    BMI = weight / (height * height)
    bmi_string = bmicalc(BMI)
   
    # BMI 정보 추가
    vJson['BMI'] = {
        'value': round(BMI, 2),
        'status': bmi_string
    }
   
    return vJson

 

BMI를 구하는 코드는 표준 BMI 구하는 공식을 참고했습니다.

# BMI 계산 함수
def bmicalc(x):
    if x < 18.5:
        y = "저체중"
    elif 18.5 <= x < 23:
        y = "정상 체중"
    elif 23 <= x < 25:
        y = "과체중"
    elif 25 <= x < 30:
        y = "경도 비만"
    else:
        y = "고도 비만"
       
    return y

 

BMI 결과에 따라서 5단계의 체중 등급을 나눕니다.

 

 

입력받은 식단으로는 먼저 하루의 평균 섭취량을 구합니다.

 

# 평균 섭취량 계산 함수
def calculate_average_intake(daily_intakes):
    total_intake = {nutrient: 0.0 for nutrient in daily_intakes[0]}
    count = len(daily_intakes)
   
    for daily_intake in daily_intakes:
        for nutrient, value in daily_intake.items():
            try:
                total_intake[nutrient] += float(value)
            except ValueError:
                # 숫자로 변환할 수 없는 값이 포함된 경우에 대한 예외 처리
                print(f"Could not convert value to float: {value}. Skipping...")
   
    # 각 영양소의 평균을 계산
    average_intake = {nutrient: round(total / count, 1) for nutrient, total in total_intake.items()}
   
    return average_intake

 

그리고 음식에 해당하는 영양소 정보를 데이터셋에서 가져와서

 

권장섭취량과 비교하여 부족한 영양소를 구합니다.

def get_nutrient_info(food_name):
    try:
        nutrient_info = food_data_recommand[food_data_recommand['음식명'] == food_name].iloc[:, 1:].fillna(0).squeeze()
        return nutrient_info.to_dict() if not nutrient_info.empty else {}
    except Exception as e:
        print(f"Error while fetching nutrient info for food '{food_name}': {e}")
        return {}
def find_deficient_nutrients(diet, recommended_intake):
    deficient_nutrients = {}
    for nutrient, intake in recommended_intake.items():
        if nutrient in diet:
            diet_value = diet[nutrient]
            print(f"Nutrient: {nutrient}, Diet Value: {diet_value}, Recommended Intake: {intake}")  # 디버깅용 출력
            diff = intake - diet_value
            if diff > 0:
                deficient_nutrients[nutrient] = diff
    print(f"Deficient Nutrients: {deficient_nutrients}")  # 디버깅용 출력
    return deficient_nutrients



 

 

추천 알고리즘 코드입니다.

 

부족한 영양소와의 유클리드 거리를 구해서

 

가장 유사한 영양제를 3개 추천합니다.

 pill_distance_list = []
    for i, pill in pill_data.iterrows():
        pill_distance = 0.0
        for nutrient, weight in nutrient_weights.items():
            pill_nutrient_value = pill.get(nutrient, 0)
            diet_nutrient_value = deficient_nutrients.get(nutrient, 0)

   
            nutrient_diff = abs(pill_nutrient_value - diet_nutrient_value)
            if nutrient_diff > 0:
                pill_distance += weight * nutrient_diff
       
        pill_distance = math.sqrt(pill_distance)  # 유클리드 거리 계산
     
        pill_distance_list.append((i, pill_distance))
       
    # 거리 기준으로 정렬
    sorted_pill_distance_list = sorted(pill_distance_list, key=lambda x: x[1])

    # 나이에 따라 다른 데이터셋 선택
    if age > 20:
        pill_dataset = Final_Pill_Dataset
    else:
        pill_dataset = Final_Pill_Dataset_KIDS

    recommendation = {}
    for idx, distance in sorted_pill_distance_list[:3]:  # 상위 3개의 영양제를 추천
        recommendation[idx] = {
            '영양제명': pill_dataset.loc[idx, '영양제명'],
            '거리': distance,
            'IFTKN_ATNT_MATR_CN': pill_dataset.loc[idx, 'IFTKN_ATNT_MATR_CN'],
            'STDR_STND': pill_dataset.loc[idx, 'STDR_STND'],
            'PRIMARY_FNCLTY': pill_dataset.loc[idx, 'PRIMARY_FNCLTY']
        }

    return recommendation




# 거리 계산 메소드
def distance(x, y):
    x_values = x.values
    y_values = np.array(list(y.values()) + [0.0] * (len(x_values) - len(y)))
    a = np.linalg.norm(x_values - y_values)
    return a

 

Api 코드입니다.

 

클라이언트에서 키, 몸무게, 식단, 성별을 입력받고

 

추천 알고리즘을 적용시켜서 클라이언트에 추천 결과를 반환합니다.

@app.route('/recommend_pill', methods=['POST'])
def recommend_pill_route():
    data = request.json
   
    # 식단 정보 추출
    food_names = data['food_names']
    age = int(data['age'])  # 문자열을 정수로 변환
    gender = data['gender']
   
    # 나이와 성별에 따른 권장 섭취량 가져오기
    recommended_intake = {
    '루테인(mg)': 0,
    '비타민A(ug)': 0,
    '비타민D(ug)': 0,
    '비타민E(mg)': 15,
    '비타민K(ug)': 120,
    '비타민C(mg)': 90,
    '비타민B2(mg)': 1.3,
    '아연(mg)': 11,
    '셀렌(ug)': 55,
    '철(mg)': 8,
    '마그네슘(mg)': 400,
    'EPA(mg)': 0,
    '프로바이오틱스(CFU)': 0,
    '실리마린(mg)': 0,
    '나이아신(mg)': 16,
    '올리고당(g)': 0,
    '칼슘(mg)': 1000,
    '비타민B6(mg)': 1.7,
    '베타카로틴(mg)': 900,
    '판토텐산(mg)': 5,
    '비오틴(ug)': 30,
    '망간(mg)': 2.3,
    '크롬(ug)': 0,
    '엽산(ug)': 400,
    '구리(mg)': 0.9,
    '몰리브덴(ug)': 55
}

 
    # 신체 정보 추출
    weight = int(data['weight'])
    height = int(data['height'])
   
    # BMI 계산
    BMI = calc_bmi(weight, height)
   
    # 식단 정보를 기반으로 영양소 정보 추출
    daily_intakes = [get_nutrient_info(food_name) for food_name in food_names]
    average_intake = calculate_average_intake(daily_intakes)  # 수정된 부분
    deficient_nutrients = find_deficient_nutrients(average_intake, recommended_intake)
   
    # 영양제 추천
    recommendation = recommend_pill(average_intake, age, gender)
   
       # 디버깅을 위한 코드 추가
    for pill_index, pill_info in recommendation.items():
        print(f"Pill Index: {pill_index}")
        print(f"Pill Info: {pill_info}")
   
    # 응답 구성
    response = {
        "recommendation": recommendation,
        "BMI": BMI,
        "average-intake": average_intake,  # 수정된 부분
        "deficient-nutritions": deficient_nutrients
    }
    return jsonify(response)

 

이 부분은 ChatGPT에 대한 질문을

 

클라이언트에게 받아와서

 

답변을 넘겨주는 코드인데

 

GPT에서 제공하는 코드를 이용했습니다.

@app.route('/ask', methods=['POST'])
def ask_question():
    data = request.get_json()
    user_question = data['question']

    # Use the user_question as the prompt for OpenAI Completion
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": user_question}
        ],
        temperature=0.7,
        max_tokens=2000
    )

    # Extract the generated answer from the OpenAI response
    answer = response.choices[0].message['content'].strip()

    return jsonify({'answer': answer})

 

클라이언트에서 식단을 입력받고

 

추천을 진행하면

 

 

이렇게 추천 결과가 서버에서 클라이언트로 넘어가게됩니다.

 

 

오늘은 제가 개발한 영양제 추천 사이트의 서버 코드를 리뷰해봤는데요.

 

여러 버그가 있어서 디버깅하는데 어려움이 있었지만

 

시간과 노력을 들여 잘 마무리한 것 같습니다.

 

읽어주셔서 감사합니다!

 

'Flask' 카테고리의 다른 글

[Flask] Nutritiguide 백엔드 서버 코드 리뷰  (0) 2024.07.10
[Flask] Flask 리액트 연동  (0) 2024.04.04