Spaces:
Running
Running
from datasets import load_dataset, concatenate_datasets, Dataset | |
from sentence_transformers import SentenceTransformer | |
import numpy as np | |
import pandas as pd | |
import gradio as gr | |
import kagglehub | |
from kagglehub import KaggleDatasetAdapter | |
import faiss | |
import os | |
from transformers import AutoModelForCausalLM, AutoTokenizer | |
import torch | |
import unicodedata | |
import random | |
# ================================================== | |
# Cargar datasets | |
# ================================================== | |
# Dataset 1: MyDramaList (5000 dramas) | |
mydramalist = kagglehub.load_dataset( | |
KaggleDatasetAdapter.PANDAS, | |
"anittasaju/complete-5000-dramas-from-mydramalist-review-info", | |
"Top_5000_popular_drama_details_from_mydramalist.csv" | |
) | |
mydramalist = Dataset.from_pandas(mydramalist) | |
# Dataset 2: Netflix Movies and Shows | |
netflix_movies_shows = load_dataset("harshi321/netflix-movies_shows")["train"] | |
# ================================================== | |
# Preprocesamiento de datos | |
# ================================================== | |
# Renombrar columnas para unificar los datasets | |
mydramalist = mydramalist.rename_column("name", "title").rename_column("content", "description") | |
# Filtrar por país (Corea del Sur) | |
def filter_kdramas(dataset): | |
if 'country' in dataset.features: | |
return dataset.filter(lambda x: x['country'] == "South Korea" if x['country'] else False) | |
return dataset | |
# Aplicar filtro a los datasets | |
kdramas1 = filter_kdramas(netflix_movies_shows) | |
kdramas2 = filter_kdramas(mydramalist) | |
# Eliminar columnas innecesarias (incluyendo 'rating') | |
columns_to_remove = ["Unnamed: 0", "no_of_reviews", "aka_names", "screenwriter", "director", | |
"no_of_viewers", "end_date", "start_date", "year", "duration", "no_of_rating", | |
"rank", "popularity", "content_rating", "where_to_watch", "main_role", | |
"support_role", "no_of_extracted_reviews", "Total_sentences", | |
"POSITIVE_people_sentiment", "POSITIVE_sentences", "NEGATIVE_people_sentiment", | |
"NEGATIVE_sentences", "rating"] # Eliminar 'rating' | |
for dataset in [kdramas1, kdramas2]: | |
dataset = dataset.remove_columns([col for col in columns_to_remove if col in dataset.features]) | |
# Asegurar que todos los datasets tengan la columna 'type' | |
def add_type_column(dataset, default_type="TV Show"): | |
if 'type' not in dataset.features: | |
dataset = dataset.map(lambda x: {"type": default_type}) | |
return dataset | |
kdramas1 = add_type_column(kdramas1, "TV Show") | |
kdramas2 = add_type_column(kdramas2, "TV Show") | |
# Asegurar que todos los datasets tengan la columna 'genres' | |
def add_genres_column(dataset, default_genres="Unknown"): | |
if 'genres' not in dataset.features: | |
dataset = dataset.map(lambda x: {"genres": default_genres}) | |
return dataset | |
kdramas1 = add_genres_column(kdramas1, "Unknown") | |
kdramas2 = add_genres_column(kdramas2, "Unknown") | |
# Asegurar que todos los datasets tengan las mismas columnas y tipos | |
def align_datasets(dataset1, dataset2): | |
# Obtener las columnas comunes | |
common_columns = set(dataset1.features.keys()).intersection(set(dataset2.features.keys())) | |
# Mantener solo las columnas comunes | |
dataset1 = dataset1.select_columns(list(common_columns)) | |
dataset2 = dataset2.select_columns(list(common_columns)) | |
return dataset1, dataset2 | |
# Alinear los datasets | |
kdramas1, kdramas2 = align_datasets(kdramas1, kdramas2) | |
# Eliminar columnas adicionales que no se usan | |
kdramas1 = kdramas1.remove_columns(['rating']) | |
kdramas2 = kdramas2.remove_columns(['rating']) | |
# Combinar todos los datasets | |
kdramas = concatenate_datasets([kdramas1, kdramas2]) | |
# Eliminar duplicados basados en el título | |
kdramas_df = kdramas.to_pandas().drop_duplicates(subset=['title']) | |
kdramas = Dataset.from_pandas(kdramas_df) | |
# ================================================== | |
# Modelo de embeddings y recomendación | |
# ================================================== | |
# Cargar el modelo de embeddings | |
model = SentenceTransformer('sentence-transformers/paraphrase-MiniLM-L6-v2') | |
# Calcular o cargar embeddings | |
# Verificar si el archivo de embeddings existe | |
if os.path.exists("kdrama_embeddings.npy"): | |
# Cargar embeddings precalculados | |
embeddings_np = np.load("kdrama_embeddings.npy") | |
else: | |
# Calcular embeddings y guardarlos | |
descriptions = kdramas["description"] | |
embeddings = model.encode(descriptions, convert_to_tensor=True) | |
embeddings_np = embeddings.cpu().numpy() | |
np.save("kdrama_embeddings.npy", embeddings_np) | |
print("¡Embeddings listos! Cada descripción ahora es un vector numérico.") | |
# Crear un índice FAISS para búsqueda eficiente | |
dimension = embeddings_np.shape[1] | |
index = faiss.IndexFlatL2(dimension) | |
index.add(embeddings_np) | |
# Función para recomendar K-Dramas similares | |
def recommend_kdramas(title, k=5): | |
title_indices = [i for i, t in enumerate(kdramas['title']) if title.lower() in t.lower()] | |
if not title_indices: | |
return f"No se encontraron títulos similares a '{title}'." | |
query_embedding = embeddings_np[title_indices[0]].reshape(1, -1) | |
distances, similar_indices = index.search(query_embedding, k + 10) # Ampliar el rango de búsqueda | |
# Seleccionar aleatoriamente k índices de los 10 más similares | |
selected_indices = random.sample(list(similar_indices[0][1:]), k) | |
recommendations = [] | |
for i in selected_indices: | |
recommended_title = kdramas["title"][i] | |
recommended_type = kdramas["type"][i] if "type" in kdramas.features else "Unknown" | |
recommended_genres = kdramas["genres"][i] if "genres" in kdramas.features else "Unknown" | |
recommendations.append( | |
f"### {recommended_title}\n" | |
f"- **Tipo**: {recommended_type}\n" | |
f"- **Géneros**: {recommended_genres}\n" | |
) | |
return "\n".join(recommendations) | |
# ================================================== | |
# Chatbot | |
# ================================================== | |
# Cargar el modelo de chat | |
tokenizer = AutoTokenizer.from_pretrained("microsoft/DialoGPT-medium") | |
model_chat = AutoModelForCausalLM.from_pretrained("microsoft/DialoGPT-medium") | |
# Mapeo de géneros (español sin tildes → inglés) | |
mapeo_generos = { | |
# Géneros (sin tildes) | |
"romantico": "romantic", | |
"accion": "action", | |
"misterio": "mystery", | |
"comedia": "comedy", | |
"drama": "drama", | |
"crimen": "crime", | |
"fantasia": "fantasy", | |
"thriller": "thriller", | |
"romance": "romance", | |
# Palabras clave adicionales (sin tildes) | |
"aventura": "adventure", | |
"historico": "historical", | |
# Agrega más según sea necesario | |
} | |
# Función para normalizar texto | |
def normalizar_texto(texto): | |
# Eliminar tildes y convertir a minúsculas | |
texto_normalizado = ''.join( | |
letra for letra in unicodedata.normalize('NFD', texto) | |
if unicodedata.category(letra) != 'Mn' | |
) | |
return texto_normalizado.lower() | |
# Función para traducir preferencias | |
def traducir_preferencia(entrada_usuario): | |
entrada_normalizada = normalizar_texto(entrada_usuario) | |
for espanol, ingles in mapeo_generos.items(): | |
if espanol in entrada_normalizada: | |
return ingles | |
return None # Si no se encuentra una traducción | |
# Función para buscar K-Dramas por género | |
def buscar_por_genero(genero, k=5): | |
# Filtrar K-Dramas que contengan el género especificado | |
genre_embedding = model.encode(genero, convert_to_tensor=True).cpu().numpy() | |
genre_embedding = genre_embedding.reshape(1, -1) | |
# Buscar en el índice FAISS | |
D, I = index.search(genre_embedding, k * 2) # Ampliar el rango de búsqueda | |
# Seleccionar aleatoriamente k índices de los resultados | |
selected_indices = random.sample(list(I[0]), k) | |
# Formatear las recomendaciones | |
recommendations = [] | |
for i in selected_indices: | |
recommended_title = kdramas["title"][i] | |
recommended_type = kdramas["type"][i] if "type" in kdramas.features else "Unknown" | |
recommended_genres = kdramas["genres"][i] if "genres" in kdramas.features else "Unknown" | |
recommendations.append( | |
f"{recommended_title}\n" | |
f"- **Tipo**: {recommended_type}\n" | |
f"- **Géneros**: {recommended_genres}\n" | |
) | |
return "\n".join(recommendations) if recommendations else f"No se encontraron K-Dramas del género '{genero}'." | |
# Función para recomendar K-Dramas basado en preferencias | |
def recomendar_kdramas_chat(entrada_usuario): | |
# Traducir preferencia del usuario | |
preferencia_traducida = traducir_preferencia(entrada_usuario) | |
if preferencia_traducida: | |
return buscar_por_genero(preferencia_traducida, k=5) | |
else: | |
return None # No se encontró una preferencia válida | |
# Función para generar respuestas del chatbot | |
def generar_respuesta(entrada_usuario, historial_chat=""): | |
inputs = tokenizer.encode(entrada_usuario + historial_chat, return_tensors="pt") | |
respuesta_ids = model_chat.generate(inputs, max_length=1000, pad_token_id=tokenizer.eos_token_id) | |
respuesta = tokenizer.decode(respuesta_ids[:, inputs.shape[-1]:][0], skip_special_tokens=True) | |
return respuesta | |
# Función para manejar la interacción del chatbot | |
def chat(entrada_usuario, historial_chat=""): | |
# Agregar el mensaje del usuario al historial | |
historial_chat += f"Usuario: {entrada_usuario}\n" | |
# Generar una respuesta natural del chatbot | |
if not historial_chat.strip(): # Si es la primera interacción | |
respuesta = "¡Hola! Soy tu asistente para recomendar K-Dramas. ¿Qué tipo de K-Drama te gustaría ver hoy?" | |
else: | |
respuesta = generar_respuesta(entrada_usuario, historial_chat) | |
# Verificar si el usuario mencionó un género | |
recomendacion = recomendar_kdramas_chat(entrada_usuario) | |
if recomendacion: | |
respuesta = f"¡Genial! Aquí tienes algunas recomendaciones de K-Dramas que podrían gustarte:\n{recomendacion}" | |
else: | |
respuesta = "¿Qué tipo de K-Drama te gustaría ver? Puedo recomendarte dramas de acción, romance, misterio y más." | |
# Agregar la respuesta del chatbot al historial | |
historial_chat += f"Chatbot: {respuesta}\n" | |
# Devolver la respuesta y el historial actualizado | |
return respuesta, historial_chat, "" # Limpiar el cuadro de texto | |
# ================================================== | |
# Interfaz de Gradio | |
# ================================================== | |
# Interfaz para el chatbot | |
interfaz_chatbot = gr.Interface( | |
fn=chat, | |
inputs=[gr.Textbox(label="Escribe tu mensaje"), gr.Textbox(label="Historial", visible=False)], | |
outputs=[gr.Textbox(label="Respuesta del chatbot"), gr.Textbox(label="Mensaje", visible=False)], | |
title="Chatbot Recomendador de K-Dramas", | |
description="Habla con el chatbot para obtener recomendaciones personalizadas de K-Dramas.", | |
allow_flagging="never" | |
) | |
# Interfaz para el recomendador tradicional | |
interfaz_recomendador = gr.Interface( | |
theme=gr.themes.Citrus(), | |
fn=recommend_kdramas, | |
inputs=[ | |
gr.Textbox(label="Ingresa el título de un K-Drama"), | |
], | |
outputs=gr.Markdown(label="K-Dramas recomendados"), | |
title="Recomendador de K-Dramas", | |
description="Ingresa el título de un K-Drama y recibe recomendaciones similares, indicando si son series o películas y sus géneros.", | |
examples=[["Vincenzo", 5], ["Crash Landing on You", 3], ["Itaewon Class", 4]], | |
allow_flagging="never", | |
) | |
# Lanzar ambas interfaces | |
gr.TabbedInterface( | |
[interfaz_recomendador, interfaz_chatbot], # Chatbot en la segunda pestaña | |
["Recomendador", "Chatbot"] | |
).launch() |