Juan Acevedo
Frist_to_end_project
9ed9a96
import gradio as gr
from joblib import load
from pathlib import Path
# =============================================================
# Demo Clasificador de Café ☕🇨🇴
# - Interfaz profesional con temática colombiana
# - Explicaciones de cada control y salida
# - Visualización de probabilidades por clase
# =============================================================
# Carga robusta del modelo previamente entrenado
# Se espera un pipeline de scikit-learn con StandardScaler + DecisionTreeClassifier
try:
model = load('coffe_model.joblib')
except Exception as e:
model = None
load_error = e
else:
load_error = None
def classify(
aftertaste: float,
overall: float,
body: float,
aroma: float,
flavor: float,
moisture_percentage: float,
):
"""
Realiza la predicción del tipo/calidad de café a partir de 3 atributos sensoriales.
Parámetros
- aftertaste: retrogusto (0 a 10)
- overall: evaluación global (0 a 10)
- body: cuerpo del café (0 a 10)
- aroma: intensidad del aroma (0 a 10)
- flavor: sabor (0 a 10)
- moisture_percentage: porcentaje de humedad del grano (~5 a 15)
Retorna
- probs: diccionario {clase: probabilidad} para gr.Label
- resumen_md: texto Markdown con el resultado y breve explicación
"""
if model is None:
raise gr.Error(
f"No fue posible cargar el modelo en: {MODEL_PATH}. Detalle: {load_error!s}.\n"
f"Verifica que 'coffe_model.joblib' exista en 'notebooks/' junto a este app.py."
)
# Predicción y probabilidades
# Construimos un DataFrame con nombres de columnas en el MISMO orden
# utilizado durante el entrenamiento:
cols = [
"Aftertaste",
"Overall",
"Body",
"Aroma",
"Flavor",
"Moisture Percentage",
]
import pandas as pd # import local para evitar conflictos en cold-start
X = pd.DataFrame(
[[aftertaste, overall, body, aroma, flavor, moisture_percentage]],
columns=cols,
)
pred = model.predict(X)[0]
# Algunos modelos pueden no implementar predict_proba; se maneja de forma segura
probs = {}
if hasattr(model, "predict_proba"):
try:
p = model.predict_proba(X)[0]
classes = getattr(model, "classes_", [str(i) for i in range(len(p))])
probs = {str(c): float(pc) for c, pc in zip(classes, p)}
except Exception:
# Fallback simple: 100% a la clase predicha
probs = {str(pred): 1.0}
else:
probs = {str(pred): 1.0}
resumen_md = (
f"### Resultado\n\n"
f"☕ Predicción: **{pred}**\n\n"
f"Las barras muestran la probabilidad estimada para cada clase."
)
return probs, resumen_md
# ============================
# Construcción de la Interfaz
# ============================
COLOR_YELLOW = "#F7D117" # Amarillo bandera 🇨🇴
COLOR_BLUE = "#0033A0" # Azul bandera 🇨🇴
COLOR_RED = "#CE1126" # Rojo bandera 🇨🇴
theme = gr.themes.Soft(primary_hue="yellow", secondary_hue="red")
CUSTOM_CSS = f"""
.hero {{
background: linear-gradient(90deg, {COLOR_YELLOW} 0%, {COLOR_BLUE} 50%, {COLOR_RED} 100%);
color: white;
padding: 18px 20px;
border-radius: 12px;
}}
.hero h1 {{
margin: 0 0 6px 0;
font-weight: 800;
}}
.hero p {{
margin: 0;
opacity: 0.95;
}}
.brand-badge {{
display: inline-block;
background: white;
color: #111827;
padding: 2px 10px;
border-radius: 999px;
font-weight: 700;
margin-left: 8px;
}}
"""
with gr.Blocks(title="Clasificador de Café ☕🇨🇴", theme=theme, css=CUSTOM_CSS) as demo:
# Encabezado hero con simbología colombiana
gr.Markdown(
"""
<div class="hero">
<h1>☕ Clasificador de Café <span class="brand-badge">Colombia 🇨🇴</span></h1>
<p>
Modelo de Árbol de Decisión (scikit-learn) integrado en un Pipeline con estandarización.
Ingresa atributos sensoriales y obtén la predicción junto a sus probabilidades.
</p>
</div>
"""
)
with gr.Row(equal_height=True):
with gr.Column(scale=1):
gr.Markdown(
"""
### Entradas del modelo (6 features)
- Aftertaste: retrogusto (0–10).
- Overall: evaluación global (0–10).
- Body: sensación en boca, de ligero a denso (0–10).
- Aroma: intensidad y complejidad aromática (0–10).
- Flavor: sabor (0–10).
- Moisture Percentage: porcentaje de humedad del grano (~5–15).
Consejo: prueba las muestras para observar cómo cambia la clasificación.
"""
)
inp_aftertaste = gr.Slider(0, 10, value=7.5, step=0.1, label="Aftertaste")
inp_overall = gr.Slider(0, 10, value=7.0, step=0.1, label="Overall")
inp_body = gr.Slider(0, 10, value=6.5, step=0.1, label="Body")
inp_aroma = gr.Slider(0, 10, value=7.0, step=0.1, label="Aroma")
inp_flavor = gr.Slider(0, 10, value=7.2, step=0.1, label="Flavor")
inp_moisture = gr.Slider(5.0, 15.0, value=11.5, step=0.1, label="Moisture Percentage")
btn = gr.Button("Clasificar ☕", variant="primary")
gr.Examples(
examples=[
# Aftertaste, Overall, Body, Aroma, Flavor, Moisture %
[7.8, 7.5, 7.0, 7.6, 7.4, 11.2],
[5.0, 5.2, 5.5, 5.1, 5.0, 12.0],
[8.9, 8.5, 8.0, 8.2, 8.6, 10.8],
[3.0, 3.5, 3.8, 3.2, 3.0, 13.5],
],
inputs=[
inp_aftertaste,
inp_overall,
inp_body,
inp_aroma,
inp_flavor,
inp_moisture,
],
label="Ejemplos rápidos",
)
with gr.Column(scale=1):
gr.Markdown("### Resultado y probabilidades")
out_label = gr.Label(num_top_classes=3)
out_md = gr.Markdown()
with gr.Accordion("¿Cómo funciona este demo?", open=False):
gr.Markdown(
"""
- El modelo fue entrenado con un Árbol de Decisión y normalización (Pipeline de scikit-learn).
- Las entradas se transforman y se calcula la probabilidad de cada clase.
- La etiqueta mostrada corresponde a la mayor probabilidad.
- Este demo es educativo: los rangos 1–10 son relativos y el rendimiento depende del dataset usado.
"""
)
with gr.Row():
gr.Markdown(
f"Hecho con ❤️ y café colombiano 🇨🇴 | Colores de interfaz: amarillo, azul y rojo | scikit-learn + Gradio"
)
# Encadenamiento de eventos
inputs = [
inp_aftertaste,
inp_overall,
inp_body,
inp_aroma,
inp_flavor,
inp_moisture,
]
outputs = [out_label, out_md]
btn.click(classify, inputs=inputs, outputs=outputs)
for w in inputs:
w.change(classify, inputs=inputs, outputs=outputs)
if __name__ == "__main__":
demo.launch()