HN presenta: Investigación sobre interpretabilidad de Llama 3.2 con Sparse Autoencoders
(github.com/PaulPauls)- Proyecto que busca descomponer las representaciones internas de Llama 3.2-3B con Sparse Autoencoder (SAE) para extraer características interpretables; publica el pipeline completo ejecutado una vez, desde la captura de activaciones hasta el entrenamiento, la interpretación y la validación, junto con sus artefactos
- El pipeline captura la activación residual de la capa 23 de Llama 3.2-3B a partir de datos de OpenWebText a nivel de oración, y entrena en PyTorch un SAE con 65,536 latentes y configuración TopK=64
- Los recursos publicados incluyen un dataset de OpenWebText a nivel de oración, 3.2 TB de activaciones de 25 millones de oraciones, logs de entrenamiento en Weights & Biases y un modelo SAE entrenado durante 10 epochs
- El entrenamiento se realizó durante unos 7 días en 8x Nvidia RTX4090; la pérdida normalizada final fue de aproximadamente 0.144, y la auxiliary loss mostró que los dead latents, que al inicio rondaban el 40%, se reactivaban rápidamente
- El análisis de interpretación consiste en analizar con Claude 3.5 las 50 oraciones principales que activaron con mayor fuerza cada latente; el feature steering es posible, pero en la primera versión beta los resultados no son consistentes
Objetivo y alcance del proyecto
- Este proyecto es un intento de aplicar Sparse Autoencoder (SAE) a Llama 3.2-3B para descomponer las representaciones internas de los LLM en características más interpretables
- Los LLM modernos usan superposition, donde varias características se almacenan superpuestas en las mismas neuronas, y los SAE buscan separar esas representaciones superpuestas proyectando las activaciones a un espacio latente muy grande y disperso
- El objetivo es ofrecer un pipeline completo que incluya los siguientes procesos
- Captura de activaciones del LLM
- Generación y preprocesamiento de datos de entrenamiento para el SAE
- Entrenamiento del SAE
- Análisis del significado de las características aprendidas
- Validación experimental y feature steering
- La versión actual 0.2 ya ejecutó una vez todo el pipeline y creó un SAE interpretable para Llama 3.2-3B, pero no es la versión final
- El proyecto tiene el carácter de reproducir investigaciones recientes de interpretabilidad mecánica basada en SAE de Anthropic, OpenAI y Google DeepMind
Funciones principales
- El pipeline está construido end-to-end, desde la captura de activaciones hasta la validación, y está escrito en PyTorch puro con dependencias mínimas
- Sus funciones principales son las siguientes
- Captura de activaciones residuales del LLM usando una variante del dataset OpenWebText a nivel de oración
- Prebatching y cálculo de estadísticas para un entrenamiento eficiente
- Entrenamiento distribuido de SAE multi-GPU en un solo nodo
- Auxiliary loss para prevenir y recuperar dead latents
- Gradient projection para estabilizar el entrenamiento
- Monitoreo de entrenamiento, validación y dead latents mediante Weights & Biases y logs de consola
- Captura de entradas que activan fuertemente los latentes y análisis semántico basado en Frontier LLM
- Implementación de chat y completado de texto para Llama 3.1/3.2 sin dependencia externa de Fairscale
- Validación del impacto del SAE y feature steering mediante completado de texto/chat y una UI opcional en Gradio
- Se indica que todos los componentes fueron diseñados teniendo en cuenta escalabilidad, eficiencia y mantenibilidad
Artefactos publicados
- OpenWebText Sentence Dataset
- Dataset variante que procesa OpenWebText a nivel de oración
- Mantiene todos los textos y el orden del OpenWebText original
- Las oraciones se almacenan individualmente en formato parquet para permitir acceso rápido
- La segmentación de oraciones se realizó con el tokenizador “Punkt” preentrenado de NLTK 3.9.1
- Captured Llama 3.2-3B Activations
- Activaciones residuales de la capa 23 de Llama 3.2-3B para 25 millones de oraciones
- Los 4 TB originales se comprimieron a 3.2 TB
- Se dividieron en 100 archivos para facilitar la gestión de descarga
- SAE Training Log
- Logs de métricas de entrenamiento, validación y depuración basados en Weights & Biases
- 10 epochs, 10,000 pasos registrados
- Incluye train/val main loss, auxiliary loss y estadísticas de dead latents
- Trained 65,536 latents SAE Model
- Modelo SAE final tras completar 10 epochs de entrenamiento
- Entrenado con 6,500 millones de activaciones provenientes de la capa 23 de Llama 3.2-3B
Estructura del código
- El proyecto se divide en cuatro componentes principales
-
Data Capture
capture_activations.py: captura de activaciones residuales del LLMopenwebtext_sentences_dataset.py: dataset personalizado para procesamiento a nivel de oración
-
SAE Training
sae.py: implementación del modelo SAE principalsae_preprocessing.py: preprocesamiento de datos de entrenamiento del SAEsae_training.py: implementación de entrenamiento distribuido del SAE
-
Interpretability
capture_top_activating_sentences.py: identificación de oraciones que maximizan la activación de featuresinterpret_top_sentences_send_batches.py: generación y envío de lotes para interpretacióninterpret_top_sentences_retrieve_batches.py: recepción de resultados de interpretacióninterpret_top_sentences_parse_responses.py: parseo y análisis de resultados de interpretación
-
Verification and Testing
llama_3_inference.py: implementación principal de inferenciallama_3_inference_text_completion_test.py: prueba de completado de textollama_3_inference_chat_completion_test.py: prueba de completado de chatllama_3_inference_text_completion_gradio.py: interfaz de Gradio para pruebas interactivas
Implementación personalizada de Llama 3.1/3.2
- La base de la investigación es la implementación del transformer Llama 3.1/3.2 en
llama_3/model_text_only.py - Esta implementación se basa en la implementación de referencia del repositorio de modelos Llama, pero fue modificada para los objetivos del proyecto
- Se eliminó la dependencia pesada de Fairscale
- Se eliminaron las funciones multimodales porque abordar también la interpretabilidad de imágenes en el lanzamiento inicial aumentaría la complejidad
- Al constructor de
Transformerse le agregaron argumentos que permiten capturar activaciones en capas específicas o inyectar un SAE entrenadostore_layer_activsae_layer_forward_fn
- La mayoría de los archivos auxiliares del directorio
llama_3/se mantienen desde el repositorio original de modelos Llama- El 95% del código auxiliar no se usa, pero se incluye tal cual porque el formateador de chat depende de imports interconectados
- La implementación real de inferencia está en
llama_3_inference.pyy admite streaming tanto para chat como para completado de texto - La inferencia admite batched inference, temperature y configuración de top-p; si temperature es 0, cambia automáticamente a greedy sampling
Captura y preprocesamiento de datos
- Para la captura de activaciones se usa un dataset personalizado derivado de OpenWebText, procesado a nivel de oración
- La configuración y escala de la captura son las siguientes
- 25 millones de oraciones
- Máximo de 192 tokens por oración
- 4 TB de activaciones sin procesar
- 3.2 TB tras compresión tar.gz
- Aproximadamente 700 millones de activaciones
- Longitud promedio de oración de 27.3 tokens
- El dataset es aproximadamente un orden de magnitud más pequeño que los cerca de 8,000 millones de activaciones únicas usadas por Anthropic y Google DeepMind
- Para compensar el dataset pequeño, se entrena el SAE durante 10 epochs para intentar igualar el número total de activaciones procesadas de los experimentos de Anthropic y Google DeepMind
- La diferencia es que el SAE de este proyecto ve cada activación 10 veces
- Se calculó que escalar a 32 TB aumentaría el costo de un bucket de GCP de unos $80/month a $800/month, por lo que existen restricciones de costo propias de un proyecto paralelo sin fines de lucro
- El procesamiento a nivel de oración es una decisión orientada a conservar el significado en unidades naturales del lenguaje
- Las oraciones se consideran unidades que contienen pensamientos y conceptos completos
- Evita cortes artificiales del contexto
- Busca reducir el contextual bleed, es decir, la mezcla de significado entre límites de oraciones
- También es una elección para usar las mismas activaciones a nivel de oración en análisis interpretativos posteriores
- Las oraciones se procesan sin token BOS
- El objetivo es evitar patrones específicos de posición e interpretar características basadas en significado
- El punto de captura es la capa 23 de las 28 capas de Llama 3.2-3B, con activaciones del residual stream después de layer normalization
- Corresponde aproximadamente al punto de 5/6 de la profundidad del modelo y sigue la implementación de OpenAI
- La captura se implementa con inferencia multi-GPU de un solo nodo basada en NCCL
- Un proceso separado gestiona I/O de disco asíncrono para reducir cuellos de botella en el procesamiento de GPU
- La captura completa tarda unas 12 horas en 4x Nvidia RTX4090
- El preprocesamiento es una etapa para crear de antemano batches de 1024 activaciones
- Se optó por un preprocesamiento separado porque las longitudes de secuencia variables y el manejo de carryover durante el entrenamiento podrían generar bugs complejos o cuellos de botella de I/O
- Se calcula el tensor de media de todas las activaciones mediante el algoritmo de Welford
- La media calculada se usa como valor inicial del bias
b_predel SAE - Todo el pipeline de preprocesamiento se paraleliza en CPU con multiprocessing
Diseño del SAE y método de entrenamiento
- El SAE usa una arquitectura TopK Autoencoder que sigue principalmente las elecciones de OpenAI
- El forward pass se compone de la siguiente forma
- Encoder:
h = TopK(W_enc(x - b_pre) + b_enc) - Decoder:
x^ = W_dec * h (+ h_bias) + b_pre
- Encoder:
b_prese usa tanto en el encoder como en el decoder y se inicializa con la media calculada en el preprocesamientob_ences un bias exclusivo del encoder y se inicializa aleatoriamente- La sparsity latente se impone con la función de activación TopK
- Solo se conservan las k activaciones más grandes y el resto se establece en 0
- No se usa una penalización L1 como en el enfoque de Anthropic
- El
h_biasopcional está desactivado durante el entrenamiento, pero puede activarse después para feature steering - Se usa float32 para la precisión numérica
- Se explica que comparte con el bfloat16 requerido por Llama 1 bit de signo y 8 bits de exponente, por lo que la conversión es rápida y precisa
- Los principales hiperparámetros del SAE en este proyecto son los siguientes
d_model = 3072n_latents = 2**16, es decir, 65,536k = 64k_aux = 2048aux_loss_coeff = 1 / 32dead_steps_threshold = 80_000batch_size = 1024num_epochs = 10learning_rate = 5e-5train_val_split = 0.95
- Se elige una dimensión latente aproximadamente 21 veces mayor que la dimensión 3,072 del residual stream de Llama 3.2 3B
- La función de pérdida es una combinación de main reconstruction loss y auxiliary loss
total_loss = main_loss + aux_loss_coeff * aux_loss- Ambas pérdidas se calculan en normalized space
- La auxiliary loss sigue el método propuesto por OpenAI y cumple la función de prevenir y reactivar latentes muertos
- Calcula el MSE entre el residual de la reconstrucción principal y la reconstrucción auxiliar
- Envía de vuelta al decoder los valores top-
k_auxentre los latentes que no se han activado recientemente, para darles señal de entrenamiento - Induce a los latentes inactivos, excluidos del entrenamiento principal que usa solo los top
klatentes, a capturar la información perdida
- Si un latente no se activa durante
dead_steps_threshold, es decir, 80,000 training steps, se considera muerto- Esta configuración equivale aproximadamente a 1 epoch
- Con un effective batch size de 8192, significa que no se activó ni una sola vez en la reconstrucción de unas 650 millones de activaciones recientes
- El entrenamiento se realiza como entrenamiento distribuido multi-GPU de un solo nodo con backend NCCL
- 8x Nvidia RTX4090
- 10 epochs
- per-GPU batch size 1024
- effective batch size 8192
- Aproximadamente 7,000 millones de activaciones procesadas
- Tardó poco más de 7 días
- La configuración de AdamW se ajustó teniendo en cuenta los patrones de activación poco frecuentes de los sparse autoencoders
beta_1 = 0.85beta_2 = 0.9999eps = 6.25e-10- El learning rate disminuye de 5e-5 a 1e-5 mediante cosine annealing
- Los pesos del decoder se normalizan con unit norm después de la inicialización y en cada training step
project_decoder_grads()elimina las componentes del gradiente paralelas al vector de diccionario existente para mantener la restricción unit-norm de los pesos del decoder
Resultados del entrenamiento
- El entrenamiento del SAE se realizó durante unos 7 días en 8 Nvidia RTX4090 y mostró una convergencia estable
- La pérdida total normalizada final llegó a aproximadamente 0.144
- La validation loss se calculó sobre un segmento held-out del 5% de los datos de entrenamiento y mostró un patrón de disminución logarítmica similar al de la training loss
- Después del warm-up de 80,000 training steps, cerca del 40% de los latents se identificaron como dead
- La auxiliary loss reactivó rápidamente los dead latents, y la proporción de dead latents disminuyó con rapidez
- La auxiliary loss solo se calcula cuando hay al menos 2,048 dead latents, el valor mínimo de
k_aux- Esta condición convirtió a alrededor del 3% de los 65,536 latents en una especie de soft lower bound
- En la parte final, al no haber suficientes dead latents, la auxiliary loss pasó a ser 0 con frecuencia
- Anthropic y OpenAI reportaron hasta 65% de dead latents en ciertas configuraciones, pero este proyecto mostró que los dead latents se reducen rápidamente con una combinación de menor latent size, auxiliary loss y gradient projection
- Se indica que, en futuros experimentos, eliminar la condición de un mínimo de dead latents para calcular la auxiliary loss podría reducir aún más los dead latents
Análisis de interpretabilidad
- El análisis de interpretabilidad toma como referencia el método de scaling monosemanticity de Anthropic, pero analiza a nivel de oración en lugar de tokens individuales
- Para cada latent, se capturaron las 50 oraciones con activación más fuerte
- La activation strength se agregó sobre todos los tokens de la oración de dos maneras
mean: método para encontrar temas semánticos que se activan de forma sostenida a lo largo de toda la oraciónlast: método para aprovechar la representación del último token, que en un modelo autorregresivo ya vio toda la oración
- Para el análisis semántico se usó Claude 3.5, específicamente
claude-3-5-sonnet-20241022 - El prompt se configuró para realizar los siguientes pasos sobre las 50 oraciones
- Identificar palabras y frases clave
- Agrupar elementos temáticos
- Considerar posibles outliers
- Proporcionar una interpretación semántica final con confidence score
- El pipeline de análisis se implementó en tres etapas
- Enviar solicitudes de análisis en batches eficientes en costo
- Recibir respuestas
- Parsear y procesar las interpretaciones semánticas
- Los artefactos intermedios se conservaron para reproducibilidad y análisis adicionales
capture_top_sentences/: oraciones originales, activation aggregation, índice de OpenWebTexttop_sentences_last_responses/ytop_sentences_mean_responses/: respuestas de análisis semántico sin procesarlatent_index_meaning/: mapeo entre latent index ycommon_semantic, certainty score
- Como ejemplo, el latent #896 se identificó como “referencias a terminología institucional formal sobre agencias, personas, operaciones y documentos oficiales de las Naciones Unidas”
- Las 50 de 50 oraciones hacen referencia directa a la ONU
- Incluyen términos como
UN,United Nations,Secretary-General,Special Rapporteur,UNDP,UNHCR,OCHA,UNODC - La certainty se calculó en 1.0
- Procesar 24,828,558 input tokens y 3,920,044 output tokens con Claude 3.5 batch mode costó $66.74
- Se indica que este enfoque fue elegido como método inicial para feature extraction y posible feature steering, y que su simplicidad tiene un costo en términos de calidad de resultados
Validación y feature steering
- La infraestructura de validación consta de tres scripts para analizar y verificar el impacto del SAE en el comportamiento del modelo
llama_3_inference_chat_completion_test.pyllama_3_inference_text_completion_test.pyllama_3_inference_text_completion_gradio.py
- Cada implementación soporta lo siguiente
- batched inference
- tratar cada línea como un batch element separado
- configuración de temperature y top-p
- inyección del SAE entrenado
- análisis de feature activation
- feature steering
- El semantic meaning y el certainty score de
latent_index_meaning/se usan como base para el análisis de feature activation y los experimentos de steering - Los prompts de ejemplo son los siguientes cuatro
The delegates gathered at theForeign officials released a statementHumanitarian staff coordinated their effortsSenior diplomats met to discuss
- El ejemplo de completado de texto se ejecutó con la configuración
max_new_tokens=128,temperature=0.7,top_p=0.9,seed=42 - El ejemplo de feature steering apunta al latent #896
- Mediante
h_bias, aumenta el valor de latent activation en 20 - Puede guiar los completados de texto del modelo hacia contenido relacionado con la ONU
- Mediante
- El feature steering de la primera versión beta no es fuerte
- Incluso en el ejemplo, solo la segunda y la tercera oración se convierten en contenido relacionado con la ONU
- Se eligieron intencionalmente inicios de oración con probabilidad de derivar hacia la ONU
- Se indica que fallaría con un inicio de oración no relacionado con la ONU, como
For any n, if 2n - 1 is odd
- Como el análisis de interpretabilidad actual se enfocó en feature extraction más que en optimización de steering, los resultados de steering no son consistentes
- Se concluye que el feature steering tiene un carácter de demostración adicional en el primer lanzamiento, mientras que la feature extraction en sí resulta útil para entender el modelo
Próximas líneas de mejora
- Se propone un experimento para aumentar la dimensión latente a al menos
2^18, es decir, 262,144 features, y reducir k a 32- Es una dirección orientada a descubrir más features únicos y mantener una sparsity más fuerte
- El mayor costo computacional debería compensarse con mejoras de eficiencia o métodos como gradient accumulation
- Hay planes para sistematizar más el tracking de activaciones latentes
- Registrar con frecuencia el estado del tensor
latent_last_nonzerodurante el entrenamiento permitiría ver con más profundidad cuándo los latentes se activan o mueren
- Registrar con frecuencia el estado del tensor
- Se propone soporte para analizar feature interaction mediante el seguimiento de patrones de coactivación en el espacio latente sparse
- Se plantea como trabajo futuro un método de análisis de interpretación que agrupe de forma más precisa oraciones de alta activación y n-grams
- Además de feature extraction, también se puede realizar análisis de interpretación basado en feature steering
- La investigación puede ampliarse a activations de Llama 3.1-8B
- Como comparte la base de código con Llama 3.2, los principales requisitos son ajustar hyperparameters y una gran cantidad de compute power
- También se proponen experimentos que cambien el punto de captura de activations
- Capas más tempranas del modelo
- La salida de attention heads dentro del transformer block
- La salida de MLP
- El mecanismo de auxiliary loss puede optimizarse más
- La implementación actual mostró un rendimiento sólido para prevenir dead latents, y se puede investigar la relación entre el threshold mínimo de dead latents y la calidad de los features
- El bias term de la arquitectura SAE y el ajuste de la main loss function también son candidatos para experimentos futuros
- Hace falta agregar docstrings en toda la base de código
- Se indica que, aunque se agregó inline documentation, no hubo tiempo para incluir proper docstrings en el primer release
1 comentarios
Opiniones de Hacker News
La interpretabilidad mecánica aborda un problema común que surge al preguntarle a un LLM “por qué respondió así”. La autoexplicación del modelo se parece más a un juego retórico en el que inventa razones plausibles y persuasivas basadas en patrones de los datos de entrenamiento, que a las razones reales
A medida que el modelo se vuelve más potente, puede justificar falsedades a posteriori de manera más convincente, por lo que a veces empeora en pruebas para detectar por sí mismo la “falta de veracidad”. El objetivo no es la verdad, sino la coherencia
La retórica no es razonamiento, y la explicabilidad real que afirman ofrecer los autoencoders dispersos sobreajustados se parece más al flujo causal de los “pensamientos” por los que pasa el modelo al generar una respuesta
Ha habido debates que ven el razonamiento como una herramienta de influencia social, lo que también explica por qué a las personas elocuentes les cuesta admitir que están equivocadas. Normalmente, porque han ganado discusiones contra otros. X viene a la mente como ejemplo representativo
Aun así, reconozco que una cosa es una gran intuición y el inicio de un programa de investigación plausible. Los espacios vectoriales de alta dimensión, acotados y casi ortogonales son muy contraintuitivos, y existen resultados previos para tratarlos rigurosamente https://en.m.wikipedia.org/wiki/Johnson%E2%80%93Lindenstraus...
Como el modelo no tiene memoria y no puede distinguir el origen del texto, un modelo “veraz” debería reconocer el error incluso sin que se le pregunte. En la práctica, es muy probable que haga una construcción paralela para respaldar “su” decisión
Es un trabajo sorprendente y muy bien documentado. En particular destacan las curvas de pérdida y la evaluación de latentes muertos
Nuestro equipo también investigó SAE, pero los entrenamos para reconstruir embeddings densos de resúmenes de papers, no tokens individuales https://arxiv.org/abs/2408.00657
Al variar el nivel de dispersión y la dimensión del espacio latente del SAE, también observamos escalado de ley de potencia en el límite inferior de las curvas de pérdida, y pudimos mitigar por completo los latentes muertos con una pérdida auxiliar. Durante las iteraciones de entrenamiento también vimos un patrón suave de onda senoidal, aunque no sé si se debe a esta aplicación específica con embeddings de resúmenes o si es un fenómeno más general
A primera vista parece un trabajo positivo para la alineación, aunque todavía no revisé los detalles. No sé si será posible hacerlo, pero me pregunto cuánto habría que pagar para compensar el tiempo, el costo y el riesgo
Hace poco leí un artículo sobre la dificultad de evaluar SAE: https://adamkarvonen.github.io/machine_learning/2024/06/11/s...
Me interesa saber cómo abordaron este problema y dónde debería mirar en el repositorio para entender ese enfoque
Incluso suponiendo que ya hubiéramos resuelto el problema de encontrar varias arquitecturas SAE perfectas y entrenarlas a la perfección, decidir cuál SAE es mejor dependería de cuál rinde mejor según las métricas de metodologías de interpretabilidad automática. En particular, la metodología de OpenAI enfatiza la interpretabilidad automática a escala, puntuando los SAE con muchas métricas técnicas
Como las mejores métricas y la metodología en sí siguen siendo preguntas abiertas de investigación, podríamos haber experimentado durante varios meses más, pero para este primer lanzamiento opté por un enfoque simple. En los detalles de implementación y en el capítulo 4, Interpretability Analysis, de los resultados, trato las diferencias entre mi metodología y la de OpenAI https://github.com/PaulPauls/llama3_interpretability_sae#4-i...
También recomiendo leer directamente el paper de OpenAI o transformer-circuits.pub de Anthropic https://transformer-circuits.pub/
Este trabajo fue retirado y el repositorio también fue archivado. No hay explicación de qué pasó
Trabajo realmente genial. Me pregunto si hay planes para integrarlo con SAELens
Como proyecto más simple, también podría mostrar cómo construir desde cero, en PyTorch puro, el modelo completo de la implementación actual de Llama 3.2. Me gusta construir desde los cimientos, y al buscar documentación para la sección de contexto de Llama 3.2 de este proyecto SAE, vi que la documentación existente suele ser demasiado superficial o está desactualizada para Llama 1/2. La documentación de machine learning se vuelve obsoleta demasiado rápido hoy en día
Tengo una pregunta medio rara sobre la interpretabilidad mecánica. Cuando se mide a los humanos con alguna métrica, los humanos intentan explotar esa métrica; me pregunto si una IA futura también podría explotar la interpretabilidad mecánica
Para explicar la idea de forma sencilla, supongamos que codificamos tokens en una matriz 2D: si el mapeo fuera Apple=1a, Pear=1b, Donkey=2a, Horse=2b, cuando las neuronas 1, 2, a y b están activadas, se vuelve difícil entender si eso representa apple+horse o donkey+pear
Si una IA futura mucho más capaz supervisara su propio entrenamiento, ¿no podría elegir pesos que conserven la posibilidad de estas colisiones de codificación, engañando a los observadores de interpretabilidad mecánica y, en la práctica, pensando en eufemismos?
Por ejemplo, se podría buscar un modelo que sea racista pero que no tenga patrones de activación interpretables identificables como racismo. El trabajo de este Show HN sugiere que incluso una persona con suficiente financiamiento apenas podría intentar este entrenamiento adversarial, y si surgieran nuevos resultados sería bastante interesante
Me alegra mucho ver más trabajo público con SAE. El esfuerzo de ingeniería tampoco parece menor, y mañana revisaré el código de carga de datos
También podría interesarte mi proyecto en curso para entrenar SAE en modelos de visión: https://github.com/samuelstevens/saev
Creo que recibiría más atención y una mejor respuesta si encontraran el latente de Golden Gate Bridge y subieran Golden Gate Llama 3.2 a HuggingFace
Mejor aún si incluyen un enlace a un Space con el que se pueda conversar. Y aunque nadie lo pidió, poner resultados o visualizaciones interesantes al principio del README es una muy buena idea