ProyectoAM / app.py
VickyVaporub's picture
Upload app.py
cf7be0a verified
Raw
History Blame Contribute Delete
22.8 kB
"""
Detector de Imágenes IA vs. Reales
Pontificia Universidad Javeriana — Técnicas de Aprendizaje de Máquina
Proyecto de Aplicación 2 — HuggingFace Spaces
"""
import os
import numpy as np
from PIL import Image
import tensorflow as tf
import gradio as gr
# ── Configuración ──────────────────────────────────────────────────────────
IMG_SIZE = 64
MODEL_PATH = "mejor_modelo_ai_vs_real.keras"
# ── Cargar modelo ──────────────────────────────────────────────────────────
try:
modelo = tf.keras.models.load_model(MODEL_PATH)
MODEL_OK = True
except Exception as e:
print(f"[ERROR] {e}")
modelo = None
MODEL_OK = False
# ── Predicción ─────────────────────────────────────────────────────────────
def predecir(imagen_np):
if not MODEL_OK or modelo is None:
return {"Error: modelo no disponible": 1.0}, "Modelo no cargado", ""
if imagen_np is None:
return {"Sin imagen": 1.0}, "Sube una imagen primero", ""
img = Image.fromarray(imagen_np.astype("uint8")).convert("RGB")
img = img.resize((IMG_SIZE, IMG_SIZE), Image.LANCZOS)
arr = np.array(img, dtype=np.float32) / 255.0
arr = np.expand_dims(arr, axis=0)
prob_real = float(modelo.predict(arr, verbose=0)[0][0])
prob_ai = 1.0 - prob_real
confianza = max(prob_ai, prob_real) * 100
probs = {
"IA-Generada": round(prob_ai, 4),
"Real": round(prob_real, 4),
}
if prob_ai >= 0.80:
veredicto = f"Imagen generada por IA — confianza {confianza:.1f}%"
elif prob_ai >= 0.60:
veredicto = f"Probablemente generada por IA — confianza {confianza:.1f}%"
elif prob_ai >= 0.40:
veredicto = f"Resultado incierto — zona de borde de decisión"
elif prob_real >= 0.60:
veredicto = f"Probablemente real — confianza {confianza:.1f}%"
else:
veredicto = f"Imagen real — confianza {confianza:.1f}%"
detalle = (
f"P(IA-Generada) = {prob_ai*100:.2f}%\n"
f"P(Real) = {prob_real*100:.2f}%\n"
f"Confianza = {confianza:.1f}%\n"
f"Resolución = {IMG_SIZE}×{IMG_SIZE} px"
)
return probs, veredicto, detalle
# ── CSS ────────────────────────────────────────────────────────────────────
css = """
@import url('https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Mono:wght@300;400;500&family=DM+Sans:wght@300;400;500&display=swap');
* { box-sizing: border-box; }
body, .gradio-container {
font-family: 'DM Sans', sans-serif !important;
background: #0a0a0f !important;
}
gradio-app {
background: #0a0a0f !important;
}
.gradio-container {
max-width: 1100px !important;
margin: 0 auto !important;
padding: 0 24px !important;
}
/* Header */
.site-header {
border-bottom: 1px solid #1e1e2e;
padding: 32px 0 28px;
margin-bottom: 36px;
}
.site-header h1 {
font-family: 'DM Serif Display', serif;
font-size: 2.1rem;
color: #f0f0f5;
margin: 0 0 6px;
letter-spacing: -0.02em;
font-weight: 400;
}
.site-header p {
font-family: 'DM Mono', monospace;
font-size: 0.72rem;
color: #4a4a6a;
margin: 0;
letter-spacing: 0.08em;
text-transform: uppercase;
}
/* Tabs */
.tab-nav {
border-bottom: 1px solid #1e1e2e !important;
background: transparent !important;
margin-bottom: 28px;
}
.tab-nav button {
font-family: 'DM Mono', monospace !important;
font-size: 0.72rem !important;
letter-spacing: 0.06em !important;
text-transform: uppercase !important;
color: #4a4a6a !important;
background: transparent !important;
border: none !important;
border-bottom: 2px solid transparent !important;
padding: 10px 18px !important;
margin-bottom: -1px !important;
transition: color 0.2s, border-color 0.2s !important;
}
.tab-nav button.selected {
color: #c8c8e8 !important;
border-bottom-color: #6060c0 !important;
}
.tab-nav button:hover {
color: #a0a0c0 !important;
}
/* Inputs & outputs */
.gr-input, .gr-output, input, textarea, .gr-box {
background: #111120 !important;
border: 1px solid #1e1e2e !important;
border-radius: 8px !important;
color: #d0d0e8 !important;
font-family: 'DM Sans', sans-serif !important;
}
label span, .gr-label {
font-family: 'DM Mono', monospace !important;
font-size: 0.7rem !important;
letter-spacing: 0.07em !important;
text-transform: uppercase !important;
color: #4a4a6a !important;
}
/* Button */
button.primary {
background: #6060c0 !important;
border: none !important;
border-radius: 6px !important;
font-family: 'DM Mono', monospace !important;
font-size: 0.75rem !important;
letter-spacing: 0.08em !important;
text-transform: uppercase !important;
color: #fff !important;
padding: 10px 24px !important;
transition: background 0.2s !important;
cursor: pointer !important;
}
button.primary:hover {
background: #7070d8 !important;
}
/* Section titles */
.section-title {
font-family: 'DM Serif Display', serif;
font-size: 1.15rem;
color: #c8c8e8;
margin: 0 0 16px;
font-weight: 400;
letter-spacing: -0.01em;
}
/* Cards */
.card {
background: #111120;
border: 1px solid #1e1e2e;
border-radius: 10px;
padding: 20px 24px;
margin-bottom: 16px;
}
/* Tables */
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 0.83rem;
color: #c0c0d8;
}
.data-table th {
font-family: 'DM Mono', monospace;
font-size: 0.68rem;
letter-spacing: 0.07em;
text-transform: uppercase;
color: #4a4a6a;
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid #1e1e2e;
font-weight: 400;
}
.data-table td {
padding: 10px 12px;
border-bottom: 1px solid #16162a;
vertical-align: top;
line-height: 1.5;
}
.data-table tr:last-child td {
border-bottom: none;
}
.data-table tr:hover td {
background: #16162a;
}
.winner-row td {
background: rgba(96, 96, 192, 0.08) !important;
}
.badge {
display: inline-block;
font-family: 'DM Mono', monospace;
font-size: 0.62rem;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 2px 8px;
border-radius: 4px;
background: rgba(96, 96, 192, 0.2);
color: #9090d8;
border: 1px solid rgba(96, 96, 192, 0.3);
margin-left: 8px;
vertical-align: middle;
}
/* Variable grid */
.var-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-top: 4px;
}
.var-card {
background: #0d0d1a;
border: 1px solid #1e1e2e;
border-radius: 8px;
padding: 14px 12px;
text-align: center;
transition: border-color 0.2s;
}
.var-card:hover {
border-color: #3a3a6a;
}
.var-val {
font-family: 'DM Mono', monospace;
font-size: 1.25rem;
font-weight: 500;
color: #e0e0f5;
letter-spacing: -0.01em;
}
.var-lbl {
font-family: 'DM Mono', monospace;
font-size: 0.62rem;
letter-spacing: 0.07em;
text-transform: uppercase;
color: #3a3a5a;
margin-top: 5px;
}
.var-card.accent-blue .var-val { color: #7878e0; }
.var-card.accent-violet .var-val { color: #a060c8; }
.var-card.accent-green .var-val { color: #40b080; }
/* Category labels */
.cat-label {
font-family: 'DM Mono', monospace;
font-size: 0.65rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #3a3a5a;
margin: 18px 0 8px;
}
/* Verdict output */
.verdict-box textarea {
font-family: 'DM Serif Display', serif !important;
font-size: 1.05rem !important;
color: #d0d0f0 !important;
font-style: italic !important;
letter-spacing: 0.01em !important;
}
/* Info note */
.note {
font-family: 'DM Mono', monospace;
font-size: 0.7rem;
color: #3a3a5a;
letter-spacing: 0.04em;
margin-top: 8px;
}
/* Divider */
hr { border: none; border-top: 1px solid #1e1e2e; margin: 28px 0; }
footer { display: none !important; }
"""
# ── HTML blocks ────────────────────────────────────────────────────────────
header_html = """
<div class="site-header">
<h1>Detector de Imágenes IA vs. Reales</h1>
<p>Pontificia Universidad Javeriana &nbsp;·&nbsp; Técnicas de Aprendizaje de Máquina &nbsp;·&nbsp; 2026</p>
</div>
"""
tabla_modelos = """
<div class="card">
<p class="section-title">Arquitecturas Comparadas</p>
<table class="data-table">
<thead>
<tr>
<th>Modelo</th>
<th>Tipo</th>
<th style="text-align:right">Parámetros</th>
<th>Características clave</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CNN Scratch</strong></td>
<td>Capas Convolucionales + Densas</td>
<td style="text-align:right; font-family:'DM Mono',monospace">~800 K</td>
<td>3 bloques Conv → BN → MaxPool → Dropout, cabeza Dense(256)</td>
</tr>
<tr class="winner-row">
<td><strong>EfficientNetB0</strong><span class="badge">mejor modelo</span></td>
<td>Transfer Learning — ImageNet</td>
<td style="text-align:right; font-family:'DM Mono',monospace">~5.3 M</td>
<td>Base congelada fase 1 → fine-tuning últimas 20 capas fase 2</td>
</tr>
<tr>
<td><strong>ViT Small</strong></td>
<td>Vision Transformer</td>
<td style="text-align:right; font-family:'DM Mono',monospace">~2.5 M</td>
<td>Patch 8×8, 64 patches, 4 bloques Transformer, EMBED_DIM 128</td>
</tr>
</tbody>
</table>
</div>
"""
tabla_metricas = """
<div class="card">
<p class="section-title">Resultados — Test Set</p>
<p class="note">Actualizar con los valores reales tras ejecutar el notebook en Colab.</p>
<table class="data-table" style="margin-top:12px">
<thead>
<tr>
<th>Modelo</th>
<th style="text-align:center">Accuracy</th>
<th style="text-align:center">Precision</th>
<th style="text-align:center">Recall</th>
<th style="text-align:center">F1-Score</th>
<th style="text-align:center">AUC-ROC</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>CNN Scratch</strong></td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
</tr>
<tr class="winner-row">
<td><strong>EfficientNetB0</strong><span class="badge">mejor</span></td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
</tr>
<tr>
<td><strong>ViT Small</strong></td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
<td style="text-align:center; font-family:'DM Mono',monospace">—</td>
</tr>
</tbody>
</table>
</div>
"""
tabla_hiperparametros = """
<div class="card">
<p class="section-title">Hiperparámetros de Entrenamiento</p>
<table class="data-table">
<thead>
<tr>
<th>Parámetro</th>
<th>CNN Scratch</th>
<th>EfficientNetB0</th>
<th>ViT Small</th>
</tr>
</thead>
<tbody>
<tr>
<td>Optimizador</td>
<td>Adam</td><td>Adam</td><td>Adam</td>
</tr>
<tr>
<td>Learning Rate</td>
<td><span style="font-family:'DM Mono',monospace">1e-3</span></td>
<td><span style="font-family:'DM Mono',monospace">1e-4 → 1e-5</span></td>
<td><span style="font-family:'DM Mono',monospace">1e-4</span></td>
</tr>
<tr>
<td>Batch Size</td>
<td><span style="font-family:'DM Mono',monospace">32</span></td>
<td><span style="font-family:'DM Mono',monospace">32</span></td>
<td><span style="font-family:'DM Mono',monospace">32</span></td>
</tr>
<tr>
<td>Épocas máx.</td>
<td><span style="font-family:'DM Mono',monospace">30</span></td>
<td><span style="font-family:'DM Mono',monospace">10 + 10 (fases)</span></td>
<td><span style="font-family:'DM Mono',monospace">30</span></td>
</tr>
<tr>
<td>Loss</td>
<td>Binary Cross-Entropy</td>
<td>Binary Cross-Entropy</td>
<td>Binary Cross-Entropy</td>
</tr>
<tr>
<td>Dropout</td>
<td><span style="font-family:'DM Mono',monospace">0.25 – 0.40</span></td>
<td><span style="font-family:'DM Mono',monospace">0.30</span></td>
<td><span style="font-family:'DM Mono',monospace">0.10 – 0.30</span></td>
</tr>
<tr>
<td>EarlyStopping</td>
<td colspan="3"><span style="font-family:'DM Mono',monospace">patience=5, monitor=val_loss, restore_best_weights=True</span></td>
</tr>
<tr>
<td>ReduceLROnPlateau</td>
<td colspan="3"><span style="font-family:'DM Mono',monospace">factor=0.5, patience=3, min_lr=1e-6</span></td>
</tr>
<tr>
<td>Class weights</td>
<td colspan="3">Balanceado via <span style="font-family:'DM Mono',monospace">sklearn.compute_class_weight</span></td>
</tr>
</tbody>
</table>
</div>
"""
tabla_dataset = """
<div class="card">
<p class="section-title">Dataset</p>
<table class="data-table">
<tbody>
<tr>
<td style="width:38%; color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Fuente</td>
<td>Parveshiiii/AI-vs-Real — HuggingFace Datasets</td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Total imágenes</td>
<td><span style="font-family:'DM Mono',monospace">13 999</span></td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Distribución</td>
<td><span style="font-family:'DM Mono',monospace">~75% IA-Generada / ~25% Real</span> — desbalanceado</td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">División</td>
<td><span style="font-family:'DM Mono',monospace">80% train+val / 20% test</span> — estratificado</td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Resolución entrada</td>
<td><span style="font-family:'DM Mono',monospace">64×64 px</span> — normalizado [0, 1]</td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Data augmentation</td>
<td>Flip horizontal, rotación ±10%, zoom ±10%</td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Pipeline</td>
<td><span style="font-family:'DM Mono',monospace">tf.data.Dataset</span> con prefetch y augmentation en GPU</td>
</tr>
</tbody>
</table>
</div>
"""
variables_html = """
<div class="card" style="margin-top:8px">
<p class="section-title">Variables del Sistema</p>
<p class="cat-label">Generales</p>
<div class="var-grid">
<div class="var-card">
<div class="var-val">64×64</div>
<div class="var-lbl">IMG_SIZE</div>
</div>
<div class="var-card">
<div class="var-val">32</div>
<div class="var-lbl">BATCH_SIZE</div>
</div>
<div class="var-card">
<div class="var-val">42</div>
<div class="var-lbl">SEED</div>
</div>
<div class="var-card">
<div class="var-val">80 / 20</div>
<div class="var-lbl">Train / Test</div>
</div>
<div class="var-card accent-blue">
<div class="var-val">1e-3</div>
<div class="var-lbl">LR — CNN</div>
</div>
<div class="var-card accent-blue">
<div class="var-val">1e-4 → 1e-5</div>
<div class="var-lbl">LR — EfficientNet</div>
</div>
<div class="var-card accent-blue">
<div class="var-val">1e-4</div>
<div class="var-lbl">LR — ViT</div>
</div>
<div class="var-card">
<div class="var-val">0.50</div>
<div class="var-lbl">Umbral decisión</div>
</div>
</div>
<p class="cat-label">Vision Transformer</p>
<div class="var-grid">
<div class="var-card accent-violet">
<div class="var-val">8×8</div>
<div class="var-lbl">PATCH_SIZE</div>
</div>
<div class="var-card accent-violet">
<div class="var-val">64</div>
<div class="var-lbl">NUM_PATCHES</div>
</div>
<div class="var-card accent-violet">
<div class="var-val">128</div>
<div class="var-lbl">EMBED_DIM</div>
</div>
<div class="var-card accent-violet">
<div class="var-val">4</div>
<div class="var-lbl">NUM_HEADS</div>
</div>
<div class="var-card accent-violet">
<div class="var-val">4</div>
<div class="var-lbl">NUM_BLOCKS</div>
</div>
<div class="var-card accent-violet">
<div class="var-val">256</div>
<div class="var-lbl">FF_DIM</div>
</div>
<div class="var-card accent-violet">
<div class="var-val">0.10</div>
<div class="var-lbl">DROPOUT_RATE</div>
</div>
</div>
<p class="cat-label">Callbacks</p>
<div class="var-grid">
<div class="var-card accent-green">
<div class="var-val">5</div>
<div class="var-lbl">EarlyStopping patience</div>
</div>
<div class="var-card accent-green">
<div class="var-val">0.5</div>
<div class="var-lbl">ReduceLR factor</div>
</div>
<div class="var-card accent-green">
<div class="var-val">3</div>
<div class="var-lbl">ReduceLR patience</div>
</div>
<div class="var-card accent-green">
<div class="var-val">1e-6</div>
<div class="var-lbl">ReduceLR min_lr</div>
</div>
</div>
</div>
"""
sobre_html = """
<div class="card">
<p class="section-title">Sobre el Proyecto</p>
<table class="data-table">
<tbody>
<tr>
<td style="width:22%; color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Problema</td>
<td>Clasificación binaria supervisada: imagen IA-Generada vs. imagen Real</td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Motivación</td>
<td>Los generadores de imagen por IA producen imágenes indistinguibles para el ojo humano, generando riesgos de desinformación, deepfakes y fraude digital</td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Autores</td>
<td>Javier Felipe Aldana Jaramillo · Integrante 2 · Integrante 3 · Integrante 4</td>
</tr>
<tr>
<td style="color:#4a4a6a; font-family:'DM Mono',monospace; font-size:0.72rem; letter-spacing:0.05em; text-transform:uppercase">Curso</td>
<td>Técnicas de Aprendizaje de Máquina — PUJ Bogotá · 2026</td>
</tr>
</tbody>
</table>
</div>
"""
# ── Interfaz ───────────────────────────────────────────────────────────────
with gr.Blocks(
title="Detector IA vs Real",
css=css,
) as demo:
gr.HTML(header_html)
with gr.Tabs():
# Tab 1 — Predictor
with gr.Tab("Predictor"):
with gr.Row(equal_height=True):
with gr.Column(scale=1):
img_input = gr.Image(
label="Imagen de entrada",
type="numpy",
height=300,
)
btn = gr.Button("Analizar", variant="primary")
with gr.Column(scale=1):
out_label = gr.Label(label="Probabilidad por clase", num_top_classes=2)
out_veredicto = gr.Textbox(
label="Veredicto",
interactive=False,
lines=1,
elem_classes=["verdict-box"],
)
out_detalle = gr.Textbox(
label="Detalle técnico",
interactive=False,
lines=4,
)
gr.HTML(variables_html)
btn.click(fn=predecir, inputs=[img_input],
outputs=[out_label, out_veredicto, out_detalle])
img_input.change(fn=predecir, inputs=[img_input],
outputs=[out_label, out_veredicto, out_detalle])
# Tab 2 — Modelos
with gr.Tab("Modelos"):
gr.HTML(tabla_modelos)
gr.HTML(tabla_metricas)
# Tab 3 — Hiperparámetros
with gr.Tab("Hiperparámetros"):
gr.HTML(tabla_hiperparametros)
# Tab 4 — Dataset
with gr.Tab("Dataset"):
gr.HTML(tabla_dataset)
# Tab 5 — Proyecto
with gr.Tab("Proyecto"):
gr.HTML(sobre_html)
if __name__ == "__main__":
demo.launch()