gcmarian's picture
kdramas ramdom
fed3071 verified
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()