# gradio final ver ---------------------------- ###### 기본 설정 ###### # OpenAI API 키 설정 openai.api_key = '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' # 모델 및 프로세서 로드 processor = AutoProcessor.from_pretrained("openai/clip-vit-large-patch14") model_clip = AutoModelForZeroShotImageClassification.from_pretrained("openai/clip-vit-large-patch14") tokenizer = KoBERTTokenizer.from_pretrained('skt/kobert-base-v1') # 예측 레이블 labels = ['a photo of a happy face', 'a photo of a joyful face', 'a photo of a loving face', 'a photo of an angry face', 'a photo of a melancholic face', 'a photo of a lonely face'] ###### 얼굴 감정 벡터 예측 함수 ###### def predict_face_emotion(image): # 이미지가 None이거나 잘못된 경우 if image is None: return np.zeros(len(labels)) # 빈 벡터 반환 # PIL 이미지를 RGB로 변환 image = image.convert("RGB") # CLIP 모델의 processor를 이용한 전처리 inputs = processor(text=labels, images=image, return_tensors="pt", padding=True) # pixel_values가 4차원인지 확인 후 강제 변환 pixel_values = inputs["pixel_values"] # (batch_size, channels, height, width) # CLIP 모델 예측: forward에 올바른 입력 전달 with torch.no_grad(): outputs = model_clip(pixel_values=pixel_values, input_ids=inputs["input_ids"]) # 확률값 계산 probs = outputs.logits_per_image.softmax(dim=1)[0] return probs.numpy() ###### 텍스트 감정 벡터 예측 함수 ###### sentence_emotions = [] def predict_text_emotion(predict_sentence): if not isinstance(predict_sentence, str): predict_sentence = str(predict_sentence) data = [predict_sentence, '0'] dataset_another = [data] another_test = BERTDataset(dataset_another, 0, 1, tokenizer, vocab, max_len, True, False) test_dataloader = torch.utils.data.DataLoader(another_test, batch_size=1, num_workers=5) model.eval() for batch_id, (token_ids, valid_length, segment_ids, label) in enumerate(test_dataloader): token_ids = token_ids.long().to(device) segment_ids = segment_ids.long().to(device) out = model(token_ids, valid_length, segment_ids) for i in out: logits = i.detach().cpu().numpy() emotions = [value.item() for value in i] sentence_emotions.append(emotions) return sentence_emotions[0] # 최종 리스트 반환 ###### 최종 감정 벡터 계산 ###### def generate_final_emotion_vector(diary_input, image_input): # 텍스트 감정 벡터 예측 text_vector = predict_text_emotion(diary_input) # 얼굴 감정 벡터 예측 image_vector = predict_face_emotion(image_input) text_vector = np.array(text_vector, dtype=float) image_vector = np.array(image_vector, dtype=float) print(text_vector) print(image_vector) # 최종 감정 벡터 가중치 적용 return (text_vector * 0.7) + (image_vector * 0.3) ####### 코사인 유사도 함수 ###### def cosine_similarity_fn(vec1, vec2): dot_product = np.dot(vec1, vec2) norm_vec1 = np.linalg.norm(vec1) norm_vec2 = np.linalg.norm(vec2) if norm_vec1 == 0 or norm_vec2 == 0: return np.nan # 제로 벡터인 경우 NaN 반환 return dot_product / (norm_vec1 * norm_vec2) ####### 이미지 다운로드 함수 (PIL 객체 반환) ###### def download_image(image_url): try: response = requests.get(image_url) response.raise_for_status() return Image.open(requests.get(image_url, stream=True).raw) except Exception as e: print(f"이미지 다운로드 오류: {e}") return None # 스타일 옵션 options = { 1: "🌼 친근한", 2: "🔥 트렌디한 MZ세대", 3: "😄 유머러스한 장난꾸러기", 4: "🧘 차분한 명상가", 5: "🎨 창의적인 예술가", } # 일기 분석 함수 def chatbot_diary_with_image(style_option, diary_input, image_input, playlist_input): style = options.get(int(style_option.split('.')[0]), "🌼 친근한") # GPT 응답 (일기 코멘트) try: response_comment = openai.ChatCompletion.create( model="gpt-4-turbo", messages=[{"role": "system", "content": f"너는 {style} 챗봇이야."}, {"role": "user", "content": diary_input}], ) comment = response_comment.choices[0].message.content except Exception as e: comment = f"💬 오류: {e}" # GPT 기반 일기 주제 추천 try: topics = get_initial_response(style_option, diary_input) except Exception as e: topics = f"📝 주제 추천 오류: {e}" # DALL·E 3 이미지 생성 요청 (3D 스타일 캐릭터) try: response = openai.Image.create( model="dall-e-3", prompt=( f"{diary_input}를 반영해서 감정을 표현하는 3D 스타일의 일러스트 캐릭터를 그려줘. " "캐릭터는 부드럽고 둥근 디자인에 표정이 감정을 잘 드러내야 해. " "감정을 시각적으로 표현할 수 있는 소품이나 작은 상징을 포함해줘. " "감정의 분위기를 반영하는 선명하고 깨끗한 색상을 사용하고, 캐릭터가 역동적이고 재미있는 자세를 취할 수 있도록 해줘. " "이미지에는 하나의 캐릭터만 나오게 해줘." "배경은 단순하고 밝은 색상으로 설정해서 캐릭터가 강조될 수 있도록 해줘." ), size="1024x1024", n=1 ) # URL 가져오기 및 다운로드 image_url = response['data'][0]['url'] print(f"Generated Image URL: {image_url}") # URL 확인 image = download_image(image_url) except Exception as e: print(f"이미지 생성 오류: {e}") # 오류 상세 출력 image = None # 사용자 최종 감정 벡터 final_user_emotions = generate_final_emotion_vector(diary_input,image_input) # 각 노래에 대한 코사인 유사도 계산 similarities = [cosine_similarity_fn(final_user_emotions, song_vec) for song_vec in emotions] #유효한 유사도 필터링 valid_indices = [i for i, sim in enumerate(similarities) if not np.isnan(sim)] filtered_similarities = [similarities[i] for i in valid_indices] recommendations = np.argsort(filtered_similarities)[::-1] # 높은 유사도 순으로 정렬 results_df = pd.DataFrame({ 'Singer' : melon_emotions['singer'].iloc[recommendations].values, 'title' : melon_emotions['Title'].iloc[recommendations].values, 'genre' : melon_emotions['genre'].iloc[recommendations].values, 'Cosine Similarity': [similarities[idx] for idx in recommendations] }) # 가중치 값 설정 gamma = 0.3 similar_playlists = results_df.head(5) similar_playlists = pd.merge(similar_playlists, melon_emotions, left_on="title", right_on="Title", how="inner") similar_playlists = similar_playlists[["title", "Emotions", "singer"]] dissimilar_playlists = results_df.tail(5) dissimilar_playlists = pd.merge(dissimilar_playlists, melon_emotions, left_on="title", right_on="Title", how="inner") dissimilar_playlists = dissimilar_playlists[["title", "Emotions", "singer"]] #감정과 유사한 플레이리스트 if playlist_input == '비슷한': results = [] seen_songs = set(similar_playlists["title"].values) # 초기 seen_songs에 similar_playlists의 곡들을 추가 # 사용자 감정 벡터 user_emotion_vector = generate_final_emotion_vector(diary_input, image_input).reshape(1, -1) for index, row in similar_playlists.iterrows(): song_title = row["title"] song_singer = row["singer"] song_vector = np.array(row["Emotions"]).reshape(1, -1) song_results = [] for i, emotion_vec in enumerate(emotions): emotion_title = melon_emotions.iloc[i]["Title"] emotion_singer = melon_emotions.iloc[i]["singer"] emotion_vec = np.array(emotion_vec).reshape(1, -1) # similar_playlists에 있는 곡과 seen_songs에 있는 곡은 제외 if ( emotion_title != song_title and emotion_title not in seen_songs ): try: # 곡 간 유사도(Song-Song Similarity) song_song_similarity = cosine_similarity(song_vector, emotion_vec)[0][0] # 사용자 감정 벡터와의 유사도(User-Song Similarity) user_song_similarity = cosine_similarity(user_emotion_vector, emotion_vec)[0][0] # Final Score 계산 final_score = gamma * song_song_similarity + (1 - gamma) * user_song_similarity song_results.append({ "Title": emotion_title, "Singer": emotion_singer, "Song-Song Similarity": song_song_similarity, "User-Song Similarity": user_song_similarity, "Final Score": final_score }) except ValueError as e: print(f"Error with {song_title} vs {emotion_title}: {e}") continue # Final Score를 기준으로 상위 3곡 선택 song_results = sorted(song_results, key=lambda x: x["Final Score"], reverse=True)[:3] seen_songs.update([entry["Title"] for entry in song_results]) results.append({"Song Title": song_title, "Singer": song_singer, "Top 3 Similarities": song_results}) # 결과 출력 for result in results: print(f"{result['Singer']} - {result['Song Title']}") for entry in result["Top 3 Similarities"]: print(f"{entry['Singer']} - {entry['Title']} : Final Score {entry['Final Score']:.4f}") print(f" (Song-Song Similarity: {entry['Song-Song Similarity']:.4f}, User-Song Similarity: {entry['User-Song Similarity']:.4f})") print("-" * 30) #반대 플레이리스트 if playlist_input == '상반된': results = [] seen_songs = set() # 사용자 감정 벡터 user_emotion_vector = generate_final_emotion_vector(diary_input, image_input).reshape(1, -1) for index, row in dissimilar_playlists.iterrows(): song_title = row["title"] song_singer = row["singer"] song_vector = np.array(row["Emotions"]).reshape(1, -1) song_results = [] for i, emotion_vec in enumerate(emotions): emotion_title = melon_emotions.iloc[i]["Title"] emotion_singer = melon_emotions.iloc[i]["singer"] emotion_vec = np.array(emotion_vec).reshape(1, -1) if ( emotion_title != song_title and emotion_title not in dissimilar_playlists["title"].values and emotion_title not in seen_songs ): try: # 곡 간 유사도(Song-Song Similarity) song_song_similarity = cosine_similarity(song_vector, emotion_vec)[0][0] # 사용자 감정 벡터와의 반대 유사도(User-Song Dissimilarity) opposite_user_song_similarity = 1 - cosine_similarity(user_emotion_vector, emotion_vec)[0][0] # Final Score 계산 final_score = gamma * song_song_similarity + (1 - gamma) * opposite_user_song_similarity song_results.append({ "Title": emotion_title, "Singer": emotion_singer, "Song-Song Similarity": song_song_similarity, "User-Song Dissimilarity": opposite_user_song_similarity, "Final Score": final_score }) except ValueError as e: print(f"Error with {song_title} vs {emotion_title}: {e}") continue # Final Score를 기준으로 상위 3곡 선택 (값이 큰 곡이 반대되는 곡) song_results = sorted(song_results, key=lambda x: x["Final Score"], reverse=True)[:3] seen_songs.update(entry["Title"] for entry in song_results) results.append({"Song Title": song_title, "Singer": song_singer, "Top 3 Similarities": song_results}) # 결과 출력 for result in results: print(f"{result['Singer']} - {result['Song Title']}") for entry in result["Top 3 Similarities"]: print(f"{entry['Singer']} - {entry['Title']} : Final Score {entry['Final Score']:.4f}") print(f' (Song-Song Similarity: {entry["Song-Song Similarity"]:.4f}, User-Song Dissimilarity: {entry["User-Song Dissimilarity"]:.4f})') print("-" * 30) # 데이터프레임 변환을 위한 리스트 생성 df_rows = [] for result in results: song_title = result['Song Title'] song_singer = result['Singer'] main_song_info = f"{song_singer} - {song_title}" for entry in result["Top 3 Similarities"]: combined_info = f"{entry['Singer']} - {entry['Title']}" df_rows.append({"1st 추천 플레이리스트": main_song_info, "2nd 추천 플레이리스트": combined_info}) # 데이터프레임 생성 final_music_playlist_recommendation = pd.DataFrame(df_rows) # 곡 제목 그룹화하여 첫 번째 행에만 곡 제목 표시 final_music_playlist_recommendation["1st 추천 플레이리스트"] = final_music_playlist_recommendation.groupby("1st 추천 플레이리스트")["1st 추천 플레이리스트"].transform( lambda x: [x.iloc[0]] + [""] * (len(x) - 1) ) return final_music_playlist_recommendation, comment, topics, image # 일기 주제 추천 함수 def get_initial_response(style, sentence): style = options.get(int(style.split('.')[0]), "🌼 친근한") system_prompt_momentum = ( f"너는 {style}의 챗봇이야. 사용자가 작성한 일기를 바탕으로 생각을 정리하고 내면을 돌아볼 수 있도록 " "도와주는 구체적인 일기 콘텐츠나 질문 4-5개를 추천해줘." ) try: response = openai.ChatCompletion.create( model="gpt-4-turbo", messages=[ {"role": "system", "content": system_prompt_momentum}, {"role": "user", "content": sentence} ], temperature=1 ) return response.choices[0].message.content except Exception as e: return f"📝 주제 추천 오류: {e}" # Gradio 인터페이스 with gr.Blocks() as app: gr.Markdown("# ✨ 스마트 감정 일기 서비스 ✨\n\n 오늘의 하루를 기록하면, 그에 맞는 플레이리스트와 일기 회고 콘텐츠를 자동으로 생성해드립니다!") with gr.Row(): with gr.Column(): chatbot_style = gr.Radio( choices=[f"{k}. {v}" for k, v in options.items()], label="🤖 원하는 챗봇 스타일 선택" ) diary_input = gr.Textbox(label="📜 오늘의 하루 기록하기", placeholder="ex)오늘 소풍가서 맛있는 걸 많이 먹어서 엄청 신났어") image_input = gr.Image(type="pil", label="📷 얼굴 표정 사진 업로드") playlist_input = gr.Radio(["비슷한", "상반된"], label="🎧 오늘의 감정과 ㅇㅇ되는 플레이리스트 추천 받기") submit_btn = gr.Button("🚀 분석 시작") with gr.Column(): output_playlist = gr.Dataframe(label="🎧 추천 플레이리스트 ") output_comment = gr.Textbox(label="💬 AI 코멘트") output_topics = gr.Textbox(label="📝 추천 일기 콘텐츠") output_image = gr.Image(label="🖼️ 생성된 오늘의 감정 캐릭터", type="pil", width=512, height=512) # 버튼 클릭 이벤트 연결 submit_btn.click( fn=chatbot_diary_with_image, inputs=[chatbot_style, diary_input, image_input, playlist_input], outputs=[output_playlist, output_comment, output_topics, output_image] ) # 앱 실행 app.launch(debug=True)