Spaces:
Running
Running
Commit ·
5b6c556
0
Parent(s):
Deploy static demo
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +48 -0
- LOGO/Logo.png +0 -0
- README.md +25 -0
- attribution_analysis/attribution_analysis_page.py +1395 -0
- cache/cached_attribution_results.json +0 -0
- circuit_analysis/CLT_IMPROVEMENTS.md +232 -0
- circuit_analysis/WORKFLOW_PER_PROMPT.md +106 -0
- circuit_analysis/attribution_graphs_olmo.py +1931 -0
- circuit_analysis/attribution_graphs_olmo_de.py +1165 -0
- circuit_analysis/attribution_graphs_olmo_offline.py +1922 -0
- circuit_analysis/calculate_cpr_cmd.py +338 -0
- circuit_analysis/circuit_trace_page.py +0 -0
- circuit_analysis/merge_circuit_results.py +57 -0
- circuit_analysis/offline_circuit_metrics.py +194 -0
- circuit_analysis/plot_offline_metrics.py +239 -0
- circuit_analysis/results/attribution_graphs_results.json +0 -0
- circuit_analysis/results/attribution_graphs_results_de.json +0 -0
- circuit_analysis/results/attribution_graphs_results_de_prompt_1.json +0 -0
- circuit_analysis/results/attribution_graphs_results_de_prompt_2.json +0 -0
- circuit_analysis/results/attribution_graphs_results_de_prompt_3.json +0 -0
- circuit_analysis/results/attribution_graphs_results_prompt_1.json +0 -0
- circuit_analysis/results/attribution_graphs_results_prompt_2.json +0 -0
- circuit_analysis/results/attribution_graphs_results_prompt_3.json +0 -0
- circuit_analysis/results/clt_training_stats.json +4508 -0
- circuit_analysis/results/cpr_cmd_results.json +108 -0
- circuit_analysis/results/feature_interpretations_cache/feature_interpretations.json +0 -0
- circuit_analysis/results/offline_circuit_metrics.json +484 -0
- circuit_analysis/train_clt_and_plot.py +353 -0
- function_vectors/data/multilingual_function_categories.py +0 -0
- function_vectors/data/visualizations/de_pca_3d_categories_layer_-1.html +0 -0
- function_vectors/data/visualizations/en_pca_3d_categories_layer_-1.html +0 -0
- function_vectors/function_vectors_page.py +1845 -0
- function_vectors/generate_function_vectors.py +85 -0
- function_vectors/generate_german_vectors.py +90 -0
- function_vectors/generate_page_assets.py +132 -0
- function_vectors/translate_prompts.py +264 -0
- influence_tracer/build_dolma_index.py +207 -0
- locales/de/attribution_analysis_page.json +174 -0
- locales/de/circuit_trace_page.json +215 -0
- locales/de/common.json +167 -0
- locales/de/function_vectors_page.json +171 -0
- locales/de/welcome_page.json +39 -0
- locales/en/attribution_analysis_page.json +174 -0
- locales/en/circuit_trace_page.json +217 -0
- locales/en/common.json +45 -0
- locales/en/function_vectors_page.json +164 -0
- locales/en/welcome_page.json +39 -0
- packages.txt +2 -0
- requirements.txt +20 -0
- run_webapp.py +40 -0
.gitignore
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Ignore large model directories
|
| 3 |
+
models/
|
| 4 |
+
circuit_analysis/models/
|
| 5 |
+
circuit_analysis/debug_cpr.log
|
| 6 |
+
circuit_analysis/debug_cpr_2.log
|
| 7 |
+
influence_tracer/influence_tracer_data/
|
| 8 |
+
influence_tracer/dolma_dataset_sample_1.6v/
|
| 9 |
+
influence_tracer/dolma
|
| 10 |
+
|
| 11 |
+
# Ignore python cache
|
| 12 |
+
__pycache__/
|
| 13 |
+
*.pyc
|
| 14 |
+
|
| 15 |
+
# Ignore local env files if any
|
| 16 |
+
.env
|
| 17 |
+
.venv
|
| 18 |
+
env/
|
| 19 |
+
|
| 20 |
+
# Ignore system files
|
| 21 |
+
.DS_Store
|
| 22 |
+
|
| 23 |
+
# User Data
|
| 24 |
+
user_study/data/
|
| 25 |
+
user_study/voice_memos/files/
|
| 26 |
+
user_study/voice_memos/merged_files/
|
| 27 |
+
user_study/voice_memos/transcripts/
|
| 28 |
+
process_faithfulness.py
|
| 29 |
+
Faithfulness.csv
|
| 30 |
+
FaithfulnessNew.csv
|
| 31 |
+
|
| 32 |
+
# Writing & Documentation artifacts
|
| 33 |
+
writing/
|
| 34 |
+
ELIA_Demo_Script.md
|
| 35 |
+
texput.log
|
| 36 |
+
|
| 37 |
+
# Binary files causing upload issues
|
| 38 |
+
|
| 39 |
+
circuit_analysis/results/attribution_graph_prompt_1.png
|
| 40 |
+
circuit_analysis/results/attribution_graph_prompt_2.png
|
| 41 |
+
circuit_analysis/results/attribution_graph_prompt_3.png
|
| 42 |
+
circuit_analysis/results/attribution_graph_prompt_de_1.png
|
| 43 |
+
circuit_analysis/results/attribution_graph_prompt_de_2.png
|
| 44 |
+
circuit_analysis/results/attribution_graph_prompt_de_3.png
|
| 45 |
+
circuit_analysis/results/clt_training_loss.png
|
| 46 |
+
circuit_analysis/results/offline_circuit_metrics_combined.png
|
| 47 |
+
function_vectors/data/vectors/de_category_vectors.npz
|
| 48 |
+
function_vectors/data/vectors/en_category_vectors.npz
|
LOGO/Logo.png
ADDED
|
README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: ELIA Analysis Suite
|
| 3 |
+
colorFrom: blue
|
| 4 |
+
colorTo: indigo
|
| 5 |
+
sdk: streamlit
|
| 6 |
+
sdk_version: 1.38.0
|
| 7 |
+
app_file: web_app.py
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# ELIA: Simplifiying Outcomes of Language Model Component Analyses
|
| 13 |
+
|
| 14 |
+
ELIA is an interactive analysis suite for exploring the internal mechanisms of the OLMo 7B language model.
|
| 15 |
+
|
| 16 |
+
## Features
|
| 17 |
+
- **Attribution Analysis:** Visualize token importance using Integrated Gradients, Occlusion, and Saliency.
|
| 18 |
+
- **Function Vectors:** Explore how the model represents different tasks in its internal activation space.
|
| 19 |
+
- **Circuit Tracing:** (Static Demo) View pre-computed circuit traces for specific behaviors.
|
| 20 |
+
|
| 21 |
+
## Note on this Demo
|
| 22 |
+
This Space runs in a **static demonstration mode**. Due to storage and compute constraints, the full OLMo-7B model is not loaded. Instead, you can explore pre-computed analyses for a set of example prompts.
|
| 23 |
+
|
| 24 |
+
The AI-powered explanations are fully functional and powered by the Qwen API.
|
| 25 |
+
|
attribution_analysis/attribution_analysis_page.py
ADDED
|
@@ -0,0 +1,1395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import inseq
|
| 3 |
+
import torch
|
| 4 |
+
import os
|
| 5 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 6 |
+
import json
|
| 7 |
+
import requests
|
| 8 |
+
from bs4 import BeautifulSoup
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import numpy as np
|
| 11 |
+
from inseq.models.huggingface_model import HuggingfaceDecoderOnlyModel
|
| 12 |
+
import base64
|
| 13 |
+
from io import BytesIO
|
| 14 |
+
from PIL import Image
|
| 15 |
+
import plotly.graph_objects as go
|
| 16 |
+
import re
|
| 17 |
+
import markdown
|
| 18 |
+
from utilities.localization import tr
|
| 19 |
+
import faiss
|
| 20 |
+
from sentence_transformers import SentenceTransformer, util
|
| 21 |
+
from sentence_splitter import SentenceSplitter
|
| 22 |
+
import html
|
| 23 |
+
from utilities.utils import init_qwen_api
|
| 24 |
+
from utilities.feedback_survey import display_attribution_feedback
|
| 25 |
+
from thefuzz import process, fuzz
|
| 26 |
+
import gc
|
| 27 |
+
import time
|
| 28 |
+
import sys
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
|
| 31 |
+
# A dictionary to map method names to translation keys.
|
| 32 |
+
METHOD_DESC_KEYS = {
|
| 33 |
+
"integrated_gradients": "desc_integrated_gradients",
|
| 34 |
+
"occlusion": "desc_occlusion",
|
| 35 |
+
"saliency": "desc_saliency"
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
# Configuration for the influence tracer.
|
| 39 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 40 |
+
INDEX_DIR = os.path.join("influence_tracer", "influence_tracer_data")
|
| 41 |
+
INDEX_PATH = os.path.join(INDEX_DIR, "dolma_index_multi.faiss")
|
| 42 |
+
MAPPING_PATH = os.path.join(INDEX_DIR, "dolma_mapping_multi.json")
|
| 43 |
+
TRACER_MODEL_NAME = 'paraphrase-multilingual-mpnet-base-v2'
|
| 44 |
+
|
| 45 |
+
class CachedAttribution:
|
| 46 |
+
# A mock object to mimic inseq's Attribution object for cached results.
|
| 47 |
+
def __init__(self, html_content):
|
| 48 |
+
self.html_content = html_content
|
| 49 |
+
|
| 50 |
+
def show(self, display=False, return_html=True):
|
| 51 |
+
return self.html_content
|
| 52 |
+
|
| 53 |
+
def load_all_attribution_models():
|
| 54 |
+
# Loads all the attribution models.
|
| 55 |
+
try:
|
| 56 |
+
# Set the device to MPS, CUDA, or CPU.
|
| 57 |
+
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
|
| 58 |
+
|
| 59 |
+
# Path to the local model.
|
| 60 |
+
model_path = "./models/OLMo-2-1124-7B"
|
| 61 |
+
hf_token = os.environ.get("HF_TOKEN")
|
| 62 |
+
|
| 63 |
+
# Load tokenizer and model.
|
| 64 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path, token=hf_token, trust_remote_code=True)
|
| 65 |
+
tokenizer.model_max_length = 512
|
| 66 |
+
|
| 67 |
+
# Load the model with half precision to save memory.
|
| 68 |
+
base_model = AutoModelForCausalLM.from_pretrained(
|
| 69 |
+
model_path,
|
| 70 |
+
token=hf_token,
|
| 71 |
+
torch_dtype=torch.float16,
|
| 72 |
+
low_cpu_mem_usage=True,
|
| 73 |
+
trust_remote_code=True
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
# Move the model to the selected device.
|
| 77 |
+
base_model = base_model.to(device)
|
| 78 |
+
|
| 79 |
+
# Add missing special tokens if necessary.
|
| 80 |
+
if tokenizer.bos_token is None:
|
| 81 |
+
tokenizer.add_special_tokens({'bos_token': '<s>'})
|
| 82 |
+
base_model.resize_token_embeddings(len(tokenizer))
|
| 83 |
+
|
| 84 |
+
# Patch the model config.
|
| 85 |
+
if base_model.config.bos_token_id is None:
|
| 86 |
+
base_model.config.bos_token_id = tokenizer.bos_token_id
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
attribution_models = {}
|
| 90 |
+
|
| 91 |
+
# Set up the Integrated Gradients model.
|
| 92 |
+
attribution_models["integrated_gradients"] = HuggingfaceDecoderOnlyModel(
|
| 93 |
+
model=base_model,
|
| 94 |
+
tokenizer=tokenizer,
|
| 95 |
+
device=device,
|
| 96 |
+
attribution_method="integrated_gradients",
|
| 97 |
+
attribution_kwargs={"n_steps": 10}
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
# Set up the Occlusion model.
|
| 102 |
+
attribution_models["occlusion"] = HuggingfaceDecoderOnlyModel(
|
| 103 |
+
model=base_model,
|
| 104 |
+
tokenizer=tokenizer,
|
| 105 |
+
device=device,
|
| 106 |
+
attribution_method="occlusion"
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
# Set up the Saliency model.
|
| 110 |
+
attribution_models["saliency"] = HuggingfaceDecoderOnlyModel(
|
| 111 |
+
model=base_model,
|
| 112 |
+
tokenizer=tokenizer,
|
| 113 |
+
device=device,
|
| 114 |
+
attribution_method="saliency"
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
return attribution_models, tokenizer, base_model, device
|
| 118 |
+
|
| 119 |
+
except Exception as e:
|
| 120 |
+
st.error(f"Error loading models: {str(e)}")
|
| 121 |
+
return None, None, None, None
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def load_influence_tracer_data():
|
| 125 |
+
# Loads the data needed for the influence tracer.
|
| 126 |
+
if not os.path.exists(INDEX_PATH) or not os.path.exists(MAPPING_PATH):
|
| 127 |
+
return None, None, None
|
| 128 |
+
|
| 129 |
+
index = faiss.read_index(INDEX_PATH)
|
| 130 |
+
with open(MAPPING_PATH, 'r', encoding='utf-8') as f:
|
| 131 |
+
mapping = json.load(f)
|
| 132 |
+
|
| 133 |
+
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
|
| 134 |
+
model = SentenceTransformer(TRACER_MODEL_NAME, device=device)
|
| 135 |
+
return index, mapping, model
|
| 136 |
+
|
| 137 |
+
@st.cache_data(persist=True)
|
| 138 |
+
def get_influential_docs(text_to_trace: str, lang: str):
|
| 139 |
+
# Finds influential documents from the training data for a given text.
|
| 140 |
+
faiss_index, doc_mapping, tracer_model = load_influence_tracer_data()
|
| 141 |
+
if not faiss_index:
|
| 142 |
+
return []
|
| 143 |
+
|
| 144 |
+
# Get the embedding for the input text.
|
| 145 |
+
doc_embedding = tracer_model.encode([text_to_trace], convert_to_numpy=True, normalize_embeddings=True)
|
| 146 |
+
|
| 147 |
+
# Search the FAISS index for the top k documents.
|
| 148 |
+
k = 3
|
| 149 |
+
similarities, indices = faiss_index.search(doc_embedding.astype('float32'), k)
|
| 150 |
+
|
| 151 |
+
# Find the most similar sentence in each influential document.
|
| 152 |
+
results = []
|
| 153 |
+
query_embedding = tracer_model.encode([text_to_trace], normalize_embeddings=True)
|
| 154 |
+
|
| 155 |
+
for i in range(k):
|
| 156 |
+
doc_id = str(indices[0][i])
|
| 157 |
+
if doc_id in doc_mapping:
|
| 158 |
+
doc_info = doc_mapping[doc_id]
|
| 159 |
+
file_path = os.path.join("influence_tracer", "dolma_dataset_sample_1.6v", doc_info['file'])
|
| 160 |
+
try:
|
| 161 |
+
full_doc_text = ""
|
| 162 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 163 |
+
for line in f:
|
| 164 |
+
try:
|
| 165 |
+
line_data = json.loads(line)
|
| 166 |
+
line_text = line_data.get('text', '')
|
| 167 |
+
# Use fuzzy matching to find the text snippet.
|
| 168 |
+
if fuzz.partial_ratio(doc_info['text_snippet'], line_text) > 95:
|
| 169 |
+
full_doc_text = line_text
|
| 170 |
+
break
|
| 171 |
+
except json.JSONDecodeError:
|
| 172 |
+
continue
|
| 173 |
+
|
| 174 |
+
# Skip if the document text wasn't found.
|
| 175 |
+
if not full_doc_text:
|
| 176 |
+
print(f"Warning: Could not find document snippet for doc {doc_id} in {file_path}. Skipping.")
|
| 177 |
+
continue
|
| 178 |
+
|
| 179 |
+
# Find the most similar sentence in the document.
|
| 180 |
+
splitter = SentenceSplitter(language=lang)
|
| 181 |
+
sentences = splitter.split(text=full_doc_text)
|
| 182 |
+
if not sentences:
|
| 183 |
+
sentences = [full_doc_text]
|
| 184 |
+
|
| 185 |
+
# Set a batch size to avoid memory issues.
|
| 186 |
+
sentence_embeddings = tracer_model.encode(sentences, batch_size=64, show_progress_bar=False, normalize_embeddings=True)
|
| 187 |
+
cos_scores = util.pytorch_cos_sim(query_embedding, sentence_embeddings)[0]
|
| 188 |
+
best_sentence_idx = torch.argmax(cos_scores).item()
|
| 189 |
+
most_similar_sentence = sentences[best_sentence_idx]
|
| 190 |
+
|
| 191 |
+
results.append({
|
| 192 |
+
'id': doc_id,
|
| 193 |
+
'file': doc_info['file'],
|
| 194 |
+
'source': doc_info['source'],
|
| 195 |
+
'text': full_doc_text,
|
| 196 |
+
'similarity': similarities[0][i],
|
| 197 |
+
'highlight_sentence': most_similar_sentence
|
| 198 |
+
})
|
| 199 |
+
except (IOError, KeyError) as e:
|
| 200 |
+
print(f"Could not retrieve full text for doc {doc_id}: {e}")
|
| 201 |
+
continue
|
| 202 |
+
return results
|
| 203 |
+
|
| 204 |
+
# --- Qwen API for Explanations ---
|
| 205 |
+
|
| 206 |
+
@st.cache_data(persist=True)
|
| 207 |
+
def _cached_explain_heatmap(api_config, img_base64, csv_text, structured_prompt):
|
| 208 |
+
# Makes a cached API call to Qwen to get an explanation for a heatmap.
|
| 209 |
+
headers = {
|
| 210 |
+
"Authorization": f"Bearer {api_config['api_key']}",
|
| 211 |
+
"Content-Type": "application/json"
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
content = [{"type": "text", "text": structured_prompt}]
|
| 215 |
+
if img_base64:
|
| 216 |
+
content.append({
|
| 217 |
+
"type": "image_url",
|
| 218 |
+
"image_url": {
|
| 219 |
+
"url": f"data:image/png;base64,{img_base64}"
|
| 220 |
+
}
|
| 221 |
+
})
|
| 222 |
+
|
| 223 |
+
data = {
|
| 224 |
+
"model": api_config["model"],
|
| 225 |
+
"messages": [
|
| 226 |
+
{
|
| 227 |
+
"role": "user",
|
| 228 |
+
"content": content
|
| 229 |
+
}
|
| 230 |
+
],
|
| 231 |
+
"max_tokens": 1200,
|
| 232 |
+
"temperature": 0.2,
|
| 233 |
+
"top_p": 0.95,
|
| 234 |
+
"seed": 42
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
response = requests.post(
|
| 238 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 239 |
+
headers=headers,
|
| 240 |
+
json=data,
|
| 241 |
+
timeout=300
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
# Raise an exception if the API call fails.
|
| 245 |
+
response.raise_for_status()
|
| 246 |
+
|
| 247 |
+
result = response.json()
|
| 248 |
+
return result["choices"][0]["message"]["content"]
|
| 249 |
+
|
| 250 |
+
@st.cache_data(persist=True)
|
| 251 |
+
def generate_all_attribution_analyses(_attribution_models, _tokenizer, _base_model, _device, prompt, max_tokens, force_exact_num_tokens=False):
|
| 252 |
+
# Generates text and runs attribution analysis for all methods.
|
| 253 |
+
# Generate the text first.
|
| 254 |
+
inputs = _tokenizer(prompt, return_tensors="pt").to(_device)
|
| 255 |
+
|
| 256 |
+
generation_args = {
|
| 257 |
+
'max_new_tokens': max_tokens,
|
| 258 |
+
'do_sample': False
|
| 259 |
+
}
|
| 260 |
+
if force_exact_num_tokens:
|
| 261 |
+
generation_args['min_new_tokens'] = max_tokens
|
| 262 |
+
|
| 263 |
+
generated_ids = _base_model.generate(
|
| 264 |
+
inputs.input_ids,
|
| 265 |
+
**generation_args
|
| 266 |
+
)
|
| 267 |
+
generated_text = _tokenizer.decode(generated_ids[0], skip_special_tokens=True)
|
| 268 |
+
|
| 269 |
+
# Run attribution analysis for all methods.
|
| 270 |
+
all_attributions = {}
|
| 271 |
+
methods = ["integrated_gradients", "occlusion", "saliency"]
|
| 272 |
+
|
| 273 |
+
for method in methods:
|
| 274 |
+
attributions = _attribution_models[method].attribute(
|
| 275 |
+
input_texts=prompt,
|
| 276 |
+
generated_texts=generated_text
|
| 277 |
+
)
|
| 278 |
+
all_attributions[method] = attributions
|
| 279 |
+
|
| 280 |
+
return generated_text, all_attributions
|
| 281 |
+
|
| 282 |
+
def explain_heatmap_with_csv_data(api_config, image_buffer, csv_data, context_prompt, generated_text, method_name="Attribution"):
|
| 283 |
+
# Generates an explanation for a heatmap using the Qwen API.
|
| 284 |
+
try:
|
| 285 |
+
# Convert the image to base64.
|
| 286 |
+
img_base64 = None
|
| 287 |
+
if image_buffer:
|
| 288 |
+
image_buffer.seek(0)
|
| 289 |
+
image = Image.open(image_buffer)
|
| 290 |
+
|
| 291 |
+
buffered = BytesIO()
|
| 292 |
+
image.save(buffered, format="PNG")
|
| 293 |
+
img_base64 = base64.b64encode(buffered.getvalue()).decode()
|
| 294 |
+
|
| 295 |
+
# Clean the dataframe to handle duplicates.
|
| 296 |
+
df_clean = csv_data.copy()
|
| 297 |
+
|
| 298 |
+
cols = pd.Series(df_clean.columns)
|
| 299 |
+
if cols.duplicated().any():
|
| 300 |
+
for dup in cols[cols.duplicated()].unique():
|
| 301 |
+
dup_indices = cols[cols == dup].index.values
|
| 302 |
+
new_names = [f"{dup} ({i+1})" for i in range(len(dup_indices))]
|
| 303 |
+
cols[dup_indices] = new_names
|
| 304 |
+
df_clean.columns = cols
|
| 305 |
+
|
| 306 |
+
if df_clean.index.has_duplicates:
|
| 307 |
+
counts = {}
|
| 308 |
+
new_index = list(df_clean.index)
|
| 309 |
+
duplicated_indices = df_clean.index[df_clean.index.duplicated(keep=False)]
|
| 310 |
+
for i, idx in enumerate(df_clean.index):
|
| 311 |
+
if idx in duplicated_indices:
|
| 312 |
+
counts[idx] = counts.get(idx, 0) + 1
|
| 313 |
+
new_index[i] = f"{idx} ({counts[idx]})"
|
| 314 |
+
df_clean.index = new_index
|
| 315 |
+
|
| 316 |
+
# --- Rule-Based Analysis ---
|
| 317 |
+
unstacked = df_clean.unstack()
|
| 318 |
+
unstacked.index = unstacked.index.map('{0[1]} -> {0[0]}'.format)
|
| 319 |
+
|
| 320 |
+
# Get the top 5 individual scores.
|
| 321 |
+
top_5_individual = unstacked.abs().nlargest(5).sort_index()
|
| 322 |
+
top_individual_text_lines = ["\n### Top 5 Strongest Individual Connections:"]
|
| 323 |
+
for label in top_5_individual.index:
|
| 324 |
+
score = unstacked[label]
|
| 325 |
+
top_individual_text_lines.append(f"- **{label}**: score {score:.2f}")
|
| 326 |
+
|
| 327 |
+
# Get the top 5 average input scores.
|
| 328 |
+
avg_input_scores = df_clean.mean(axis=1)
|
| 329 |
+
top_5_average = avg_input_scores.abs().nlargest(5).sort_index()
|
| 330 |
+
top_average_text_lines = ["\n### Top 5 Most Influential Input Tokens (on average over the whole generation):"]
|
| 331 |
+
for input_token in top_5_average.index:
|
| 332 |
+
score = avg_input_scores[input_token]
|
| 333 |
+
top_average_text_lines.append(f"- **'{input_token}'**: average score {score:.2f}")
|
| 334 |
+
|
| 335 |
+
# Get the top output token sources.
|
| 336 |
+
top_output_text_lines = []
|
| 337 |
+
if not df_clean.empty:
|
| 338 |
+
avg_output_scores = df_clean.mean(axis=0)
|
| 339 |
+
top_3_output = avg_output_scores.abs().nlargest(min(3, len(df_clean.columns))).sort_index()
|
| 340 |
+
if not top_3_output.empty:
|
| 341 |
+
top_output_text_lines.append("\n### Top 3 Most Influenced Generated Tokens:")
|
| 342 |
+
for output_token in top_3_output.index:
|
| 343 |
+
# Find which input tokens influenced this output token the most.
|
| 344 |
+
top_sources_for_output = df_clean[output_token].abs().nlargest(min(2, len(df_clean.index))).sort_index().index.tolist()
|
| 345 |
+
if top_sources_for_output:
|
| 346 |
+
top_output_text_lines.append(f"- **'{output_token}'** was most influenced by **'{', '.join(top_sources_for_output)}'**.")
|
| 347 |
+
|
| 348 |
+
data_text_for_llm = "\n".join(top_individual_text_lines + top_average_text_lines + top_output_text_lines)
|
| 349 |
+
|
| 350 |
+
# Get method-specific context from the translation files.
|
| 351 |
+
desc_key = METHOD_DESC_KEYS.get(method_name, "unsupported_method_desc")
|
| 352 |
+
method_context = tr(desc_key)
|
| 353 |
+
|
| 354 |
+
# Format the instruction for the LLM.
|
| 355 |
+
instruction_p1 = tr('instruction_part_1_desc').format(method_name=method_name.replace('_', ' ').title())
|
| 356 |
+
|
| 357 |
+
# Create the prompt for the LLM.
|
| 358 |
+
structured_prompt = f"""{tr('ai_expert_intro')}
|
| 359 |
+
|
| 360 |
+
## {tr('analysis_details')}
|
| 361 |
+
- **{tr('method_being_used')}** {method_name.replace('_', ' ').title()}
|
| 362 |
+
- **{tr('prompt_analyzed')}** "{context_prompt}"
|
| 363 |
+
- **{tr('full_generated_text')}** "{generated_text}"
|
| 364 |
+
|
| 365 |
+
## {tr('method_specific_context')}
|
| 366 |
+
{method_context}
|
| 367 |
+
|
| 368 |
+
## {tr('instructions_for_analysis')}
|
| 369 |
+
|
| 370 |
+
{tr('instruction_part_1_header')}
|
| 371 |
+
{instruction_p1}
|
| 372 |
+
|
| 373 |
+
{tr('instruction_synthesis_header')}
|
| 374 |
+
{tr('instruction_synthesis_desc')}
|
| 375 |
+
|
| 376 |
+
{tr('instruction_color_coding')}
|
| 377 |
+
|
| 378 |
+
## {tr('data_section_header')}
|
| 379 |
+
{data_text_for_llm}
|
| 380 |
+
|
| 381 |
+
{tr('begin_analysis_now')}"""
|
| 382 |
+
|
| 383 |
+
# Call the cached function to get the explanation.
|
| 384 |
+
explanation = _cached_explain_heatmap(api_config, img_base64, data_text_for_llm, structured_prompt)
|
| 385 |
+
return explanation
|
| 386 |
+
|
| 387 |
+
except Exception as e:
|
| 388 |
+
# Catch errors from data prep or the API call.
|
| 389 |
+
st.error(f"Error generating AI explanation: {str(e)}")
|
| 390 |
+
return tr("unable_to_generate_explanation")
|
| 391 |
+
|
| 392 |
+
# --- Faithfulness Verification ---
|
| 393 |
+
|
| 394 |
+
@st.cache_data(persist=True)
|
| 395 |
+
def _cached_extract_claims_from_explanation(api_config, explanation_text, analysis_method):
|
| 396 |
+
# Makes a cached API call to Qwen to get claims from an explanation.
|
| 397 |
+
headers = {"Authorization": f"Bearer {api_config['api_key']}", "Content-Type": "application/json"}
|
| 398 |
+
|
| 399 |
+
# Dynamically set claim types based on the analysis method.
|
| 400 |
+
claim_types_details = tr("claim_extraction_prompt_types_details")
|
| 401 |
+
|
| 402 |
+
claim_extraction_prompt = f"""{tr('claim_extraction_prompt_header')}
|
| 403 |
+
|
| 404 |
+
{tr('claim_extraction_prompt_instruction')}
|
| 405 |
+
|
| 406 |
+
{tr('claim_extraction_prompt_context_header').format(analysis_method=analysis_method)}
|
| 407 |
+
|
| 408 |
+
{tr('claim_extraction_prompt_types_header')}
|
| 409 |
+
{claim_types_details}
|
| 410 |
+
|
| 411 |
+
{tr('claim_extraction_prompt_example_header')}
|
| 412 |
+
{tr('claim_extraction_prompt_example_explanation')}
|
| 413 |
+
{tr('claim_extraction_prompt_example_json')}
|
| 414 |
+
|
| 415 |
+
{tr('claim_extraction_prompt_analyze_header')}
|
| 416 |
+
"{explanation_text}"
|
| 417 |
+
|
| 418 |
+
{tr('claim_extraction_prompt_instruction_footer')}
|
| 419 |
+
"""
|
| 420 |
+
|
| 421 |
+
data = {
|
| 422 |
+
"model": api_config["model"],
|
| 423 |
+
"messages": [
|
| 424 |
+
{
|
| 425 |
+
"role": "user",
|
| 426 |
+
"content": [{"type": "text", "text": claim_extraction_prompt}]
|
| 427 |
+
}
|
| 428 |
+
],
|
| 429 |
+
"max_tokens": 1500,
|
| 430 |
+
"temperature": 0.0, # Set to 0 for deterministic output.
|
| 431 |
+
"seed": 42
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
response = requests.post(
|
| 435 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 436 |
+
headers=headers,
|
| 437 |
+
json=data,
|
| 438 |
+
timeout=300
|
| 439 |
+
)
|
| 440 |
+
response.raise_for_status()
|
| 441 |
+
claims_text = response.json()["choices"][0]["message"]["content"]
|
| 442 |
+
|
| 443 |
+
try:
|
| 444 |
+
# The response might be inside a markdown code block, so we try to extract it.
|
| 445 |
+
if '```json' in claims_text:
|
| 446 |
+
claims_text = re.search(r'```json\n(.*?)\n```', claims_text, re.DOTALL).group(1)
|
| 447 |
+
|
| 448 |
+
# Parse the JSON string into a Python list.
|
| 449 |
+
return json.loads(claims_text)
|
| 450 |
+
except (AttributeError, json.JSONDecodeError):
|
| 451 |
+
return []
|
| 452 |
+
|
| 453 |
+
@st.cache_data(persist=True)
|
| 454 |
+
def _cached_verify_token_justification(api_config, analysis_method, input_prompt, generated_text, token, justification):
|
| 455 |
+
# Uses an LLM to verify if a justification for a token's importance is sound.
|
| 456 |
+
headers = {"Authorization": f"Bearer {api_config['api_key']}", "Content-Type": "application/json"}
|
| 457 |
+
|
| 458 |
+
verification_prompt = f"""{tr('justification_verification_prompt_header')}
|
| 459 |
+
|
| 460 |
+
{tr('justification_verification_prompt_crucial_rule')}
|
| 461 |
+
|
| 462 |
+
{tr('justification_verification_prompt_token_location')}
|
| 463 |
+
|
| 464 |
+
{tr('justification_verification_prompt_special_tokens')}
|
| 465 |
+
|
| 466 |
+
{tr('justification_verification_prompt_evaluating_justifications')}
|
| 467 |
+
|
| 468 |
+
{tr('justification_verification_prompt_linguistic_context')}
|
| 469 |
+
|
| 470 |
+
{tr('justification_verification_prompt_collective_reasoning')}
|
| 471 |
+
|
| 472 |
+
**Analysis Method:** {analysis_method}
|
| 473 |
+
**Input Prompt:** "{input_prompt}"
|
| 474 |
+
**Generated Text:** "{generated_text}"
|
| 475 |
+
**Token in Question:** "{token}"
|
| 476 |
+
**Provided Justification:** "{justification}"
|
| 477 |
+
|
| 478 |
+
{tr('justification_verification_prompt_task_header')}
|
| 479 |
+
{tr('justification_verification_prompt_task_instruction')}
|
| 480 |
+
|
| 481 |
+
{tr('justification_verification_prompt_json_instruction')}
|
| 482 |
+
|
| 483 |
+
{tr('justification_verification_prompt_footer')}
|
| 484 |
+
"""
|
| 485 |
+
|
| 486 |
+
data = {
|
| 487 |
+
"model": "qwen2.5-vl-72b-instruct",
|
| 488 |
+
"messages": [{"role": "user", "content": verification_prompt}],
|
| 489 |
+
"max_tokens": 400,
|
| 490 |
+
"temperature": 0.0,
|
| 491 |
+
"seed": 42,
|
| 492 |
+
"response_format": {"type": "json_object"}
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
response = requests.post(
|
| 496 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 497 |
+
headers=headers,
|
| 498 |
+
json=data,
|
| 499 |
+
timeout=300
|
| 500 |
+
)
|
| 501 |
+
response.raise_for_status()
|
| 502 |
+
|
| 503 |
+
try:
|
| 504 |
+
result_json = response.json()["choices"][0]["message"]["content"]
|
| 505 |
+
return json.loads(result_json)
|
| 506 |
+
except (json.JSONDecodeError, KeyError):
|
| 507 |
+
return {"is_verified": False, "reasoning": "Could not parse the semantic justification result."}
|
| 508 |
+
|
| 509 |
+
def verify_claims(claims, analysis_data):
|
| 510 |
+
# Verifies the extracted claims against the analysis data.
|
| 511 |
+
verification_results = []
|
| 512 |
+
|
| 513 |
+
# Pre-calculate thresholds and rankings for efficiency.
|
| 514 |
+
all_scores_flat = analysis_data['scores_df'].abs().values.flatten()
|
| 515 |
+
|
| 516 |
+
# Average influence of each input token.
|
| 517 |
+
avg_input_scores_abs = analysis_data['scores_df'].mean(axis=1).abs().sort_values(ascending=False)
|
| 518 |
+
avg_input_scores_raw = analysis_data['scores_df'].mean(axis=1) # Keep signs for specific value checks
|
| 519 |
+
# Average influence on each generated token.
|
| 520 |
+
avg_output_scores = analysis_data['scores_df'].mean(axis=0).abs().sort_values(ascending=False)
|
| 521 |
+
|
| 522 |
+
input_tokens = analysis_data['scores_df'].index.tolist()
|
| 523 |
+
generated_tokens = analysis_data['scores_df'].columns.tolist()
|
| 524 |
+
|
| 525 |
+
for claim in claims:
|
| 526 |
+
is_verified = False
|
| 527 |
+
evidence = "Could not be verified."
|
| 528 |
+
details = claim.get('details', {})
|
| 529 |
+
claim_type = claim.get('claim_type')
|
| 530 |
+
|
| 531 |
+
try:
|
| 532 |
+
# Clean tokens in the claim's details, as the LLM sometimes includes extra quotes.
|
| 533 |
+
if 'token' in details and isinstance(details['token'], str):
|
| 534 |
+
details['token'] = re.sub(r"^\s*['\"]|['\"]\s*$", '', details['token']).strip()
|
| 535 |
+
if 'tokens' in details and isinstance(details['tokens'], list):
|
| 536 |
+
details['tokens'] = [re.sub(r"^\s*['\"]|['\"]\s*$", '', t).strip() for t in details['tokens']]
|
| 537 |
+
|
| 538 |
+
if claim_type == 'attribution_claim':
|
| 539 |
+
tokens_claimed = details.get('tokens', [])
|
| 540 |
+
qualifier = details.get('qualifier', 'significant') # Default to the lower bar
|
| 541 |
+
score_type = details.get('score_type', 'peak')
|
| 542 |
+
|
| 543 |
+
# Calculate the correct scores based on the claim's score_type.
|
| 544 |
+
if score_type == 'average':
|
| 545 |
+
score_series = analysis_data['scores_df'].abs().mean(axis=1)
|
| 546 |
+
score_name = "average score"
|
| 547 |
+
else: # peak
|
| 548 |
+
score_series = analysis_data['scores_df'].abs().max(axis=1)
|
| 549 |
+
score_name = "peak score"
|
| 550 |
+
|
| 551 |
+
if score_series.empty:
|
| 552 |
+
evidence = "No attribution data available to verify claim."
|
| 553 |
+
else:
|
| 554 |
+
all_attributions = sorted(
|
| 555 |
+
[{'token': token, 'attribution': score} for token, score in score_series.items()],
|
| 556 |
+
key=lambda x: x['attribution'],
|
| 557 |
+
reverse=True
|
| 558 |
+
)
|
| 559 |
+
max_score = all_attributions[0]['attribution'] if all_attributions else 0
|
| 560 |
+
|
| 561 |
+
if qualifier == 'high':
|
| 562 |
+
threshold = 0.70 * max_score
|
| 563 |
+
threshold_name = "high"
|
| 564 |
+
else: # 'significant' or default
|
| 565 |
+
threshold = 0.50 * max_score
|
| 566 |
+
threshold_name = "significant"
|
| 567 |
+
|
| 568 |
+
token_scores_dict = {item['token'].lower().strip(): item['attribution'] for item in all_attributions}
|
| 569 |
+
|
| 570 |
+
unverified_tokens = []
|
| 571 |
+
verified_tokens_details = []
|
| 572 |
+
|
| 573 |
+
for token in tokens_claimed:
|
| 574 |
+
# New, more robust matching logic.
|
| 575 |
+
# First, check for a direct match for specific claims like ', (1)'.
|
| 576 |
+
token_lower = token.lower().strip()
|
| 577 |
+
if token_lower in token_scores_dict:
|
| 578 |
+
matching_keys = [token_lower]
|
| 579 |
+
else:
|
| 580 |
+
# If no direct match, fall back to a generic search for claims like ','.
|
| 581 |
+
# This finds all instances: ', (1)', ', (2)', etc.
|
| 582 |
+
matching_keys = [
|
| 583 |
+
k for k in token_scores_dict.keys()
|
| 584 |
+
if re.sub(r'\s\(\d+\)$', '', k).strip() == token_lower
|
| 585 |
+
]
|
| 586 |
+
|
| 587 |
+
if not matching_keys:
|
| 588 |
+
unverified_tokens.append(f"'{token}' (not found in analysis)")
|
| 589 |
+
continue
|
| 590 |
+
|
| 591 |
+
# Check each matching instance against the threshold.
|
| 592 |
+
for key in matching_keys:
|
| 593 |
+
actual_score = token_scores_dict.get(key)
|
| 594 |
+
|
| 595 |
+
if abs(actual_score) < threshold:
|
| 596 |
+
unverified_tokens.append(f"'{key}' ({score_name}: {abs(actual_score):.2f})")
|
| 597 |
+
else:
|
| 598 |
+
verified_tokens_details.append(f"'{key}' ({score_name}: {abs(actual_score):.2f})")
|
| 599 |
+
|
| 600 |
+
is_verified = not unverified_tokens
|
| 601 |
+
if is_verified:
|
| 602 |
+
evidence = f"Verified. All claimed tokens passed the {threshold_name} threshold (> {threshold:.2f}). Details: {', '.join(verified_tokens_details)}."
|
| 603 |
+
else:
|
| 604 |
+
fail_reason = f"the following did not meet the {threshold_name} threshold (> {threshold:.2f}): {', '.join(unverified_tokens)}"
|
| 605 |
+
if verified_tokens_details:
|
| 606 |
+
evidence = f"While some tokens passed ({', '.join(verified_tokens_details)}), {fail_reason}."
|
| 607 |
+
else:
|
| 608 |
+
evidence = f"The following did not meet the {threshold_name} threshold (> {threshold:.2f}): {', '.join(unverified_tokens)}."
|
| 609 |
+
|
| 610 |
+
elif claim_type in ['token_justification_claim', 'token_begruendung_anspruch']:
|
| 611 |
+
token_val = details.get('token') or details.get('tokens')
|
| 612 |
+
if isinstance(token_val, list):
|
| 613 |
+
token = ", ".join(map(str, token_val))
|
| 614 |
+
else:
|
| 615 |
+
token = token_val
|
| 616 |
+
|
| 617 |
+
justification = details.get('justification') or details.get('begruendung')
|
| 618 |
+
input_prompt = analysis_data.get('prompt', '')
|
| 619 |
+
generated_text = analysis_data.get('generated_text', '')
|
| 620 |
+
|
| 621 |
+
if not all([token, justification, input_prompt, generated_text]):
|
| 622 |
+
evidence = "Missing data for justification verification (token, justification, or prompt)."
|
| 623 |
+
else:
|
| 624 |
+
api_config = init_qwen_api()
|
| 625 |
+
if api_config:
|
| 626 |
+
verification = _cached_verify_token_justification(api_config, analysis_data['method'], input_prompt, generated_text, token, justification)
|
| 627 |
+
is_verified = verification.get('is_verified', False)
|
| 628 |
+
evidence = verification.get('reasoning', "Failed to get semantic reasoning for justification.")
|
| 629 |
+
else:
|
| 630 |
+
is_verified = False
|
| 631 |
+
evidence = "API key not configured for semantic verification."
|
| 632 |
+
|
| 633 |
+
except Exception as e:
|
| 634 |
+
evidence = f"An error occurred during verification: {str(e)}"
|
| 635 |
+
|
| 636 |
+
verification_results.append({
|
| 637 |
+
'claim_text': claim.get('claim_text', 'N/A'),
|
| 638 |
+
'verified': is_verified,
|
| 639 |
+
'evidence': evidence
|
| 640 |
+
})
|
| 641 |
+
|
| 642 |
+
return verification_results
|
| 643 |
+
|
| 644 |
+
# --- End Faithfulness Verification ---
|
| 645 |
+
|
| 646 |
+
def create_heatmap_visualization(attributions, method_name="Attribution"):
|
| 647 |
+
# Creates a heatmap visualization from attribution scores.
|
| 648 |
+
try:
|
| 649 |
+
# Get the HTML content from the attributions.
|
| 650 |
+
html_content = attributions.show(display=False, return_html=True)
|
| 651 |
+
|
| 652 |
+
if not html_content:
|
| 653 |
+
st.error(tr("error_inseq_no_html").format(method_name=method_name))
|
| 654 |
+
return None, None, None, None
|
| 655 |
+
|
| 656 |
+
# Parse the HTML to extract the data table.
|
| 657 |
+
soup = BeautifulSoup(html_content, 'html.parser')
|
| 658 |
+
table = soup.find('table')
|
| 659 |
+
|
| 660 |
+
if not table:
|
| 661 |
+
st.error(tr("error_no_table_in_html").format(method_name=method_name))
|
| 662 |
+
return None, None, None, None
|
| 663 |
+
|
| 664 |
+
# A more structured approach to parsing the HTML.
|
| 665 |
+
header_row_element = table.find('thead')
|
| 666 |
+
if header_row_element:
|
| 667 |
+
headers = [th.get_text(strip=True) for th in header_row_element.find_all('th')[1:]]
|
| 668 |
+
else:
|
| 669 |
+
# Fallback if no <thead> is found.
|
| 670 |
+
first_row = table.find('tr')
|
| 671 |
+
if not first_row:
|
| 672 |
+
st.error(tr("error_table_no_rows").format(method_name=method_name))
|
| 673 |
+
return None, None, None, None
|
| 674 |
+
headers = [th.get_text(strip=True) for th in first_row.find_all('th')[1:]]
|
| 675 |
+
|
| 676 |
+
data_rows = []
|
| 677 |
+
row_labels = []
|
| 678 |
+
|
| 679 |
+
# Find all `<tbody>` elements and iterate through their rows.
|
| 680 |
+
table_bodies = table.find_all('tbody')
|
| 681 |
+
if not table_bodies:
|
| 682 |
+
# Fallback if no <tbody> is found.
|
| 683 |
+
all_trs = table.find_all('tr')
|
| 684 |
+
data_trs = all_trs[1:] if len(all_trs) > 1 else []
|
| 685 |
+
else:
|
| 686 |
+
data_trs = []
|
| 687 |
+
for tbody in table_bodies:
|
| 688 |
+
data_trs.extend(tbody.find_all('tr'))
|
| 689 |
+
|
| 690 |
+
for tr_element in data_trs:
|
| 691 |
+
all_cells = tr_element.find_all(['th', 'td'])
|
| 692 |
+
if not all_cells or len(all_cells) <= 1:
|
| 693 |
+
continue
|
| 694 |
+
|
| 695 |
+
row_labels.append(all_cells[0].get_text(strip=True))
|
| 696 |
+
|
| 697 |
+
# Convert text values to float, handling empty strings as 0.
|
| 698 |
+
row_data = []
|
| 699 |
+
for cell in all_cells[1:]:
|
| 700 |
+
text_val = cell.get_text(strip=True)
|
| 701 |
+
# Remove non-breaking spaces.
|
| 702 |
+
clean_text = text_val.replace('\xa0', '').strip()
|
| 703 |
+
if clean_text:
|
| 704 |
+
try:
|
| 705 |
+
row_data.append(float(clean_text))
|
| 706 |
+
except ValueError:
|
| 707 |
+
# Default to 0 if conversion fails.
|
| 708 |
+
row_data.append(0.0)
|
| 709 |
+
else:
|
| 710 |
+
row_data.append(0.0)
|
| 711 |
+
data_rows.append(row_data)
|
| 712 |
+
|
| 713 |
+
# Create the dataframe from the parsed data.
|
| 714 |
+
if not data_rows or not data_rows[0]:
|
| 715 |
+
st.error(tr("error_failed_to_parse_rows").format(method_name=method_name))
|
| 716 |
+
return None, None, None, None
|
| 717 |
+
|
| 718 |
+
# --- Make token labels unique for duplicates ---
|
| 719 |
+
def make_labels_unique(labels):
|
| 720 |
+
counts = {}
|
| 721 |
+
new_labels = []
|
| 722 |
+
# First, count all occurrences to decide which ones need numbering.
|
| 723 |
+
label_counts = {label: labels.count(label) for label in set(labels)}
|
| 724 |
+
|
| 725 |
+
for label in labels:
|
| 726 |
+
if label_counts[label] > 1:
|
| 727 |
+
counts[label] = counts.get(label, 0) + 1
|
| 728 |
+
new_labels.append(f"{label} ({counts[label]})")
|
| 729 |
+
else:
|
| 730 |
+
new_labels.append(label)
|
| 731 |
+
return new_labels
|
| 732 |
+
|
| 733 |
+
unique_row_labels = make_labels_unique(row_labels)
|
| 734 |
+
unique_headers = make_labels_unique(headers)
|
| 735 |
+
|
| 736 |
+
parsed_df = pd.DataFrame(data_rows, index=unique_row_labels, columns=unique_headers)
|
| 737 |
+
attribution_scores = parsed_df.values
|
| 738 |
+
|
| 739 |
+
# Clean tokens for display.
|
| 740 |
+
clean_headers = parsed_df.columns.tolist()
|
| 741 |
+
clean_row_labels = parsed_df.index.tolist()
|
| 742 |
+
|
| 743 |
+
# Use numerical indices for the heatmap to handle duplicate labels.
|
| 744 |
+
x_indices = list(range(len(clean_headers)))
|
| 745 |
+
y_indices = list(range(len(clean_row_labels)))
|
| 746 |
+
|
| 747 |
+
# Prepare custom data for hover labels.
|
| 748 |
+
custom_data = np.empty(attribution_scores.shape, dtype=object)
|
| 749 |
+
for i in range(len(clean_row_labels)):
|
| 750 |
+
for j in range(len(clean_headers)):
|
| 751 |
+
custom_data[i, j] = (clean_row_labels[i], clean_headers[j])
|
| 752 |
+
|
| 753 |
+
|
| 754 |
+
fig = go.Figure(data=go.Heatmap(
|
| 755 |
+
z=attribution_scores,
|
| 756 |
+
x=x_indices,
|
| 757 |
+
y=y_indices,
|
| 758 |
+
customdata=custom_data,
|
| 759 |
+
hovertemplate="Input: %{customdata[0]}<br>Generated: %{customdata[1]}<br>Score: %{z:.4f}<extra></extra>",
|
| 760 |
+
colorscale='Plasma',
|
| 761 |
+
hoverongaps=False,
|
| 762 |
+
))
|
| 763 |
+
|
| 764 |
+
fig.update_layout(
|
| 765 |
+
title=tr('heatmap_title').format(method_name=method_name),
|
| 766 |
+
xaxis_title=tr('heatmap_xaxis'),
|
| 767 |
+
yaxis_title=tr('heatmap_yaxis'),
|
| 768 |
+
xaxis=dict(
|
| 769 |
+
tickmode='array',
|
| 770 |
+
tickvals=x_indices,
|
| 771 |
+
ticktext=clean_headers,
|
| 772 |
+
tickangle=45
|
| 773 |
+
),
|
| 774 |
+
yaxis=dict(
|
| 775 |
+
tickmode='array',
|
| 776 |
+
tickvals=y_indices,
|
| 777 |
+
ticktext=clean_row_labels,
|
| 778 |
+
autorange='reversed'
|
| 779 |
+
),
|
| 780 |
+
height=max(400, len(clean_row_labels) * 30),
|
| 781 |
+
width=max(600, len(clean_headers) * 50)
|
| 782 |
+
)
|
| 783 |
+
|
| 784 |
+
# Save the plot to a buffer.
|
| 785 |
+
buffer = BytesIO()
|
| 786 |
+
fig.write_image(buffer, format='png', scale=2)
|
| 787 |
+
buffer.seek(0)
|
| 788 |
+
|
| 789 |
+
return fig, html_content, buffer, parsed_df
|
| 790 |
+
|
| 791 |
+
except Exception as e:
|
| 792 |
+
st.error(tr("error_creating_heatmap").format(e=str(e)))
|
| 793 |
+
return None, None, None, None
|
| 794 |
+
|
| 795 |
+
def start_new_analysis(prompt, max_tokens, enable_explanations):
|
| 796 |
+
# Clears old results and starts a new analysis.
|
| 797 |
+
# Clear old results from the session state.
|
| 798 |
+
keys_to_clear = [
|
| 799 |
+
'generated_text',
|
| 800 |
+
'all_attributions'
|
| 801 |
+
]
|
| 802 |
+
for key in keys_to_clear:
|
| 803 |
+
if key in st.session_state:
|
| 804 |
+
del st.session_state[key]
|
| 805 |
+
|
| 806 |
+
# Clear any old cached items.
|
| 807 |
+
for key in list(st.session_state.keys()):
|
| 808 |
+
if key.startswith('influential_docs_'):
|
| 809 |
+
del st.session_state[key]
|
| 810 |
+
|
| 811 |
+
# Update the text area with the new prompt.
|
| 812 |
+
st.session_state.attr_prompt = prompt
|
| 813 |
+
|
| 814 |
+
# Set parameters for the new analysis.
|
| 815 |
+
st.session_state.run_request = {
|
| 816 |
+
"prompt": prompt,
|
| 817 |
+
"max_tokens": max_tokens,
|
| 818 |
+
"enable_explanations": enable_explanations
|
| 819 |
+
}
|
| 820 |
+
|
| 821 |
+
def run_analysis(prompt, max_tokens, enable_explanations, force_exact_num_tokens=False):
|
| 822 |
+
# Runs the full analysis pipeline.
|
| 823 |
+
if not prompt.strip():
|
| 824 |
+
st.warning(tr('please_enter_prompt_warning'))
|
| 825 |
+
return
|
| 826 |
+
|
| 827 |
+
# Check for cached results first
|
| 828 |
+
cache_file = os.path.join("cache", "cached_attribution_results.json")
|
| 829 |
+
if os.path.exists(cache_file):
|
| 830 |
+
with open(cache_file, "r", encoding="utf-8") as f:
|
| 831 |
+
cached_data = json.load(f)
|
| 832 |
+
if prompt in cached_data:
|
| 833 |
+
print("Loading full attribution analysis from cache.")
|
| 834 |
+
cached_result = cached_data[prompt]
|
| 835 |
+
|
| 836 |
+
# Populate session state from the comprehensive cache
|
| 837 |
+
st.session_state.generated_text = cached_result["generated_text"]
|
| 838 |
+
st.session_state.prompt = prompt
|
| 839 |
+
st.session_state.enable_explanations = enable_explanations
|
| 840 |
+
st.session_state.qwen_api_config = init_qwen_api() if enable_explanations else None
|
| 841 |
+
|
| 842 |
+
# Reconstruct attribution objects and store explanations/faithfulness
|
| 843 |
+
reconstructed_attributions = {}
|
| 844 |
+
for method, data in cached_result["html_contents"].items():
|
| 845 |
+
reconstructed_attributions[method] = CachedAttribution(data)
|
| 846 |
+
|
| 847 |
+
# Use a consistent key for caching in session state
|
| 848 |
+
cache_key_base = f"{method}_{cached_result['generated_text']}"
|
| 849 |
+
if "explanation" in data:
|
| 850 |
+
st.session_state[f"explanation_{cache_key_base}"] = data["explanation"]
|
| 851 |
+
if "faithfulness_results" in data:
|
| 852 |
+
st.session_state[f"faithfulness_check_{cache_key_base}"] = data["faithfulness_results"]
|
| 853 |
+
|
| 854 |
+
st.session_state.all_attributions = reconstructed_attributions
|
| 855 |
+
|
| 856 |
+
# Store influential docs
|
| 857 |
+
if "influential_docs" in cached_result:
|
| 858 |
+
# Use a key that the UI part can check for
|
| 859 |
+
st.session_state.cached_influential_docs = cached_result["influential_docs"]
|
| 860 |
+
|
| 861 |
+
st.success(tr('analysis_complete_success'))
|
| 862 |
+
return
|
| 863 |
+
|
| 864 |
+
# If not in cache, check if models exist before trying to load
|
| 865 |
+
model_path = "./models/OLMo-2-1124-7B"
|
| 866 |
+
if not os.path.exists(model_path):
|
| 867 |
+
st.info("This live demo is running in a static environment. Only the pre-cached example prompts are available. Please select an example to view its analysis.")
|
| 868 |
+
return
|
| 869 |
+
|
| 870 |
+
# Load the models.
|
| 871 |
+
with st.spinner(tr('loading_models_spinner')):
|
| 872 |
+
attribution_models, tokenizer, base_model, device = load_all_attribution_models()
|
| 873 |
+
|
| 874 |
+
if not attribution_models:
|
| 875 |
+
st.error(tr('failed_to_load_models_error'))
|
| 876 |
+
return
|
| 877 |
+
|
| 878 |
+
st.session_state.qwen_api_config = init_qwen_api() if enable_explanations else None
|
| 879 |
+
st.session_state.enable_explanations = enable_explanations
|
| 880 |
+
st.session_state.prompt = prompt
|
| 881 |
+
|
| 882 |
+
# Generate text and attributions.
|
| 883 |
+
with st.spinner(tr('running_attribution_analysis_spinner')):
|
| 884 |
+
try:
|
| 885 |
+
generated_text, all_attributions = generate_all_attribution_analyses(
|
| 886 |
+
attribution_models,
|
| 887 |
+
tokenizer,
|
| 888 |
+
base_model,
|
| 889 |
+
device,
|
| 890 |
+
prompt,
|
| 891 |
+
max_tokens,
|
| 892 |
+
force_exact_num_tokens=force_exact_num_tokens
|
| 893 |
+
)
|
| 894 |
+
except Exception as e:
|
| 895 |
+
st.error(f"Error in attribution analysis: {str(e)}")
|
| 896 |
+
# Let the rest of the function know it failed.
|
| 897 |
+
generated_text, all_attributions = None, None
|
| 898 |
+
|
| 899 |
+
if not generated_text or not all_attributions:
|
| 900 |
+
st.error(tr('failed_to_generate_analysis_error'))
|
| 901 |
+
return
|
| 902 |
+
|
| 903 |
+
# Store the results in the session state.
|
| 904 |
+
st.session_state.generated_text = generated_text
|
| 905 |
+
st.session_state.all_attributions = all_attributions
|
| 906 |
+
|
| 907 |
+
# --- New: Save the new result back to the cache ---
|
| 908 |
+
try:
|
| 909 |
+
cache_file = os.path.join("cache", "cached_attribution_results.json")
|
| 910 |
+
os.makedirs("cache", exist_ok=True)
|
| 911 |
+
|
| 912 |
+
# Load existing cache or create new
|
| 913 |
+
if os.path.exists(cache_file):
|
| 914 |
+
with open(cache_file, "r", encoding="utf-8") as f:
|
| 915 |
+
cached_data = json.load(f)
|
| 916 |
+
else:
|
| 917 |
+
cached_data = {}
|
| 918 |
+
|
| 919 |
+
# Add new result
|
| 920 |
+
html_contents = {method: attr.show(display=False, return_html=True) for method, attr in all_attributions.items()}
|
| 921 |
+
cached_data[prompt] = {
|
| 922 |
+
"generated_text": generated_text,
|
| 923 |
+
"html_contents": html_contents
|
| 924 |
+
}
|
| 925 |
+
|
| 926 |
+
# Write back to file
|
| 927 |
+
with open(cache_file, "w", encoding="utf-8") as f:
|
| 928 |
+
json.dump(cached_data, f, ensure_ascii=False, indent=4)
|
| 929 |
+
print(f"Saved new analysis for '{prompt}' to cache.")
|
| 930 |
+
|
| 931 |
+
except Exception as e:
|
| 932 |
+
print(f"Warning: Could not save result to cache file. {e}")
|
| 933 |
+
# --- End new section ---
|
| 934 |
+
|
| 935 |
+
# Clean up models to free memory.
|
| 936 |
+
del attribution_models
|
| 937 |
+
del tokenizer
|
| 938 |
+
del base_model
|
| 939 |
+
gc.collect()
|
| 940 |
+
if device == 'mps':
|
| 941 |
+
torch.mps.empty_cache()
|
| 942 |
+
elif device == 'cuda':
|
| 943 |
+
torch.cuda.empty_cache()
|
| 944 |
+
|
| 945 |
+
st.success(tr('analysis_complete_success'))
|
| 946 |
+
|
| 947 |
+
def show_attribution_analysis():
|
| 948 |
+
# Shows the main attribution analysis page.
|
| 949 |
+
# Add some CSS for icons.
|
| 950 |
+
st.markdown('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">', unsafe_allow_html=True)
|
| 951 |
+
|
| 952 |
+
st.markdown(f"<h1>{tr('attr_page_title')}</h1>", unsafe_allow_html=True)
|
| 953 |
+
st.markdown(f"{tr('attr_page_desc')}", unsafe_allow_html=True)
|
| 954 |
+
|
| 955 |
+
# Check if a new analysis has been requested by the user.
|
| 956 |
+
if 'run_request' in st.session_state:
|
| 957 |
+
request = st.session_state.pop('run_request')
|
| 958 |
+
run_analysis(
|
| 959 |
+
prompt=request['prompt'],
|
| 960 |
+
max_tokens=request['max_tokens'],
|
| 961 |
+
enable_explanations=request['enable_explanations']
|
| 962 |
+
)
|
| 963 |
+
|
| 964 |
+
# Set up the main layout.
|
| 965 |
+
col1, col2 = st.columns([1, 1])
|
| 966 |
+
|
| 967 |
+
with col1:
|
| 968 |
+
st.markdown(f"<h2>{tr('input_header')}</h2>", unsafe_allow_html=True)
|
| 969 |
+
|
| 970 |
+
# Get the current language from the session state.
|
| 971 |
+
lang = st.session_state.get('lang', 'en')
|
| 972 |
+
|
| 973 |
+
# Example prompts for English and German.
|
| 974 |
+
example_prompts = {
|
| 975 |
+
'en': [
|
| 976 |
+
"The capital of France is",
|
| 977 |
+
"The first person to walk on the moon was",
|
| 978 |
+
"To be or not to be, that is the",
|
| 979 |
+
"Once upon a time, in a land far, far away,",
|
| 980 |
+
"The chemical formula for water is",
|
| 981 |
+
"A stitch in time saves",
|
| 982 |
+
"The opposite of hot is",
|
| 983 |
+
"The main ingredients of a pizza are",
|
| 984 |
+
"She opened the door and saw"
|
| 985 |
+
],
|
| 986 |
+
'de': [
|
| 987 |
+
"Die Hauptstadt von Frankreich ist",
|
| 988 |
+
"Die erste Person auf dem Mond war",
|
| 989 |
+
"Sein oder Nichtsein, das ist hier die",
|
| 990 |
+
"Es war einmal, in einem weit, weit entfernten Land,",
|
| 991 |
+
"Die chemische Formel für Wasser ist",
|
| 992 |
+
"Was du heute kannst besorgen, das verschiebe nicht auf",
|
| 993 |
+
"Das Gegenteil von heiß ist",
|
| 994 |
+
"Die Hauptzutaten einer Pizza sind",
|
| 995 |
+
"Sie öffnete die Tür und sah"
|
| 996 |
+
]
|
| 997 |
+
}
|
| 998 |
+
|
| 999 |
+
st.markdown('**<i class="bi bi-lightbulb"></i> Example Prompts:**', unsafe_allow_html=True)
|
| 1000 |
+
cols = st.columns(3)
|
| 1001 |
+
for i, example in enumerate(example_prompts[lang][:9]):
|
| 1002 |
+
with cols[i % 3]:
|
| 1003 |
+
st.button(
|
| 1004 |
+
example,
|
| 1005 |
+
key=f"example_{i}",
|
| 1006 |
+
use_container_width=True,
|
| 1007 |
+
on_click=start_new_analysis,
|
| 1008 |
+
args=(example, 10, st.session_state.get('enable_explanations', True))
|
| 1009 |
+
)
|
| 1010 |
+
|
| 1011 |
+
# Text input area for the user's prompt.
|
| 1012 |
+
prompt = st.text_area(
|
| 1013 |
+
tr('enter_prompt'),
|
| 1014 |
+
value=st.session_state.get('attr_prompt', ""),
|
| 1015 |
+
height=100,
|
| 1016 |
+
help=tr('enter_prompt_help'),
|
| 1017 |
+
placeholder="Sadly no GPU available. Please select an example above.",
|
| 1018 |
+
disabled=True
|
| 1019 |
+
)
|
| 1020 |
+
|
| 1021 |
+
# Slider for the number of tokens to generate.
|
| 1022 |
+
max_tokens = st.slider(
|
| 1023 |
+
tr('max_new_tokens_slider'),
|
| 1024 |
+
min_value=1,
|
| 1025 |
+
max_value=50,
|
| 1026 |
+
value=5,
|
| 1027 |
+
help=tr('max_new_tokens_slider_help')
|
| 1028 |
+
)
|
| 1029 |
+
|
| 1030 |
+
# Checkbox to enable or disable AI explanations.
|
| 1031 |
+
enable_explanations = st.checkbox(
|
| 1032 |
+
tr('enable_ai_explanations'),
|
| 1033 |
+
value=True,
|
| 1034 |
+
help=tr('enable_ai_explanations_help')
|
| 1035 |
+
)
|
| 1036 |
+
|
| 1037 |
+
# Button to start the analysis.
|
| 1038 |
+
st.button(
|
| 1039 |
+
tr('generate_and_analyze_button'),
|
| 1040 |
+
type="primary",
|
| 1041 |
+
on_click=start_new_analysis,
|
| 1042 |
+
args=(prompt, max_tokens, enable_explanations)
|
| 1043 |
+
)
|
| 1044 |
+
|
| 1045 |
+
with col2:
|
| 1046 |
+
st.markdown(f"<h2>{tr('output_header')}</h2>", unsafe_allow_html=True)
|
| 1047 |
+
|
| 1048 |
+
if hasattr(st.session_state, 'generated_text'):
|
| 1049 |
+
st.subheader(tr('generated_text_subheader'))
|
| 1050 |
+
|
| 1051 |
+
# Extract the generated part of the text.
|
| 1052 |
+
prompt_part = st.session_state.prompt
|
| 1053 |
+
full_text = st.session_state.generated_text
|
| 1054 |
+
|
| 1055 |
+
generated_part = full_text
|
| 1056 |
+
if full_text.startswith(prompt_part):
|
| 1057 |
+
generated_part = full_text[len(prompt_part):].lstrip()
|
| 1058 |
+
else:
|
| 1059 |
+
# A fallback in case tokenization changes the prompt slightly.
|
| 1060 |
+
generated_part = full_text.replace(prompt_part, "", 1).strip()
|
| 1061 |
+
|
| 1062 |
+
# Clean up the generated text for display.
|
| 1063 |
+
cleaned_generated_part = re.sub(r'\n{2,}', '\n', generated_part).strip()
|
| 1064 |
+
escaped_generated = html.escape(cleaned_generated_part)
|
| 1065 |
+
escaped_prompt = html.escape(prompt_part)
|
| 1066 |
+
|
| 1067 |
+
st.markdown(f"""
|
| 1068 |
+
<div style="background-color: #2b2b2b; color: #ffffff; padding: 1.2rem; border-radius: 10px; margin: 1rem 0; border: 1px solid #444;">
|
| 1069 |
+
<strong>{tr('input_label')}</strong> <span style="color: #60a5fa;">{escaped_prompt}</span><br>
|
| 1070 |
+
<strong>{tr('generated_label')}</strong> <span style="font-weight: bold; color: #fca5a5; white-space: pre-wrap;">{escaped_generated}</span>
|
| 1071 |
+
</div>
|
| 1072 |
+
""", unsafe_allow_html=True)
|
| 1073 |
+
|
| 1074 |
+
# Display the visualizations for each method.
|
| 1075 |
+
if hasattr(st.session_state, 'all_attributions'):
|
| 1076 |
+
st.header(tr('attribution_analysis_results_header'))
|
| 1077 |
+
|
| 1078 |
+
# Create tabs for each analysis method.
|
| 1079 |
+
tab_titles = [
|
| 1080 |
+
tr('saliency_tab'),
|
| 1081 |
+
tr('attr_tab'),
|
| 1082 |
+
tr('occlusion_tab')
|
| 1083 |
+
]
|
| 1084 |
+
tabs = st.tabs(tab_titles)
|
| 1085 |
+
|
| 1086 |
+
# Define the order of the methods in the tabs.
|
| 1087 |
+
methods = {
|
| 1088 |
+
"saliency": {
|
| 1089 |
+
"tab": tabs[0],
|
| 1090 |
+
"title": tr('saliency_title'),
|
| 1091 |
+
"description": tr('saliency_viz_desc')
|
| 1092 |
+
},
|
| 1093 |
+
"integrated_gradients": {
|
| 1094 |
+
"tab": tabs[1],
|
| 1095 |
+
"title": tr('attr_title'),
|
| 1096 |
+
"description": tr('attr_viz_desc')
|
| 1097 |
+
},
|
| 1098 |
+
"occlusion": {
|
| 1099 |
+
"tab": tabs[2],
|
| 1100 |
+
"title": tr('occlusion_title'),
|
| 1101 |
+
"description": tr('occlusion_viz_desc')
|
| 1102 |
+
}
|
| 1103 |
+
}
|
| 1104 |
+
|
| 1105 |
+
# Generate and display the visualization for each method.
|
| 1106 |
+
for method_name, method_info in methods.items():
|
| 1107 |
+
with method_info["tab"]:
|
| 1108 |
+
st.subheader(f"{method_info['title']} Analysis")
|
| 1109 |
+
|
| 1110 |
+
# Generate the heatmap.
|
| 1111 |
+
with st.spinner(tr('creating_viz_spinner').format(method_title=method_info['title'])):
|
| 1112 |
+
heatmap_fig, html_content, heatmap_buffer, scores_df = create_heatmap_visualization(
|
| 1113 |
+
st.session_state.all_attributions[method_name],
|
| 1114 |
+
method_name=method_info['title']
|
| 1115 |
+
)
|
| 1116 |
+
|
| 1117 |
+
if heatmap_fig:
|
| 1118 |
+
st.plotly_chart(heatmap_fig, use_container_width=True)
|
| 1119 |
+
|
| 1120 |
+
# Add an explanation of how to read the heatmap.
|
| 1121 |
+
explanation_html = f"""
|
| 1122 |
+
<div style="background-color: #0E1117; border-radius: 10px; padding: 15px; margin: 10px 0; border: 1px solid #262730;">
|
| 1123 |
+
<h4 style="color: #FAFAFA; margin-bottom: 10px;">{tr('how_to_read_heatmap')}</h4>
|
| 1124 |
+
<ul style="color: #DCDCDC; margin-left: 20px; padding-left: 0;">
|
| 1125 |
+
<li style="margin-bottom: 5px;"><strong>{tr('xaxis_label')}:</strong> {tr('xaxis_desc')}</li>
|
| 1126 |
+
<li style="margin-bottom: 5px;"><strong>{tr('yaxis_label')}:</strong> {tr('yaxis_desc')}</li>
|
| 1127 |
+
<li style="margin-bottom: 5px;"><strong>{tr('color_intensity_label')}:</strong> {tr('color_intensity_desc')}</li>
|
| 1128 |
+
<li style="margin-bottom: 5px;"><strong>{tr('interpretation_label')}:</strong> {tr('interpretation_desc')}</li>
|
| 1129 |
+
<li style="margin-bottom: 5px;"><strong>{tr('special_tokens_label')}:</strong> {tr('special_tokens_desc')}</li>
|
| 1130 |
+
</ul>
|
| 1131 |
+
</div>
|
| 1132 |
+
"""
|
| 1133 |
+
st.markdown(explanation_html, unsafe_allow_html=True)
|
| 1134 |
+
|
| 1135 |
+
# Generate an AI explanation for the heatmap.
|
| 1136 |
+
if (st.session_state.get('enable_explanations') and
|
| 1137 |
+
st.session_state.get('qwen_api_config') and
|
| 1138 |
+
heatmap_buffer is not None and scores_df is not None):
|
| 1139 |
+
|
| 1140 |
+
explanation_cache_key = f"explanation_{method_name}_{st.session_state.generated_text}"
|
| 1141 |
+
|
| 1142 |
+
# Get the explanation from the cache or generate it.
|
| 1143 |
+
if explanation_cache_key not in st.session_state:
|
| 1144 |
+
with st.spinner(tr('generating_ai_explanations_spinner').format(method_title=method_info['title'])):
|
| 1145 |
+
explanation = explain_heatmap_with_csv_data(
|
| 1146 |
+
st.session_state.qwen_api_config,
|
| 1147 |
+
heatmap_buffer,
|
| 1148 |
+
scores_df,
|
| 1149 |
+
st.session_state.prompt,
|
| 1150 |
+
st.session_state.generated_text,
|
| 1151 |
+
method_name
|
| 1152 |
+
)
|
| 1153 |
+
st.session_state[explanation_cache_key] = explanation
|
| 1154 |
+
|
| 1155 |
+
explanation = st.session_state.get(explanation_cache_key)
|
| 1156 |
+
|
| 1157 |
+
if explanation and not explanation.startswith("Error:"):
|
| 1158 |
+
simple_desc = tr(METHOD_DESC_KEYS.get(method_name, "unsupported_method_desc"))
|
| 1159 |
+
st.markdown(f"#### {tr('what_this_method_shows')}")
|
| 1160 |
+
st.markdown(f"""
|
| 1161 |
+
<div style="background-color: #2f3f70; color: #f5f7fb; padding: 1.2rem; border-radius: 12px; margin-bottom: 1rem; box-shadow: 0 12px 24px rgba(47, 63, 112, 0.35);">
|
| 1162 |
+
<p style='font-size: 1.05em; font-weight: 500; margin:0; color: #f5f7fb;'>{simple_desc}</p>
|
| 1163 |
+
</div>
|
| 1164 |
+
""", unsafe_allow_html=True)
|
| 1165 |
+
|
| 1166 |
+
html_explanation = markdown.markdown(explanation)
|
| 1167 |
+
st.markdown(f"#### {tr('ai_generated_analysis')}")
|
| 1168 |
+
st.markdown(f"""
|
| 1169 |
+
<div style="background-color: #2b2b2b; color: #ffffff; padding: 1.2rem; border-radius: 10px; border-left: 4px solid #dcae36; font-size: 0.9rem; margin-bottom: 1rem;">
|
| 1170 |
+
{html_explanation}
|
| 1171 |
+
</div>
|
| 1172 |
+
""", unsafe_allow_html=True)
|
| 1173 |
+
|
| 1174 |
+
# Faithfulness Check Expander
|
| 1175 |
+
with st.expander(tr('faithfulness_check_expander')):
|
| 1176 |
+
st.markdown(tr('faithfulness_check_explanation_html'), unsafe_allow_html=True)
|
| 1177 |
+
with st.spinner(tr('running_faithfulness_check_spinner')):
|
| 1178 |
+
try:
|
| 1179 |
+
# Use a cache key to avoid re-running the check unnecessarily.
|
| 1180 |
+
check_cache_key = f"faithfulness_check_{method_name}_{st.session_state.generated_text}"
|
| 1181 |
+
|
| 1182 |
+
if check_cache_key not in st.session_state:
|
| 1183 |
+
claims = _cached_extract_claims_from_explanation(
|
| 1184 |
+
st.session_state.qwen_api_config,
|
| 1185 |
+
explanation,
|
| 1186 |
+
method_name
|
| 1187 |
+
)
|
| 1188 |
+
if claims:
|
| 1189 |
+
analysis_data = {
|
| 1190 |
+
'scores_df': scores_df,
|
| 1191 |
+
'method': method_name,
|
| 1192 |
+
'prompt': st.session_state.prompt,
|
| 1193 |
+
'generated_text': st.session_state.generated_text
|
| 1194 |
+
}
|
| 1195 |
+
verification_results = verify_claims(claims, analysis_data)
|
| 1196 |
+
st.session_state[check_cache_key] = verification_results
|
| 1197 |
+
else:
|
| 1198 |
+
st.session_state[check_cache_key] = []
|
| 1199 |
+
|
| 1200 |
+
verification_results = st.session_state[check_cache_key]
|
| 1201 |
+
|
| 1202 |
+
if verification_results:
|
| 1203 |
+
st.markdown(f"<h6>{tr('faithfulness_check_results_header')}</h6>", unsafe_allow_html=True)
|
| 1204 |
+
for result in verification_results:
|
| 1205 |
+
status_text = tr('verified_status') if result['verified'] else tr('contradicted_status')
|
| 1206 |
+
|
| 1207 |
+
st.markdown(f"""
|
| 1208 |
+
<div style="margin-bottom: 1rem; padding: 0.8rem; border-radius: 8px; border-left: 5px solid {'#28a745' if result['verified'] else '#dc3545'}; background-color: #1a1a1a;">
|
| 1209 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('claim_label')}:</strong> <em>"{result['claim_text']}"</em></p>
|
| 1210 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('status_label')}:</strong> {status_text}</p>
|
| 1211 |
+
<p style="margin-bottom: 0;"><strong>{tr('evidence_label')}:</strong> {result['evidence']}</p>
|
| 1212 |
+
</div>
|
| 1213 |
+
""", unsafe_allow_html=True)
|
| 1214 |
+
else:
|
| 1215 |
+
st.info(tr('no_verifiable_claims_info'))
|
| 1216 |
+
|
| 1217 |
+
except Exception as e:
|
| 1218 |
+
st.error(tr('faithfulness_check_error').format(e=str(e)))
|
| 1219 |
+
|
| 1220 |
+
# Add download buttons for the results.
|
| 1221 |
+
st.subheader(tr("download_results_subheader"))
|
| 1222 |
+
col1, col2 = st.columns(2)
|
| 1223 |
+
|
| 1224 |
+
with col1:
|
| 1225 |
+
if html_content:
|
| 1226 |
+
st.download_button(
|
| 1227 |
+
label=tr("download_html_button").format(method_title=method_info['title']),
|
| 1228 |
+
data=html_content,
|
| 1229 |
+
file_name=f"{method_name}_analysis.html",
|
| 1230 |
+
mime="text/html",
|
| 1231 |
+
key=f"html_{method_name}"
|
| 1232 |
+
)
|
| 1233 |
+
if scores_df is not None:
|
| 1234 |
+
st.download_button(
|
| 1235 |
+
label=tr("download_csv_button"),
|
| 1236 |
+
data=scores_df.to_csv().encode('utf-8'),
|
| 1237 |
+
file_name=f"{method_name}_scores.csv",
|
| 1238 |
+
mime="text/csv",
|
| 1239 |
+
key=f"csv_raw_{method_name}"
|
| 1240 |
+
)
|
| 1241 |
+
|
| 1242 |
+
with col2:
|
| 1243 |
+
if heatmap_fig:
|
| 1244 |
+
img_bytes = heatmap_fig.to_image(format="png", scale=2)
|
| 1245 |
+
st.download_button(
|
| 1246 |
+
label=tr("download_png_button").format(method_title=method_info['title']),
|
| 1247 |
+
data=img_bytes,
|
| 1248 |
+
file_name=f"{method_name}_heatmap.png",
|
| 1249 |
+
mime="image/png",
|
| 1250 |
+
key=f"png_{method_name}"
|
| 1251 |
+
)
|
| 1252 |
+
|
| 1253 |
+
# Display the influence tracer section.
|
| 1254 |
+
st.markdown("---")
|
| 1255 |
+
st.markdown(f'<h3><i class="bi bi-compass"></i> {tr("influence_tracer_title")}</h3>', unsafe_allow_html=True)
|
| 1256 |
+
st.markdown(f"<div style='font-size: 1.1rem;'>{tr('influence_tracer_desc')}</div>", unsafe_allow_html=True)
|
| 1257 |
+
|
| 1258 |
+
# Add a visual explanation of cosine similarity.
|
| 1259 |
+
# Get translated text.
|
| 1260 |
+
sentence_a = tr('influence_example_sentence_a')
|
| 1261 |
+
sentence_b = tr('influence_example_sentence_b')
|
| 1262 |
+
|
| 1263 |
+
# Create the SVG for the diagram.
|
| 1264 |
+
svg_code = f"""
|
| 1265 |
+
<svg width="250" height="150" viewBox="0 0 250 150" xmlns="http://www.w3.org/2000/svg">
|
| 1266 |
+
<line x1="10" y1="130" x2="240" y2="130" stroke="#555" stroke-width="2"></line>
|
| 1267 |
+
<line x1="10" y1="130" x2="10" y2="10" stroke="#555" stroke-width="2"></line>
|
| 1268 |
+
<!-- Corrected angle arc and theta position -->
|
| 1269 |
+
<path d="M 49 123 A 40 40 0 0 0 42 107" fill="none" stroke="#FFD700" stroke-width="2"></path>
|
| 1270 |
+
<text x="50" y="115" font-family="monospace" font-size="12" fill="#FFD700">θ</text>
|
| 1271 |
+
<line x1="10" y1="130" x2="150" y2="30" stroke="#87CEEB" stroke-width="3"></line>
|
| 1272 |
+
<text x="155" y="25" font-family="monospace" font-size="12" fill="#87CEEB">Vector A</text>
|
| 1273 |
+
<text x="155" y="40" font-family="monospace" font-size="10" fill="#aaa">{sentence_a}</text>
|
| 1274 |
+
<line x1="10" y1="130" x2="170" y2="100" stroke="#90EE90" stroke-width="3"></line>
|
| 1275 |
+
<text x="175" y="95" font-family="monospace" font-size="12" fill="#90EE90">Vector B</text>
|
| 1276 |
+
<text x="175" y="110" font-family="monospace" font-size="10" fill="#aaa">{sentence_b}</text>
|
| 1277 |
+
</svg>
|
| 1278 |
+
"""
|
| 1279 |
+
|
| 1280 |
+
# Encode the SVG to base64.
|
| 1281 |
+
encoded_svg = base64.b64encode(svg_code.encode("utf-8")).decode("utf-8")
|
| 1282 |
+
image_uri = f"data:image/svg+xml;base64,{encoded_svg}"
|
| 1283 |
+
|
| 1284 |
+
# Display the explanation and diagram.
|
| 1285 |
+
st.markdown(f"""
|
| 1286 |
+
<div style="background-color: #2b2b2b; border-radius: 10px; padding: 1.5rem; margin: 1rem 0; border-left: 4px solid #FFD700;">
|
| 1287 |
+
<h4 style="color: #FFD700; margin-top: 0; margin-bottom: 1rem;">{tr('how_influence_is_found_header')}</h4>
|
| 1288 |
+
<div>
|
| 1289 |
+
<p style="font-size: 1rem;">{tr('how_influence_is_found_desc')}</p>
|
| 1290 |
+
<div style="font-family: 'SF Mono', 'Consolas', 'Menlo', monospace; margin-top: 1.5rem; font-size: 0.95em;">
|
| 1291 |
+
<p>{tr('influence_step_1_title')}: {tr('influence_step_1_desc')}</p>
|
| 1292 |
+
<p>{tr('influence_step_2_title')}: {tr('influence_step_2_desc')}</p>
|
| 1293 |
+
<p>{tr('influence_step_3_title')}: {tr('influence_step_3_desc')}</p>
|
| 1294 |
+
</div>
|
| 1295 |
+
</div>
|
| 1296 |
+
<div style="text-align: center; margin-top: 2rem;">
|
| 1297 |
+
<img src="{image_uri}" alt="Cosine Similarity Diagram" />
|
| 1298 |
+
</div>
|
| 1299 |
+
</div>
|
| 1300 |
+
""", unsafe_allow_html=True)
|
| 1301 |
+
|
| 1302 |
+
st.write("")
|
| 1303 |
+
|
| 1304 |
+
if hasattr(st.session_state, 'generated_text'):
|
| 1305 |
+
# First, check if influential docs are available in the cache from session_state
|
| 1306 |
+
if 'cached_influential_docs' in st.session_state:
|
| 1307 |
+
influential_docs = st.session_state.pop('cached_influential_docs') # Use and remove
|
| 1308 |
+
else:
|
| 1309 |
+
with st.spinner(tr('running_influence_trace_spinner')):
|
| 1310 |
+
lang = st.session_state.get('lang', 'en')
|
| 1311 |
+
influential_docs = get_influential_docs(st.session_state.prompt, lang)
|
| 1312 |
+
|
| 1313 |
+
# Display the results.
|
| 1314 |
+
if influential_docs:
|
| 1315 |
+
st.markdown(f"#### {tr('top_influential_docs_header').format(num_docs=len(influential_docs))}")
|
| 1316 |
+
|
| 1317 |
+
# A nice visualization for the influential documents.
|
| 1318 |
+
for i, doc in enumerate(influential_docs):
|
| 1319 |
+
colors = ["#A78BFA", "#7F9CF5", "#6EE7B7", "#FBBF24", "#F472B6"]
|
| 1320 |
+
card_color = colors[i % len(colors)]
|
| 1321 |
+
|
| 1322 |
+
full_text = doc['text']
|
| 1323 |
+
highlight_sentence = doc.get('highlight_sentence', '')
|
| 1324 |
+
|
| 1325 |
+
highlighted_html = ""
|
| 1326 |
+
lang = st.session_state.get('lang', 'en')
|
| 1327 |
+
|
| 1328 |
+
if highlight_sentence:
|
| 1329 |
+
# Normalize the sentence to be highlighted.
|
| 1330 |
+
normalized_highlight = re.sub(r'\s+', ' ', highlight_sentence).strip()
|
| 1331 |
+
|
| 1332 |
+
# Use fuzzy matching to find the best match in the document.
|
| 1333 |
+
splitter = SentenceSplitter(language=lang)
|
| 1334 |
+
sentences_in_doc = splitter.split(text=full_text)
|
| 1335 |
+
|
| 1336 |
+
if sentences_in_doc:
|
| 1337 |
+
best_match, score = process.extractOne(normalized_highlight, sentences_in_doc)
|
| 1338 |
+
start_index = full_text.find(best_match)
|
| 1339 |
+
|
| 1340 |
+
if start_index != -1:
|
| 1341 |
+
end_index = start_index + len(best_match)
|
| 1342 |
+
|
| 1343 |
+
# Create a context window around the matched sentence.
|
| 1344 |
+
context_window = 500
|
| 1345 |
+
snippet_start = max(0, start_index - context_window)
|
| 1346 |
+
snippet_end = min(len(full_text), end_index + context_window)
|
| 1347 |
+
|
| 1348 |
+
# Reconstruct the HTML with the highlighted sentence.
|
| 1349 |
+
before = html.escape(full_text[snippet_start:start_index])
|
| 1350 |
+
highlight = html.escape(best_match)
|
| 1351 |
+
after = html.escape(full_text[end_index:snippet_end])
|
| 1352 |
+
|
| 1353 |
+
# Add ellipses if we're not showing the full text.
|
| 1354 |
+
start_ellipsis = "... " if snippet_start > 0 else ""
|
| 1355 |
+
end_ellipsis = " ..." if snippet_end < len(full_text) else ""
|
| 1356 |
+
|
| 1357 |
+
highlighted_html = (
|
| 1358 |
+
f"{start_ellipsis}{before}"
|
| 1359 |
+
f'<mark style="background-color: {card_color}77; color: #DCDCDC; padding: 2px 4px; border-radius: 4px; font-weight: bold;">{highlight}</mark>'
|
| 1360 |
+
f"{after}{end_ellipsis}"
|
| 1361 |
+
)
|
| 1362 |
+
|
| 1363 |
+
# If no highlight was applied, just show the full text.
|
| 1364 |
+
if not highlighted_html:
|
| 1365 |
+
highlighted_html = html.escape(full_text)
|
| 1366 |
+
|
| 1367 |
+
st.markdown(f"""
|
| 1368 |
+
<div style="border: 1px solid #262730; border-left: 5px solid {card_color}; border-radius: 10px; padding: 1.5rem; margin-bottom: 1.5rem; background-color: #0E1117; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
|
| 1369 |
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
| 1370 |
+
<span style="font-size: 1.1rem; color: #FAFAFA; font-weight: 600;"><i class="bi bi-journal-text"></i> {tr('source_label')}: {doc['source']}</span>
|
| 1371 |
+
<span style="font-size: 1.1rem; color: {card_color}; background-color: {card_color}22; padding: 0.3rem 0.8rem; border-radius: 15px; font-weight: bold;">
|
| 1372 |
+
<i class="bi bi-stars"></i> {tr('similarity_label')}: {doc['similarity']:.3f}
|
| 1373 |
+
</span>
|
| 1374 |
+
</div>
|
| 1375 |
+
<div style="background-color: #1a1a1a; color: #DCDCDC; padding: 1rem; border-radius: 8px; font-family: 'Courier New', Courier, monospace; white-space: pre-wrap; word-wrap: break-word; max-height: 300px; overflow-y: auto;">
|
| 1376 |
+
{highlighted_html.strip()}
|
| 1377 |
+
</div>
|
| 1378 |
+
</div>
|
| 1379 |
+
""", unsafe_allow_html=True)
|
| 1380 |
+
else:
|
| 1381 |
+
# Give a helpful message if the index is missing.
|
| 1382 |
+
if not os.path.exists(INDEX_PATH) or not os.path.exists(MAPPING_PATH):
|
| 1383 |
+
st.warning(tr('influence_index_not_found_warning'))
|
| 1384 |
+
else:
|
| 1385 |
+
st.info(tr('no_influential_docs_found'))
|
| 1386 |
+
else:
|
| 1387 |
+
st.info(tr('run_analysis_for_influence_info'))
|
| 1388 |
+
|
| 1389 |
+
# Show the feedback survey in the sidebar.
|
| 1390 |
+
#if 'all_attributions' in st.session_state:
|
| 1391 |
+
# display_attribution_feedback()
|
| 1392 |
+
|
| 1393 |
+
|
| 1394 |
+
if __name__ == "__main__":
|
| 1395 |
+
show_attribution_analysis()
|
cache/cached_attribution_results.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/CLT_IMPROVEMENTS.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CLT Improvement Guide
|
| 2 |
+
|
| 3 |
+
## Current Issues & Solutions
|
| 4 |
+
|
| 5 |
+
### 1. **Weak Sparsity Loss** (Critical)
|
| 6 |
+
**Current:** `torch.mean(torch.tanh(sparsity_lambda * features))`
|
| 7 |
+
- `tanh` saturates and doesn't strongly penalize activations
|
| 8 |
+
- Results in dense, non-interpretable features
|
| 9 |
+
|
| 10 |
+
**Better:** Use L1 sparsity (standard in SAEs)
|
| 11 |
+
```python
|
| 12 |
+
# Replace line 931 in train_clt:
|
| 13 |
+
sparsity_loss += torch.mean(torch.abs(features)) # L1 norm
|
| 14 |
+
# Or per-feature L1:
|
| 15 |
+
sparsity_loss += torch.sum(torch.abs(features), dim=-1).mean() # Sum over features, mean over batch/seq
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
**Even Better:** Use top-k sparsity (encourages dead features)
|
| 19 |
+
```python
|
| 20 |
+
# Only penalize top-k active features per position
|
| 21 |
+
k = int(0.1 * self.n_features) # Top 10% most active
|
| 22 |
+
for features in feature_activations:
|
| 23 |
+
# features: [batch, seq, n_features]
|
| 24 |
+
topk_vals, _ = torch.topk(features, k=k, dim=-1)
|
| 25 |
+
sparsity_loss += torch.mean(topk_vals)
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
### 2. **Dead Feature Resampling** (Important)
|
| 29 |
+
**Problem:** Many features never activate → wasted capacity
|
| 30 |
+
|
| 31 |
+
**Solution:** Periodically resample dead features
|
| 32 |
+
```python
|
| 33 |
+
def resample_dead_features(self, feature_activations: List[torch.Tensor],
|
| 34 |
+
dead_threshold: float = 1e-6):
|
| 35 |
+
"""Resample encoder weights for features that never activate."""
|
| 36 |
+
for layer_idx, features in enumerate(feature_activations):
|
| 37 |
+
# features: [batch, seq, n_features]
|
| 38 |
+
avg_activation = features.mean(dim=(0, 1)) # [n_features]
|
| 39 |
+
dead_mask = avg_activation < dead_threshold
|
| 40 |
+
|
| 41 |
+
if dead_mask.any():
|
| 42 |
+
n_dead = dead_mask.sum().item()
|
| 43 |
+
logger.info(f"Layer {layer_idx}: Resampling {n_dead} dead features")
|
| 44 |
+
|
| 45 |
+
# Resample dead encoder weights
|
| 46 |
+
with torch.no_grad():
|
| 47 |
+
# Copy from a random active feature
|
| 48 |
+
active_indices = torch.nonzero(~dead_mask).squeeze(-1)
|
| 49 |
+
if len(active_indices) > 0:
|
| 50 |
+
for dead_idx in torch.nonzero(dead_mask).squeeze(-1):
|
| 51 |
+
source_idx = active_indices[torch.randint(0, len(active_indices), (1,))]
|
| 52 |
+
self.encoders[layer_idx].weight[dead_idx] = \
|
| 53 |
+
self.encoders[layer_idx].weight[source_idx].clone()
|
| 54 |
+
# Add small noise to break symmetry
|
| 55 |
+
self.encoders[layer_idx].weight[dead_idx] += \
|
| 56 |
+
torch.randn_like(self.encoders[layer_idx].weight[dead_idx]) * 0.01
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
Call this every 100-500 training steps.
|
| 60 |
+
|
| 61 |
+
### 3. **Better Weight Initialization**
|
| 62 |
+
**Current:** `nn.init.normal_(weight, mean=0.0, std=0.01)` - too small
|
| 63 |
+
|
| 64 |
+
**Better:** Use geometric mean initialization (Anthropic SAE style)
|
| 65 |
+
```python
|
| 66 |
+
def _init_weights(self):
|
| 67 |
+
for layer_idx, encoder in enumerate(self.encoders):
|
| 68 |
+
# Initialize to have roughly unit norm
|
| 69 |
+
nn.init.xavier_uniform_(encoder.weight, gain=0.1)
|
| 70 |
+
# Or: initialize from pretrained if available
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### 4. **Learning Rate Scheduling**
|
| 74 |
+
**Current:** Fixed learning rate
|
| 75 |
+
|
| 76 |
+
**Better:** Cosine annealing or warmup
|
| 77 |
+
```python
|
| 78 |
+
from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR
|
| 79 |
+
|
| 80 |
+
# In train_clt:
|
| 81 |
+
optimizer = torch.optim.Adam(self.clt.parameters(), lr=self.config.learning_rate)
|
| 82 |
+
scheduler = CosineAnnealingLR(optimizer, T_max=self.config.training_steps, eta_min=1e-6)
|
| 83 |
+
# Or OneCycleLR for faster convergence:
|
| 84 |
+
# scheduler = OneCycleLR(optimizer, max_lr=self.config.learning_rate,
|
| 85 |
+
# total_steps=self.config.training_steps)
|
| 86 |
+
|
| 87 |
+
# In training loop:
|
| 88 |
+
optimizer.step()
|
| 89 |
+
scheduler.step()
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
### 5. **Gradient Clipping**
|
| 93 |
+
**Problem:** Large gradients can destabilize training
|
| 94 |
+
|
| 95 |
+
**Solution:**
|
| 96 |
+
```python
|
| 97 |
+
# After loss.backward(), before optimizer.step():
|
| 98 |
+
torch.nn.utils.clip_grad_norm_(self.clt.parameters(), max_norm=1.0)
|
| 99 |
+
optimizer.step()
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
### 6. **Better Reconstruction Target**
|
| 103 |
+
**Current:** Reconstructs `hidden_states` (residual stream)
|
| 104 |
+
|
| 105 |
+
**Question:** Should you reconstruct MLP outputs instead?
|
| 106 |
+
- If CLT is meant to approximate MLP computation, target should be MLP outputs
|
| 107 |
+
- Current approach learns residual stream → feature → residual stream mapping
|
| 108 |
+
|
| 109 |
+
**If targeting MLP outputs:**
|
| 110 |
+
```python
|
| 111 |
+
# Get MLP outputs instead of hidden states
|
| 112 |
+
with torch.no_grad():
|
| 113 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 114 |
+
# Extract MLP outputs (hidden_states[layer] - hidden_states[layer-1] for some models)
|
| 115 |
+
# Or use model's intermediate outputs if available
|
| 116 |
+
mlp_outputs = [...] # Extract MLP outputs per layer
|
| 117 |
+
|
| 118 |
+
# Then reconstruct mlp_outputs instead of hidden_states
|
| 119 |
+
recon_loss = sum(F.mse_loss(pred, target)
|
| 120 |
+
for target, pred in zip(mlp_outputs, reconstructed_outputs))
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
### 7. **Feature Density Monitoring**
|
| 124 |
+
**Add metrics to track:**
|
| 125 |
+
```python
|
| 126 |
+
# In training loop, track:
|
| 127 |
+
active_features = (features > 0.01).sum(dim=-1).float().mean() # Avg active per position
|
| 128 |
+
feature_density = (features > 0).float().mean() # Fraction of features active
|
| 129 |
+
max_activation = features.max().item()
|
| 130 |
+
|
| 131 |
+
# Log these to understand sparsity
|
| 132 |
+
if step % 100 == 0:
|
| 133 |
+
logger.info(f"Active features/position: {active_features:.1f}, "
|
| 134 |
+
f"Density: {feature_density:.3f}, Max act: {max_activation:.4f}")
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
### 8. **Orthogonality Regularization** (Optional)
|
| 138 |
+
**Encourages diverse features:**
|
| 139 |
+
```python
|
| 140 |
+
# Add to loss:
|
| 141 |
+
ortho_loss = 0.0
|
| 142 |
+
for encoder in self.encoders:
|
| 143 |
+
W = encoder.weight # [n_features, hidden_size]
|
| 144 |
+
# Encourage orthogonality: W @ W.T should be close to identity
|
| 145 |
+
gram = torch.mm(W, W.T)
|
| 146 |
+
identity = torch.eye(self.n_features, device=W.device)
|
| 147 |
+
ortho_loss += F.mse_loss(gram, identity)
|
| 148 |
+
|
| 149 |
+
loss += 0.01 * ortho_loss # Small weight
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
### 9. **Batch Processing** (Performance)
|
| 153 |
+
**Current:** Processes texts one-by-one in batch
|
| 154 |
+
|
| 155 |
+
**Better:** True batch processing
|
| 156 |
+
```python
|
| 157 |
+
# Tokenize all texts at once
|
| 158 |
+
inputs = self.tokenizer(
|
| 159 |
+
batch_texts, # List of strings
|
| 160 |
+
return_tensors="pt",
|
| 161 |
+
padding=True,
|
| 162 |
+
truncation=True,
|
| 163 |
+
max_length=self.config.max_seq_length
|
| 164 |
+
).to(self.device)
|
| 165 |
+
|
| 166 |
+
# Get activations for entire batch
|
| 167 |
+
with torch.no_grad():
|
| 168 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 169 |
+
hidden_states = outputs.hidden_states[1:]
|
| 170 |
+
|
| 171 |
+
# Forward pass (now handles batch dimension properly)
|
| 172 |
+
feature_activations, reconstructed_outputs = self.clt(hidden_states)
|
| 173 |
+
|
| 174 |
+
# Loss computation (already batched)
|
| 175 |
+
recon_loss = sum(F.mse_loss(pred, target)
|
| 176 |
+
for target, pred in zip(hidden_states, reconstructed_outputs))
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
### 10. **Hyperparameter Tuning**
|
| 180 |
+
**Recommended starting values:**
|
| 181 |
+
```python
|
| 182 |
+
config = AttributionGraphConfig(
|
| 183 |
+
n_features_per_layer=2048, # Increase from 512 (more capacity)
|
| 184 |
+
sparsity_lambda=1e-3, # Start small, increase if too dense
|
| 185 |
+
reconstruction_loss_weight=1.0, # Keep at 1.0
|
| 186 |
+
learning_rate=3e-4, # Standard for Adam
|
| 187 |
+
training_steps=10000, # More steps for better convergence
|
| 188 |
+
batch_size=32, # Larger batches stabilize training
|
| 189 |
+
)
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
## Implementation Priority
|
| 193 |
+
|
| 194 |
+
1. **High Priority:**
|
| 195 |
+
- Change sparsity loss to L1 (easy, big impact)
|
| 196 |
+
- Add gradient clipping
|
| 197 |
+
- Add learning rate scheduling
|
| 198 |
+
- Fix batch processing
|
| 199 |
+
|
| 200 |
+
2. **Medium Priority:**
|
| 201 |
+
- Dead feature resampling
|
| 202 |
+
- Better weight initialization
|
| 203 |
+
- Feature density monitoring
|
| 204 |
+
|
| 205 |
+
3. **Low Priority:**
|
| 206 |
+
- Orthogonality regularization
|
| 207 |
+
- MLP output targeting (if architecture change needed)
|
| 208 |
+
|
| 209 |
+
## Quick Win: Minimal Changes
|
| 210 |
+
|
| 211 |
+
If you want the biggest improvement with minimal code changes:
|
| 212 |
+
|
| 213 |
+
1. Replace sparsity loss (line 931):
|
| 214 |
+
```python
|
| 215 |
+
sparsity_loss += torch.mean(torch.abs(features)) # L1 instead of tanh
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
2. Add gradient clipping (after line 948):
|
| 219 |
+
```python
|
| 220 |
+
torch.nn.utils.clip_grad_norm_(self.clt.parameters(), max_norm=1.0)
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
3. Add learning rate scheduler (after line 894):
|
| 224 |
+
```python
|
| 225 |
+
from torch.optim.lr_scheduler import CosineAnnealingLR
|
| 226 |
+
scheduler = CosineAnnealingLR(optimizer, T_max=self.config.training_steps)
|
| 227 |
+
# Then after optimizer.step():
|
| 228 |
+
scheduler.step()
|
| 229 |
+
```
|
| 230 |
+
|
| 231 |
+
These three changes should significantly improve feature quality and training stability.
|
| 232 |
+
|
circuit_analysis/WORKFLOW_PER_PROMPT.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Workflow for Per-Prompt Analysis with Feature Interpretations
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
This workflow allows you to run feature interpretations and offline circuit metrics for each prompt separately to stay within API rate limits (200 calls/hour), then merge the results.
|
| 5 |
+
|
| 6 |
+
## Step-by-Step Process
|
| 7 |
+
|
| 8 |
+
### 1. Run Feature Interpretations Per Prompt
|
| 9 |
+
|
| 10 |
+
For each prompt (0, 1, 2), run the main analysis script:
|
| 11 |
+
|
| 12 |
+
```bash
|
| 13 |
+
# Prompt 0
|
| 14 |
+
python3 circuit_analysis/attribution_graphs_olmo.py --prompt-index 0
|
| 15 |
+
|
| 16 |
+
# Prompt 1
|
| 17 |
+
python3 circuit_analysis/attribution_graphs_olmo.py --prompt-index 1
|
| 18 |
+
|
| 19 |
+
# Prompt 2
|
| 20 |
+
python3 circuit_analysis/attribution_graphs_olmo.py --prompt-index 2
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
This will:
|
| 24 |
+
- Generate feature interpretations using Qwen API (stays within rate limits per prompt)
|
| 25 |
+
- Save results to `circuit_analysis/results/attribution_graphs_results_prompt_{1,2,3}.json`
|
| 26 |
+
- Use consistent config: `n_features_per_layer=512`, `sparsity_lambda=1e-3`, etc.
|
| 27 |
+
|
| 28 |
+
### 2. Run Offline Circuit Metrics Per Prompt
|
| 29 |
+
|
| 30 |
+
For each prompt, run the offline metrics script:
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
# Prompt 0
|
| 34 |
+
python3 circuit_analysis/offline_circuit_metrics.py --prompt-index 0
|
| 35 |
+
|
| 36 |
+
# Prompt 1
|
| 37 |
+
python3 circuit_analysis/offline_circuit_metrics.py --prompt-index 1
|
| 38 |
+
|
| 39 |
+
# Prompt 2
|
| 40 |
+
python3 circuit_analysis/offline_circuit_metrics.py --prompt-index 2
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
This will:
|
| 44 |
+
- Calculate **full universe delta p** (ablating all CLT features)
|
| 45 |
+
- Calculate **full circuit delta p** (ablating all graph features)
|
| 46 |
+
- Use the same config numbers as the main analysis
|
| 47 |
+
- Save to `circuit_analysis/results/offline_circuit_metrics_prompt_{1,2,3}.json`
|
| 48 |
+
|
| 49 |
+
### 3. Merge Results
|
| 50 |
+
|
| 51 |
+
Merge all the per-prompt results into a single file:
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
# Start with prompt 1
|
| 55 |
+
python3 circuit_analysis/merge_circuit_results.py attribution_graphs_results.json attribution_graphs_results_prompt_1.json
|
| 56 |
+
|
| 57 |
+
# Add prompt 2
|
| 58 |
+
python3 circuit_analysis/merge_circuit_results.py attribution_graphs_results.json attribution_graphs_results_prompt_2.json
|
| 59 |
+
|
| 60 |
+
# Add prompt 3
|
| 61 |
+
python3 circuit_analysis/merge_circuit_results.py attribution_graphs_results.json attribution_graphs_results_prompt_3.json
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
For offline metrics (if you want to merge those too):
|
| 65 |
+
|
| 66 |
+
```bash
|
| 67 |
+
python3 circuit_analysis/merge_circuit_results.py offline_circuit_metrics.json offline_circuit_metrics_prompt_1.json
|
| 68 |
+
python3 circuit_analysis/merge_circuit_results.py offline_circuit_metrics.json offline_circuit_metrics_prompt_2.json
|
| 69 |
+
python3 circuit_analysis/merge_circuit_results.py offline_circuit_metrics.json offline_circuit_metrics_prompt_3.json
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
### 4. Plot Offline Metrics
|
| 73 |
+
|
| 74 |
+
After merging, plot the combined results:
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
python3 circuit_analysis/plot_offline_metrics.py
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
## Configuration Consistency
|
| 81 |
+
|
| 82 |
+
All scripts now use the same configuration:
|
| 83 |
+
- `n_features_per_layer=512` (matches trained CLT)
|
| 84 |
+
- `sparsity_lambda=1e-3` (L1 sparsity, matches training)
|
| 85 |
+
- `graph_feature_activation_threshold=0.01`
|
| 86 |
+
- `graph_edge_weight_threshold=0.003`
|
| 87 |
+
- `graph_max_features_per_layer=40`
|
| 88 |
+
- `graph_max_edges_per_node=20`
|
| 89 |
+
|
| 90 |
+
## Output Format
|
| 91 |
+
|
| 92 |
+
Each per-prompt result file contains:
|
| 93 |
+
- `analyses`: Dictionary with `prompt_{N}` keys
|
| 94 |
+
- `config`: Configuration used
|
| 95 |
+
- `timestamp`: When the analysis was run
|
| 96 |
+
|
| 97 |
+
The offline metrics additionally include:
|
| 98 |
+
- `full_universe_ablation`: Delta p when ablating all CLT features
|
| 99 |
+
- `full_circuit_ablation`: Delta p when ablating all graph features
|
| 100 |
+
|
| 101 |
+
## Notes
|
| 102 |
+
|
| 103 |
+
- Feature interpretations are cached, so re-running won't make duplicate API calls
|
| 104 |
+
- Wait at least 1 hour between prompts if you're hitting rate limits
|
| 105 |
+
- The merge script preserves all data and just combines the `analyses` dictionaries
|
| 106 |
+
|
circuit_analysis/attribution_graphs_olmo.py
ADDED
|
@@ -0,0 +1,1931 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# This script generates attribution graphs for the OLMo2 7B model.
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
import numpy as np
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
import seaborn as sns
|
| 10 |
+
from typing import Dict, List, Tuple, Optional, Any, Set
|
| 11 |
+
import json
|
| 12 |
+
import logging
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 15 |
+
from collections import defaultdict
|
| 16 |
+
import networkx as nx
|
| 17 |
+
from dataclasses import dataclass
|
| 18 |
+
from tqdm import tqdm
|
| 19 |
+
import pickle
|
| 20 |
+
import requests
|
| 21 |
+
import time
|
| 22 |
+
import random
|
| 23 |
+
import copy
|
| 24 |
+
import os
|
| 25 |
+
import argparse
|
| 26 |
+
|
| 27 |
+
# --- Add this block to fix the import path ---
|
| 28 |
+
import sys
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 31 |
+
# ---------------------------------------------
|
| 32 |
+
|
| 33 |
+
from utilities.utils import init_qwen_api, set_seed
|
| 34 |
+
|
| 35 |
+
# --- Constants ---
|
| 36 |
+
RESULTS_DIR = "circuit_analysis/results"
|
| 37 |
+
CLT_SAVE_PATH = "circuit_analysis/models/clt_model.pth"
|
| 38 |
+
|
| 39 |
+
# Configure logging.
|
| 40 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 41 |
+
logger = logging.getLogger(__name__)
|
| 42 |
+
|
| 43 |
+
# Set the device for training.
|
| 44 |
+
if torch.backends.mps.is_available():
|
| 45 |
+
DEVICE = torch.device("mps")
|
| 46 |
+
logger.info("Using MPS (Metal Performance Shaders) for GPU acceleration")
|
| 47 |
+
elif torch.cuda.is_available():
|
| 48 |
+
DEVICE = torch.device("cuda")
|
| 49 |
+
logger.info("Using CUDA for GPU acceleration")
|
| 50 |
+
else:
|
| 51 |
+
DEVICE = torch.device("cpu")
|
| 52 |
+
logger.info("Using CPU")
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class AttributionGraphConfig:
|
| 56 |
+
# Configuration for building the attribution graph.
|
| 57 |
+
model_path: str = "./models/OLMo-2-1124-7B"
|
| 58 |
+
max_seq_length: int = 512
|
| 59 |
+
n_features_per_layer: int = 512 # Number of features in each CLT layer
|
| 60 |
+
sparsity_lambda: float = 1e-3 # Updated for L1 sparsity
|
| 61 |
+
reconstruction_loss_weight: float = 1.0
|
| 62 |
+
batch_size: int = 8
|
| 63 |
+
learning_rate: float = 1e-4
|
| 64 |
+
training_steps: int = 1000
|
| 65 |
+
device: str = str(DEVICE)
|
| 66 |
+
pruning_threshold: float = 0.8 # For graph pruning
|
| 67 |
+
intervention_strength: float = 5.0 # For perturbation experiments
|
| 68 |
+
qwen_api_config: Optional[Dict[str, str]] = None
|
| 69 |
+
max_ablation_experiments: Optional[int] = None
|
| 70 |
+
ablation_top_k_tokens: int = 5
|
| 71 |
+
ablation_features_per_layer: Optional[int] = 2
|
| 72 |
+
summary_max_layers: Optional[int] = None
|
| 73 |
+
summary_features_per_layer: Optional[int] = 2
|
| 74 |
+
random_baseline_trials: int = 5
|
| 75 |
+
random_baseline_features: int = 1
|
| 76 |
+
random_baseline_seed: int = 1234
|
| 77 |
+
path_ablation_top_k: int = 3
|
| 78 |
+
random_path_baseline_trials: int = 5
|
| 79 |
+
graph_max_features_per_layer: int = 40
|
| 80 |
+
graph_feature_activation_threshold: float = 0.01
|
| 81 |
+
graph_edge_weight_threshold: float = 0.0
|
| 82 |
+
graph_max_edges_per_node: int = 12
|
| 83 |
+
|
| 84 |
+
class JumpReLU(nn.Module):
|
| 85 |
+
# The JumpReLU activation function.
|
| 86 |
+
|
| 87 |
+
def __init__(self, threshold: float = 0.0):
|
| 88 |
+
super().__init__()
|
| 89 |
+
self.threshold = threshold
|
| 90 |
+
|
| 91 |
+
def forward(self, x):
|
| 92 |
+
return F.relu(x - self.threshold)
|
| 93 |
+
|
| 94 |
+
class CrossLayerTranscoder(nn.Module):
|
| 95 |
+
# The Cross-Layer Transcoder (CLT) model.
|
| 96 |
+
|
| 97 |
+
def __init__(self, model_config: Dict, clt_config: AttributionGraphConfig):
|
| 98 |
+
super().__init__()
|
| 99 |
+
self.config = clt_config
|
| 100 |
+
self.model_config = model_config
|
| 101 |
+
self.n_layers = model_config['num_hidden_layers']
|
| 102 |
+
self.hidden_size = model_config['hidden_size']
|
| 103 |
+
self.n_features = clt_config.n_features_per_layer
|
| 104 |
+
|
| 105 |
+
# Encoder weights for each layer.
|
| 106 |
+
self.encoders = nn.ModuleList([
|
| 107 |
+
nn.Linear(self.hidden_size, self.n_features, bias=False)
|
| 108 |
+
for _ in range(self.n_layers)
|
| 109 |
+
])
|
| 110 |
+
|
| 111 |
+
# Decoder weights for cross-layer connections.
|
| 112 |
+
self.decoders = nn.ModuleDict()
|
| 113 |
+
for source_layer in range(self.n_layers):
|
| 114 |
+
for target_layer in range(source_layer, self.n_layers):
|
| 115 |
+
key = f"{source_layer}_to_{target_layer}"
|
| 116 |
+
self.decoders[key] = nn.Linear(self.n_features, self.hidden_size, bias=False)
|
| 117 |
+
|
| 118 |
+
# The activation function.
|
| 119 |
+
self.activation = JumpReLU(threshold=0.0)
|
| 120 |
+
|
| 121 |
+
# Initialize the weights.
|
| 122 |
+
self._init_weights()
|
| 123 |
+
|
| 124 |
+
def _init_weights(self):
|
| 125 |
+
# Initializes the weights with small random values.
|
| 126 |
+
for module in self.modules():
|
| 127 |
+
if isinstance(module, nn.Linear):
|
| 128 |
+
nn.init.normal_(module.weight, mean=0.0, std=0.01)
|
| 129 |
+
|
| 130 |
+
def encode(self, layer_idx: int, residual_activations: torch.Tensor) -> torch.Tensor:
|
| 131 |
+
# Encodes residual stream activations to feature activations.
|
| 132 |
+
return self.activation(self.encoders[layer_idx](residual_activations))
|
| 133 |
+
|
| 134 |
+
def decode(self, source_layer: int, target_layer: int, feature_activations: torch.Tensor) -> torch.Tensor:
|
| 135 |
+
# Decodes feature activations to the MLP output space.
|
| 136 |
+
key = f"{source_layer}_to_{target_layer}"
|
| 137 |
+
return self.decoders[key](feature_activations)
|
| 138 |
+
|
| 139 |
+
def forward(self, residual_activations: List[torch.Tensor]) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:
|
| 140 |
+
# The forward pass of the CLT.
|
| 141 |
+
feature_activations = []
|
| 142 |
+
reconstructed_mlp_outputs = []
|
| 143 |
+
|
| 144 |
+
# Encode features for each layer.
|
| 145 |
+
for layer_idx, residual in enumerate(residual_activations):
|
| 146 |
+
features = self.encode(layer_idx, residual)
|
| 147 |
+
feature_activations.append(features)
|
| 148 |
+
|
| 149 |
+
# Reconstruct MLP outputs with cross-layer connections.
|
| 150 |
+
for target_layer in range(self.n_layers):
|
| 151 |
+
reconstruction = torch.zeros_like(residual_activations[target_layer])
|
| 152 |
+
|
| 153 |
+
# Sum contributions from all previous layers.
|
| 154 |
+
for source_layer in range(target_layer + 1):
|
| 155 |
+
decoded = self.decode(source_layer, target_layer, feature_activations[source_layer])
|
| 156 |
+
reconstruction += decoded
|
| 157 |
+
|
| 158 |
+
reconstructed_mlp_outputs.append(reconstruction)
|
| 159 |
+
|
| 160 |
+
return feature_activations, reconstructed_mlp_outputs
|
| 161 |
+
|
| 162 |
+
class FeatureVisualizer:
|
| 163 |
+
# A class to visualize and interpret individual features.
|
| 164 |
+
|
| 165 |
+
def __init__(self, tokenizer, cache_dir: Optional[Path] = None):
|
| 166 |
+
self.tokenizer = tokenizer
|
| 167 |
+
self.feature_interpretations: Dict[str, str] = {}
|
| 168 |
+
self.cache_dir = cache_dir
|
| 169 |
+
if self.cache_dir is not None:
|
| 170 |
+
self.cache_dir = Path(self.cache_dir)
|
| 171 |
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
| 172 |
+
self._load_cache()
|
| 173 |
+
|
| 174 |
+
def _cache_file(self) -> Optional[Path]:
|
| 175 |
+
if self.cache_dir is None:
|
| 176 |
+
return None
|
| 177 |
+
return self.cache_dir / "feature_interpretations.json"
|
| 178 |
+
|
| 179 |
+
def _load_cache(self):
|
| 180 |
+
cache_file = self._cache_file()
|
| 181 |
+
if cache_file is None or not cache_file.exists():
|
| 182 |
+
return
|
| 183 |
+
try:
|
| 184 |
+
with open(cache_file, 'r', encoding='utf-8') as f:
|
| 185 |
+
data = json.load(f)
|
| 186 |
+
if isinstance(data, dict):
|
| 187 |
+
self.feature_interpretations.update({str(k): str(v) for k, v in data.items()})
|
| 188 |
+
except Exception as e:
|
| 189 |
+
logger.warning(f"Failed to load feature interpretation cache: {e}")
|
| 190 |
+
|
| 191 |
+
def _save_cache(self):
|
| 192 |
+
cache_file = self._cache_file()
|
| 193 |
+
if cache_file is None:
|
| 194 |
+
return
|
| 195 |
+
try:
|
| 196 |
+
with open(cache_file, 'w', encoding='utf-8') as f:
|
| 197 |
+
json.dump(self.feature_interpretations, f, indent=2)
|
| 198 |
+
except Exception as e:
|
| 199 |
+
logger.warning(f"Failed to save feature interpretation cache: {e}")
|
| 200 |
+
|
| 201 |
+
def visualize_feature(self, feature_idx: int, layer_idx: int,
|
| 202 |
+
activations: torch.Tensor, input_tokens: List[str],
|
| 203 |
+
top_k: int = 10) -> Dict:
|
| 204 |
+
# Creates a visualization for a single feature.
|
| 205 |
+
feature_acts = activations[:, feature_idx].detach().cpu().numpy()
|
| 206 |
+
|
| 207 |
+
# Find the top activating positions.
|
| 208 |
+
top_positions = np.argsort(feature_acts)[-top_k:][::-1]
|
| 209 |
+
|
| 210 |
+
visualization = {
|
| 211 |
+
'feature_idx': feature_idx,
|
| 212 |
+
'layer_idx': layer_idx,
|
| 213 |
+
'max_activation': float(feature_acts.max()),
|
| 214 |
+
'mean_activation': float(feature_acts.mean()),
|
| 215 |
+
'sparsity': float((feature_acts > 0.1).mean()),
|
| 216 |
+
'top_activations': []
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
for pos in top_positions:
|
| 220 |
+
if pos < len(input_tokens):
|
| 221 |
+
visualization['top_activations'].append({
|
| 222 |
+
'token': input_tokens[pos],
|
| 223 |
+
'position': int(pos),
|
| 224 |
+
'activation': float(feature_acts[pos])
|
| 225 |
+
})
|
| 226 |
+
|
| 227 |
+
return visualization
|
| 228 |
+
|
| 229 |
+
def interpret_feature(self, feature_idx: int, layer_idx: int,
|
| 230 |
+
visualization_data: Dict,
|
| 231 |
+
qwen_api_config: Optional[Dict[str, str]] = None) -> str:
|
| 232 |
+
# Interprets a feature based on its top activating tokens.
|
| 233 |
+
top_tokens = [item['token'] for item in visualization_data['top_activations']]
|
| 234 |
+
|
| 235 |
+
cache_key = f"L{layer_idx}_F{feature_idx}"
|
| 236 |
+
|
| 237 |
+
if cache_key in self.feature_interpretations:
|
| 238 |
+
return self.feature_interpretations[cache_key]
|
| 239 |
+
|
| 240 |
+
# Use the Qwen API if it is configured.
|
| 241 |
+
if qwen_api_config and qwen_api_config.get('api_key'):
|
| 242 |
+
feature_name = cache_key
|
| 243 |
+
interpretation = get_feature_interpretation_with_qwen(
|
| 244 |
+
qwen_api_config, top_tokens, feature_name, layer_idx
|
| 245 |
+
)
|
| 246 |
+
else:
|
| 247 |
+
# Use a simple heuristic as a fallback.
|
| 248 |
+
if len(set(top_tokens)) == 1 and top_tokens:
|
| 249 |
+
interpretation = f"Specific token: '{top_tokens[0]}'"
|
| 250 |
+
elif top_tokens and all(token.isalpha() for token in top_tokens):
|
| 251 |
+
interpretation = "Word/alphabetic tokens"
|
| 252 |
+
elif top_tokens and all(token.isdigit() for token in top_tokens):
|
| 253 |
+
interpretation = "Numeric tokens"
|
| 254 |
+
elif top_tokens and all(token in '.,!?;:' for token in top_tokens):
|
| 255 |
+
interpretation = "Punctuation"
|
| 256 |
+
else:
|
| 257 |
+
interpretation = "Mixed/polysemantic feature"
|
| 258 |
+
|
| 259 |
+
self.feature_interpretations[cache_key] = interpretation
|
| 260 |
+
self._save_cache()
|
| 261 |
+
return interpretation
|
| 262 |
+
|
| 263 |
+
class AttributionGraph:
|
| 264 |
+
# A class to construct and analyze attribution graphs.
|
| 265 |
+
|
| 266 |
+
def __init__(self, clt: CrossLayerTranscoder, tokenizer, config: AttributionGraphConfig):
|
| 267 |
+
self.clt = clt
|
| 268 |
+
self.tokenizer = tokenizer
|
| 269 |
+
self.config = config
|
| 270 |
+
self.graph = nx.DiGraph()
|
| 271 |
+
self.node_types = {} # Track node types (feature, embedding, error, output)
|
| 272 |
+
self.edge_weights = {}
|
| 273 |
+
self.feature_metadata: Dict[str, Dict[str, Any]] = {}
|
| 274 |
+
|
| 275 |
+
def compute_virtual_weights(self, source_layer: int, target_layer: int,
|
| 276 |
+
source_feature: int, target_feature: int) -> float:
|
| 277 |
+
# Computes the virtual weight between two features.
|
| 278 |
+
if target_layer <= source_layer:
|
| 279 |
+
return 0.0
|
| 280 |
+
|
| 281 |
+
# Get the encoder and decoder weights.
|
| 282 |
+
encoder_weight = self.clt.encoders[target_layer].weight[target_feature] # [hidden_size]
|
| 283 |
+
|
| 284 |
+
total_weight = 0.0
|
| 285 |
+
for intermediate_layer in range(source_layer, target_layer):
|
| 286 |
+
decoder_key = f"{source_layer}_to_{intermediate_layer}"
|
| 287 |
+
if decoder_key in self.clt.decoders:
|
| 288 |
+
decoder_weight = self.clt.decoders[decoder_key].weight[:, source_feature] # [hidden_size]
|
| 289 |
+
# The virtual weight is inner product
|
| 290 |
+
virtual_weight = torch.dot(decoder_weight, encoder_weight).item()
|
| 291 |
+
total_weight += virtual_weight
|
| 292 |
+
|
| 293 |
+
return total_weight
|
| 294 |
+
|
| 295 |
+
def construct_graph(self, input_tokens: List[str],
|
| 296 |
+
feature_activations: List[torch.Tensor],
|
| 297 |
+
target_token_idx: int = -1) -> nx.DiGraph:
|
| 298 |
+
# Constructs the attribution graph for a prompt.
|
| 299 |
+
self.graph.clear()
|
| 300 |
+
self.node_types.clear()
|
| 301 |
+
self.edge_weights.clear()
|
| 302 |
+
|
| 303 |
+
seq_len = len(input_tokens)
|
| 304 |
+
n_layers = len(feature_activations)
|
| 305 |
+
|
| 306 |
+
# Add embedding nodes for the input tokens.
|
| 307 |
+
for i, token in enumerate(input_tokens):
|
| 308 |
+
node_id = f"emb_{i}_{token}"
|
| 309 |
+
self.graph.add_node(node_id)
|
| 310 |
+
self.node_types[node_id] = "embedding"
|
| 311 |
+
|
| 312 |
+
# Add nodes for the features.
|
| 313 |
+
active_features = {} # Track which features are significantly active
|
| 314 |
+
max_features_per_layer = self.config.graph_max_features_per_layer or 20 # Limit features per layer to prevent explosion
|
| 315 |
+
activation_threshold = self.config.graph_feature_activation_threshold
|
| 316 |
+
edge_weight_threshold = self.config.graph_edge_weight_threshold
|
| 317 |
+
max_edges_per_node_cfg = self.config.graph_max_edges_per_node or 5
|
| 318 |
+
|
| 319 |
+
for layer_idx, features in enumerate(feature_activations):
|
| 320 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 321 |
+
batch_size, seq_len_layer, n_features = features.shape
|
| 322 |
+
|
| 323 |
+
# Get the top activating features for this layer.
|
| 324 |
+
layer_activations = features[0].mean(dim=0) # Average across sequence
|
| 325 |
+
top_features = torch.topk(layer_activations,
|
| 326 |
+
k=min(max_features_per_layer, n_features)).indices
|
| 327 |
+
|
| 328 |
+
for token_pos in range(min(seq_len, seq_len_layer)):
|
| 329 |
+
for feat_idx in top_features:
|
| 330 |
+
activation = features[0, token_pos, feat_idx.item()].item()
|
| 331 |
+
if activation > activation_threshold:
|
| 332 |
+
node_id = f"feat_L{layer_idx}_T{token_pos}_F{feat_idx.item()}"
|
| 333 |
+
self.graph.add_node(node_id)
|
| 334 |
+
self.node_types[node_id] = "feature"
|
| 335 |
+
active_features[node_id] = {
|
| 336 |
+
'layer': layer_idx,
|
| 337 |
+
'token_pos': token_pos,
|
| 338 |
+
'feature_idx': feat_idx.item(),
|
| 339 |
+
'activation': activation
|
| 340 |
+
}
|
| 341 |
+
self.feature_metadata[node_id] = {
|
| 342 |
+
'layer': layer_idx,
|
| 343 |
+
'token_position': token_pos,
|
| 344 |
+
'feature_index': feat_idx.item(),
|
| 345 |
+
'activation': activation,
|
| 346 |
+
'input_token': input_tokens[token_pos] if token_pos < len(input_tokens) else None
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
# Add an output node for the target token.
|
| 350 |
+
output_node = f"output_{target_token_idx}"
|
| 351 |
+
self.graph.add_node(output_node)
|
| 352 |
+
self.node_types[output_node] = "output"
|
| 353 |
+
|
| 354 |
+
# Add edges based on virtual weights and activations.
|
| 355 |
+
feature_nodes = [node for node, type_ in self.node_types.items() if type_ == "feature"]
|
| 356 |
+
print(f" Building attribution graph: {len(feature_nodes)} feature nodes, {len(self.graph.nodes())} total nodes")
|
| 357 |
+
|
| 358 |
+
# Limit the number of edges to compute.
|
| 359 |
+
max_edges_per_node = max(max_edges_per_node_cfg, 1) # Limit connections per node
|
| 360 |
+
|
| 361 |
+
for i, source_node in enumerate(feature_nodes):
|
| 362 |
+
if i % 50 == 0: # Progress indicator
|
| 363 |
+
print(f" Processing node {i+1}/{len(feature_nodes)}")
|
| 364 |
+
|
| 365 |
+
edges_added = 0
|
| 366 |
+
source_info = active_features[source_node]
|
| 367 |
+
source_activation = source_info['activation']
|
| 368 |
+
|
| 369 |
+
# Add edges to other features.
|
| 370 |
+
for target_node in feature_nodes:
|
| 371 |
+
if source_node == target_node or edges_added >= max_edges_per_node:
|
| 372 |
+
continue
|
| 373 |
+
|
| 374 |
+
target_info = active_features[target_node]
|
| 375 |
+
|
| 376 |
+
# Only add edges that go forward in the network.
|
| 377 |
+
if (target_info['layer'] > source_info['layer'] or
|
| 378 |
+
(target_info['layer'] == source_info['layer'] and
|
| 379 |
+
target_info['token_pos'] > source_info['token_pos'])):
|
| 380 |
+
|
| 381 |
+
virtual_weight = self.compute_virtual_weights(
|
| 382 |
+
source_info['layer'], target_info['layer'],
|
| 383 |
+
source_info['feature_idx'], target_info['feature_idx']
|
| 384 |
+
)
|
| 385 |
+
|
| 386 |
+
if abs(virtual_weight) > edge_weight_threshold:
|
| 387 |
+
edge_weight = source_activation * virtual_weight
|
| 388 |
+
self.graph.add_edge(source_node, target_node, weight=edge_weight)
|
| 389 |
+
self.edge_weights[(source_node, target_node)] = edge_weight
|
| 390 |
+
edges_added += 1
|
| 391 |
+
|
| 392 |
+
# Add edges to the output node.
|
| 393 |
+
layer_position = source_info['layer']
|
| 394 |
+
# Allow contributions from all layers, with smaller weights for early layers.
|
| 395 |
+
layer_scale = 0.1 if layer_position >= n_layers - 2 else max(0.05, 0.1 * (layer_position + 1) / n_layers)
|
| 396 |
+
output_weight = source_activation * layer_scale
|
| 397 |
+
if abs(output_weight) > 0:
|
| 398 |
+
self.graph.add_edge(source_node, output_node, weight=output_weight)
|
| 399 |
+
self.edge_weights[(source_node, output_node)] = output_weight
|
| 400 |
+
|
| 401 |
+
# Add edges from embeddings to early features.
|
| 402 |
+
for emb_node in [node for node, type_ in self.node_types.items() if type_ == "embedding"]:
|
| 403 |
+
token_idx = int(emb_node.split('_')[1])
|
| 404 |
+
for feat_node in feature_nodes:
|
| 405 |
+
feat_info = active_features[feat_node]
|
| 406 |
+
if feat_info['layer'] == 0 and feat_info['token_pos'] == token_idx:
|
| 407 |
+
# Direct connection from an embedding to a first-layer feature.
|
| 408 |
+
weight = feat_info['activation'] * 0.5 # Simplified
|
| 409 |
+
self.graph.add_edge(emb_node, feat_node, weight=weight)
|
| 410 |
+
self.edge_weights[(emb_node, feat_node)] = weight
|
| 411 |
+
|
| 412 |
+
return self.graph
|
| 413 |
+
|
| 414 |
+
def prune_graph(self, threshold: float = 0.8) -> nx.DiGraph:
|
| 415 |
+
# Prunes the graph to keep only the most important nodes.
|
| 416 |
+
# Calculate node importance based on edge weights.
|
| 417 |
+
node_importance = defaultdict(float)
|
| 418 |
+
|
| 419 |
+
for (source, target), weight in self.edge_weights.items():
|
| 420 |
+
node_importance[source] += abs(weight)
|
| 421 |
+
node_importance[target] += abs(weight)
|
| 422 |
+
|
| 423 |
+
# Keep the top nodes by importance.
|
| 424 |
+
sorted_nodes = sorted(node_importance.items(), key=lambda x: x[1], reverse=True)
|
| 425 |
+
n_keep = int(len(sorted_nodes) * threshold)
|
| 426 |
+
important_nodes = set([node for node, _ in sorted_nodes[:n_keep]])
|
| 427 |
+
|
| 428 |
+
# Always keep the output and embedding nodes.
|
| 429 |
+
for node, type_ in self.node_types.items():
|
| 430 |
+
if type_ in ["output", "embedding"]:
|
| 431 |
+
important_nodes.add(node)
|
| 432 |
+
|
| 433 |
+
# Create the pruned graph.
|
| 434 |
+
pruned_graph = self.graph.subgraph(important_nodes).copy()
|
| 435 |
+
|
| 436 |
+
return pruned_graph
|
| 437 |
+
|
| 438 |
+
def visualize_graph(self, graph: nx.DiGraph = None, save_path: str = None):
|
| 439 |
+
# Visualizes the attribution graph.
|
| 440 |
+
if graph is None:
|
| 441 |
+
graph = self.graph
|
| 442 |
+
|
| 443 |
+
plt.figure(figsize=(12, 8))
|
| 444 |
+
|
| 445 |
+
# Create a layout for the graph.
|
| 446 |
+
pos = nx.spring_layout(graph, k=1, iterations=50)
|
| 447 |
+
|
| 448 |
+
# Color the nodes by type.
|
| 449 |
+
node_colors = []
|
| 450 |
+
for node in graph.nodes():
|
| 451 |
+
node_type = self.node_types.get(node, "unknown")
|
| 452 |
+
if node_type == "embedding":
|
| 453 |
+
node_colors.append('lightblue')
|
| 454 |
+
elif node_type == "feature":
|
| 455 |
+
node_colors.append('lightgreen')
|
| 456 |
+
elif node_type == "output":
|
| 457 |
+
node_colors.append('orange')
|
| 458 |
+
else:
|
| 459 |
+
node_colors.append('gray')
|
| 460 |
+
|
| 461 |
+
# Draw the nodes.
|
| 462 |
+
nx.draw_networkx_nodes(graph, pos, node_color=node_colors,
|
| 463 |
+
node_size=300, alpha=0.8)
|
| 464 |
+
|
| 465 |
+
# Draw the edges with thickness based on weight.
|
| 466 |
+
edges = graph.edges()
|
| 467 |
+
edge_weights = [abs(self.edge_weights.get((u, v), 0.1)) for u, v in edges]
|
| 468 |
+
max_weight = max(edge_weights) if edge_weights else 1
|
| 469 |
+
edge_widths = [w / max_weight * 3 for w in edge_weights]
|
| 470 |
+
|
| 471 |
+
nx.draw_networkx_edges(graph, pos, width=edge_widths, alpha=0.6,
|
| 472 |
+
edge_color='gray', arrows=True)
|
| 473 |
+
|
| 474 |
+
# Draw the labels.
|
| 475 |
+
nx.draw_networkx_labels(graph, pos, font_size=8)
|
| 476 |
+
|
| 477 |
+
plt.title("Attribution Graph")
|
| 478 |
+
plt.axis('off')
|
| 479 |
+
|
| 480 |
+
if save_path:
|
| 481 |
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
| 482 |
+
plt.show()
|
| 483 |
+
|
| 484 |
+
class PerturbationExperiments:
|
| 485 |
+
# Conducts perturbation experiments to validate hypotheses.
|
| 486 |
+
|
| 487 |
+
def __init__(self, model, clt: CrossLayerTranscoder, tokenizer):
|
| 488 |
+
self.model = model
|
| 489 |
+
self.clt = clt
|
| 490 |
+
self.tokenizer = tokenizer
|
| 491 |
+
self._transformer_blocks: Optional[List[nn.Module]] = None
|
| 492 |
+
|
| 493 |
+
def _get_transformer_blocks(self) -> List[nn.Module]:
|
| 494 |
+
if self._transformer_blocks is not None:
|
| 495 |
+
return self._transformer_blocks
|
| 496 |
+
|
| 497 |
+
n_layers = getattr(self.model.config, "num_hidden_layers", None)
|
| 498 |
+
if n_layers is None:
|
| 499 |
+
raise ValueError("Model config does not expose num_hidden_layers; cannot resolve transformer blocks.")
|
| 500 |
+
|
| 501 |
+
candidate_lists: List[Tuple[str, nn.ModuleList]] = []
|
| 502 |
+
for name, module in self.model.named_modules():
|
| 503 |
+
if isinstance(module, nn.ModuleList) and len(module) == n_layers:
|
| 504 |
+
candidate_lists.append((name, module))
|
| 505 |
+
|
| 506 |
+
if not candidate_lists:
|
| 507 |
+
raise ValueError("Unable to locate transformer block ModuleList in model.")
|
| 508 |
+
|
| 509 |
+
# Prefer names that look like transformer blocks.
|
| 510 |
+
def _score(name: str) -> Tuple[int, str]:
|
| 511 |
+
preferred_suffixes = ("layers", "blocks", "h")
|
| 512 |
+
for idx, suffix in enumerate(preferred_suffixes):
|
| 513 |
+
if name.endswith(suffix):
|
| 514 |
+
return (idx, name)
|
| 515 |
+
return (len(preferred_suffixes), name)
|
| 516 |
+
|
| 517 |
+
selected_name, selected_list = sorted(candidate_lists, key=lambda item: _score(item[0]))[0]
|
| 518 |
+
self._transformer_blocks = list(selected_list)
|
| 519 |
+
logger.debug(f"Resolved transformer blocks from ModuleList '{selected_name}'.")
|
| 520 |
+
return self._transformer_blocks
|
| 521 |
+
|
| 522 |
+
def _format_top_tokens(self, top_tokens: torch.return_types.topk) -> List[Tuple[str, float]]:
|
| 523 |
+
return [
|
| 524 |
+
(self.tokenizer.decode([idx]), prob.item())
|
| 525 |
+
for idx, prob in zip(top_tokens.indices, top_tokens.values)
|
| 526 |
+
]
|
| 527 |
+
|
| 528 |
+
def _prepare_inputs(self, input_text: str, top_k: int) -> Dict[str, Any]:
|
| 529 |
+
if torch.backends.mps.is_available():
|
| 530 |
+
torch.mps.empty_cache()
|
| 531 |
+
|
| 532 |
+
device = next(self.model.parameters()).device
|
| 533 |
+
inputs = self.tokenizer(
|
| 534 |
+
input_text,
|
| 535 |
+
return_tensors="pt",
|
| 536 |
+
padding=True,
|
| 537 |
+
truncation=True,
|
| 538 |
+
max_length=512
|
| 539 |
+
)
|
| 540 |
+
if inputs["input_ids"].size(0) != 1:
|
| 541 |
+
raise ValueError("Perturbation experiments currently support only batch size 1.")
|
| 542 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 543 |
+
|
| 544 |
+
with torch.no_grad():
|
| 545 |
+
baseline_outputs = self.model(**inputs, output_hidden_states=True, return_dict=True)
|
| 546 |
+
|
| 547 |
+
baseline_logits = baseline_outputs.logits[0]
|
| 548 |
+
target_position = baseline_logits.size(0) - 1
|
| 549 |
+
baseline_last_token_logits = baseline_logits[target_position]
|
| 550 |
+
baseline_probs = F.softmax(baseline_last_token_logits, dim=-1)
|
| 551 |
+
baseline_top_tokens = torch.topk(baseline_probs, k=top_k)
|
| 552 |
+
|
| 553 |
+
hidden_states: List[torch.Tensor] = list(baseline_outputs.hidden_states[1:])
|
| 554 |
+
with torch.no_grad():
|
| 555 |
+
feature_activations, _ = self.clt(hidden_states)
|
| 556 |
+
|
| 557 |
+
return {
|
| 558 |
+
'inputs': inputs,
|
| 559 |
+
'baseline_outputs': baseline_outputs,
|
| 560 |
+
'baseline_logits': baseline_logits,
|
| 561 |
+
'baseline_last_token_logits': baseline_last_token_logits,
|
| 562 |
+
'baseline_probs': baseline_probs,
|
| 563 |
+
'baseline_top_tokens': baseline_top_tokens,
|
| 564 |
+
'target_position': target_position,
|
| 565 |
+
'hidden_states': hidden_states,
|
| 566 |
+
'feature_activations': feature_activations,
|
| 567 |
+
'default_target_token_id': baseline_top_tokens.indices[0].item()
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
def _compute_feature_contributions(
|
| 571 |
+
self,
|
| 572 |
+
feature_activations: List[torch.Tensor],
|
| 573 |
+
feature_set: List[Tuple[int, int]]
|
| 574 |
+
) -> Dict[int, torch.Tensor]:
|
| 575 |
+
contributions: Dict[int, torch.Tensor] = {}
|
| 576 |
+
with torch.no_grad():
|
| 577 |
+
for layer_idx, feature_idx in feature_set:
|
| 578 |
+
if layer_idx >= len(feature_activations):
|
| 579 |
+
continue
|
| 580 |
+
features = feature_activations[layer_idx]
|
| 581 |
+
if feature_idx >= features.size(-1):
|
| 582 |
+
continue
|
| 583 |
+
feature_values = features[:, :, feature_idx].detach()
|
| 584 |
+
|
| 585 |
+
for dest_layer in range(layer_idx, self.clt.n_layers):
|
| 586 |
+
decoder_key = f"{layer_idx}_to_{dest_layer}"
|
| 587 |
+
if decoder_key not in self.clt.decoders:
|
| 588 |
+
continue
|
| 589 |
+
decoder = self.clt.decoders[decoder_key]
|
| 590 |
+
weight_column = decoder.weight[:, feature_idx]
|
| 591 |
+
contrib = torch.einsum('bs,h->bsh', feature_values, weight_column).detach()
|
| 592 |
+
if dest_layer in contributions:
|
| 593 |
+
contributions[dest_layer] += contrib
|
| 594 |
+
else:
|
| 595 |
+
contributions[dest_layer] = contrib
|
| 596 |
+
return contributions
|
| 597 |
+
|
| 598 |
+
def _run_with_hooks(
|
| 599 |
+
self,
|
| 600 |
+
inputs: Dict[str, torch.Tensor],
|
| 601 |
+
contributions: Dict[int, torch.Tensor],
|
| 602 |
+
intervention_strength: float
|
| 603 |
+
):
|
| 604 |
+
blocks = self._get_transformer_blocks()
|
| 605 |
+
handles: List[Any] = []
|
| 606 |
+
|
| 607 |
+
def _make_hook(cached_contrib: torch.Tensor):
|
| 608 |
+
def hook(module, module_input, module_output):
|
| 609 |
+
if isinstance(module_output, torch.Tensor):
|
| 610 |
+
target_tensor = module_output
|
| 611 |
+
elif isinstance(module_output, (tuple, list)):
|
| 612 |
+
target_tensor = module_output[0]
|
| 613 |
+
elif hasattr(module_output, "last_hidden_state"):
|
| 614 |
+
target_tensor = module_output.last_hidden_state
|
| 615 |
+
else:
|
| 616 |
+
raise TypeError(
|
| 617 |
+
f"Unsupported module output type '{type(module_output)}' for perturbation hook."
|
| 618 |
+
)
|
| 619 |
+
|
| 620 |
+
tensor_contrib = cached_contrib.to(target_tensor.device).to(target_tensor.dtype)
|
| 621 |
+
scaled = intervention_strength * tensor_contrib
|
| 622 |
+
|
| 623 |
+
if isinstance(module_output, torch.Tensor):
|
| 624 |
+
return module_output - scaled
|
| 625 |
+
elif isinstance(module_output, tuple):
|
| 626 |
+
modified = module_output[0] - scaled
|
| 627 |
+
return (modified,) + tuple(module_output[1:])
|
| 628 |
+
elif isinstance(module_output, list):
|
| 629 |
+
modified = [module_output[0] - scaled, *module_output[1:]]
|
| 630 |
+
return modified
|
| 631 |
+
else:
|
| 632 |
+
module_output.last_hidden_state = module_output.last_hidden_state - scaled
|
| 633 |
+
return module_output
|
| 634 |
+
return hook
|
| 635 |
+
|
| 636 |
+
try:
|
| 637 |
+
for dest_layer, contrib in contributions.items():
|
| 638 |
+
if dest_layer >= len(blocks):
|
| 639 |
+
continue
|
| 640 |
+
handles.append(blocks[dest_layer].register_forward_hook(_make_hook(contrib)))
|
| 641 |
+
|
| 642 |
+
with torch.no_grad():
|
| 643 |
+
outputs = self.model(**inputs, output_hidden_states=True, return_dict=True)
|
| 644 |
+
finally:
|
| 645 |
+
for handle in handles:
|
| 646 |
+
handle.remove()
|
| 647 |
+
|
| 648 |
+
return outputs
|
| 649 |
+
|
| 650 |
+
def feature_set_ablation_experiment(
|
| 651 |
+
self,
|
| 652 |
+
input_text: str,
|
| 653 |
+
feature_set: List[Tuple[int, int]],
|
| 654 |
+
intervention_strength: float = 5.0,
|
| 655 |
+
target_token_id: Optional[int] = None,
|
| 656 |
+
top_k: int = 5,
|
| 657 |
+
ablation_label: str = "feature_set",
|
| 658 |
+
extra_metadata: Optional[Dict[str, Any]] = None
|
| 659 |
+
) -> Dict[str, Any]:
|
| 660 |
+
try:
|
| 661 |
+
baseline_data = self._prepare_inputs(input_text, top_k)
|
| 662 |
+
if target_token_id is None:
|
| 663 |
+
target_token_id = baseline_data['default_target_token_id']
|
| 664 |
+
|
| 665 |
+
feature_set_normalized = [
|
| 666 |
+
(int(layer_idx), int(feature_idx)) for layer_idx, feature_idx in feature_set
|
| 667 |
+
]
|
| 668 |
+
contributions = self._compute_feature_contributions(
|
| 669 |
+
baseline_data['feature_activations'],
|
| 670 |
+
feature_set_normalized
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
baseline_probs = baseline_data['baseline_probs']
|
| 674 |
+
baseline_top_tokens = baseline_data['baseline_top_tokens']
|
| 675 |
+
baseline_last_token_logits = baseline_data['baseline_last_token_logits']
|
| 676 |
+
target_position = baseline_data['target_position']
|
| 677 |
+
hidden_states = baseline_data['hidden_states']
|
| 678 |
+
|
| 679 |
+
baseline_prob = baseline_probs[target_token_id].item()
|
| 680 |
+
baseline_logit = baseline_last_token_logits[target_token_id].item()
|
| 681 |
+
baseline_summary = {
|
| 682 |
+
'baseline_top_tokens': self._format_top_tokens(baseline_top_tokens),
|
| 683 |
+
'baseline_probability': baseline_prob,
|
| 684 |
+
'baseline_logit': baseline_logit
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
if not contributions:
|
| 688 |
+
result = {
|
| 689 |
+
**baseline_summary,
|
| 690 |
+
'ablated_top_tokens': baseline_summary['baseline_top_tokens'],
|
| 691 |
+
'ablated_probability': baseline_prob,
|
| 692 |
+
'ablated_logit': baseline_logit,
|
| 693 |
+
'probability_change': 0.0,
|
| 694 |
+
'logit_change': 0.0,
|
| 695 |
+
'kl_divergence': 0.0,
|
| 696 |
+
'entropy_change': 0.0,
|
| 697 |
+
'hidden_state_delta_norm': 0.0,
|
| 698 |
+
'hidden_state_relative_change': 0.0,
|
| 699 |
+
'ablation_flips_top_prediction': False,
|
| 700 |
+
'feature_set': [
|
| 701 |
+
{'layer': layer_idx, 'feature': feature_idx}
|
| 702 |
+
for layer_idx, feature_idx in feature_set_normalized
|
| 703 |
+
],
|
| 704 |
+
'feature_set_size': len(feature_set_normalized),
|
| 705 |
+
'intervention_strength': intervention_strength,
|
| 706 |
+
'target_token_id': target_token_id,
|
| 707 |
+
'target_token': self.tokenizer.decode([target_token_id]),
|
| 708 |
+
'contributing_layers': [],
|
| 709 |
+
'ablation_applied': False,
|
| 710 |
+
'ablation_type': ablation_label,
|
| 711 |
+
'warning': 'no_contributions_found'
|
| 712 |
+
}
|
| 713 |
+
if extra_metadata:
|
| 714 |
+
result.update(extra_metadata)
|
| 715 |
+
return result
|
| 716 |
+
|
| 717 |
+
ablated_outputs = self._run_with_hooks(
|
| 718 |
+
baseline_data['inputs'],
|
| 719 |
+
contributions,
|
| 720 |
+
intervention_strength
|
| 721 |
+
)
|
| 722 |
+
|
| 723 |
+
ablated_logits = ablated_outputs.logits[0, target_position]
|
| 724 |
+
ablated_probs = F.softmax(ablated_logits, dim=-1)
|
| 725 |
+
ablated_top_tokens = torch.topk(ablated_probs, k=top_k)
|
| 726 |
+
|
| 727 |
+
ablated_prob = ablated_probs[target_token_id].item()
|
| 728 |
+
ablated_logit = ablated_logits[target_token_id].item()
|
| 729 |
+
|
| 730 |
+
epsilon = 1e-9
|
| 731 |
+
kl_divergence = torch.sum(
|
| 732 |
+
baseline_probs * (torch.log(baseline_probs + epsilon) - torch.log(ablated_probs + epsilon))
|
| 733 |
+
).item()
|
| 734 |
+
if not np.isfinite(kl_divergence):
|
| 735 |
+
kl_divergence = 0.0
|
| 736 |
+
|
| 737 |
+
entropy_baseline = -(baseline_probs * torch.log(baseline_probs + epsilon)).sum().item()
|
| 738 |
+
entropy_ablated = -(ablated_probs * torch.log(ablated_probs + epsilon)).sum().item()
|
| 739 |
+
entropy_change = entropy_ablated - entropy_baseline
|
| 740 |
+
if not np.isfinite(entropy_change):
|
| 741 |
+
entropy_change = 0.0
|
| 742 |
+
|
| 743 |
+
baseline_hidden = hidden_states[-1][:, target_position, :]
|
| 744 |
+
ablated_hidden = ablated_outputs.hidden_states[-1][:, target_position, :]
|
| 745 |
+
hidden_delta_norm = torch.norm(baseline_hidden - ablated_hidden, dim=-1).item()
|
| 746 |
+
hidden_baseline_norm = torch.norm(baseline_hidden, dim=-1).item()
|
| 747 |
+
hidden_relative_change = hidden_delta_norm / (hidden_baseline_norm + 1e-9)
|
| 748 |
+
|
| 749 |
+
result = {
|
| 750 |
+
**baseline_summary,
|
| 751 |
+
'ablated_top_tokens': self._format_top_tokens(ablated_top_tokens),
|
| 752 |
+
'ablated_probability': ablated_prob,
|
| 753 |
+
'ablated_logit': ablated_logit,
|
| 754 |
+
'probability_change': baseline_prob - ablated_prob,
|
| 755 |
+
'logit_change': baseline_logit - ablated_logit,
|
| 756 |
+
'kl_divergence': kl_divergence,
|
| 757 |
+
'entropy_change': entropy_change,
|
| 758 |
+
'hidden_state_delta_norm': hidden_delta_norm,
|
| 759 |
+
'hidden_state_relative_change': hidden_relative_change,
|
| 760 |
+
'ablation_flips_top_prediction': bool(
|
| 761 |
+
baseline_top_tokens.indices[0].item() != ablated_top_tokens.indices[0].item()
|
| 762 |
+
),
|
| 763 |
+
'feature_set': [
|
| 764 |
+
{'layer': layer_idx, 'feature': feature_idx}
|
| 765 |
+
for layer_idx, feature_idx in feature_set_normalized
|
| 766 |
+
],
|
| 767 |
+
'feature_set_size': len(feature_set_normalized),
|
| 768 |
+
'intervention_strength': intervention_strength,
|
| 769 |
+
'target_token_id': target_token_id,
|
| 770 |
+
'target_token': self.tokenizer.decode([target_token_id]),
|
| 771 |
+
'contributing_layers': sorted(list(contributions.keys())),
|
| 772 |
+
'ablation_applied': True,
|
| 773 |
+
'ablation_type': ablation_label
|
| 774 |
+
}
|
| 775 |
+
if extra_metadata:
|
| 776 |
+
result.update(extra_metadata)
|
| 777 |
+
return result
|
| 778 |
+
|
| 779 |
+
except Exception as e:
|
| 780 |
+
logger.warning(f"Perturbation experiment failed: {e}")
|
| 781 |
+
return {
|
| 782 |
+
'baseline_top_tokens': [],
|
| 783 |
+
'ablated_top_tokens': [],
|
| 784 |
+
'feature_set': [
|
| 785 |
+
{'layer': layer_idx, 'feature': feature_idx}
|
| 786 |
+
for layer_idx, feature_idx in feature_set
|
| 787 |
+
],
|
| 788 |
+
'feature_set_size': len(feature_set),
|
| 789 |
+
'intervention_strength': intervention_strength,
|
| 790 |
+
'probability_change': 0.0,
|
| 791 |
+
'logit_change': 0.0,
|
| 792 |
+
'kl_divergence': 0.0,
|
| 793 |
+
'entropy_change': 0.0,
|
| 794 |
+
'hidden_state_delta_norm': 0.0,
|
| 795 |
+
'hidden_state_relative_change': 0.0,
|
| 796 |
+
'ablation_flips_top_prediction': False,
|
| 797 |
+
'ablation_applied': False,
|
| 798 |
+
'ablation_type': ablation_label,
|
| 799 |
+
'error': str(e)
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
def feature_ablation_experiment(
|
| 803 |
+
self,
|
| 804 |
+
input_text: str,
|
| 805 |
+
target_layer: int,
|
| 806 |
+
target_feature: int,
|
| 807 |
+
intervention_strength: float = 5.0,
|
| 808 |
+
target_token_id: Optional[int] = None,
|
| 809 |
+
top_k: int = 5,
|
| 810 |
+
) -> Dict[str, Any]:
|
| 811 |
+
return self.feature_set_ablation_experiment(
|
| 812 |
+
input_text=input_text,
|
| 813 |
+
feature_set=[(target_layer, target_feature)],
|
| 814 |
+
intervention_strength=intervention_strength,
|
| 815 |
+
target_token_id=target_token_id,
|
| 816 |
+
top_k=top_k,
|
| 817 |
+
ablation_label="targeted_feature"
|
| 818 |
+
)
|
| 819 |
+
|
| 820 |
+
def random_feature_ablation_experiment(
|
| 821 |
+
self,
|
| 822 |
+
input_text: str,
|
| 823 |
+
num_features: int = 1,
|
| 824 |
+
intervention_strength: float = 5.0,
|
| 825 |
+
target_token_id: Optional[int] = None,
|
| 826 |
+
top_k: int = 5,
|
| 827 |
+
seed: Optional[int] = None
|
| 828 |
+
) -> Dict[str, Any]:
|
| 829 |
+
rng = random.Random(seed)
|
| 830 |
+
num_features = max(1, int(num_features))
|
| 831 |
+
feature_set: List[Tuple[int, int]] = []
|
| 832 |
+
for _ in range(num_features):
|
| 833 |
+
layer_idx = rng.randrange(self.clt.n_layers)
|
| 834 |
+
feature_idx = rng.randrange(self.clt.n_features)
|
| 835 |
+
feature_set.append((layer_idx, feature_idx))
|
| 836 |
+
|
| 837 |
+
result = self.feature_set_ablation_experiment(
|
| 838 |
+
input_text=input_text,
|
| 839 |
+
feature_set=feature_set,
|
| 840 |
+
intervention_strength=intervention_strength,
|
| 841 |
+
target_token_id=target_token_id,
|
| 842 |
+
top_k=top_k,
|
| 843 |
+
ablation_label="random_baseline",
|
| 844 |
+
extra_metadata={'random_seed': seed}
|
| 845 |
+
)
|
| 846 |
+
return result
|
| 847 |
+
|
| 848 |
+
class AttributionGraphsPipeline:
|
| 849 |
+
# The main pipeline for the attribution graph analysis.
|
| 850 |
+
|
| 851 |
+
def __init__(self, config: AttributionGraphConfig):
|
| 852 |
+
self.config = config
|
| 853 |
+
self.device = torch.device(config.device)
|
| 854 |
+
|
| 855 |
+
# Load the model and tokenizer.
|
| 856 |
+
logger.info(f"Loading OLMo2 7B model from {config.model_path}")
|
| 857 |
+
self.tokenizer = AutoTokenizer.from_pretrained(config.model_path)
|
| 858 |
+
|
| 859 |
+
# Configure model loading based on the device.
|
| 860 |
+
if "mps" in config.device:
|
| 861 |
+
# MPS supports float16 but not device_map.
|
| 862 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 863 |
+
config.model_path,
|
| 864 |
+
torch_dtype=torch.float16,
|
| 865 |
+
device_map=None
|
| 866 |
+
).to(self.device)
|
| 867 |
+
elif "cuda" in config.device:
|
| 868 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 869 |
+
config.model_path,
|
| 870 |
+
torch_dtype=torch.float16,
|
| 871 |
+
device_map="auto"
|
| 872 |
+
)
|
| 873 |
+
else:
|
| 874 |
+
# CPU
|
| 875 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 876 |
+
config.model_path,
|
| 877 |
+
torch_dtype=torch.float32,
|
| 878 |
+
device_map=None
|
| 879 |
+
).to(self.device)
|
| 880 |
+
|
| 881 |
+
if self.tokenizer.pad_token is None:
|
| 882 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 883 |
+
|
| 884 |
+
# Initialize the CLT.
|
| 885 |
+
model_config = self.model.config.to_dict()
|
| 886 |
+
self.clt = CrossLayerTranscoder(model_config, config).to(self.device)
|
| 887 |
+
|
| 888 |
+
# Initialize the other components.
|
| 889 |
+
# cache_dir = Path(RESULTS_DIR) / "feature_interpretations_cache"
|
| 890 |
+
# Disable persistent caching to ensure interpretations are prompt-specific and not reused from other contexts.
|
| 891 |
+
self.feature_visualizer = FeatureVisualizer(self.tokenizer, cache_dir=None)
|
| 892 |
+
self.attribution_graph = AttributionGraph(self.clt, self.tokenizer, config)
|
| 893 |
+
self.perturbation_experiments = PerturbationExperiments(self.model, self.clt, self.tokenizer)
|
| 894 |
+
|
| 895 |
+
logger.info("Attribution Graphs Pipeline initialized successfully")
|
| 896 |
+
|
| 897 |
+
def train_clt(self, training_texts: List[str]) -> Dict:
|
| 898 |
+
# Trains the Cross-Layer Transcoder.
|
| 899 |
+
logger.info("Starting CLT training...")
|
| 900 |
+
|
| 901 |
+
optimizer = torch.optim.Adam(self.clt.parameters(), lr=self.config.learning_rate)
|
| 902 |
+
|
| 903 |
+
training_stats = {
|
| 904 |
+
'reconstruction_losses': [],
|
| 905 |
+
'sparsity_losses': [],
|
| 906 |
+
'total_losses': []
|
| 907 |
+
}
|
| 908 |
+
|
| 909 |
+
for step in tqdm(range(self.config.training_steps), desc="Training CLT"):
|
| 910 |
+
# Sample a batch of texts.
|
| 911 |
+
batch_texts = np.random.choice(training_texts, size=self.config.batch_size)
|
| 912 |
+
|
| 913 |
+
total_loss = 0.0
|
| 914 |
+
total_recon_loss = 0.0
|
| 915 |
+
total_sparsity_loss = 0.0
|
| 916 |
+
|
| 917 |
+
for text in batch_texts:
|
| 918 |
+
# Tokenize the text.
|
| 919 |
+
inputs = self.tokenizer(text, return_tensors="pt", max_length=self.config.max_seq_length,
|
| 920 |
+
truncation=True, padding=True).to(self.device)
|
| 921 |
+
|
| 922 |
+
# Get the model activations.
|
| 923 |
+
with torch.no_grad():
|
| 924 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 925 |
+
hidden_states = outputs.hidden_states[1:]
|
| 926 |
+
|
| 927 |
+
# Forward pass through the CLT.
|
| 928 |
+
feature_activations, reconstructed_outputs = self.clt(hidden_states)
|
| 929 |
+
|
| 930 |
+
# Compute the reconstruction loss.
|
| 931 |
+
recon_loss = 0.0
|
| 932 |
+
for i, (target, pred) in enumerate(zip(hidden_states, reconstructed_outputs)):
|
| 933 |
+
recon_loss += F.mse_loss(pred, target)
|
| 934 |
+
|
| 935 |
+
# Compute the sparsity loss.
|
| 936 |
+
sparsity_loss = 0.0
|
| 937 |
+
for features in feature_activations:
|
| 938 |
+
sparsity_loss += torch.mean(torch.tanh(self.config.sparsity_lambda * features))
|
| 939 |
+
|
| 940 |
+
# Total loss.
|
| 941 |
+
loss = (self.config.reconstruction_loss_weight * recon_loss +
|
| 942 |
+
self.config.sparsity_lambda * sparsity_loss)
|
| 943 |
+
|
| 944 |
+
total_loss += loss
|
| 945 |
+
total_recon_loss += recon_loss
|
| 946 |
+
total_sparsity_loss += sparsity_loss
|
| 947 |
+
|
| 948 |
+
# Average the losses.
|
| 949 |
+
total_loss /= self.config.batch_size
|
| 950 |
+
total_recon_loss /= self.config.batch_size
|
| 951 |
+
total_sparsity_loss /= self.config.batch_size
|
| 952 |
+
|
| 953 |
+
# Backward pass.
|
| 954 |
+
optimizer.zero_grad()
|
| 955 |
+
total_loss.backward()
|
| 956 |
+
optimizer.step()
|
| 957 |
+
|
| 958 |
+
# Log the progress.
|
| 959 |
+
training_stats['total_losses'].append(total_loss.item())
|
| 960 |
+
training_stats['reconstruction_losses'].append(total_recon_loss.item())
|
| 961 |
+
training_stats['sparsity_losses'].append(total_sparsity_loss.item())
|
| 962 |
+
|
| 963 |
+
if step % 100 == 0:
|
| 964 |
+
logger.info(f"Step {step}: Total Loss = {total_loss.item():.4f}, "
|
| 965 |
+
f"Recon Loss = {total_recon_loss.item():.4f}, "
|
| 966 |
+
f"Sparsity Loss = {total_sparsity_loss.item():.4f}")
|
| 967 |
+
|
| 968 |
+
logger.info("CLT training completed")
|
| 969 |
+
return training_stats
|
| 970 |
+
|
| 971 |
+
def analyze_prompt(self, prompt: str, target_token_idx: int = -1) -> Dict:
|
| 972 |
+
# Performs a complete analysis for a single prompt.
|
| 973 |
+
logger.info(f"Analyzing prompt: '{prompt[:50]}...'")
|
| 974 |
+
|
| 975 |
+
# Tokenize the prompt.
|
| 976 |
+
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
|
| 977 |
+
input_tokens = self.tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
|
| 978 |
+
|
| 979 |
+
# Get the model activations.
|
| 980 |
+
with torch.no_grad():
|
| 981 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 982 |
+
hidden_states = outputs.hidden_states[1:]
|
| 983 |
+
|
| 984 |
+
# Forward pass through the CLT.
|
| 985 |
+
feature_activations, reconstructed_outputs = self.clt(hidden_states)
|
| 986 |
+
|
| 987 |
+
logger.info(" > Starting feature visualization and interpretation...")
|
| 988 |
+
feature_visualizations = {}
|
| 989 |
+
for layer_idx, features in enumerate(feature_activations):
|
| 990 |
+
logger.info(f" - Processing Layer {layer_idx}...")
|
| 991 |
+
layer_viz = {}
|
| 992 |
+
# Analyze the top features for this layer.
|
| 993 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 994 |
+
feature_importance = torch.mean(features, dim=(0, 1)) # Average over batch and sequence
|
| 995 |
+
top_features = torch.topk(feature_importance, k=min(5, feature_importance.size(0))).indices
|
| 996 |
+
|
| 997 |
+
for feat_idx in top_features:
|
| 998 |
+
viz = self.feature_visualizer.visualize_feature(
|
| 999 |
+
feat_idx.item(), layer_idx, features[0], input_tokens
|
| 1000 |
+
)
|
| 1001 |
+
interpretation = self.feature_visualizer.interpret_feature(
|
| 1002 |
+
feat_idx.item(), layer_idx, viz, self.config.qwen_api_config
|
| 1003 |
+
)
|
| 1004 |
+
viz['interpretation'] = interpretation
|
| 1005 |
+
layer_viz[f"feature_{feat_idx.item()}"] = viz
|
| 1006 |
+
|
| 1007 |
+
feature_visualizations[f"layer_{layer_idx}"] = layer_viz
|
| 1008 |
+
|
| 1009 |
+
# Construct the attribution graph.
|
| 1010 |
+
graph = self.attribution_graph.construct_graph(
|
| 1011 |
+
input_tokens, feature_activations, target_token_idx
|
| 1012 |
+
)
|
| 1013 |
+
|
| 1014 |
+
# Prune the graph.
|
| 1015 |
+
pruned_graph = self.attribution_graph.prune_graph(self.config.pruning_threshold)
|
| 1016 |
+
|
| 1017 |
+
# Analyze the most important paths.
|
| 1018 |
+
important_paths = []
|
| 1019 |
+
if len(pruned_graph.nodes()) > 0:
|
| 1020 |
+
# Find paths from embeddings to the output.
|
| 1021 |
+
embedding_nodes = [node for node, type_ in self.attribution_graph.node_types.items()
|
| 1022 |
+
if type_ == "embedding" and node in pruned_graph]
|
| 1023 |
+
output_nodes = [node for node, type_ in self.attribution_graph.node_types.items()
|
| 1024 |
+
if type_ == "output" and node in pruned_graph]
|
| 1025 |
+
|
| 1026 |
+
for emb_node in embedding_nodes[:3]: # Top 3 embedding nodes
|
| 1027 |
+
for out_node in output_nodes:
|
| 1028 |
+
try:
|
| 1029 |
+
paths = list(nx.all_simple_paths(pruned_graph, emb_node, out_node, cutoff=5))
|
| 1030 |
+
for path in paths[:2]: # Top 2 paths
|
| 1031 |
+
path_weight = 1.0
|
| 1032 |
+
for i in range(len(path) - 1):
|
| 1033 |
+
edge_weight = self.attribution_graph.edge_weights.get(
|
| 1034 |
+
(path[i], path[i+1]), 0.0
|
| 1035 |
+
)
|
| 1036 |
+
path_weight *= abs(edge_weight)
|
| 1037 |
+
|
| 1038 |
+
important_paths.append({
|
| 1039 |
+
'path': path,
|
| 1040 |
+
'weight': path_weight,
|
| 1041 |
+
'description': self._describe_path(path)
|
| 1042 |
+
})
|
| 1043 |
+
except nx.NetworkXNoPath:
|
| 1044 |
+
continue
|
| 1045 |
+
|
| 1046 |
+
# Sort paths by importance.
|
| 1047 |
+
important_paths.sort(key=lambda x: x['weight'], reverse=True)
|
| 1048 |
+
|
| 1049 |
+
# Run targeted perturbation experiments for highlighted features.
|
| 1050 |
+
targeted_feature_ablation_results: List[Dict[str, Any]] = []
|
| 1051 |
+
max_total_experiments = self.config.max_ablation_experiments
|
| 1052 |
+
per_layer_limit = self.config.ablation_features_per_layer
|
| 1053 |
+
total_run = 0
|
| 1054 |
+
stop_all = False
|
| 1055 |
+
for layer_name, layer_features in feature_visualizations.items():
|
| 1056 |
+
if stop_all:
|
| 1057 |
+
break
|
| 1058 |
+
try:
|
| 1059 |
+
layer_idx = int(layer_name.split('_')[1])
|
| 1060 |
+
except (IndexError, ValueError):
|
| 1061 |
+
logger.warning(f"Unable to parse layer index from key '{layer_name}'. Skipping perturbation experiments for this layer.")
|
| 1062 |
+
continue
|
| 1063 |
+
|
| 1064 |
+
feature_items = list(layer_features.items())
|
| 1065 |
+
if per_layer_limit is not None:
|
| 1066 |
+
feature_items = feature_items[:per_layer_limit]
|
| 1067 |
+
|
| 1068 |
+
for feature_name, feature_payload in feature_items:
|
| 1069 |
+
if max_total_experiments is not None and total_run >= max_total_experiments:
|
| 1070 |
+
stop_all = True
|
| 1071 |
+
break
|
| 1072 |
+
try:
|
| 1073 |
+
feature_idx = int(feature_name.split('_')[1])
|
| 1074 |
+
except (IndexError, ValueError):
|
| 1075 |
+
logger.warning(f"Unable to parse feature index from key '{feature_name}'. Skipping perturbation experiment.")
|
| 1076 |
+
continue
|
| 1077 |
+
|
| 1078 |
+
ablation = self.perturbation_experiments.feature_ablation_experiment(
|
| 1079 |
+
prompt,
|
| 1080 |
+
layer_idx,
|
| 1081 |
+
feature_idx,
|
| 1082 |
+
intervention_strength=self.config.intervention_strength,
|
| 1083 |
+
target_token_id=None,
|
| 1084 |
+
top_k=self.config.ablation_top_k_tokens,
|
| 1085 |
+
)
|
| 1086 |
+
ablation.update({
|
| 1087 |
+
'layer_name': layer_name,
|
| 1088 |
+
'feature_name': feature_name,
|
| 1089 |
+
'feature_interpretation': feature_payload.get('interpretation'),
|
| 1090 |
+
'feature_max_activation': feature_payload.get('max_activation'),
|
| 1091 |
+
})
|
| 1092 |
+
targeted_feature_ablation_results.append(ablation)
|
| 1093 |
+
total_run += 1
|
| 1094 |
+
|
| 1095 |
+
# Random baseline perturbations for comparison.
|
| 1096 |
+
random_baseline_results: List[Dict[str, Any]] = []
|
| 1097 |
+
baseline_trials = self.config.random_baseline_trials
|
| 1098 |
+
if baseline_trials and baseline_trials > 0:
|
| 1099 |
+
num_features = self.config.random_baseline_features or 1
|
| 1100 |
+
for trial_idx in range(baseline_trials):
|
| 1101 |
+
seed = None
|
| 1102 |
+
if self.config.random_baseline_seed is not None:
|
| 1103 |
+
seed = self.config.random_baseline_seed + trial_idx
|
| 1104 |
+
random_result = self.perturbation_experiments.random_feature_ablation_experiment(
|
| 1105 |
+
prompt,
|
| 1106 |
+
num_features=num_features,
|
| 1107 |
+
intervention_strength=self.config.intervention_strength,
|
| 1108 |
+
target_token_id=None,
|
| 1109 |
+
top_k=self.config.ablation_top_k_tokens,
|
| 1110 |
+
seed=seed
|
| 1111 |
+
)
|
| 1112 |
+
random_result['trial_index'] = trial_idx
|
| 1113 |
+
random_baseline_results.append(random_result)
|
| 1114 |
+
|
| 1115 |
+
# Path-level ablations for the most important circuits.
|
| 1116 |
+
path_ablation_results: List[Dict[str, Any]] = []
|
| 1117 |
+
max_paths = self.config.path_ablation_top_k or 0
|
| 1118 |
+
extracted_paths: List[Dict[str, Any]] = []
|
| 1119 |
+
if max_paths > 0 and important_paths:
|
| 1120 |
+
for path_info in important_paths[:max_paths]:
|
| 1121 |
+
feature_set = self._extract_feature_set_from_path(path_info.get('path', []))
|
| 1122 |
+
if not feature_set:
|
| 1123 |
+
continue
|
| 1124 |
+
path_result = self.perturbation_experiments.feature_set_ablation_experiment(
|
| 1125 |
+
prompt,
|
| 1126 |
+
feature_set=feature_set,
|
| 1127 |
+
intervention_strength=self.config.intervention_strength,
|
| 1128 |
+
target_token_id=None,
|
| 1129 |
+
top_k=self.config.ablation_top_k_tokens,
|
| 1130 |
+
ablation_label="path",
|
| 1131 |
+
extra_metadata={
|
| 1132 |
+
'path_nodes': path_info.get('path'),
|
| 1133 |
+
'path_description': path_info.get('description'),
|
| 1134 |
+
'path_weight': path_info.get('weight')
|
| 1135 |
+
}
|
| 1136 |
+
)
|
| 1137 |
+
path_ablation_results.append(path_result)
|
| 1138 |
+
enriched_path_info = path_info.copy()
|
| 1139 |
+
enriched_path_info['feature_set'] = feature_set
|
| 1140 |
+
extracted_paths.append(enriched_path_info)
|
| 1141 |
+
|
| 1142 |
+
random_path_baseline_results: List[Dict[str, Any]] = []
|
| 1143 |
+
path_baseline_trials = self.config.random_path_baseline_trials
|
| 1144 |
+
if path_baseline_trials and path_baseline_trials > 0 and extracted_paths:
|
| 1145 |
+
rng = random.Random(self.config.random_baseline_seed)
|
| 1146 |
+
available_nodes = [
|
| 1147 |
+
data for data in self.attribution_graph.node_types.items()
|
| 1148 |
+
if data[1] == "feature"
|
| 1149 |
+
]
|
| 1150 |
+
for trial in range(path_baseline_trials):
|
| 1151 |
+
selected_path = extracted_paths[min(trial % len(extracted_paths), len(extracted_paths) - 1)]
|
| 1152 |
+
target_length = len(selected_path.get('feature_set', []))
|
| 1153 |
+
source_layers = [layer for layer, _ in selected_path.get('feature_set', [])]
|
| 1154 |
+
min_layer = min(source_layers) if source_layers else 0
|
| 1155 |
+
max_layer = max(source_layers) if source_layers else self.clt.n_layers - 1
|
| 1156 |
+
excluded_keys = {
|
| 1157 |
+
(layer, feature)
|
| 1158 |
+
for layer, feature in selected_path.get('feature_set', [])
|
| 1159 |
+
}
|
| 1160 |
+
random_feature_set: List[Tuple[int, int]] = []
|
| 1161 |
+
attempts = 0
|
| 1162 |
+
while len(random_feature_set) < target_length and attempts < target_length * 5:
|
| 1163 |
+
attempts += 1
|
| 1164 |
+
if not available_nodes:
|
| 1165 |
+
break
|
| 1166 |
+
node_name, node_type = rng.choice(available_nodes)
|
| 1167 |
+
metadata = self.attribution_graph.feature_metadata.get(node_name)
|
| 1168 |
+
if metadata is None:
|
| 1169 |
+
continue
|
| 1170 |
+
if metadata['layer'] < min_layer or metadata['layer'] > max_layer:
|
| 1171 |
+
continue
|
| 1172 |
+
key = (metadata['layer'], metadata['feature_index'])
|
| 1173 |
+
if key in excluded_keys:
|
| 1174 |
+
continue
|
| 1175 |
+
if key not in random_feature_set:
|
| 1176 |
+
random_feature_set.append(key)
|
| 1177 |
+
if not random_feature_set:
|
| 1178 |
+
continue
|
| 1179 |
+
if len(random_feature_set) < max(1, target_length):
|
| 1180 |
+
continue
|
| 1181 |
+
random_path_result = self.perturbation_experiments.feature_set_ablation_experiment(
|
| 1182 |
+
prompt,
|
| 1183 |
+
feature_set=random_feature_set,
|
| 1184 |
+
intervention_strength=self.config.intervention_strength,
|
| 1185 |
+
target_token_id=None,
|
| 1186 |
+
top_k=self.config.ablation_top_k_tokens,
|
| 1187 |
+
ablation_label="random_path_baseline",
|
| 1188 |
+
extra_metadata={
|
| 1189 |
+
'trial_index': trial,
|
| 1190 |
+
'sampled_feature_set': random_feature_set,
|
| 1191 |
+
'reference_path_weight': selected_path.get('weight')
|
| 1192 |
+
}
|
| 1193 |
+
)
|
| 1194 |
+
random_path_baseline_results.append(random_path_result)
|
| 1195 |
+
|
| 1196 |
+
targeted_summary = self._summarize_ablation_results(targeted_feature_ablation_results)
|
| 1197 |
+
random_summary = self._summarize_ablation_results(random_baseline_results)
|
| 1198 |
+
path_summary = self._summarize_ablation_results(path_ablation_results)
|
| 1199 |
+
random_path_summary = self._summarize_ablation_results(random_path_baseline_results)
|
| 1200 |
+
summary_statistics = {
|
| 1201 |
+
'targeted': targeted_summary,
|
| 1202 |
+
'random_baseline': random_summary,
|
| 1203 |
+
'path': path_summary,
|
| 1204 |
+
'random_path_baseline': random_path_summary,
|
| 1205 |
+
'target_minus_random_abs_probability_change': targeted_summary.get('avg_abs_probability_change', 0.0) - random_summary.get('avg_abs_probability_change', 0.0),
|
| 1206 |
+
'target_flip_rate_minus_random': targeted_summary.get('flip_rate', 0.0) - random_summary.get('flip_rate', 0.0),
|
| 1207 |
+
'path_minus_random_abs_probability_change': path_summary.get('avg_abs_probability_change', 0.0) - random_path_summary.get('avg_abs_probability_change', 0.0),
|
| 1208 |
+
'path_flip_rate_minus_random': path_summary.get('flip_rate', 0.0) - random_path_summary.get('flip_rate', 0.0)
|
| 1209 |
+
}
|
| 1210 |
+
|
| 1211 |
+
results = {
|
| 1212 |
+
'prompt': prompt,
|
| 1213 |
+
'input_tokens': input_tokens,
|
| 1214 |
+
'feature_visualizations': feature_visualizations,
|
| 1215 |
+
'full_graph_stats': {
|
| 1216 |
+
'n_nodes': len(graph.nodes()),
|
| 1217 |
+
'n_edges': len(graph.edges()),
|
| 1218 |
+
'node_types': dict(self.attribution_graph.node_types)
|
| 1219 |
+
},
|
| 1220 |
+
'pruned_graph_stats': {
|
| 1221 |
+
'n_nodes': len(pruned_graph.nodes()),
|
| 1222 |
+
'n_edges': len(pruned_graph.edges())
|
| 1223 |
+
},
|
| 1224 |
+
'important_paths': important_paths[:5], # Top 5 paths
|
| 1225 |
+
'graph': pruned_graph,
|
| 1226 |
+
'perturbation_experiments': targeted_feature_ablation_results,
|
| 1227 |
+
'random_baseline_experiments': random_baseline_results,
|
| 1228 |
+
'path_ablation_experiments': path_ablation_results,
|
| 1229 |
+
'random_path_baseline_experiments': random_path_baseline_results,
|
| 1230 |
+
'summary_statistics': summary_statistics
|
| 1231 |
+
}
|
| 1232 |
+
|
| 1233 |
+
return results
|
| 1234 |
+
|
| 1235 |
+
def _extract_feature_set_from_path(self, path: List[str]) -> List[Tuple[int, int]]:
|
| 1236 |
+
feature_set: List[Tuple[int, int]] = []
|
| 1237 |
+
seen: Set[Tuple[int, int]] = set()
|
| 1238 |
+
for node in path:
|
| 1239 |
+
if not isinstance(node, str):
|
| 1240 |
+
continue
|
| 1241 |
+
if not node.startswith("feat_"):
|
| 1242 |
+
continue
|
| 1243 |
+
parts = node.split('_')
|
| 1244 |
+
try:
|
| 1245 |
+
layer_str = parts[1] # e.g., "L0"
|
| 1246 |
+
feature_str = parts[3] # e.g., "F123"
|
| 1247 |
+
layer_idx = int(layer_str[1:])
|
| 1248 |
+
feature_idx = int(feature_str[1:])
|
| 1249 |
+
except (IndexError, ValueError):
|
| 1250 |
+
continue
|
| 1251 |
+
key = (layer_idx, feature_idx)
|
| 1252 |
+
if key not in seen:
|
| 1253 |
+
seen.add(key)
|
| 1254 |
+
feature_set.append(key)
|
| 1255 |
+
return feature_set
|
| 1256 |
+
|
| 1257 |
+
def _summarize_ablation_results(self, experiments: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 1258 |
+
summary = {
|
| 1259 |
+
'count': len(experiments),
|
| 1260 |
+
'avg_probability_change': 0.0,
|
| 1261 |
+
'avg_abs_probability_change': 0.0,
|
| 1262 |
+
'std_probability_change': 0.0,
|
| 1263 |
+
'avg_logit_change': 0.0,
|
| 1264 |
+
'avg_abs_logit_change': 0.0,
|
| 1265 |
+
'std_logit_change': 0.0,
|
| 1266 |
+
'avg_kl_divergence': 0.0,
|
| 1267 |
+
'avg_entropy_change': 0.0,
|
| 1268 |
+
'avg_hidden_state_delta_norm': 0.0,
|
| 1269 |
+
'avg_hidden_state_relative_change': 0.0,
|
| 1270 |
+
'flip_rate': 0.0,
|
| 1271 |
+
'count_flipped': 0
|
| 1272 |
+
}
|
| 1273 |
+
if not experiments:
|
| 1274 |
+
return summary
|
| 1275 |
+
|
| 1276 |
+
probability_changes = np.array([exp.get('probability_change', 0.0) for exp in experiments], dtype=float)
|
| 1277 |
+
logit_changes = np.array([exp.get('logit_change', 0.0) for exp in experiments], dtype=float)
|
| 1278 |
+
kl_divergences = np.array([exp.get('kl_divergence', 0.0) for exp in experiments], dtype=float)
|
| 1279 |
+
entropy_changes = np.array([exp.get('entropy_change', 0.0) for exp in experiments], dtype=float)
|
| 1280 |
+
hidden_norms = np.array([exp.get('hidden_state_delta_norm', 0.0) for exp in experiments], dtype=float)
|
| 1281 |
+
hidden_relative = np.array([exp.get('hidden_state_relative_change', 0.0) for exp in experiments], dtype=float)
|
| 1282 |
+
flip_flags = np.array([1.0 if exp.get('ablation_flips_top_prediction') else 0.0 for exp in experiments], dtype=float)
|
| 1283 |
+
|
| 1284 |
+
# Helper to safely compute mean/std ignoring NaNs
|
| 1285 |
+
def safe_mean(arr):
|
| 1286 |
+
with np.errstate(all='ignore'):
|
| 1287 |
+
m = np.nanmean(arr)
|
| 1288 |
+
return float(m) if np.isfinite(m) else 0.0
|
| 1289 |
+
|
| 1290 |
+
def safe_std(arr):
|
| 1291 |
+
with np.errstate(all='ignore'):
|
| 1292 |
+
s = np.nanstd(arr)
|
| 1293 |
+
return float(s) if np.isfinite(s) else 0.0
|
| 1294 |
+
|
| 1295 |
+
summary.update({
|
| 1296 |
+
'avg_probability_change': safe_mean(probability_changes),
|
| 1297 |
+
'avg_abs_probability_change': safe_mean(np.abs(probability_changes)),
|
| 1298 |
+
'std_probability_change': safe_std(probability_changes),
|
| 1299 |
+
'avg_logit_change': safe_mean(logit_changes),
|
| 1300 |
+
'avg_abs_logit_change': safe_mean(np.abs(logit_changes)),
|
| 1301 |
+
'std_logit_change': safe_std(logit_changes),
|
| 1302 |
+
'avg_kl_divergence': safe_mean(kl_divergences),
|
| 1303 |
+
'avg_entropy_change': safe_mean(entropy_changes),
|
| 1304 |
+
'avg_hidden_state_delta_norm': safe_mean(hidden_norms),
|
| 1305 |
+
'avg_hidden_state_relative_change': safe_mean(hidden_relative),
|
| 1306 |
+
'flip_rate': safe_mean(flip_flags),
|
| 1307 |
+
'count_flipped': int(np.round(np.nansum(flip_flags)))
|
| 1308 |
+
})
|
| 1309 |
+
return summary
|
| 1310 |
+
|
| 1311 |
+
def analyze_prompts_batch(self, prompts: List[str]) -> Dict[str, Any]:
|
| 1312 |
+
analyses: Dict[str, Dict[str, Any]] = {}
|
| 1313 |
+
aggregated_targeted: List[Dict[str, Any]] = []
|
| 1314 |
+
aggregated_random: List[Dict[str, Any]] = []
|
| 1315 |
+
aggregated_path: List[Dict[str, Any]] = []
|
| 1316 |
+
|
| 1317 |
+
for idx, prompt in enumerate(prompts):
|
| 1318 |
+
logger.info(f"[Batch Eval] Processing prompt {idx + 1}/{len(prompts)}")
|
| 1319 |
+
analysis = self.analyze_prompt(prompt)
|
| 1320 |
+
key = f"prompt_{idx + 1}"
|
| 1321 |
+
analyses[key] = analysis
|
| 1322 |
+
aggregated_targeted.extend(analysis.get('perturbation_experiments', []))
|
| 1323 |
+
aggregated_random.extend(analysis.get('random_baseline_experiments', []))
|
| 1324 |
+
aggregated_path.extend(analysis.get('path_ablation_experiments', []))
|
| 1325 |
+
|
| 1326 |
+
aggregate_summary = {
|
| 1327 |
+
'targeted': self._summarize_ablation_results(aggregated_targeted),
|
| 1328 |
+
'random_baseline': self._summarize_ablation_results(aggregated_random),
|
| 1329 |
+
'path': self._summarize_ablation_results(aggregated_path),
|
| 1330 |
+
'random_path_baseline': self._summarize_ablation_results(
|
| 1331 |
+
[
|
| 1332 |
+
exp
|
| 1333 |
+
for analysis in analyses.values()
|
| 1334 |
+
for exp in analysis.get('random_path_baseline_experiments', [])
|
| 1335 |
+
]
|
| 1336 |
+
)
|
| 1337 |
+
}
|
| 1338 |
+
aggregate_summary['target_minus_random_abs_probability_change'] = (
|
| 1339 |
+
aggregate_summary['targeted'].get('avg_abs_probability_change', 0.0)
|
| 1340 |
+
- aggregate_summary['random_baseline'].get('avg_abs_probability_change', 0.0)
|
| 1341 |
+
)
|
| 1342 |
+
aggregate_summary['target_flip_rate_minus_random'] = (
|
| 1343 |
+
aggregate_summary['targeted'].get('flip_rate', 0.0)
|
| 1344 |
+
- aggregate_summary['random_baseline'].get('flip_rate', 0.0)
|
| 1345 |
+
)
|
| 1346 |
+
aggregate_summary['path_minus_random_abs_probability_change'] = (
|
| 1347 |
+
aggregate_summary['path'].get('avg_abs_probability_change', 0.0)
|
| 1348 |
+
- aggregate_summary['random_path_baseline'].get('avg_abs_probability_change', 0.0)
|
| 1349 |
+
)
|
| 1350 |
+
aggregate_summary['path_flip_rate_minus_random'] = (
|
| 1351 |
+
aggregate_summary['path'].get('flip_rate', 0.0)
|
| 1352 |
+
- aggregate_summary['random_path_baseline'].get('flip_rate', 0.0)
|
| 1353 |
+
)
|
| 1354 |
+
|
| 1355 |
+
return {
|
| 1356 |
+
'analyses': analyses,
|
| 1357 |
+
'aggregate_summary': aggregate_summary,
|
| 1358 |
+
'prompt_texts': prompts
|
| 1359 |
+
}
|
| 1360 |
+
|
| 1361 |
+
def _describe_path(self, path: List[str]) -> str:
|
| 1362 |
+
# Generates a human-readable description of a path.
|
| 1363 |
+
descriptions = []
|
| 1364 |
+
for node in path:
|
| 1365 |
+
if self.attribution_graph.node_types[node] == "embedding":
|
| 1366 |
+
token = node.split('_')[2]
|
| 1367 |
+
descriptions.append(f"Token '{token}'")
|
| 1368 |
+
elif self.attribution_graph.node_types[node] == "feature":
|
| 1369 |
+
parts = node.split('_')
|
| 1370 |
+
layer = parts[1][1:] # Remove 'L'
|
| 1371 |
+
feature = parts[3][1:] # Remove 'F'
|
| 1372 |
+
# Try to get the interpretation.
|
| 1373 |
+
key = f"L{layer}_F{feature}"
|
| 1374 |
+
interpretation = self.feature_visualizer.feature_interpretations.get(key, "unknown")
|
| 1375 |
+
descriptions.append(f"Feature L{layer}F{feature} ({interpretation})")
|
| 1376 |
+
elif self.attribution_graph.node_types[node] == "output":
|
| 1377 |
+
descriptions.append("Output")
|
| 1378 |
+
|
| 1379 |
+
return " → ".join(descriptions)
|
| 1380 |
+
|
| 1381 |
+
def save_results(self, results: Dict, save_path: str):
|
| 1382 |
+
# Saves the analysis results to a file.
|
| 1383 |
+
serializable_results = copy.deepcopy(results)
|
| 1384 |
+
|
| 1385 |
+
if 'graph' in serializable_results:
|
| 1386 |
+
serializable_results['graph'] = nx.node_link_data(serializable_results['graph'])
|
| 1387 |
+
|
| 1388 |
+
analyses = serializable_results.get('analyses', {})
|
| 1389 |
+
for key, analysis in analyses.items():
|
| 1390 |
+
if 'graph' in analysis:
|
| 1391 |
+
analysis['graph'] = nx.node_link_data(analysis['graph'])
|
| 1392 |
+
|
| 1393 |
+
with open(save_path, 'w') as f:
|
| 1394 |
+
json.dump(serializable_results, f, indent=2, default=str)
|
| 1395 |
+
|
| 1396 |
+
logger.info(f"Results saved to {save_path}")
|
| 1397 |
+
|
| 1398 |
+
def save_clt(self, path: str):
|
| 1399 |
+
# Saves the trained CLT model.
|
| 1400 |
+
torch.save(self.clt.state_dict(), path)
|
| 1401 |
+
logger.info(f"CLT model saved to {path}")
|
| 1402 |
+
|
| 1403 |
+
def load_clt(self, path: str):
|
| 1404 |
+
# Loads a trained CLT model.
|
| 1405 |
+
self.clt.load_state_dict(torch.load(path, map_location=self.device))
|
| 1406 |
+
self.clt.to(self.device)
|
| 1407 |
+
self.clt.eval() # Set the model to evaluation mode
|
| 1408 |
+
logger.info(f"Loaded CLT model from {path}")
|
| 1409 |
+
|
| 1410 |
+
# --- Configuration ---
|
| 1411 |
+
MAX_SEQ_LEN = 256
|
| 1412 |
+
N_FEATURES_PER_LAYER = 512
|
| 1413 |
+
TRAINING_STEPS = 2500
|
| 1414 |
+
BATCH_SIZE = 64
|
| 1415 |
+
LEARNING_RATE = 1e-3
|
| 1416 |
+
|
| 1417 |
+
# Prompts for generating the final analysis.
|
| 1418 |
+
ANALYSIS_PROMPTS = [
|
| 1419 |
+
"The capital of France is",
|
| 1420 |
+
"def factorial(n):",
|
| 1421 |
+
"The literary device in the phrase 'The wind whispered through the trees' is"
|
| 1422 |
+
]
|
| 1423 |
+
|
| 1424 |
+
# A larger set of prompts for training.
|
| 1425 |
+
TRAINING_PROMPTS = [
|
| 1426 |
+
"The capital of France is", "To be or not to be, that is the", "A stitch in time saves",
|
| 1427 |
+
"The first person to walk on the moon was", "The chemical formula for water is H2O.",
|
| 1428 |
+
"Translate to German: 'The cat sits on the mat.'", "def factorial(n):", "import numpy as np",
|
| 1429 |
+
"The main ingredients in a pizza are", "What is the powerhouse of the cell?",
|
| 1430 |
+
"The equation E=mc^2 relates energy to", "Continue the story: Once upon a time, there was a",
|
| 1431 |
+
"Classify the sentiment: 'I am overjoyed!'", "Extract the entities: 'Apple Inc. is in Cupertino.'",
|
| 1432 |
+
"What is the next number: 2, 4, 8, 16, __?", "A rolling stone gathers no",
|
| 1433 |
+
"The opposite of hot is", "import torch", "import pandas as pd", "class MyClass:",
|
| 1434 |
+
"def __init__(self):", "The primary colors are", "What is the capital of Japan?",
|
| 1435 |
+
"Who wrote 'Hamlet'?", "The square root of 64 is", "The sun rises in the",
|
| 1436 |
+
"The Pacific Ocean is the largest ocean on Earth.", "The mitochondria is the powerhouse of the cell.",
|
| 1437 |
+
"What is the capital of Mongolia?", "The movie 'The Matrix' can be classified into the following genre:",
|
| 1438 |
+
"The French translation of 'I would like to order a coffee, please.' is:",
|
| 1439 |
+
"The literary device in the phrase 'The wind whispered through the trees' is",
|
| 1440 |
+
"A Python function that calculates the factorial of a number is:",
|
| 1441 |
+
"The main ingredient in a Negroni cocktail is",
|
| 1442 |
+
"Summarize the plot of 'Hamlet' in one sentence:",
|
| 1443 |
+
"The sentence 'The cake was eaten by the dog' is in the following voice:",
|
| 1444 |
+
"A good headline for an article about a new breakthrough in battery technology would be:"
|
| 1445 |
+
]
|
| 1446 |
+
|
| 1447 |
+
|
| 1448 |
+
# --- Qwen API for Feature Interpretation ---
|
| 1449 |
+
@torch.no_grad()
|
| 1450 |
+
def get_feature_interpretation_with_qwen(
|
| 1451 |
+
api_config: dict,
|
| 1452 |
+
top_tokens: list[str],
|
| 1453 |
+
feature_name: str,
|
| 1454 |
+
layer_index: int,
|
| 1455 |
+
max_retries: int = 3,
|
| 1456 |
+
initial_backoff: float = 2.0
|
| 1457 |
+
) -> str:
|
| 1458 |
+
# Generates a high-quality interpretation for a feature using the Qwen API.
|
| 1459 |
+
if not api_config or not api_config.get('api_key'):
|
| 1460 |
+
logger.warning("Qwen API not configured. Skipping interpretation.")
|
| 1461 |
+
return "API not configured"
|
| 1462 |
+
|
| 1463 |
+
headers = {
|
| 1464 |
+
"Authorization": f"Bearer {api_config['api_key']}",
|
| 1465 |
+
"Content-Type": "application/json"
|
| 1466 |
+
}
|
| 1467 |
+
|
| 1468 |
+
# Create a specialized prompt.
|
| 1469 |
+
prompt_text = f"""
|
| 1470 |
+
You are an expert in transformer interpretability. A feature in a language model (feature '{feature_name}' at layer {layer_index}) is most strongly activated by the following tokens:
|
| 1471 |
+
|
| 1472 |
+
{', '.join(f"'{token}'" for token in top_tokens)}
|
| 1473 |
+
|
| 1474 |
+
Based *only* on these tokens, what is the most likely function or role of this feature?
|
| 1475 |
+
Your answer must be a short, concise phrase (e.g., "Detecting proper nouns", "Identifying JSON syntax", "Completing lists", "Recognizing negative sentiment"). Do not write a full sentence.
|
| 1476 |
+
"""
|
| 1477 |
+
|
| 1478 |
+
data = {
|
| 1479 |
+
"model": api_config["model"],
|
| 1480 |
+
"messages": [
|
| 1481 |
+
{
|
| 1482 |
+
"role": "user",
|
| 1483 |
+
"content": [{"type": "text", "text": prompt_text}]
|
| 1484 |
+
}
|
| 1485 |
+
],
|
| 1486 |
+
"max_tokens": 50,
|
| 1487 |
+
"temperature": 0.1,
|
| 1488 |
+
"top_p": 0.9,
|
| 1489 |
+
"seed": 42
|
| 1490 |
+
}
|
| 1491 |
+
|
| 1492 |
+
logger.info(f" > Interpreting {feature_name} (Layer {layer_index})...")
|
| 1493 |
+
|
| 1494 |
+
for attempt in range(max_retries):
|
| 1495 |
+
try:
|
| 1496 |
+
logger.info(f" - Attempt {attempt + 1}/{max_retries}: Sending request to Qwen API...")
|
| 1497 |
+
response = requests.post(
|
| 1498 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 1499 |
+
headers=headers,
|
| 1500 |
+
json=data,
|
| 1501 |
+
timeout=60
|
| 1502 |
+
)
|
| 1503 |
+
response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
|
| 1504 |
+
|
| 1505 |
+
result = response.json()
|
| 1506 |
+
interpretation = result["choices"][0]["message"]["content"].strip()
|
| 1507 |
+
|
| 1508 |
+
# Remove quotes from the output.
|
| 1509 |
+
if interpretation.startswith('"') and interpretation.endswith('"'):
|
| 1510 |
+
interpretation = interpretation[1:-1]
|
| 1511 |
+
|
| 1512 |
+
logger.info(f" - Success! Interpretation: '{interpretation}'")
|
| 1513 |
+
return interpretation
|
| 1514 |
+
|
| 1515 |
+
except requests.exceptions.RequestException as e:
|
| 1516 |
+
logger.warning(f" - Qwen API request failed (Attempt {attempt + 1}/{max_retries}): {e}")
|
| 1517 |
+
if attempt < max_retries - 1:
|
| 1518 |
+
backoff_time = initial_backoff * (2 ** attempt)
|
| 1519 |
+
logger.info(f" - Retrying in {backoff_time:.1f} seconds...")
|
| 1520 |
+
time.sleep(backoff_time)
|
| 1521 |
+
else:
|
| 1522 |
+
logger.error(" - Max retries reached. Failing.")
|
| 1523 |
+
return f"API Error: {e}"
|
| 1524 |
+
except (KeyError, IndexError) as e:
|
| 1525 |
+
logger.error(f" - Failed to parse Qwen API response: {e}")
|
| 1526 |
+
return "API Error: Invalid response format"
|
| 1527 |
+
finally:
|
| 1528 |
+
# Add a delay to respect API rate limits.
|
| 1529 |
+
time.sleep(2.1)
|
| 1530 |
+
|
| 1531 |
+
return "API Error: Max retries exceeded"
|
| 1532 |
+
|
| 1533 |
+
|
| 1534 |
+
def train_transcoder(transcoder, model, tokenizer, training_prompts, device, steps=1000, batch_size=16, optimizer=None):
|
| 1535 |
+
# Trains the Cross-Layer Transcoder.
|
| 1536 |
+
transcoder.train()
|
| 1537 |
+
|
| 1538 |
+
# Use a progress bar for visual feedback.
|
| 1539 |
+
progress_bar = tqdm(range(steps), desc="Training CLT")
|
| 1540 |
+
|
| 1541 |
+
for step in progress_bar:
|
| 1542 |
+
# Get a random batch of prompts.
|
| 1543 |
+
batch_prompts = random.choices(training_prompts, k=batch_size)
|
| 1544 |
+
|
| 1545 |
+
# Tokenize the batch.
|
| 1546 |
+
inputs = tokenizer(
|
| 1547 |
+
batch_prompts,
|
| 1548 |
+
return_tensors="pt",
|
| 1549 |
+
padding=True,
|
| 1550 |
+
truncation=True,
|
| 1551 |
+
max_length=MAX_SEQ_LEN
|
| 1552 |
+
)
|
| 1553 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 1554 |
+
|
| 1555 |
+
# Get the model activations.
|
| 1556 |
+
with torch.no_grad():
|
| 1557 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 1558 |
+
hidden_states = outputs.hidden_states[1:]
|
| 1559 |
+
|
| 1560 |
+
# Forward pass through the CLT.
|
| 1561 |
+
feature_activations, reconstructed_outputs = transcoder(hidden_states)
|
| 1562 |
+
|
| 1563 |
+
# Compute the reconstruction loss.
|
| 1564 |
+
recon_loss = 0.0
|
| 1565 |
+
for i, (target, pred) in enumerate(zip(hidden_states, reconstructed_outputs)):
|
| 1566 |
+
recon_loss += F.mse_loss(pred, target)
|
| 1567 |
+
|
| 1568 |
+
# Compute the sparsity loss.
|
| 1569 |
+
sparsity_loss = 0.0
|
| 1570 |
+
for features in feature_activations:
|
| 1571 |
+
sparsity_loss += torch.mean(torch.tanh(0.01 * features)) # Use config.sparsity_lambda
|
| 1572 |
+
|
| 1573 |
+
# Total loss.
|
| 1574 |
+
loss = (0.8 * recon_loss + 0.2 * sparsity_loss) # Use config.reconstruction_loss_weight
|
| 1575 |
+
|
| 1576 |
+
if optimizer:
|
| 1577 |
+
optimizer.zero_grad()
|
| 1578 |
+
loss.backward()
|
| 1579 |
+
optimizer.step()
|
| 1580 |
+
|
| 1581 |
+
progress_bar.set_postfix({
|
| 1582 |
+
"Recon Loss": f"{recon_loss.item():.4f}",
|
| 1583 |
+
"Sparsity Loss": f"{sparsity_loss.item():.4f}",
|
| 1584 |
+
"Total Loss": f"{loss.item():.4f}"
|
| 1585 |
+
})
|
| 1586 |
+
|
| 1587 |
+
def generate_feature_visualizations(transcoder, model, tokenizer, prompt, device, qwen_api_config=None, graph_config: Optional[AttributionGraphConfig] = None):
|
| 1588 |
+
# Generates feature visualizations and interpretations for a prompt.
|
| 1589 |
+
# Tokenize the prompt.
|
| 1590 |
+
inputs = tokenizer(
|
| 1591 |
+
prompt,
|
| 1592 |
+
return_tensors="pt",
|
| 1593 |
+
padding=True,
|
| 1594 |
+
truncation=True,
|
| 1595 |
+
max_length=MAX_SEQ_LEN
|
| 1596 |
+
)
|
| 1597 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 1598 |
+
|
| 1599 |
+
# Get the model activations.
|
| 1600 |
+
with torch.no_grad():
|
| 1601 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 1602 |
+
hidden_states = outputs.hidden_states[1:]
|
| 1603 |
+
|
| 1604 |
+
# Forward pass through the CLT.
|
| 1605 |
+
feature_activations, reconstructed_outputs = transcoder(hidden_states)
|
| 1606 |
+
|
| 1607 |
+
# Visualize the features.
|
| 1608 |
+
feature_visualizations = {}
|
| 1609 |
+
for layer_idx, features in enumerate(feature_activations):
|
| 1610 |
+
layer_viz = {}
|
| 1611 |
+
# Analyze the top features for this layer.
|
| 1612 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 1613 |
+
feature_importance = torch.mean(features, dim=(0, 1)) # Average over batch and sequence
|
| 1614 |
+
top_features = torch.topk(feature_importance, k=min(5, feature_importance.size(0))).indices
|
| 1615 |
+
|
| 1616 |
+
for feat_idx in top_features:
|
| 1617 |
+
viz = FeatureVisualizer(tokenizer).visualize_feature(
|
| 1618 |
+
feat_idx.item(), layer_idx, features[0], tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
|
| 1619 |
+
)
|
| 1620 |
+
interpretation = FeatureVisualizer(tokenizer).interpret_feature(
|
| 1621 |
+
feat_idx.item(), layer_idx, viz, qwen_api_config
|
| 1622 |
+
)
|
| 1623 |
+
viz['interpretation'] = interpretation
|
| 1624 |
+
layer_viz[f"feature_{feat_idx.item()}"] = viz
|
| 1625 |
+
|
| 1626 |
+
feature_visualizations[f"layer_{layer_idx}"] = layer_viz
|
| 1627 |
+
|
| 1628 |
+
# Construct the attribution graph.
|
| 1629 |
+
if graph_config is None:
|
| 1630 |
+
graph_config = AttributionGraphConfig()
|
| 1631 |
+
attribution_graph = AttributionGraph(transcoder, tokenizer, graph_config)
|
| 1632 |
+
graph = attribution_graph.construct_graph(
|
| 1633 |
+
tokenizer.convert_ids_to_tokens(inputs["input_ids"][0]), feature_activations, -1 # No target token for visualization
|
| 1634 |
+
)
|
| 1635 |
+
|
| 1636 |
+
# Prune the graph.
|
| 1637 |
+
pruned_graph = attribution_graph.prune_graph(0.8) # Use config.pruning_threshold
|
| 1638 |
+
|
| 1639 |
+
# Analyze the most important paths.
|
| 1640 |
+
important_paths = []
|
| 1641 |
+
if len(pruned_graph.nodes()) > 0:
|
| 1642 |
+
# Find paths from embeddings to the output.
|
| 1643 |
+
embedding_nodes = [node for node, type_ in attribution_graph.node_types.items()
|
| 1644 |
+
if type_ == "embedding" and node in pruned_graph]
|
| 1645 |
+
output_nodes = [node for node, type_ in attribution_graph.node_types.items()
|
| 1646 |
+
if type_ == "output" and node in pruned_graph]
|
| 1647 |
+
|
| 1648 |
+
for emb_node in embedding_nodes[:3]: # Top 3 embedding nodes
|
| 1649 |
+
for out_node in output_nodes:
|
| 1650 |
+
try:
|
| 1651 |
+
paths = list(nx.all_simple_paths(pruned_graph, emb_node, out_node, cutoff=5))
|
| 1652 |
+
for path in paths[:2]: # Top 2 paths
|
| 1653 |
+
path_weight = 1.0
|
| 1654 |
+
for i in range(len(path) - 1):
|
| 1655 |
+
edge_weight = attribution_graph.edge_weights.get(
|
| 1656 |
+
(path[i], path[i+1]), 0.0
|
| 1657 |
+
)
|
| 1658 |
+
path_weight *= abs(edge_weight)
|
| 1659 |
+
|
| 1660 |
+
important_paths.append({
|
| 1661 |
+
'path': path,
|
| 1662 |
+
'weight': path_weight,
|
| 1663 |
+
'description': attribution_graph._describe_path(path)
|
| 1664 |
+
})
|
| 1665 |
+
except nx.NetworkXNoPath:
|
| 1666 |
+
continue
|
| 1667 |
+
|
| 1668 |
+
# Sort paths by importance.
|
| 1669 |
+
important_paths.sort(key=lambda x: x['weight'], reverse=True)
|
| 1670 |
+
|
| 1671 |
+
return {
|
| 1672 |
+
"prompt": prompt,
|
| 1673 |
+
"full_graph_stats": {
|
| 1674 |
+
"n_nodes": len(graph.nodes()),
|
| 1675 |
+
"n_edges": len(graph.edges()),
|
| 1676 |
+
"node_types": dict(attribution_graph.node_types)
|
| 1677 |
+
},
|
| 1678 |
+
"pruned_graph_stats": {
|
| 1679 |
+
"n_nodes": len(pruned_graph.nodes()),
|
| 1680 |
+
"n_edges": len(pruned_graph.edges())
|
| 1681 |
+
},
|
| 1682 |
+
"feature_visualizations": feature_visualizations,
|
| 1683 |
+
"important_paths": important_paths[:5] # Top 5 paths
|
| 1684 |
+
}
|
| 1685 |
+
|
| 1686 |
+
def main():
|
| 1687 |
+
# Main function to run the analysis for a single prompt.
|
| 1688 |
+
|
| 1689 |
+
# Set a seed for reproducibility.
|
| 1690 |
+
set_seed()
|
| 1691 |
+
|
| 1692 |
+
# --- Argument Parser ---
|
| 1693 |
+
parser = argparse.ArgumentParser(description="Run Attribution Graph analysis for a single prompt.")
|
| 1694 |
+
parser.add_argument(
|
| 1695 |
+
'--prompt-index',
|
| 1696 |
+
type=int,
|
| 1697 |
+
required=True,
|
| 1698 |
+
help=f"The 0-based index of the prompt to analyze from the ANALYSIS_PROMPTS list (0 to {len(ANALYSIS_PROMPTS) - 1})."
|
| 1699 |
+
)
|
| 1700 |
+
parser.add_argument(
|
| 1701 |
+
'--force-retrain-clt',
|
| 1702 |
+
action='store_true',
|
| 1703 |
+
help="Force re-training of the Cross-Layer Transcoder, even if a saved model exists."
|
| 1704 |
+
)
|
| 1705 |
+
parser.add_argument(
|
| 1706 |
+
'--batch-eval',
|
| 1707 |
+
action='store_true',
|
| 1708 |
+
help="Analyze all predefined prompts and compute aggregate faithfulness metrics."
|
| 1709 |
+
)
|
| 1710 |
+
args = parser.parse_args()
|
| 1711 |
+
|
| 1712 |
+
prompt_idx = args.prompt_index
|
| 1713 |
+
if not (0 <= prompt_idx < len(ANALYSIS_PROMPTS)):
|
| 1714 |
+
print(f"❌ Error: --prompt-index must be between 0 and {len(ANALYSIS_PROMPTS) - 1}.")
|
| 1715 |
+
return
|
| 1716 |
+
|
| 1717 |
+
# Get the API config from the utility function.
|
| 1718 |
+
qwen_api_config = init_qwen_api()
|
| 1719 |
+
|
| 1720 |
+
# Configuration - Use consistent settings matching trained CLT
|
| 1721 |
+
config = AttributionGraphConfig(
|
| 1722 |
+
model_path="./models/OLMo-2-1124-7B",
|
| 1723 |
+
n_features_per_layer=512, # Match trained CLT
|
| 1724 |
+
training_steps=500,
|
| 1725 |
+
batch_size=4,
|
| 1726 |
+
max_seq_length=256,
|
| 1727 |
+
learning_rate=1e-4,
|
| 1728 |
+
sparsity_lambda=1e-3, # Match training (L1 sparsity)
|
| 1729 |
+
graph_feature_activation_threshold=0.01,
|
| 1730 |
+
graph_edge_weight_threshold=0.003,
|
| 1731 |
+
graph_max_features_per_layer=40,
|
| 1732 |
+
graph_max_edges_per_node=20,
|
| 1733 |
+
qwen_api_config=qwen_api_config
|
| 1734 |
+
)
|
| 1735 |
+
|
| 1736 |
+
print("Attribution Graphs for OLMo2 7B - Single Prompt Pipeline")
|
| 1737 |
+
print("=" * 50)
|
| 1738 |
+
print(f"Model path: {config.model_path}")
|
| 1739 |
+
print(f"Device: {config.device}")
|
| 1740 |
+
|
| 1741 |
+
try:
|
| 1742 |
+
# Initialize the full pipeline.
|
| 1743 |
+
print("🚀 Initializing Attribution Graphs Pipeline...")
|
| 1744 |
+
pipeline = AttributionGraphsPipeline(config)
|
| 1745 |
+
print("✓ Pipeline initialized successfully")
|
| 1746 |
+
print()
|
| 1747 |
+
|
| 1748 |
+
# Load an existing CLT model or train a new one.
|
| 1749 |
+
if os.path.exists(CLT_SAVE_PATH) and not args.force_retrain_clt:
|
| 1750 |
+
print(f"🧠 Loading existing CLT model from {CLT_SAVE_PATH}...")
|
| 1751 |
+
pipeline.load_clt(CLT_SAVE_PATH)
|
| 1752 |
+
print("✓ CLT model loaded successfully.")
|
| 1753 |
+
else:
|
| 1754 |
+
if args.force_retrain_clt and os.path.exists(CLT_SAVE_PATH):
|
| 1755 |
+
print("��♂️ --force-retrain-clt flag is set. Overwriting existing model.")
|
| 1756 |
+
|
| 1757 |
+
# Train a new CLT model.
|
| 1758 |
+
print("📚 Training a new CLT model...")
|
| 1759 |
+
print(f" Training on {len(TRAINING_PROMPTS)} example texts...")
|
| 1760 |
+
training_stats = pipeline.train_clt(TRAINING_PROMPTS)
|
| 1761 |
+
print("✓ CLT training completed.")
|
| 1762 |
+
|
| 1763 |
+
# Save the training statistics.
|
| 1764 |
+
stats_save_path = os.path.join(RESULTS_DIR, "clt_training_stats.json")
|
| 1765 |
+
with open(stats_save_path, 'w') as f:
|
| 1766 |
+
json.dump(training_stats, f, indent=2)
|
| 1767 |
+
print(f" Saved training stats to {stats_save_path}")
|
| 1768 |
+
|
| 1769 |
+
# Save the new model.
|
| 1770 |
+
pipeline.save_clt(CLT_SAVE_PATH)
|
| 1771 |
+
print(f" Saved trained model to {CLT_SAVE_PATH} for future use.")
|
| 1772 |
+
|
| 1773 |
+
print()
|
| 1774 |
+
|
| 1775 |
+
if args.batch_eval:
|
| 1776 |
+
print("📊 Running batch faithfulness evaluation across all prompts...")
|
| 1777 |
+
batch_payload = pipeline.analyze_prompts_batch(ANALYSIS_PROMPTS)
|
| 1778 |
+
final_results = copy.deepcopy(batch_payload)
|
| 1779 |
+
final_results['config'] = config.__dict__
|
| 1780 |
+
final_results['timestamp'] = str(time.time())
|
| 1781 |
+
for analysis_entry in final_results['analyses'].values():
|
| 1782 |
+
analysis_entry.pop('graph', None)
|
| 1783 |
+
batch_save_path = os.path.join(RESULTS_DIR, "attribution_graphs_batch_results.json")
|
| 1784 |
+
pipeline.save_results(final_results, batch_save_path)
|
| 1785 |
+
print(f"💾 Batch results saved to {batch_save_path}")
|
| 1786 |
+
|
| 1787 |
+
aggregate_summary = batch_payload['aggregate_summary']
|
| 1788 |
+
targeted_summary = aggregate_summary.get('targeted', {})
|
| 1789 |
+
random_summary = aggregate_summary.get('random_baseline', {})
|
| 1790 |
+
path_summary = aggregate_summary.get('path', {})
|
| 1791 |
+
|
| 1792 |
+
def _format_summary(label: str, summary: Dict[str, Any]) -> str:
|
| 1793 |
+
return (
|
| 1794 |
+
f"{label}: count={summary.get('count', 0)}, "
|
| 1795 |
+
f"avg|Δp|={summary.get('avg_abs_probability_change', 0.0):.4f}, "
|
| 1796 |
+
f"flip_rate={summary.get('flip_rate', 0.0):.2%}"
|
| 1797 |
+
)
|
| 1798 |
+
|
| 1799 |
+
print("📈 Aggregate faithfulness summary")
|
| 1800 |
+
print(f" {_format_summary('Targeted', targeted_summary)}")
|
| 1801 |
+
print(f" {_format_summary('Random baseline', random_summary)}")
|
| 1802 |
+
print(f" {_format_summary('Path', path_summary)}")
|
| 1803 |
+
print(f" {_format_summary('Random path baseline', aggregate_summary.get('random_path_baseline', {}))}")
|
| 1804 |
+
diff_abs = aggregate_summary.get('target_minus_random_abs_probability_change', 0.0)
|
| 1805 |
+
diff_flip = aggregate_summary.get('target_flip_rate_minus_random', 0.0)
|
| 1806 |
+
path_diff_abs = aggregate_summary.get('path_minus_random_abs_probability_change', 0.0)
|
| 1807 |
+
path_diff_flip = aggregate_summary.get('path_flip_rate_minus_random', 0.0)
|
| 1808 |
+
print(f" Targeted vs Random |Δp| difference: {diff_abs:.4f}")
|
| 1809 |
+
print(f" Targeted vs Random flip rate difference: {diff_flip:.4f}")
|
| 1810 |
+
print(f" Path vs Random path |Δp| difference: {path_diff_abs:.4f}")
|
| 1811 |
+
print(f" Path vs Random path flip rate difference: {path_diff_flip:.4f}")
|
| 1812 |
+
print("\n🎉 Batch evaluation completed successfully!")
|
| 1813 |
+
return
|
| 1814 |
+
|
| 1815 |
+
# Analyze the selected prompt.
|
| 1816 |
+
prompt_to_analyze = ANALYSIS_PROMPTS[prompt_idx]
|
| 1817 |
+
print(f"🔍 Analyzing prompt {prompt_idx + 1}/{len(ANALYSIS_PROMPTS)}: '{prompt_to_analyze}'")
|
| 1818 |
+
|
| 1819 |
+
analysis = pipeline.analyze_prompt(prompt_to_analyze, target_token_idx=-1)
|
| 1820 |
+
|
| 1821 |
+
# Display the key results.
|
| 1822 |
+
print(f" ✓ Tokenized into {len(analysis['input_tokens'])} tokens")
|
| 1823 |
+
print(f" ✓ Full graph: {analysis['full_graph_stats']['n_nodes']} nodes, {analysis['full_graph_stats']['n_edges']} edges")
|
| 1824 |
+
print(f" ✓ Pruned graph: {analysis['pruned_graph_stats']['n_nodes']} nodes, {analysis['pruned_graph_stats']['n_edges']} edges")
|
| 1825 |
+
|
| 1826 |
+
# Show the top features.
|
| 1827 |
+
print(" 📊 Top active features:")
|
| 1828 |
+
feature_layers_items = list(analysis['feature_visualizations'].items())
|
| 1829 |
+
if config.summary_max_layers is not None:
|
| 1830 |
+
feature_layers_items = feature_layers_items[:config.summary_max_layers]
|
| 1831 |
+
for layer_name, layer_features in feature_layers_items:
|
| 1832 |
+
print(f" {layer_name}:")
|
| 1833 |
+
feature_items = layer_features.items()
|
| 1834 |
+
if config.summary_features_per_layer is not None:
|
| 1835 |
+
feature_items = list(feature_items)[:config.summary_features_per_layer]
|
| 1836 |
+
for feat_name, feat_data in feature_items:
|
| 1837 |
+
print(f" {feat_name}: {feat_data['interpretation']} (max: {feat_data['max_activation']:.3f})")
|
| 1838 |
+
|
| 1839 |
+
print()
|
| 1840 |
+
|
| 1841 |
+
# Summarize perturbation experiments and baselines.
|
| 1842 |
+
print("🧪 Targeted feature ablations:")
|
| 1843 |
+
targeted_results = analysis.get('perturbation_experiments', [])
|
| 1844 |
+
if targeted_results:
|
| 1845 |
+
for experiment in targeted_results:
|
| 1846 |
+
layer_name = experiment.get('layer_name', f"L{experiment.get('feature_set', [{}])[0].get('layer', '?')}")
|
| 1847 |
+
feature_name = experiment.get('feature_name', f"F{experiment.get('feature_set', [{}])[0].get('feature', '?')}")
|
| 1848 |
+
prob_delta = experiment.get('probability_change', 0.0)
|
| 1849 |
+
logit_delta = experiment.get('logit_change', 0.0)
|
| 1850 |
+
flips = experiment.get('ablation_flips_top_prediction', False)
|
| 1851 |
+
print(f" {layer_name}/{feature_name}: Δp={prob_delta:.4f}, Δlogit={logit_delta:.4f}, flips_top={flips}")
|
| 1852 |
+
else:
|
| 1853 |
+
print(" - No targeted ablations were recorded.")
|
| 1854 |
+
|
| 1855 |
+
print("\n🎲 Random baseline ablations:")
|
| 1856 |
+
random_baseline = analysis.get('random_baseline_experiments', [])
|
| 1857 |
+
if random_baseline:
|
| 1858 |
+
for experiment in random_baseline:
|
| 1859 |
+
prob_delta = experiment.get('probability_change', 0.0)
|
| 1860 |
+
logit_delta = experiment.get('logit_change', 0.0)
|
| 1861 |
+
flips = experiment.get('ablation_flips_top_prediction', False)
|
| 1862 |
+
trial_idx = experiment.get('trial_index', '?')
|
| 1863 |
+
print(f" Trial {trial_idx}: Δp={prob_delta:.4f}, Δlogit={logit_delta:.4f}, flips_top={flips}")
|
| 1864 |
+
else:
|
| 1865 |
+
print(" - No random baseline trials were run.")
|
| 1866 |
+
|
| 1867 |
+
print("\n🛤️ Path ablations:")
|
| 1868 |
+
path_results = analysis.get('path_ablation_experiments', [])
|
| 1869 |
+
if path_results:
|
| 1870 |
+
for path_exp in path_results:
|
| 1871 |
+
description = path_exp.get('path_description', 'Path')
|
| 1872 |
+
prob_delta = path_exp.get('probability_change', 0.0)
|
| 1873 |
+
logit_delta = path_exp.get('logit_change', 0.0)
|
| 1874 |
+
flips = path_exp.get('ablation_flips_top_prediction', False)
|
| 1875 |
+
print(f" {description}: Δp={prob_delta:.4f}, Δlogit={logit_delta:.4f}, flips_top={flips}")
|
| 1876 |
+
else:
|
| 1877 |
+
print(" - No path ablations were run.")
|
| 1878 |
+
|
| 1879 |
+
summary_stats = analysis.get('summary_statistics', {})
|
| 1880 |
+
targeted_summary = summary_stats.get('targeted', {})
|
| 1881 |
+
random_summary = summary_stats.get('random_baseline', {})
|
| 1882 |
+
path_summary = summary_stats.get('path', {})
|
| 1883 |
+
random_path_summary = summary_stats.get('random_path_baseline', {})
|
| 1884 |
+
print("\n📈 Summary statistics:")
|
| 1885 |
+
print(f" Targeted: avg|Δp|={targeted_summary.get('avg_abs_probability_change', 0.0):.4f}, flip_rate={targeted_summary.get('flip_rate', 0.0):.2%}")
|
| 1886 |
+
print(f" Random baseline: avg|Δp|={random_summary.get('avg_abs_probability_change', 0.0):.4f}, flip_rate={random_summary.get('flip_rate', 0.0):.2%}")
|
| 1887 |
+
print(f" Path: avg|Δp|={path_summary.get('avg_abs_probability_change', 0.0):.4f}, flip_rate={path_summary.get('flip_rate', 0.0):.2%}")
|
| 1888 |
+
print(f" Random path baseline: avg|Δp|={random_path_summary.get('avg_abs_probability_change', 0.0):.4f}, flip_rate={random_path_summary.get('flip_rate', 0.0):.2%}")
|
| 1889 |
+
print(f" Targeted vs Random |Δp| diff: {summary_stats.get('target_minus_random_abs_probability_change', 0.0):.4f}")
|
| 1890 |
+
print(f" Targeted vs Random flip diff: {summary_stats.get('target_flip_rate_minus_random', 0.0):.4f}")
|
| 1891 |
+
print(f" Path vs Random path |Δp| diff: {summary_stats.get('path_minus_random_abs_probability_change', 0.0):.4f}")
|
| 1892 |
+
print(f" Path vs Random path flip diff: {summary_stats.get('path_flip_rate_minus_random', 0.0):.4f}")
|
| 1893 |
+
print("\n✓ Faithfulness experiments summarized\n")
|
| 1894 |
+
|
| 1895 |
+
# Generate a visualization for the prompt.
|
| 1896 |
+
print("📈 Generating visualization...")
|
| 1897 |
+
if 'graph' in analysis and analysis['pruned_graph_stats']['n_nodes'] > 0:
|
| 1898 |
+
viz_path = os.path.join(RESULTS_DIR, f"attribution_graph_prompt_{prompt_idx + 1}.png")
|
| 1899 |
+
pipeline.attribution_graph.visualize_graph(analysis['graph'], save_path=viz_path)
|
| 1900 |
+
print(f" ✓ Graph visualization saved to {viz_path}")
|
| 1901 |
+
else:
|
| 1902 |
+
print(" - Skipping visualization as no graph was generated or it was empty.")
|
| 1903 |
+
|
| 1904 |
+
# Save the results in a format for the web app.
|
| 1905 |
+
save_path = os.path.join(RESULTS_DIR, f"attribution_graphs_results_prompt_{prompt_idx + 1}.json")
|
| 1906 |
+
|
| 1907 |
+
# Create a JSON file that can be merged with others.
|
| 1908 |
+
final_results = {
|
| 1909 |
+
"analyses": {
|
| 1910 |
+
f"prompt_{prompt_idx + 1}": analysis
|
| 1911 |
+
},
|
| 1912 |
+
"config": config.__dict__,
|
| 1913 |
+
"timestamp": str(time.time())
|
| 1914 |
+
}
|
| 1915 |
+
|
| 1916 |
+
# The web page doesn't use the graph object, so remove it.
|
| 1917 |
+
if 'graph' in final_results['analyses'][f"prompt_{prompt_idx + 1}"]:
|
| 1918 |
+
del final_results['analyses'][f"prompt_{prompt_idx + 1}"]['graph']
|
| 1919 |
+
|
| 1920 |
+
pipeline.save_results(final_results, save_path)
|
| 1921 |
+
print(f"💾 Results saved to {save_path}")
|
| 1922 |
+
|
| 1923 |
+
print("\n🎉 Analysis for this prompt completed successfully!")
|
| 1924 |
+
|
| 1925 |
+
except Exception as e:
|
| 1926 |
+
print(f"❌ Error during execution: {e}")
|
| 1927 |
+
import traceback
|
| 1928 |
+
traceback.print_exc()
|
| 1929 |
+
|
| 1930 |
+
if __name__ == "__main__":
|
| 1931 |
+
main()
|
circuit_analysis/attribution_graphs_olmo_de.py
ADDED
|
@@ -0,0 +1,1165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# This script generates attribution graphs for the German OLMo2 7B model.
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
import numpy as np
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
import seaborn as sns
|
| 10 |
+
from typing import Dict, List, Tuple, Optional, Any
|
| 11 |
+
import json
|
| 12 |
+
import logging
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 15 |
+
from collections import defaultdict
|
| 16 |
+
import networkx as nx
|
| 17 |
+
from dataclasses import dataclass
|
| 18 |
+
from tqdm import tqdm
|
| 19 |
+
import pickle
|
| 20 |
+
import requests
|
| 21 |
+
import time
|
| 22 |
+
import random
|
| 23 |
+
import os
|
| 24 |
+
import argparse
|
| 25 |
+
|
| 26 |
+
# --- Add this block to fix the import path ---
|
| 27 |
+
import sys
|
| 28 |
+
from pathlib import Path
|
| 29 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 30 |
+
# ---------------------------------------------
|
| 31 |
+
|
| 32 |
+
from utils import init_qwen_api, set_seed
|
| 33 |
+
|
| 34 |
+
# --- Constants ---
|
| 35 |
+
# Configuration for the attribution graph generation pipeline.
|
| 36 |
+
RESULTS_DIR = "circuit_analysis/results"
|
| 37 |
+
CLT_SAVE_PATH = "circuit_analysis/models/clt_model_de.pth"
|
| 38 |
+
|
| 39 |
+
# Configure logging.
|
| 40 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime=s - %(levelname=s - %(message=s')
|
| 41 |
+
logger = logging.getLogger(__name__)
|
| 42 |
+
|
| 43 |
+
# Set the device for training.
|
| 44 |
+
if torch.backends.mps.is_available():
|
| 45 |
+
DEVICE = torch.device("mps")
|
| 46 |
+
logger.info("Using MPS (Metal Performance Shaders) for GPU acceleration")
|
| 47 |
+
elif torch.cuda.is_available():
|
| 48 |
+
DEVICE = torch.device("cuda")
|
| 49 |
+
logger.info("Using CUDA for GPU acceleration")
|
| 50 |
+
else:
|
| 51 |
+
DEVICE = torch.device("cpu")
|
| 52 |
+
logger.info("Using CPU")
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class AttributionGraphConfig:
|
| 56 |
+
# Configuration for building the attribution graph.
|
| 57 |
+
model_path: str = "./models/OLMo-2-1124-7B"
|
| 58 |
+
max_seq_length: int = 512
|
| 59 |
+
n_features_per_layer: int = 512
|
| 60 |
+
sparsity_lambda: float = 0.01
|
| 61 |
+
reconstruction_loss_weight: float = 1.0
|
| 62 |
+
batch_size: int = 8
|
| 63 |
+
learning_rate: float = 1e-4
|
| 64 |
+
training_steps: int = 1000
|
| 65 |
+
device: str = str(DEVICE)
|
| 66 |
+
pruning_threshold: float = 0.8 # For graph pruning
|
| 67 |
+
intervention_strength: float = 5.0 # For perturbation experiments
|
| 68 |
+
qwen_api_config: Optional[Dict[str, str]] = None
|
| 69 |
+
|
| 70 |
+
class JumpReLU(nn.Module):
|
| 71 |
+
# The JumpReLU activation function.
|
| 72 |
+
|
| 73 |
+
def __init__(self, threshold: float = 0.0):
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.threshold = threshold
|
| 76 |
+
|
| 77 |
+
def forward(self, x):
|
| 78 |
+
return F.relu(x - self.threshold)
|
| 79 |
+
|
| 80 |
+
class CrossLayerTranscoder(nn.Module):
|
| 81 |
+
# The Cross-Layer Transcoder (CLT) model.
|
| 82 |
+
|
| 83 |
+
def __init__(self, model_config: Dict, clt_config: AttributionGraphConfig):
|
| 84 |
+
super().__init__()
|
| 85 |
+
self.config = clt_config
|
| 86 |
+
self.model_config = model_config
|
| 87 |
+
self.n_layers = model_config['num_hidden_layers']
|
| 88 |
+
self.hidden_size = model_config['hidden_size']
|
| 89 |
+
self.n_features = clt_config.n_features_per_layer
|
| 90 |
+
|
| 91 |
+
# Encoder weights for each layer.
|
| 92 |
+
self.encoders = nn.ModuleList([
|
| 93 |
+
nn.Linear(self.hidden_size, self.n_features, bias=False)
|
| 94 |
+
for _ in range(self.n_layers)
|
| 95 |
+
])
|
| 96 |
+
|
| 97 |
+
# Decoder weights for cross-layer connections.
|
| 98 |
+
self.decoders = nn.ModuleDict()
|
| 99 |
+
for source_layer in range(self.n_layers):
|
| 100 |
+
for target_layer in range(source_layer, self.n_layers):
|
| 101 |
+
key = f"{source_layer}_to_{target_layer}"
|
| 102 |
+
self.decoders[key] = nn.Linear(self.n_features, self.hidden_size, bias=False)
|
| 103 |
+
|
| 104 |
+
# The activation function.
|
| 105 |
+
self.activation = JumpReLU(threshold=0.0)
|
| 106 |
+
|
| 107 |
+
# Initialize the weights.
|
| 108 |
+
self._init_weights()
|
| 109 |
+
|
| 110 |
+
def _init_weights(self):
|
| 111 |
+
# Initializes the weights with small random values.
|
| 112 |
+
for module in self.modules():
|
| 113 |
+
if isinstance(module, nn.Linear):
|
| 114 |
+
nn.init.normal_(module.weight, mean=0.0, std=0.01)
|
| 115 |
+
|
| 116 |
+
def encode(self, layer_idx: int, residual_activations: torch.Tensor) -> torch.Tensor:
|
| 117 |
+
# Encodes residual stream activations to feature activations.
|
| 118 |
+
return self.activation(self.encoders[layer_idx](residual_activations))
|
| 119 |
+
|
| 120 |
+
def decode(self, source_layer: int, target_layer: int, feature_activations: torch.Tensor) -> torch.Tensor:
|
| 121 |
+
# Decodes feature activations to the MLP output space.
|
| 122 |
+
key = f"{source_layer}_to_{target_layer}"
|
| 123 |
+
return self.decoders[key](feature_activations)
|
| 124 |
+
|
| 125 |
+
def forward(self, residual_activations: List[torch.Tensor]) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:
|
| 126 |
+
# The forward pass of the CLT.
|
| 127 |
+
feature_activations = []
|
| 128 |
+
reconstructed_mlp_outputs = []
|
| 129 |
+
|
| 130 |
+
# Encode features for each layer.
|
| 131 |
+
for layer_idx, residual in enumerate(residual_activations):
|
| 132 |
+
features = self.encode(layer_idx, residual)
|
| 133 |
+
feature_activations.append(features)
|
| 134 |
+
|
| 135 |
+
# Reconstruct MLP outputs with cross-layer connections.
|
| 136 |
+
for target_layer in range(self.n_layers):
|
| 137 |
+
reconstruction = torch.zeros_like(residual_activations[target_layer])
|
| 138 |
+
|
| 139 |
+
# Sum contributions from all previous layers.
|
| 140 |
+
for source_layer in range(target_layer + 1):
|
| 141 |
+
decoded = self.decode(source_layer, target_layer, feature_activations[source_layer])
|
| 142 |
+
reconstruction += decoded
|
| 143 |
+
|
| 144 |
+
reconstructed_mlp_outputs.append(reconstruction)
|
| 145 |
+
|
| 146 |
+
return feature_activations, reconstructed_mlp_outputs
|
| 147 |
+
|
| 148 |
+
class FeatureVisualizer:
|
| 149 |
+
# A class to visualize and interpret individual features.
|
| 150 |
+
|
| 151 |
+
def __init__(self, tokenizer):
|
| 152 |
+
self.tokenizer = tokenizer
|
| 153 |
+
self.feature_interpretations = {}
|
| 154 |
+
|
| 155 |
+
def visualize_feature(self, feature_idx: int, layer_idx: int,
|
| 156 |
+
activations: torch.Tensor, input_tokens: List[str],
|
| 157 |
+
top_k: int = 10) -> Dict:
|
| 158 |
+
# Creates a visualization for a single feature.
|
| 159 |
+
feature_acts = activations[:, feature_idx].detach().cpu().numpy()
|
| 160 |
+
|
| 161 |
+
# Find the top activating positions.
|
| 162 |
+
top_positions = np.argsort(feature_acts)[-top_k:][::-1]
|
| 163 |
+
|
| 164 |
+
visualization = {
|
| 165 |
+
'feature_idx': feature_idx,
|
| 166 |
+
'layer_idx': layer_idx,
|
| 167 |
+
'max_activation': float(feature_acts.max()),
|
| 168 |
+
'mean_activation': float(feature_acts.mean()),
|
| 169 |
+
'sparsity': float((feature_acts > 0.1).mean()),
|
| 170 |
+
'top_activations': []
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
for pos in top_positions:
|
| 174 |
+
if pos < len(input_tokens):
|
| 175 |
+
visualization['top_activations'].append({
|
| 176 |
+
'token': input_tokens[pos],
|
| 177 |
+
'position': int(pos),
|
| 178 |
+
'activation': float(feature_acts[pos])
|
| 179 |
+
})
|
| 180 |
+
|
| 181 |
+
return visualization
|
| 182 |
+
|
| 183 |
+
def interpret_feature(self, feature_idx: int, layer_idx: int,
|
| 184 |
+
visualization_data: Dict,
|
| 185 |
+
qwen_api_config: Optional[Dict[str, str]] = None) -> str:
|
| 186 |
+
# Interprets a feature based on its top activating tokens.
|
| 187 |
+
top_tokens = [item['token'] for item in visualization_data['top_activations']]
|
| 188 |
+
|
| 189 |
+
# Use the Qwen API if it is configured.
|
| 190 |
+
if qwen_api_config and qwen_api_config.get('api_key'):
|
| 191 |
+
feature_name = f"L{layer_idx}_F{feature_idx}"
|
| 192 |
+
interpretation = get_feature_interpretation_with_qwen(
|
| 193 |
+
qwen_api_config, top_tokens, feature_name, layer_idx
|
| 194 |
+
)
|
| 195 |
+
else:
|
| 196 |
+
# Use a simple heuristic as a fallback.
|
| 197 |
+
if len(set(top_tokens)) == 1:
|
| 198 |
+
interpretation = f"Spezifischer Token: '{top_tokens[0]}'"
|
| 199 |
+
elif all(token.isalpha() for token in top_tokens):
|
| 200 |
+
interpretation = "Wort/alphabetische Tokens"
|
| 201 |
+
elif all(token.isdigit() for token in top_tokens):
|
| 202 |
+
interpretation = "Numerische Tokens"
|
| 203 |
+
elif all(token in '.,!?;:' for token in top_tokens):
|
| 204 |
+
interpretation = "Interpunktion"
|
| 205 |
+
else:
|
| 206 |
+
interpretation = "Gemischte/polysemische Merkmale"
|
| 207 |
+
|
| 208 |
+
self.feature_interpretations[f"L{layer_idx}_F{feature_idx}"] = interpretation
|
| 209 |
+
return interpretation
|
| 210 |
+
|
| 211 |
+
class AttributionGraph:
|
| 212 |
+
# A class to construct and analyze attribution graphs.
|
| 213 |
+
|
| 214 |
+
def __init__(self, clt: CrossLayerTranscoder, tokenizer):
|
| 215 |
+
self.clt = clt
|
| 216 |
+
self.tokenizer = tokenizer
|
| 217 |
+
self.graph = nx.DiGraph()
|
| 218 |
+
self.node_types = {} # Track node types (feature, embedding, error, output)
|
| 219 |
+
self.edge_weights = {}
|
| 220 |
+
|
| 221 |
+
def compute_virtual_weights(self, source_layer: int, target_layer: int,
|
| 222 |
+
source_feature: int, target_feature: int) -> float:
|
| 223 |
+
# Computes the virtual weight between two features.
|
| 224 |
+
if target_layer <= source_layer:
|
| 225 |
+
return 0.0
|
| 226 |
+
|
| 227 |
+
# Get the encoder and decoder weights.
|
| 228 |
+
encoder_weight = self.clt.encoders[target_layer].weight[target_feature]
|
| 229 |
+
|
| 230 |
+
total_weight = 0.0
|
| 231 |
+
for intermediate_layer in range(source_layer, target_layer):
|
| 232 |
+
decoder_key = f"{source_layer}_to_{intermediate_layer}"
|
| 233 |
+
if decoder_key in self.clt.decoders:
|
| 234 |
+
decoder_weight = self.clt.decoders[decoder_key].weight[:, source_feature]
|
| 235 |
+
# The virtual weight is the inner product.
|
| 236 |
+
virtual_weight = torch.dot(decoder_weight, encoder_weight).item()
|
| 237 |
+
total_weight += virtual_weight
|
| 238 |
+
|
| 239 |
+
return total_weight
|
| 240 |
+
|
| 241 |
+
def construct_graph(self, input_tokens: List[str],
|
| 242 |
+
feature_activations: List[torch.Tensor],
|
| 243 |
+
target_token_idx: int = -1) -> nx.DiGraph:
|
| 244 |
+
# Constructs the attribution graph for a prompt.
|
| 245 |
+
self.graph.clear()
|
| 246 |
+
self.node_types.clear()
|
| 247 |
+
self.edge_weights.clear()
|
| 248 |
+
|
| 249 |
+
seq_len = len(input_tokens)
|
| 250 |
+
n_layers = len(feature_activations)
|
| 251 |
+
|
| 252 |
+
# Add embedding nodes for the input tokens.
|
| 253 |
+
for i, token in enumerate(input_tokens):
|
| 254 |
+
node_id = f"emb_{i}_{token}"
|
| 255 |
+
self.graph.add_node(node_id)
|
| 256 |
+
self.node_types[node_id] = "embedding"
|
| 257 |
+
|
| 258 |
+
# Add nodes for the features.
|
| 259 |
+
active_features = {}
|
| 260 |
+
max_features_per_layer = 20
|
| 261 |
+
|
| 262 |
+
for layer_idx, features in enumerate(feature_activations):
|
| 263 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 264 |
+
batch_size, seq_len_layer, n_features = features.shape
|
| 265 |
+
|
| 266 |
+
# Get the top activating features for this layer.
|
| 267 |
+
layer_activations = features[0].mean(dim=0)
|
| 268 |
+
top_features = torch.topk(layer_activations,
|
| 269 |
+
k=min(max_features_per_layer, n_features)).indices
|
| 270 |
+
|
| 271 |
+
for token_pos in range(min(seq_len, seq_len_layer)):
|
| 272 |
+
for feat_idx in top_features:
|
| 273 |
+
activation = features[0, token_pos, feat_idx.item()].item()
|
| 274 |
+
if activation > 0.05:
|
| 275 |
+
node_id = f"feat_L{layer_idx}_T{token_pos}_F{feat_idx.item()}"
|
| 276 |
+
self.graph.add_node(node_id)
|
| 277 |
+
self.node_types[node_id] = "feature"
|
| 278 |
+
active_features[node_id] = {
|
| 279 |
+
'layer': layer_idx,
|
| 280 |
+
'token_pos': token_pos,
|
| 281 |
+
'feature_idx': feat_idx.item(),
|
| 282 |
+
'activation': activation
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
# Add an output node for the target token.
|
| 286 |
+
output_node = f"output_{target_token_idx}"
|
| 287 |
+
self.graph.add_node(output_node)
|
| 288 |
+
self.node_types[output_node] = "output"
|
| 289 |
+
|
| 290 |
+
# Add edges based on virtual weights and activations.
|
| 291 |
+
feature_nodes = [node for node, type_ in self.node_types.items() if type_ == "feature"]
|
| 292 |
+
print(f" Building attribution graph: {len(feature_nodes)} feature nodes, {len(self.graph.nodes())} total nodes")
|
| 293 |
+
|
| 294 |
+
# Limit the number of edges to compute.
|
| 295 |
+
max_edges_per_node = 5
|
| 296 |
+
|
| 297 |
+
for i, source_node in enumerate(feature_nodes):
|
| 298 |
+
if i % 50 == 0:
|
| 299 |
+
print(f" Processing node {i+1}/{len(feature_nodes)}")
|
| 300 |
+
|
| 301 |
+
edges_added = 0
|
| 302 |
+
source_info = active_features[source_node]
|
| 303 |
+
source_activation = source_info['activation']
|
| 304 |
+
|
| 305 |
+
# Add edges to other features.
|
| 306 |
+
for target_node in feature_nodes:
|
| 307 |
+
if source_node == target_node or edges_added >= max_edges_per_node:
|
| 308 |
+
continue
|
| 309 |
+
|
| 310 |
+
target_info = active_features[target_node]
|
| 311 |
+
|
| 312 |
+
# Only add edges that go forward in the network.
|
| 313 |
+
if (target_info['layer'] > source_info['layer'] or
|
| 314 |
+
(target_info['layer'] == source_info['layer'] and
|
| 315 |
+
target_info['token_pos'] > source_info['token_pos'])):
|
| 316 |
+
|
| 317 |
+
virtual_weight = self.compute_virtual_weights(
|
| 318 |
+
source_info['layer'], target_info['layer'],
|
| 319 |
+
source_info['feature_idx'], target_info['feature_idx']
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
if abs(virtual_weight) > 0.05:
|
| 323 |
+
edge_weight = source_activation * virtual_weight
|
| 324 |
+
self.graph.add_edge(source_node, target_node, weight=edge_weight)
|
| 325 |
+
self.edge_weights[(source_node, target_node)] = edge_weight
|
| 326 |
+
edges_added += 1
|
| 327 |
+
|
| 328 |
+
# Add edges to the output node.
|
| 329 |
+
if source_info['layer'] >= n_layers - 2:
|
| 330 |
+
output_weight = source_activation * 0.1
|
| 331 |
+
self.graph.add_edge(source_node, output_node, weight=output_weight)
|
| 332 |
+
self.edge_weights[(source_node, output_node)] = output_weight
|
| 333 |
+
|
| 334 |
+
# Add edges from embeddings to early features.
|
| 335 |
+
for emb_node in [node for node, type_ in self.node_types.items() if type_ == "embedding"]:
|
| 336 |
+
token_idx = int(emb_node.split('_')[1])
|
| 337 |
+
for feat_node in feature_nodes:
|
| 338 |
+
feat_info = active_features[feat_node]
|
| 339 |
+
if feat_info['layer'] == 0 and feat_info['token_pos'] == token_idx:
|
| 340 |
+
# Direct connection from an embedding to a first-layer feature.
|
| 341 |
+
weight = feat_info['activation'] * 0.5
|
| 342 |
+
self.graph.add_edge(emb_node, feat_node, weight=weight)
|
| 343 |
+
self.edge_weights[(emb_node, feat_node)] = weight
|
| 344 |
+
|
| 345 |
+
return self.graph
|
| 346 |
+
|
| 347 |
+
def prune_graph(self, threshold: float = 0.8) -> nx.DiGraph:
|
| 348 |
+
# Prunes the graph to keep only the most important nodes.
|
| 349 |
+
# Calculate node importance based on edge weights.
|
| 350 |
+
node_importance = defaultdict(float)
|
| 351 |
+
|
| 352 |
+
for (source, target), weight in self.edge_weights.items():
|
| 353 |
+
node_importance[source] += abs(weight)
|
| 354 |
+
node_importance[target] += abs(weight)
|
| 355 |
+
|
| 356 |
+
# Keep the top nodes by importance.
|
| 357 |
+
sorted_nodes = sorted(node_importance.items(), key=lambda x: x[1], reverse=True)
|
| 358 |
+
n_keep = int(len(sorted_nodes) * threshold)
|
| 359 |
+
important_nodes = set([node for node, _ in sorted_nodes[:n_keep]])
|
| 360 |
+
|
| 361 |
+
# Always keep the output and embedding nodes.
|
| 362 |
+
for node, type_ in self.node_types.items():
|
| 363 |
+
if type_ in ["output", "embedding"]:
|
| 364 |
+
important_nodes.add(node)
|
| 365 |
+
|
| 366 |
+
# Create the pruned graph.
|
| 367 |
+
pruned_graph = self.graph.subgraph(important_nodes).copy()
|
| 368 |
+
|
| 369 |
+
return pruned_graph
|
| 370 |
+
|
| 371 |
+
def visualize_graph(self, graph: nx.DiGraph = None, save_path: str = None):
|
| 372 |
+
# Visualizes the attribution graph.
|
| 373 |
+
if graph is None:
|
| 374 |
+
graph = self.graph
|
| 375 |
+
|
| 376 |
+
plt.figure(figsize=(12, 8))
|
| 377 |
+
|
| 378 |
+
# Create a layout for the graph.
|
| 379 |
+
pos = nx.spring_layout(graph, k=1, iterations=50)
|
| 380 |
+
|
| 381 |
+
# Color the nodes by type.
|
| 382 |
+
node_colors = []
|
| 383 |
+
for node in graph.nodes():
|
| 384 |
+
node_type = self.node_types.get(node, "unknown")
|
| 385 |
+
if node_type == "embedding":
|
| 386 |
+
node_colors.append('lightblue')
|
| 387 |
+
elif node_type == "feature":
|
| 388 |
+
node_colors.append('lightgreen')
|
| 389 |
+
elif node_type == "output":
|
| 390 |
+
node_colors.append('orange')
|
| 391 |
+
else:
|
| 392 |
+
node_colors.append('gray')
|
| 393 |
+
|
| 394 |
+
# Draw the nodes.
|
| 395 |
+
nx.draw_networkx_nodes(graph, pos, node_color=node_colors,
|
| 396 |
+
node_size=300, alpha=0.8)
|
| 397 |
+
|
| 398 |
+
# Draw the edges with thickness based on weight.
|
| 399 |
+
edges = graph.edges()
|
| 400 |
+
edge_weights = [abs(self.edge_weights.get((u, v), 0.1)) for u, v in edges]
|
| 401 |
+
max_weight = max(edge_weights) if edge_weights else 1
|
| 402 |
+
edge_widths = [w / max_weight * 3 for w in edge_weights]
|
| 403 |
+
|
| 404 |
+
nx.draw_networkx_edges(graph, pos, width=edge_widths, alpha=0.6,
|
| 405 |
+
edge_color='gray', arrows=True)
|
| 406 |
+
|
| 407 |
+
# Draw the labels.
|
| 408 |
+
nx.draw_networkx_labels(graph, pos, font_size=8)
|
| 409 |
+
|
| 410 |
+
plt.title("Attribution Graph (German)")
|
| 411 |
+
plt.axis('off')
|
| 412 |
+
|
| 413 |
+
if save_path:
|
| 414 |
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
| 415 |
+
plt.show()
|
| 416 |
+
|
| 417 |
+
class PerturbationExperiments:
|
| 418 |
+
# Conducts perturbation experiments to validate hypotheses.
|
| 419 |
+
|
| 420 |
+
def __init__(self, model, clt: CrossLayerTranscoder, tokenizer):
|
| 421 |
+
self.model = model
|
| 422 |
+
self.clt = clt
|
| 423 |
+
self.tokenizer = tokenizer
|
| 424 |
+
|
| 425 |
+
def feature_ablation_experiment(self, input_text: str,
|
| 426 |
+
target_layer: int, target_feature: int,
|
| 427 |
+
intervention_strength: float = 5.0) -> Dict:
|
| 428 |
+
# Ablates a feature and measures the effect on the model's output.
|
| 429 |
+
try:
|
| 430 |
+
# Clear the MPS cache to prevent memory issues.
|
| 431 |
+
if torch.backends.mps.is_available():
|
| 432 |
+
torch.mps.empty_cache()
|
| 433 |
+
|
| 434 |
+
# Tokenize the input.
|
| 435 |
+
inputs = self.tokenizer(input_text, return_tensors="pt", padding=True,
|
| 436 |
+
truncation=True, max_length=512)
|
| 437 |
+
|
| 438 |
+
# Move inputs to the correct device.
|
| 439 |
+
device = next(self.model.parameters()).device
|
| 440 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 441 |
+
|
| 442 |
+
# Get the baseline predictions.
|
| 443 |
+
with torch.no_grad():
|
| 444 |
+
baseline_outputs = self.model(**inputs)
|
| 445 |
+
baseline_logits = baseline_outputs.logits[0, -1, :]
|
| 446 |
+
baseline_probs = F.softmax(baseline_logits, dim=-1)
|
| 447 |
+
baseline_top_tokens = torch.topk(baseline_probs, k=5)
|
| 448 |
+
|
| 449 |
+
# TODO: Implement the actual feature intervention.
|
| 450 |
+
|
| 451 |
+
# Simulate the effect of the intervention.
|
| 452 |
+
intervention_effect = {
|
| 453 |
+
'baseline_top_tokens': [
|
| 454 |
+
(self.tokenizer.decode([idx]), prob.item())
|
| 455 |
+
for idx, prob in zip(baseline_top_tokens.indices, baseline_top_tokens.values)
|
| 456 |
+
],
|
| 457 |
+
'intervention_layer': target_layer,
|
| 458 |
+
'intervention_feature': target_feature,
|
| 459 |
+
'intervention_strength': intervention_strength,
|
| 460 |
+
'effect_magnitude': 0.1,
|
| 461 |
+
'probability_change': 0.05
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
return intervention_effect
|
| 465 |
+
|
| 466 |
+
except Exception as e:
|
| 467 |
+
# Handle MPS memory issues.
|
| 468 |
+
print(f" Warning: Perturbation experiment failed due to device issue: {e}")
|
| 469 |
+
return {
|
| 470 |
+
'baseline_top_tokens': [],
|
| 471 |
+
'intervention_layer': target_layer,
|
| 472 |
+
'intervention_feature': target_feature,
|
| 473 |
+
'intervention_strength': intervention_strength,
|
| 474 |
+
'effect_magnitude': 0.0,
|
| 475 |
+
'probability_change': 0.0,
|
| 476 |
+
'error': str(e)
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
class AttributionGraphsPipeline:
|
| 480 |
+
# The main pipeline for the attribution graph analysis.
|
| 481 |
+
|
| 482 |
+
def __init__(self, config: AttributionGraphConfig):
|
| 483 |
+
self.config = config
|
| 484 |
+
self.device = torch.device(config.device)
|
| 485 |
+
|
| 486 |
+
# Load the model and tokenizer.
|
| 487 |
+
logger.info(f"Loading OLMo2 7B model from {config.model_path}")
|
| 488 |
+
self.tokenizer = AutoTokenizer.from_pretrained(config.model_path)
|
| 489 |
+
|
| 490 |
+
# Configure model loading based on the device.
|
| 491 |
+
if "mps" in config.device:
|
| 492 |
+
# MPS supports float16 but not device_map.
|
| 493 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 494 |
+
config.model_path,
|
| 495 |
+
torch_dtype=torch.float16,
|
| 496 |
+
device_map=None
|
| 497 |
+
).to(self.device)
|
| 498 |
+
elif "cuda" in config.device:
|
| 499 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 500 |
+
config.model_path,
|
| 501 |
+
torch_dtype=torch.float16,
|
| 502 |
+
device_map="auto"
|
| 503 |
+
)
|
| 504 |
+
else:
|
| 505 |
+
# CPU
|
| 506 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 507 |
+
config.model_path,
|
| 508 |
+
torch_dtype=torch.float32,
|
| 509 |
+
device_map=None
|
| 510 |
+
).to(self.device)
|
| 511 |
+
|
| 512 |
+
if self.tokenizer.pad_token is None:
|
| 513 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 514 |
+
|
| 515 |
+
# Initialize the CLT.
|
| 516 |
+
model_config = self.model.config.to_dict()
|
| 517 |
+
self.clt = CrossLayerTranscoder(model_config, config).to(self.device)
|
| 518 |
+
|
| 519 |
+
# Initialize the other components.
|
| 520 |
+
self.feature_visualizer = FeatureVisualizer(self.tokenizer)
|
| 521 |
+
self.attribution_graph = AttributionGraph(self.clt, self.tokenizer)
|
| 522 |
+
self.perturbation_experiments = PerturbationExperiments(self.model, self.clt, self.tokenizer)
|
| 523 |
+
|
| 524 |
+
logger.info("Attribution Graphs Pipeline initialized successfully")
|
| 525 |
+
|
| 526 |
+
def train_clt(self, training_texts: List[str]) -> Dict:
|
| 527 |
+
# Trains the Cross-Layer Transcoder.
|
| 528 |
+
logger.info("Starting CLT training...")
|
| 529 |
+
|
| 530 |
+
optimizer = torch.optim.Adam(self.clt.parameters(), lr=self.config.learning_rate)
|
| 531 |
+
|
| 532 |
+
training_stats = {
|
| 533 |
+
'reconstruction_losses': [],
|
| 534 |
+
'sparsity_losses': [],
|
| 535 |
+
'total_losses': []
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
for step in tqdm(range(self.config.training_steps), desc="Training CLT"):
|
| 539 |
+
# Sample a batch of texts.
|
| 540 |
+
batch_texts = np.random.choice(training_texts, size=self.config.batch_size)
|
| 541 |
+
|
| 542 |
+
total_loss = 0.0
|
| 543 |
+
total_recon_loss = 0.0
|
| 544 |
+
total_sparsity_loss = 0.0
|
| 545 |
+
|
| 546 |
+
for text in batch_texts:
|
| 547 |
+
# Tokenize the text.
|
| 548 |
+
inputs = self.tokenizer(text, return_tensors="pt", max_length=self.config.max_seq_length,
|
| 549 |
+
truncation=True, padding=True).to(self.device)
|
| 550 |
+
|
| 551 |
+
# Get the model activations.
|
| 552 |
+
with torch.no_grad():
|
| 553 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 554 |
+
hidden_states = outputs.hidden_states[1:]
|
| 555 |
+
|
| 556 |
+
# Forward pass through the CLT.
|
| 557 |
+
feature_activations, reconstructed_outputs = self.clt(hidden_states)
|
| 558 |
+
|
| 559 |
+
# Compute the reconstruction loss.
|
| 560 |
+
recon_loss = 0.0
|
| 561 |
+
for i, (target, pred) in enumerate(zip(hidden_states, reconstructed_outputs)):
|
| 562 |
+
recon_loss += F.mse_loss(pred, target)
|
| 563 |
+
|
| 564 |
+
# Compute the sparsity loss.
|
| 565 |
+
sparsity_loss = 0.0
|
| 566 |
+
for features in feature_activations:
|
| 567 |
+
sparsity_loss += torch.mean(torch.tanh(self.config.sparsity_lambda * features))
|
| 568 |
+
|
| 569 |
+
# Total loss.
|
| 570 |
+
loss = (self.config.reconstruction_loss_weight * recon_loss +
|
| 571 |
+
self.config.sparsity_lambda * sparsity_loss)
|
| 572 |
+
|
| 573 |
+
total_loss += loss
|
| 574 |
+
total_recon_loss += recon_loss
|
| 575 |
+
total_sparsity_loss += sparsity_loss
|
| 576 |
+
|
| 577 |
+
# Average the losses.
|
| 578 |
+
total_loss /= self.config.batch_size
|
| 579 |
+
total_recon_loss /= self.config.batch_size
|
| 580 |
+
total_sparsity_loss /= self.config.batch_size
|
| 581 |
+
|
| 582 |
+
# Backward pass.
|
| 583 |
+
optimizer.zero_grad()
|
| 584 |
+
total_loss.backward()
|
| 585 |
+
optimizer.step()
|
| 586 |
+
|
| 587 |
+
# Log the progress.
|
| 588 |
+
training_stats['total_losses'].append(total_loss.item())
|
| 589 |
+
training_stats['reconstruction_losses'].append(total_recon_loss.item())
|
| 590 |
+
training_stats['sparsity_losses'].append(total_sparsity_loss.item())
|
| 591 |
+
|
| 592 |
+
if step % 100 == 0:
|
| 593 |
+
logger.info(f"Step {step}: Total Loss = {total_loss.item():.4f}, "
|
| 594 |
+
f"Recon Loss = {total_recon_loss.item():.4f}, "
|
| 595 |
+
f"Sparsity Loss = {total_sparsity_loss.item():.4f}")
|
| 596 |
+
|
| 597 |
+
logger.info("CLT training completed")
|
| 598 |
+
return training_stats
|
| 599 |
+
|
| 600 |
+
def analyze_prompt(self, prompt: str, target_token_idx: int = -1) -> Dict:
|
| 601 |
+
# Performs a complete analysis for a single prompt.
|
| 602 |
+
logger.info(f"Analyzing prompt: '{prompt[:50]}...'")
|
| 603 |
+
|
| 604 |
+
# Tokenize the prompt.
|
| 605 |
+
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
|
| 606 |
+
input_tokens = self.tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
|
| 607 |
+
|
| 608 |
+
# Get the model activations.
|
| 609 |
+
with torch.no_grad():
|
| 610 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 611 |
+
hidden_states = outputs.hidden_states[1:]
|
| 612 |
+
|
| 613 |
+
# Forward pass through the CLT.
|
| 614 |
+
feature_activations, reconstructed_outputs = self.clt(hidden_states)
|
| 615 |
+
|
| 616 |
+
logger.info(" > Starting feature visualization and interpretation...")
|
| 617 |
+
feature_visualizations = {}
|
| 618 |
+
for layer_idx, features in enumerate(feature_activations):
|
| 619 |
+
logger.info(f" - Processing Layer {layer_idx}...")
|
| 620 |
+
layer_viz = {}
|
| 621 |
+
# Analyze the top features for this layer.
|
| 622 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 623 |
+
feature_importance = torch.mean(features, dim=(0, 1))
|
| 624 |
+
top_features = torch.topk(feature_importance, k=min(5, feature_importance.size(0))).indices
|
| 625 |
+
|
| 626 |
+
for feat_idx in top_features:
|
| 627 |
+
viz = self.feature_visualizer.visualize_feature(
|
| 628 |
+
feat_idx.item(), layer_idx, features[0], input_tokens
|
| 629 |
+
)
|
| 630 |
+
interpretation = self.feature_visualizer.interpret_feature(
|
| 631 |
+
feat_idx.item(), layer_idx, viz, self.config.qwen_api_config
|
| 632 |
+
)
|
| 633 |
+
viz['interpretation'] = interpretation
|
| 634 |
+
layer_viz[f"feature_{feat_idx.item()}"] = viz
|
| 635 |
+
|
| 636 |
+
feature_visualizations[f"layer_{layer_idx}"] = layer_viz
|
| 637 |
+
|
| 638 |
+
# Construct the attribution graph.
|
| 639 |
+
graph = self.attribution_graph.construct_graph(
|
| 640 |
+
input_tokens, feature_activations, target_token_idx
|
| 641 |
+
)
|
| 642 |
+
|
| 643 |
+
# Prune the graph.
|
| 644 |
+
pruned_graph = self.attribution_graph.prune_graph(self.config.pruning_threshold)
|
| 645 |
+
|
| 646 |
+
# Analyze the most important paths.
|
| 647 |
+
important_paths = []
|
| 648 |
+
if len(pruned_graph.nodes()) > 0:
|
| 649 |
+
# Find paths from embeddings to the output.
|
| 650 |
+
embedding_nodes = [node for node, type_ in self.attribution_graph.node_types.items()
|
| 651 |
+
if type_ == "embedding" and node in pruned_graph]
|
| 652 |
+
output_nodes = [node for node, type_ in self.attribution_graph.node_types.items()
|
| 653 |
+
if type_ == "output" and node in pruned_graph]
|
| 654 |
+
|
| 655 |
+
for emb_node in embedding_nodes[:3]:
|
| 656 |
+
for out_node in output_nodes:
|
| 657 |
+
try:
|
| 658 |
+
paths = list(nx.all_simple_paths(pruned_graph, emb_node, out_node, cutoff=5))
|
| 659 |
+
for path in paths[:2]:
|
| 660 |
+
path_weight = 1.0
|
| 661 |
+
for i in range(len(path) - 1):
|
| 662 |
+
edge_weight = self.attribution_graph.edge_weights.get(
|
| 663 |
+
(path[i], path[i+1]), 0.0
|
| 664 |
+
)
|
| 665 |
+
path_weight *= abs(edge_weight)
|
| 666 |
+
|
| 667 |
+
important_paths.append({
|
| 668 |
+
'path': path,
|
| 669 |
+
'weight': path_weight,
|
| 670 |
+
'description': self._describe_path(path)
|
| 671 |
+
})
|
| 672 |
+
except nx.NetworkXNoPath:
|
| 673 |
+
continue
|
| 674 |
+
|
| 675 |
+
# Sort paths by importance.
|
| 676 |
+
important_paths.sort(key=lambda x: x['weight'], reverse=True)
|
| 677 |
+
|
| 678 |
+
results = {
|
| 679 |
+
'prompt': prompt,
|
| 680 |
+
'input_tokens': input_tokens,
|
| 681 |
+
'feature_visualizations': feature_visualizations,
|
| 682 |
+
'full_graph_stats': {
|
| 683 |
+
'n_nodes': len(graph.nodes()),
|
| 684 |
+
'n_edges': len(graph.edges()),
|
| 685 |
+
'node_types': dict(self.attribution_graph.node_types)
|
| 686 |
+
},
|
| 687 |
+
'pruned_graph_stats': {
|
| 688 |
+
'n_nodes': len(pruned_graph.nodes()),
|
| 689 |
+
'n_edges': len(pruned_graph.edges())
|
| 690 |
+
},
|
| 691 |
+
'important_paths': important_paths[:5],
|
| 692 |
+
'graph': pruned_graph
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
+
return results
|
| 696 |
+
|
| 697 |
+
def _describe_path(self, path: List[str]) -> str:
|
| 698 |
+
# Generates a human-readable description of a path.
|
| 699 |
+
descriptions = []
|
| 700 |
+
for node in path:
|
| 701 |
+
if self.attribution_graph.node_types[node] == "embedding":
|
| 702 |
+
token = node.split('_')[2]
|
| 703 |
+
descriptions.append(f"Token '{token}'")
|
| 704 |
+
elif self.attribution_graph.node_types[node] == "feature":
|
| 705 |
+
parts = node.split('_')
|
| 706 |
+
layer = parts[1][1:]
|
| 707 |
+
feature = parts[3][1:]
|
| 708 |
+
# Try to get the interpretation.
|
| 709 |
+
key = f"L{layer}_F{feature}"
|
| 710 |
+
interpretation = self.feature_visualizer.feature_interpretations.get(key, "unknown")
|
| 711 |
+
descriptions.append(f"Feature L{layer}F{feature} ({interpretation})")
|
| 712 |
+
elif self.attribution_graph.node_types[node] == "output":
|
| 713 |
+
descriptions.append("Output")
|
| 714 |
+
|
| 715 |
+
return " → ".join(descriptions)
|
| 716 |
+
|
| 717 |
+
def save_results(self, results: Dict, save_path: str):
|
| 718 |
+
# Saves the analysis results to a file.
|
| 719 |
+
# Convert the graph to a serializable format.
|
| 720 |
+
serializable_results = results.copy()
|
| 721 |
+
if 'graph' in serializable_results:
|
| 722 |
+
graph_data = nx.node_link_data(serializable_results['graph'])
|
| 723 |
+
serializable_results['graph'] = graph_data
|
| 724 |
+
|
| 725 |
+
with open(save_path, 'w', encoding='utf-8') as f:
|
| 726 |
+
json.dump(serializable_results, f, indent=2, default=str, ensure_ascii=False)
|
| 727 |
+
|
| 728 |
+
logger.info(f"Results saved to {save_path}")
|
| 729 |
+
|
| 730 |
+
def save_clt(self, path: str):
|
| 731 |
+
# Saves the trained CLT model.
|
| 732 |
+
torch.save(self.clt.state_dict(), path)
|
| 733 |
+
logger.info(f"CLT model saved to {path}")
|
| 734 |
+
|
| 735 |
+
def load_clt(self, path: str):
|
| 736 |
+
# Loads a trained CLT model.
|
| 737 |
+
self.clt.load_state_dict(torch.load(path, map_location=self.device))
|
| 738 |
+
self.clt.to(self.device)
|
| 739 |
+
self.clt.eval()
|
| 740 |
+
logger.info(f"Loaded CLT model from {path}")
|
| 741 |
+
|
| 742 |
+
# --- Configuration ---
|
| 743 |
+
MAX_SEQ_LEN = 256
|
| 744 |
+
N_FEATURES_PER_LAYER = 512
|
| 745 |
+
TRAINING_STEPS = 2500
|
| 746 |
+
BATCH_SIZE = 64
|
| 747 |
+
LEARNING_RATE = 1e-3
|
| 748 |
+
|
| 749 |
+
# German prompts for the final analysis.
|
| 750 |
+
ANALYSIS_PROMPTS = [
|
| 751 |
+
"Die Hauptstadt von Frankreich ist",
|
| 752 |
+
"def fakultaet(n):",
|
| 753 |
+
"Das literarische Stilmittel im Satz 'Der Wind flüsterte durch die Bäume' ist"
|
| 754 |
+
]
|
| 755 |
+
|
| 756 |
+
# A larger set of German prompts for training.
|
| 757 |
+
TRAINING_PROMPTS = [
|
| 758 |
+
"Die Hauptstadt von Frankreich ist", "Sein oder Nichtsein, das ist hier die Frage", "Was du heute kannst besorgen, das verschiebe nicht auf morgen",
|
| 759 |
+
"Der erste Mensch auf dem Mond war", "Die chemische Formel für Wasser ist H2O.",
|
| 760 |
+
"Übersetze ins Englische: 'Die Katze sitzt auf der Matte.'", "def fakultaet(n):", "import numpy as np",
|
| 761 |
+
"Die Hauptzutaten einer Pizza sind", "Was ist das Kraftwerk der Zelle?",
|
| 762 |
+
"Die Gleichung E=mc^2 beschreibt die Beziehung zwischen Energie und", "Setze die Geschichte fort: Es war einmal, da war ein",
|
| 763 |
+
"Klassifiziere das Sentiment: 'Ich bin überglücklich!'", "Extrahiere die Entitäten: 'Apple Inc. ist in Cupertino.'",
|
| 764 |
+
"Was ist die nächste Zahl: 2, 4, 8, 16, __?", "Ein rollender Stein setzt kein Moos an",
|
| 765 |
+
"Das Gegenteil von heiß ist", "import torch", "import pandas as pd", "class MeineKlasse:",
|
| 766 |
+
"def __init__(self):", "Die Primärfarben sind", "Was ist die Hauptstadt von Japan?",
|
| 767 |
+
"Wer hat 'Hamlet' geschrieben?", "Die Quadratwurzel von 64 ist", "Die Sonne geht im Osten auf",
|
| 768 |
+
"Der Pazifische Ozean ist der größte Ozean der Erde.", "Die Mitochondrien sind das Kraftwerk der Zelle.",
|
| 769 |
+
"Was ist die Hauptstadt der Mongolei?", "Der Film 'Matrix' kann folgendem Genre zugeordnet werden:",
|
| 770 |
+
"Die englische Übersetzung von 'Ich möchte bitte einen Kaffee bestellen.' lautet:",
|
| 771 |
+
"Das literarische Stilmittel im Satz 'Der Wind flüsterte durch die Bäume' ist",
|
| 772 |
+
"Eine Python-Funktion, die die Fakultät einer Zahl berechnet, lautet:",
|
| 773 |
+
"Die Hauptzutat eines Negroni-Cocktails ist",
|
| 774 |
+
"Fasse die Handlung von 'Hamlet' in einem Satz zusammen:",
|
| 775 |
+
"Der Satz 'Der Kuchen wurde vom Hund gefressen' steht in folgender Form:",
|
| 776 |
+
"Eine gute Überschrift für einen Artikel über einen neuen Durchbruch in der Batterietechnologie wäre:"
|
| 777 |
+
]
|
| 778 |
+
|
| 779 |
+
# --- Qwen API for Feature Interpretation ---
|
| 780 |
+
@torch.no_grad()
|
| 781 |
+
def get_feature_interpretation_with_qwen(
|
| 782 |
+
api_config: dict,
|
| 783 |
+
top_tokens: list[str],
|
| 784 |
+
feature_name: str,
|
| 785 |
+
layer_index: int,
|
| 786 |
+
max_retries: int = 3,
|
| 787 |
+
initial_backoff: float = 2.0
|
| 788 |
+
) -> str:
|
| 789 |
+
# Generates a high-quality interpretation for a feature using the Qwen API.
|
| 790 |
+
if not api_config or not api_config.get('api_key'):
|
| 791 |
+
logger.warning("Qwen API not configured. Skipping interpretation.")
|
| 792 |
+
return "API not configured"
|
| 793 |
+
|
| 794 |
+
headers = {
|
| 795 |
+
"Authorization": f"Bearer {api_config['api_key']}",
|
| 796 |
+
"Content-Type": "application/json"
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
# Create a specialized German prompt.
|
| 800 |
+
prompt_text = f"""
|
| 801 |
+
Sie sind ein Experte für die Interpretierbarkeit von Transformern. Ein Merkmal in einem Sprachmodell (Merkmal '{feature_name}' auf Schicht {layer_index}) wird am stärksten durch die folgenden Token aktiviert:
|
| 802 |
+
|
| 803 |
+
{', '.join(f"'{token}'" for token in top_tokens)}
|
| 804 |
+
|
| 805 |
+
Was ist, basierend *nur* auf diesen Token, die wahrscheinlichste Funktion oder Rolle dieses Merkmals?
|
| 806 |
+
Ihre Antwort muss ein kurzer, prägnanter Ausdruck sein (z.B. "Erkennen von Eigennamen", "Identifizieren von JSON-Syntax", "Vervollständigen von Listen", "Erkennen negativer Stimmung"). Schreiben Sie keinen ganzen Satz.
|
| 807 |
+
"""
|
| 808 |
+
|
| 809 |
+
data = {
|
| 810 |
+
"model": api_config["model"],
|
| 811 |
+
"messages": [
|
| 812 |
+
{
|
| 813 |
+
"role": "user",
|
| 814 |
+
"content": [{"type": "text", "text": prompt_text}]
|
| 815 |
+
}
|
| 816 |
+
],
|
| 817 |
+
"max_tokens": 50,
|
| 818 |
+
"temperature": 0.1,
|
| 819 |
+
"top_p": 0.9,
|
| 820 |
+
"seed": 42
|
| 821 |
+
}
|
| 822 |
+
|
| 823 |
+
logger.info(f" > Interpreting {feature_name} (Layer {layer_index})...")
|
| 824 |
+
|
| 825 |
+
for attempt in range(max_retries):
|
| 826 |
+
try:
|
| 827 |
+
logger.info(f" - Attempt {attempt + 1}/{max_retries}: Sending request to Qwen API...")
|
| 828 |
+
response = requests.post(
|
| 829 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 830 |
+
headers=headers,
|
| 831 |
+
json=data,
|
| 832 |
+
timeout=60
|
| 833 |
+
)
|
| 834 |
+
response.raise_for_status()
|
| 835 |
+
|
| 836 |
+
result = response.json()
|
| 837 |
+
interpretation = result["choices"][0]["message"]["content"].strip()
|
| 838 |
+
|
| 839 |
+
# Remove quotes from the output.
|
| 840 |
+
if interpretation.startswith('"') and interpretation.endswith('"'):
|
| 841 |
+
interpretation = interpretation[1:-1]
|
| 842 |
+
|
| 843 |
+
logger.info(f" - Success! Interpretation: '{interpretation}'")
|
| 844 |
+
return interpretation
|
| 845 |
+
|
| 846 |
+
except requests.exceptions.RequestException as e:
|
| 847 |
+
logger.warning(f" - Qwen API request failed (Attempt {attempt + 1}/{max_retries}): {e}")
|
| 848 |
+
if attempt < max_retries - 1:
|
| 849 |
+
backoff_time = initial_backoff * (2 ** attempt)
|
| 850 |
+
logger.info(f" - Retrying in {backoff_time:.1f} seconds...")
|
| 851 |
+
time.sleep(backoff_time)
|
| 852 |
+
else:
|
| 853 |
+
logger.error(" - Max retries reached. Failing.")
|
| 854 |
+
return f"API Error: {e}"
|
| 855 |
+
except (KeyError, IndexError) as e:
|
| 856 |
+
logger.error(f" - Failed to parse Qwen API response: {e}")
|
| 857 |
+
return "API Error: Invalid response format"
|
| 858 |
+
finally:
|
| 859 |
+
# Add a delay to respect API rate limits.
|
| 860 |
+
time.sleep(2.1)
|
| 861 |
+
|
| 862 |
+
return "API Error: Max retries exceeded"
|
| 863 |
+
|
| 864 |
+
|
| 865 |
+
def train_transcoder(transcoder, model, tokenizer, training_prompts, device, steps=1000, batch_size=16, optimizer=None):
|
| 866 |
+
# Trains the Cross-Layer Transcoder.
|
| 867 |
+
transcoder.train()
|
| 868 |
+
|
| 869 |
+
# Use a progress bar for visual feedback.
|
| 870 |
+
progress_bar = tqdm(range(steps), desc="Training CLT")
|
| 871 |
+
|
| 872 |
+
for step in progress_bar:
|
| 873 |
+
# Get a random batch of prompts.
|
| 874 |
+
batch_prompts = random.choices(training_prompts, k=batch_size)
|
| 875 |
+
|
| 876 |
+
# Tokenize the batch.
|
| 877 |
+
inputs = tokenizer(
|
| 878 |
+
batch_prompts,
|
| 879 |
+
return_tensors="pt",
|
| 880 |
+
padding=True,
|
| 881 |
+
truncation=True,
|
| 882 |
+
max_length=MAX_SEQ_LEN
|
| 883 |
+
)
|
| 884 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 885 |
+
|
| 886 |
+
# Get the model activations.
|
| 887 |
+
with torch.no_grad():
|
| 888 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 889 |
+
hidden_states = outputs.hidden_states[1:]
|
| 890 |
+
|
| 891 |
+
# Forward pass through the CLT.
|
| 892 |
+
feature_activations, reconstructed_outputs = transcoder(hidden_states)
|
| 893 |
+
|
| 894 |
+
# Compute the reconstruction loss.
|
| 895 |
+
recon_loss = 0.0
|
| 896 |
+
for i, (target, pred) in enumerate(zip(hidden_states, reconstructed_outputs)):
|
| 897 |
+
recon_loss += F.mse_loss(pred, target)
|
| 898 |
+
|
| 899 |
+
# Compute the sparsity loss.
|
| 900 |
+
sparsity_loss = 0.0
|
| 901 |
+
for features in feature_activations:
|
| 902 |
+
sparsity_loss += torch.mean(torch.tanh(0.01 * features))
|
| 903 |
+
|
| 904 |
+
# Total loss.
|
| 905 |
+
loss = (0.8 * recon_loss + 0.2 * sparsity_loss)
|
| 906 |
+
|
| 907 |
+
if optimizer:
|
| 908 |
+
optimizer.zero_grad()
|
| 909 |
+
loss.backward()
|
| 910 |
+
optimizer.step()
|
| 911 |
+
|
| 912 |
+
progress_bar.set_postfix({
|
| 913 |
+
"Recon Loss": f"{recon_loss.item():.4f}",
|
| 914 |
+
"Sparsity Loss": f"{sparsity_loss.item():.4f}",
|
| 915 |
+
"Total Loss": f"{loss.item():.4f}"
|
| 916 |
+
})
|
| 917 |
+
|
| 918 |
+
def generate_feature_visualizations(transcoder, model, tokenizer, prompt, device, qwen_api_config=None, graph_config: Optional[AttributionGraphConfig] = None):
|
| 919 |
+
# Generates feature visualizations and interpretations for a prompt.
|
| 920 |
+
# Tokenize the prompt.
|
| 921 |
+
inputs = tokenizer(
|
| 922 |
+
prompt,
|
| 923 |
+
return_tensors="pt",
|
| 924 |
+
padding=True,
|
| 925 |
+
truncation=True,
|
| 926 |
+
max_length=MAX_SEQ_LEN
|
| 927 |
+
)
|
| 928 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 929 |
+
|
| 930 |
+
# Get the model activations.
|
| 931 |
+
with torch.no_grad():
|
| 932 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 933 |
+
hidden_states = outputs.hidden_states[1:]
|
| 934 |
+
|
| 935 |
+
# Forward pass through the CLT.
|
| 936 |
+
feature_activations, reconstructed_outputs = transcoder(hidden_states)
|
| 937 |
+
|
| 938 |
+
# Visualize the features.
|
| 939 |
+
feature_visualizations = {}
|
| 940 |
+
for layer_idx, features in enumerate(feature_activations):
|
| 941 |
+
layer_viz = {}
|
| 942 |
+
# Analyze the top features for this layer.
|
| 943 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 944 |
+
feature_importance = torch.mean(features, dim=(0, 1))
|
| 945 |
+
top_features = torch.topk(feature_importance, k=min(5, feature_importance.size(0))).indices
|
| 946 |
+
|
| 947 |
+
for feat_idx in top_features:
|
| 948 |
+
viz = FeatureVisualizer(tokenizer).visualize_feature(
|
| 949 |
+
feat_idx.item(), layer_idx, features[0], tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
|
| 950 |
+
)
|
| 951 |
+
interpretation = FeatureVisualizer(tokenizer).interpret_feature(
|
| 952 |
+
feat_idx.item(), layer_idx, viz, qwen_api_config
|
| 953 |
+
)
|
| 954 |
+
viz['interpretation'] = interpretation
|
| 955 |
+
layer_viz[f"feature_{feat_idx.item()}"] = viz
|
| 956 |
+
|
| 957 |
+
feature_visualizations[f"layer_{layer_idx}"] = layer_viz
|
| 958 |
+
|
| 959 |
+
# Construct the attribution graph.
|
| 960 |
+
if graph_config is None:
|
| 961 |
+
graph_config = AttributionGraphConfig()
|
| 962 |
+
attribution_graph = AttributionGraph(transcoder, tokenizer, graph_config)
|
| 963 |
+
graph = attribution_graph.construct_graph(
|
| 964 |
+
tokenizer.convert_ids_to_tokens(inputs["input_ids"][0]), feature_activations, -1
|
| 965 |
+
)
|
| 966 |
+
|
| 967 |
+
# Prune the graph.
|
| 968 |
+
pruned_graph = attribution_graph.prune_graph(0.8)
|
| 969 |
+
|
| 970 |
+
# Analyze the most important paths.
|
| 971 |
+
important_paths = []
|
| 972 |
+
if len(pruned_graph.nodes()) > 0:
|
| 973 |
+
# Find paths from embeddings to the output.
|
| 974 |
+
embedding_nodes = [node for node, type_ in attribution_graph.node_types.items()
|
| 975 |
+
if type_ == "embedding" and node in pruned_graph]
|
| 976 |
+
output_nodes = [node for node, type_ in attribution_graph.node_types.items()
|
| 977 |
+
if type_ == "output" and node in pruned_graph]
|
| 978 |
+
|
| 979 |
+
for emb_node in embedding_nodes[:3]:
|
| 980 |
+
for out_node in output_nodes:
|
| 981 |
+
try:
|
| 982 |
+
paths = list(nx.all_simple_paths(pruned_graph, emb_node, out_node, cutoff=5))
|
| 983 |
+
for path in paths[:2]:
|
| 984 |
+
path_weight = 1.0
|
| 985 |
+
for i in range(len(path) - 1):
|
| 986 |
+
edge_weight = attribution_graph.edge_weights.get(
|
| 987 |
+
(path[i], path[i+1]), 0.0
|
| 988 |
+
)
|
| 989 |
+
path_weight *= abs(edge_weight)
|
| 990 |
+
|
| 991 |
+
important_paths.append({
|
| 992 |
+
'path': path,
|
| 993 |
+
'weight': path_weight,
|
| 994 |
+
'description': attribution_graph._describe_path(path)
|
| 995 |
+
})
|
| 996 |
+
except nx.NetworkXNoPath:
|
| 997 |
+
continue
|
| 998 |
+
|
| 999 |
+
# Sort paths by importance.
|
| 1000 |
+
important_paths.sort(key=lambda x: x['weight'], reverse=True)
|
| 1001 |
+
|
| 1002 |
+
return {
|
| 1003 |
+
"prompt": prompt,
|
| 1004 |
+
"full_graph_stats": {
|
| 1005 |
+
"n_nodes": len(graph.nodes()),
|
| 1006 |
+
"n_edges": len(graph.edges()),
|
| 1007 |
+
"node_types": dict(attribution_graph.node_types)
|
| 1008 |
+
},
|
| 1009 |
+
"pruned_graph_stats": {
|
| 1010 |
+
"n_nodes": len(pruned_graph.nodes()),
|
| 1011 |
+
"n_edges": len(pruned_graph.edges())
|
| 1012 |
+
},
|
| 1013 |
+
"feature_visualizations": feature_visualizations,
|
| 1014 |
+
"important_paths": important_paths[:5]
|
| 1015 |
+
}
|
| 1016 |
+
|
| 1017 |
+
def main():
|
| 1018 |
+
# Main function to run the analysis for a single prompt.
|
| 1019 |
+
|
| 1020 |
+
# Set a seed for reproducibility.
|
| 1021 |
+
set_seed()
|
| 1022 |
+
|
| 1023 |
+
# --- Argument Parser ---
|
| 1024 |
+
parser = argparse.ArgumentParser(description="Run Attribution Graph analysis for a single prompt.")
|
| 1025 |
+
parser.add_argument(
|
| 1026 |
+
'--prompt-index',
|
| 1027 |
+
type=int,
|
| 1028 |
+
required=True,
|
| 1029 |
+
help=f"The 0-based index of the prompt to analyze from the ANALYSIS_PROMPTS list (0 to {len(ANALYSIS_PROMPTS) - 1})."
|
| 1030 |
+
)
|
| 1031 |
+
parser.add_argument(
|
| 1032 |
+
'--force-retrain-clt',
|
| 1033 |
+
action='store_true',
|
| 1034 |
+
help="Force re-training of the Cross-Layer Transcoder, even if a saved model exists."
|
| 1035 |
+
)
|
| 1036 |
+
args = parser.parse_args()
|
| 1037 |
+
|
| 1038 |
+
prompt_idx = args.prompt_index
|
| 1039 |
+
if not (0 <= prompt_idx < len(ANALYSIS_PROMPTS)):
|
| 1040 |
+
print(f"❌ Error: --prompt-index must be between 0 and {len(ANALYSIS_PROMPTS) - 1}.")
|
| 1041 |
+
return
|
| 1042 |
+
|
| 1043 |
+
# Get the API config from the utility function.
|
| 1044 |
+
qwen_api_config = init_qwen_api()
|
| 1045 |
+
|
| 1046 |
+
# Configuration
|
| 1047 |
+
config = AttributionGraphConfig(
|
| 1048 |
+
model_path="./models/OLMo-2-1124-7B",
|
| 1049 |
+
n_features_per_layer=512,
|
| 1050 |
+
training_steps=500,
|
| 1051 |
+
batch_size=4,
|
| 1052 |
+
max_seq_length=256,
|
| 1053 |
+
learning_rate=1e-4,
|
| 1054 |
+
sparsity_lambda=0.01,
|
| 1055 |
+
qwen_api_config=qwen_api_config
|
| 1056 |
+
)
|
| 1057 |
+
|
| 1058 |
+
print("Attribution Graphs for OLMo2 7B - Single Prompt Pipeline (German)")
|
| 1059 |
+
print("=" * 50)
|
| 1060 |
+
print(f"Model path: {config.model_path}")
|
| 1061 |
+
print(f"Device: {config.device}")
|
| 1062 |
+
|
| 1063 |
+
try:
|
| 1064 |
+
# Initialize the full pipeline.
|
| 1065 |
+
print("🚀 Initializing Attribution Graphs Pipeline...")
|
| 1066 |
+
pipeline = AttributionGraphsPipeline(config)
|
| 1067 |
+
print("✓ Pipeline initialized successfully")
|
| 1068 |
+
print()
|
| 1069 |
+
|
| 1070 |
+
# Load an existing CLT model or train a new one.
|
| 1071 |
+
if os.path.exists(CLT_SAVE_PATH) and not args.force_retrain_clt:
|
| 1072 |
+
print(f"🧠 Loading existing CLT model from {CLT_SAVE_PATH}...")
|
| 1073 |
+
pipeline.load_clt(CLT_SAVE_PATH)
|
| 1074 |
+
print("✓ CLT model loaded successfully.")
|
| 1075 |
+
else:
|
| 1076 |
+
if args.force_retrain_clt and os.path.exists(CLT_SAVE_PATH):
|
| 1077 |
+
print("🏃♂️ --force-retrain-clt flag is set. Overwriting existing model.")
|
| 1078 |
+
|
| 1079 |
+
# Train a new CLT model.
|
| 1080 |
+
print("📚 Training a new CLT model...")
|
| 1081 |
+
print(f" Training on {len(TRAINING_PROMPTS)} example texts...")
|
| 1082 |
+
training_stats = pipeline.train_clt(TRAINING_PROMPTS)
|
| 1083 |
+
print("✓ CLT training completed.")
|
| 1084 |
+
|
| 1085 |
+
# Save the new model.
|
| 1086 |
+
pipeline.save_clt(CLT_SAVE_PATH)
|
| 1087 |
+
print(f" Saved trained model to {CLT_SAVE_PATH} for future use.")
|
| 1088 |
+
|
| 1089 |
+
print()
|
| 1090 |
+
|
| 1091 |
+
# Analyze the selected prompt.
|
| 1092 |
+
prompt_to_analyze = ANALYSIS_PROMPTS[prompt_idx]
|
| 1093 |
+
print(f"🔍 Analyzing prompt {prompt_idx + 1}/{len(ANALYSIS_PROMPTS)}: '{prompt_to_analyze}'")
|
| 1094 |
+
|
| 1095 |
+
analysis = pipeline.analyze_prompt(prompt_to_analyze, target_token_idx=-1)
|
| 1096 |
+
|
| 1097 |
+
# Display the key results.
|
| 1098 |
+
print(f" ✓ Tokenized into {len(analysis['input_tokens'])} tokens")
|
| 1099 |
+
print(f" ✓ Full graph: {analysis['full_graph_stats']['n_nodes']} nodes, {analysis['full_graph_stats']['n_edges']} edges")
|
| 1100 |
+
print(f" ✓ Pruned graph: {analysis['pruned_graph_stats']['n_nodes']} nodes, {analysis['pruned_graph_stats']['n_edges']} edges")
|
| 1101 |
+
|
| 1102 |
+
# Show the top features.
|
| 1103 |
+
print(" 📊 Top active features:")
|
| 1104 |
+
for layer_name, layer_features in list(analysis['feature_visualizations'].items())[:3]:
|
| 1105 |
+
print(f" {layer_name}:")
|
| 1106 |
+
for feat_name, feat_data in list(layer_features.items())[:2]:
|
| 1107 |
+
print(f" {feat_name}: {feat_data['interpretation']} (max: {feat_data['max_activation']:.3f})")
|
| 1108 |
+
|
| 1109 |
+
print()
|
| 1110 |
+
|
| 1111 |
+
# Run a perturbation experiment.
|
| 1112 |
+
print("🧪 Running perturbation experiment...")
|
| 1113 |
+
# (No need to pass training_stats to the experiment)
|
| 1114 |
+
if analysis['feature_visualizations']:
|
| 1115 |
+
first_layer_key = next(iter(analysis['feature_visualizations']), None)
|
| 1116 |
+
if first_layer_key:
|
| 1117 |
+
layer_idx = int(first_layer_key.split('_')[1])
|
| 1118 |
+
first_feature_key = next(iter(analysis['feature_visualizations'][first_layer_key]), None)
|
| 1119 |
+
if first_feature_key:
|
| 1120 |
+
feature_idx = int(first_feature_key.split('_')[1])
|
| 1121 |
+
|
| 1122 |
+
ablation_result = pipeline.perturbation_experiments.feature_ablation_experiment(
|
| 1123 |
+
prompt_to_analyze, layer_idx, feature_idx, intervention_strength=3.0
|
| 1124 |
+
)
|
| 1125 |
+
print(f" Ablated L{layer_idx}F{feature_idx}: Δ probability = {ablation_result['probability_change']:.4f}")
|
| 1126 |
+
print("✓ Perturbation experiment completed")
|
| 1127 |
+
print()
|
| 1128 |
+
|
| 1129 |
+
# Generate a visualization for the prompt.
|
| 1130 |
+
print("📈 Generating visualization...")
|
| 1131 |
+
if 'graph' in analysis and analysis['pruned_graph_stats']['n_nodes'] > 0:
|
| 1132 |
+
viz_path = os.path.join(RESULTS_DIR, f"attribution_graph_prompt_de_{prompt_idx + 1}.png")
|
| 1133 |
+
pipeline.attribution_graph.visualize_graph(analysis['graph'], save_path=viz_path)
|
| 1134 |
+
print(f" ✓ Graph visualization saved to {viz_path}")
|
| 1135 |
+
else:
|
| 1136 |
+
print(" - Skipping visualization as no graph was generated or it was empty.")
|
| 1137 |
+
|
| 1138 |
+
# Save the results in a format for the web app.
|
| 1139 |
+
save_path = os.path.join(RESULTS_DIR, f"attribution_graphs_results_de_prompt_{prompt_idx + 1}.json")
|
| 1140 |
+
|
| 1141 |
+
# Create a JSON file that can be merged with others.
|
| 1142 |
+
final_results = {
|
| 1143 |
+
"analyses": {
|
| 1144 |
+
f"prompt_de_{prompt_idx + 1}": analysis
|
| 1145 |
+
},
|
| 1146 |
+
"config": config.__dict__,
|
| 1147 |
+
"timestamp": str(time.time())
|
| 1148 |
+
}
|
| 1149 |
+
|
| 1150 |
+
# The web page doesn't use the graph object, so remove it.
|
| 1151 |
+
if 'graph' in final_results['analyses'][f"prompt_de_{prompt_idx + 1}"]:
|
| 1152 |
+
del final_results['analyses'][f"prompt_de_{prompt_idx + 1}"]['graph']
|
| 1153 |
+
|
| 1154 |
+
pipeline.save_results(final_results, save_path)
|
| 1155 |
+
print(f"💾 Results saved to {save_path}")
|
| 1156 |
+
|
| 1157 |
+
print("\n🎉 Analysis for this prompt completed successfully!")
|
| 1158 |
+
|
| 1159 |
+
except Exception as e:
|
| 1160 |
+
print(f"❌ Error during execution: {e}")
|
| 1161 |
+
import traceback
|
| 1162 |
+
traceback.print_exc()
|
| 1163 |
+
|
| 1164 |
+
if __name__ == "__main__":
|
| 1165 |
+
main()
|
circuit_analysis/attribution_graphs_olmo_offline.py
ADDED
|
@@ -0,0 +1,1922 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# This script generates attribution graphs for the OLMo2 7B model.
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
import numpy as np
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
import seaborn as sns
|
| 10 |
+
from typing import Dict, List, Tuple, Optional, Any, Set
|
| 11 |
+
import json
|
| 12 |
+
import logging
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 15 |
+
from collections import defaultdict
|
| 16 |
+
import networkx as nx
|
| 17 |
+
from dataclasses import dataclass
|
| 18 |
+
from tqdm import tqdm
|
| 19 |
+
import pickle
|
| 20 |
+
import requests
|
| 21 |
+
import time
|
| 22 |
+
import random
|
| 23 |
+
import copy
|
| 24 |
+
import os
|
| 25 |
+
import argparse
|
| 26 |
+
|
| 27 |
+
# --- Add this block to fix the import path ---
|
| 28 |
+
import sys
|
| 29 |
+
from pathlib import Path
|
| 30 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 31 |
+
# ---------------------------------------------
|
| 32 |
+
|
| 33 |
+
from utilities.utils import init_qwen_api, set_seed
|
| 34 |
+
|
| 35 |
+
# --- Constants ---
|
| 36 |
+
RESULTS_DIR = "circuit_analysis/results"
|
| 37 |
+
CLT_SAVE_PATH = "circuit_analysis/models/clt_model.pth"
|
| 38 |
+
|
| 39 |
+
# Configure logging.
|
| 40 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 41 |
+
logger = logging.getLogger(__name__)
|
| 42 |
+
|
| 43 |
+
# Set the device for training.
|
| 44 |
+
if torch.backends.mps.is_available():
|
| 45 |
+
DEVICE = torch.device("mps")
|
| 46 |
+
logger.info("Using MPS (Metal Performance Shaders) for GPU acceleration")
|
| 47 |
+
elif torch.cuda.is_available():
|
| 48 |
+
DEVICE = torch.device("cuda")
|
| 49 |
+
logger.info("Using CUDA for GPU acceleration")
|
| 50 |
+
else:
|
| 51 |
+
DEVICE = torch.device("cpu")
|
| 52 |
+
logger.info("Using CPU")
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class AttributionGraphConfig:
|
| 56 |
+
# Configuration for building the attribution graph.
|
| 57 |
+
model_path: str = "./models/OLMo-2-1124-7B"
|
| 58 |
+
max_seq_length: int = 512
|
| 59 |
+
n_features_per_layer: int = 512 # Number of features in each CLT layer
|
| 60 |
+
sparsity_lambda: float = 0.01
|
| 61 |
+
reconstruction_loss_weight: float = 1.0
|
| 62 |
+
batch_size: int = 8
|
| 63 |
+
learning_rate: float = 1e-4
|
| 64 |
+
training_steps: int = 1000
|
| 65 |
+
device: str = str(DEVICE)
|
| 66 |
+
pruning_threshold: float = 0.8 # For graph pruning
|
| 67 |
+
intervention_strength: float = 5.0 # For perturbation experiments
|
| 68 |
+
qwen_api_config: Optional[Dict[str, str]] = None
|
| 69 |
+
max_ablation_experiments: Optional[int] = None
|
| 70 |
+
ablation_top_k_tokens: int = 5
|
| 71 |
+
ablation_features_per_layer: Optional[int] = 6
|
| 72 |
+
summary_max_layers: Optional[int] = None
|
| 73 |
+
summary_features_per_layer: Optional[int] = 2
|
| 74 |
+
feature_visualization_top_k: int = 24
|
| 75 |
+
random_baseline_trials: int = 12
|
| 76 |
+
random_baseline_features: int = 1
|
| 77 |
+
random_baseline_seed: int = 1234
|
| 78 |
+
path_ablation_top_k: int = 6
|
| 79 |
+
path_search_cutoff: int = 6
|
| 80 |
+
random_path_baseline_trials: int = 12
|
| 81 |
+
graph_max_features_per_layer: int = 48
|
| 82 |
+
graph_feature_activation_threshold: float = 0.01
|
| 83 |
+
graph_edge_weight_threshold: float = 0.0
|
| 84 |
+
graph_max_edges_per_node: int = 12
|
| 85 |
+
|
| 86 |
+
class JumpReLU(nn.Module):
|
| 87 |
+
# The JumpReLU activation function.
|
| 88 |
+
|
| 89 |
+
def __init__(self, threshold: float = 0.0):
|
| 90 |
+
super().__init__()
|
| 91 |
+
self.threshold = threshold
|
| 92 |
+
|
| 93 |
+
def forward(self, x):
|
| 94 |
+
return F.relu(x - self.threshold)
|
| 95 |
+
|
| 96 |
+
class CrossLayerTranscoder(nn.Module):
|
| 97 |
+
# The Cross-Layer Transcoder (CLT) model.
|
| 98 |
+
|
| 99 |
+
def __init__(self, model_config: Dict, clt_config: AttributionGraphConfig):
|
| 100 |
+
super().__init__()
|
| 101 |
+
self.config = clt_config
|
| 102 |
+
self.model_config = model_config
|
| 103 |
+
self.n_layers = model_config['num_hidden_layers']
|
| 104 |
+
self.hidden_size = model_config['hidden_size']
|
| 105 |
+
self.n_features = clt_config.n_features_per_layer
|
| 106 |
+
|
| 107 |
+
# Encoder weights for each layer.
|
| 108 |
+
self.encoders = nn.ModuleList([
|
| 109 |
+
nn.Linear(self.hidden_size, self.n_features, bias=False)
|
| 110 |
+
for _ in range(self.n_layers)
|
| 111 |
+
])
|
| 112 |
+
|
| 113 |
+
# Decoder weights for cross-layer connections.
|
| 114 |
+
self.decoders = nn.ModuleDict()
|
| 115 |
+
for source_layer in range(self.n_layers):
|
| 116 |
+
for target_layer in range(source_layer, self.n_layers):
|
| 117 |
+
key = f"{source_layer}_to_{target_layer}"
|
| 118 |
+
self.decoders[key] = nn.Linear(self.n_features, self.hidden_size, bias=False)
|
| 119 |
+
|
| 120 |
+
# The activation function.
|
| 121 |
+
self.activation = JumpReLU(threshold=0.0)
|
| 122 |
+
|
| 123 |
+
# Initialize the weights.
|
| 124 |
+
self._init_weights()
|
| 125 |
+
|
| 126 |
+
def _init_weights(self):
|
| 127 |
+
# Initializes the weights with small random values.
|
| 128 |
+
for module in self.modules():
|
| 129 |
+
if isinstance(module, nn.Linear):
|
| 130 |
+
nn.init.normal_(module.weight, mean=0.0, std=0.01)
|
| 131 |
+
|
| 132 |
+
def encode(self, layer_idx: int, residual_activations: torch.Tensor) -> torch.Tensor:
|
| 133 |
+
# Encodes residual stream activations to feature activations.
|
| 134 |
+
return self.activation(self.encoders[layer_idx](residual_activations))
|
| 135 |
+
|
| 136 |
+
def decode(self, source_layer: int, target_layer: int, feature_activations: torch.Tensor) -> torch.Tensor:
|
| 137 |
+
# Decodes feature activations to the MLP output space.
|
| 138 |
+
key = f"{source_layer}_to_{target_layer}"
|
| 139 |
+
return self.decoders[key](feature_activations)
|
| 140 |
+
|
| 141 |
+
def forward(self, residual_activations: List[torch.Tensor]) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:
|
| 142 |
+
# The forward pass of the CLT.
|
| 143 |
+
feature_activations = []
|
| 144 |
+
reconstructed_mlp_outputs = []
|
| 145 |
+
|
| 146 |
+
# Encode features for each layer.
|
| 147 |
+
for layer_idx, residual in enumerate(residual_activations):
|
| 148 |
+
features = self.encode(layer_idx, residual)
|
| 149 |
+
feature_activations.append(features)
|
| 150 |
+
|
| 151 |
+
# Reconstruct MLP outputs with cross-layer connections.
|
| 152 |
+
for target_layer in range(self.n_layers):
|
| 153 |
+
reconstruction = torch.zeros_like(residual_activations[target_layer])
|
| 154 |
+
|
| 155 |
+
# Sum contributions from all previous layers.
|
| 156 |
+
for source_layer in range(target_layer + 1):
|
| 157 |
+
decoded = self.decode(source_layer, target_layer, feature_activations[source_layer])
|
| 158 |
+
reconstruction += decoded
|
| 159 |
+
|
| 160 |
+
reconstructed_mlp_outputs.append(reconstruction)
|
| 161 |
+
|
| 162 |
+
return feature_activations, reconstructed_mlp_outputs
|
| 163 |
+
|
| 164 |
+
class FeatureVisualizer:
|
| 165 |
+
# A class to visualize and interpret individual features.
|
| 166 |
+
|
| 167 |
+
def __init__(self, tokenizer, cache_dir: Optional[Path] = None):
|
| 168 |
+
self.tokenizer = tokenizer
|
| 169 |
+
self.feature_interpretations: Dict[str, str] = {}
|
| 170 |
+
self.cache_dir = cache_dir
|
| 171 |
+
if self.cache_dir is not None:
|
| 172 |
+
self.cache_dir = Path(self.cache_dir)
|
| 173 |
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
| 174 |
+
self._load_cache()
|
| 175 |
+
|
| 176 |
+
def _cache_file(self) -> Optional[Path]:
|
| 177 |
+
if self.cache_dir is None:
|
| 178 |
+
return None
|
| 179 |
+
return self.cache_dir / "feature_interpretations.json"
|
| 180 |
+
|
| 181 |
+
def _load_cache(self):
|
| 182 |
+
cache_file = self._cache_file()
|
| 183 |
+
if cache_file is None or not cache_file.exists():
|
| 184 |
+
return
|
| 185 |
+
try:
|
| 186 |
+
with open(cache_file, 'r', encoding='utf-8') as f:
|
| 187 |
+
data = json.load(f)
|
| 188 |
+
if isinstance(data, dict):
|
| 189 |
+
self.feature_interpretations.update({str(k): str(v) for k, v in data.items()})
|
| 190 |
+
except Exception as e:
|
| 191 |
+
logger.warning(f"Failed to load feature interpretation cache: {e}")
|
| 192 |
+
|
| 193 |
+
def _save_cache(self):
|
| 194 |
+
cache_file = self._cache_file()
|
| 195 |
+
if cache_file is None:
|
| 196 |
+
return
|
| 197 |
+
try:
|
| 198 |
+
with open(cache_file, 'w', encoding='utf-8') as f:
|
| 199 |
+
json.dump(self.feature_interpretations, f, indent=2)
|
| 200 |
+
except Exception as e:
|
| 201 |
+
logger.warning(f"Failed to save feature interpretation cache: {e}")
|
| 202 |
+
|
| 203 |
+
def visualize_feature(self, feature_idx: int, layer_idx: int,
|
| 204 |
+
activations: torch.Tensor, input_tokens: List[str],
|
| 205 |
+
top_k: int = 10) -> Dict:
|
| 206 |
+
# Creates a visualization for a single feature.
|
| 207 |
+
feature_acts = activations[:, feature_idx].detach().cpu().numpy()
|
| 208 |
+
|
| 209 |
+
# Find the top activating positions.
|
| 210 |
+
top_positions = np.argsort(feature_acts)[-top_k:][::-1]
|
| 211 |
+
|
| 212 |
+
visualization = {
|
| 213 |
+
'feature_idx': feature_idx,
|
| 214 |
+
'layer_idx': layer_idx,
|
| 215 |
+
'max_activation': float(feature_acts.max()),
|
| 216 |
+
'mean_activation': float(feature_acts.mean()),
|
| 217 |
+
'sparsity': float((feature_acts > 0.1).mean()),
|
| 218 |
+
'top_activations': []
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
for pos in top_positions:
|
| 222 |
+
if pos < len(input_tokens):
|
| 223 |
+
visualization['top_activations'].append({
|
| 224 |
+
'token': input_tokens[pos],
|
| 225 |
+
'position': int(pos),
|
| 226 |
+
'activation': float(feature_acts[pos])
|
| 227 |
+
})
|
| 228 |
+
|
| 229 |
+
return visualization
|
| 230 |
+
|
| 231 |
+
def interpret_feature(self, feature_idx: int, layer_idx: int,
|
| 232 |
+
visualization_data: Dict,
|
| 233 |
+
qwen_api_config: Optional[Dict[str, str]] = None) -> str:
|
| 234 |
+
# Interprets a feature based on its top activating tokens.
|
| 235 |
+
top_tokens = [item['token'] for item in visualization_data['top_activations']]
|
| 236 |
+
|
| 237 |
+
cache_key = f"L{layer_idx}_F{feature_idx}"
|
| 238 |
+
|
| 239 |
+
if cache_key in self.feature_interpretations:
|
| 240 |
+
return self.feature_interpretations[cache_key]
|
| 241 |
+
|
| 242 |
+
# Use the Qwen API if it is configured.
|
| 243 |
+
if qwen_api_config and qwen_api_config.get('api_key'):
|
| 244 |
+
feature_name = cache_key
|
| 245 |
+
interpretation = get_feature_interpretation_with_qwen(
|
| 246 |
+
qwen_api_config, top_tokens, feature_name, layer_idx
|
| 247 |
+
)
|
| 248 |
+
else:
|
| 249 |
+
# Use a simple heuristic as a fallback.
|
| 250 |
+
if len(set(top_tokens)) == 1 and top_tokens:
|
| 251 |
+
interpretation = f"Specific token: '{top_tokens[0]}'"
|
| 252 |
+
elif top_tokens and all(token.isalpha() for token in top_tokens):
|
| 253 |
+
interpretation = "Word/alphabetic tokens"
|
| 254 |
+
elif top_tokens and all(token.isdigit() for token in top_tokens):
|
| 255 |
+
interpretation = "Numeric tokens"
|
| 256 |
+
elif top_tokens and all(token in '.,!?;:' for token in top_tokens):
|
| 257 |
+
interpretation = "Punctuation"
|
| 258 |
+
else:
|
| 259 |
+
interpretation = "Mixed/polysemantic feature"
|
| 260 |
+
|
| 261 |
+
self.feature_interpretations[cache_key] = interpretation
|
| 262 |
+
self._save_cache()
|
| 263 |
+
return interpretation
|
| 264 |
+
|
| 265 |
+
class AttributionGraph:
|
| 266 |
+
# A class to construct and analyze attribution graphs.
|
| 267 |
+
|
| 268 |
+
def __init__(self, clt: CrossLayerTranscoder, tokenizer, config: AttributionGraphConfig):
|
| 269 |
+
self.clt = clt
|
| 270 |
+
self.tokenizer = tokenizer
|
| 271 |
+
self.config = config
|
| 272 |
+
self.graph = nx.DiGraph()
|
| 273 |
+
self.node_types = {} # Track node types (feature, embedding, error, output)
|
| 274 |
+
self.edge_weights = {}
|
| 275 |
+
self.feature_metadata: Dict[str, Dict[str, Any]] = {}
|
| 276 |
+
|
| 277 |
+
def compute_virtual_weights(self, source_layer: int, target_layer: int,
|
| 278 |
+
source_feature: int, target_feature: int) -> float:
|
| 279 |
+
# Computes the virtual weight between two features.
|
| 280 |
+
if target_layer <= source_layer:
|
| 281 |
+
return 0.0
|
| 282 |
+
|
| 283 |
+
# Get the encoder and decoder weights.
|
| 284 |
+
encoder_weight = self.clt.encoders[target_layer].weight[target_feature] # [hidden_size]
|
| 285 |
+
|
| 286 |
+
total_weight = 0.0
|
| 287 |
+
for intermediate_layer in range(source_layer, target_layer):
|
| 288 |
+
decoder_key = f"{source_layer}_to_{intermediate_layer}"
|
| 289 |
+
if decoder_key in self.clt.decoders:
|
| 290 |
+
decoder_weight = self.clt.decoders[decoder_key].weight[:, source_feature] # [hidden_size]
|
| 291 |
+
# The virtual weight is inner product
|
| 292 |
+
virtual_weight = torch.dot(decoder_weight, encoder_weight).item()
|
| 293 |
+
total_weight += virtual_weight
|
| 294 |
+
|
| 295 |
+
return total_weight
|
| 296 |
+
|
| 297 |
+
def construct_graph(self, input_tokens: List[str],
|
| 298 |
+
feature_activations: List[torch.Tensor],
|
| 299 |
+
target_token_idx: int = -1) -> nx.DiGraph:
|
| 300 |
+
# Constructs the attribution graph for a prompt.
|
| 301 |
+
self.graph.clear()
|
| 302 |
+
self.node_types.clear()
|
| 303 |
+
self.edge_weights.clear()
|
| 304 |
+
|
| 305 |
+
seq_len = len(input_tokens)
|
| 306 |
+
n_layers = len(feature_activations)
|
| 307 |
+
|
| 308 |
+
# Add embedding nodes for the input tokens.
|
| 309 |
+
for i, token in enumerate(input_tokens):
|
| 310 |
+
node_id = f"emb_{i}_{token}"
|
| 311 |
+
self.graph.add_node(node_id)
|
| 312 |
+
self.node_types[node_id] = "embedding"
|
| 313 |
+
|
| 314 |
+
# Add nodes for the features.
|
| 315 |
+
active_features = {} # Track which features are significantly active
|
| 316 |
+
max_features_per_layer = self.config.graph_max_features_per_layer or 20 # Limit features per layer to prevent explosion
|
| 317 |
+
activation_threshold = self.config.graph_feature_activation_threshold
|
| 318 |
+
edge_weight_threshold = self.config.graph_edge_weight_threshold
|
| 319 |
+
max_edges_per_node_cfg = self.config.graph_max_edges_per_node or 5
|
| 320 |
+
|
| 321 |
+
for layer_idx, features in enumerate(feature_activations):
|
| 322 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 323 |
+
batch_size, seq_len_layer, n_features = features.shape
|
| 324 |
+
|
| 325 |
+
# Get the top activating features for this layer.
|
| 326 |
+
layer_activations = features[0].mean(dim=0) # Average across sequence
|
| 327 |
+
top_features = torch.topk(layer_activations,
|
| 328 |
+
k=min(max_features_per_layer, n_features)).indices
|
| 329 |
+
|
| 330 |
+
for token_pos in range(min(seq_len, seq_len_layer)):
|
| 331 |
+
for feat_idx in top_features:
|
| 332 |
+
activation = features[0, token_pos, feat_idx.item()].item()
|
| 333 |
+
if activation > activation_threshold:
|
| 334 |
+
node_id = f"feat_L{layer_idx}_T{token_pos}_F{feat_idx.item()}"
|
| 335 |
+
self.graph.add_node(node_id)
|
| 336 |
+
self.node_types[node_id] = "feature"
|
| 337 |
+
active_features[node_id] = {
|
| 338 |
+
'layer': layer_idx,
|
| 339 |
+
'token_pos': token_pos,
|
| 340 |
+
'feature_idx': feat_idx.item(),
|
| 341 |
+
'activation': activation
|
| 342 |
+
}
|
| 343 |
+
self.feature_metadata[node_id] = {
|
| 344 |
+
'layer': layer_idx,
|
| 345 |
+
'token_position': token_pos,
|
| 346 |
+
'feature_index': feat_idx.item(),
|
| 347 |
+
'activation': activation,
|
| 348 |
+
'input_token': input_tokens[token_pos] if token_pos < len(input_tokens) else None
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
# Add an output node for the target token.
|
| 352 |
+
output_node = f"output_{target_token_idx}"
|
| 353 |
+
self.graph.add_node(output_node)
|
| 354 |
+
self.node_types[output_node] = "output"
|
| 355 |
+
|
| 356 |
+
# Add edges based on virtual weights and activations.
|
| 357 |
+
feature_nodes = [node for node, type_ in self.node_types.items() if type_ == "feature"]
|
| 358 |
+
tqdm.write(f" Building attribution graph: {len(feature_nodes)} feature nodes, {len(self.graph.nodes())} total nodes")
|
| 359 |
+
|
| 360 |
+
# Limit the number of edges to compute.
|
| 361 |
+
max_edges_per_node = max(max_edges_per_node_cfg, 1) # Limit connections per node
|
| 362 |
+
|
| 363 |
+
for i, source_node in enumerate(tqdm(feature_nodes, desc="Adding feature edges")):
|
| 364 |
+
edges_added = 0
|
| 365 |
+
source_info = active_features[source_node]
|
| 366 |
+
source_activation = source_info['activation']
|
| 367 |
+
|
| 368 |
+
for target_node in feature_nodes:
|
| 369 |
+
if source_node == target_node or edges_added >= max_edges_per_node:
|
| 370 |
+
continue
|
| 371 |
+
|
| 372 |
+
target_info = active_features[target_node]
|
| 373 |
+
|
| 374 |
+
if (target_info['layer'] > source_info['layer'] or
|
| 375 |
+
(target_info['layer'] == source_info['layer'] and
|
| 376 |
+
target_info['token_pos'] > source_info['token_pos'])):
|
| 377 |
+
|
| 378 |
+
virtual_weight = self.compute_virtual_weights(
|
| 379 |
+
source_info['layer'], target_info['layer'],
|
| 380 |
+
source_info['feature_idx'], target_info['feature_idx']
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
if abs(virtual_weight) > edge_weight_threshold:
|
| 384 |
+
edge_weight = source_activation * virtual_weight
|
| 385 |
+
self.graph.add_edge(source_node, target_node, weight=edge_weight)
|
| 386 |
+
self.edge_weights[(source_node, target_node)] = edge_weight
|
| 387 |
+
edges_added += 1
|
| 388 |
+
|
| 389 |
+
# Add edges to the output node.
|
| 390 |
+
layer_position = source_info['layer']
|
| 391 |
+
# Allow contributions from all layers, with smaller weights for early layers.
|
| 392 |
+
layer_scale = 0.1 if layer_position >= n_layers - 2 else max(0.05, 0.1 * (layer_position + 1) / n_layers)
|
| 393 |
+
output_weight = source_activation * layer_scale
|
| 394 |
+
if abs(output_weight) > 0:
|
| 395 |
+
self.graph.add_edge(source_node, output_node, weight=output_weight)
|
| 396 |
+
self.edge_weights[(source_node, output_node)] = output_weight
|
| 397 |
+
|
| 398 |
+
first_layer_features = [
|
| 399 |
+
node for node in feature_nodes if active_features[node]['layer'] == 0
|
| 400 |
+
]
|
| 401 |
+
embedding_nodes = [node for node, type_ in self.node_types.items() if type_ == "embedding"]
|
| 402 |
+
|
| 403 |
+
for emb_idx, emb_node in enumerate(tqdm(embedding_nodes, desc="Linking embeddings"), start=1):
|
| 404 |
+
token_idx = int(emb_node.split('_')[1])
|
| 405 |
+
linked = 0
|
| 406 |
+
for feat_node in first_layer_features:
|
| 407 |
+
feat_info = active_features[feat_node]
|
| 408 |
+
if feat_info['token_pos'] == token_idx:
|
| 409 |
+
weight = feat_info['activation'] * 0.5
|
| 410 |
+
self.graph.add_edge(emb_node, feat_node, weight=weight)
|
| 411 |
+
self.edge_weights[(emb_node, feat_node)] = weight
|
| 412 |
+
linked += 1
|
| 413 |
+
|
| 414 |
+
return self.graph
|
| 415 |
+
|
| 416 |
+
def prune_graph(self, threshold: float = 0.8) -> nx.DiGraph:
|
| 417 |
+
# Prunes the graph to keep only the most important nodes.
|
| 418 |
+
# Calculate node importance based on edge weights.
|
| 419 |
+
node_importance = defaultdict(float)
|
| 420 |
+
|
| 421 |
+
for (source, target), weight in self.edge_weights.items():
|
| 422 |
+
node_importance[source] += abs(weight)
|
| 423 |
+
node_importance[target] += abs(weight)
|
| 424 |
+
|
| 425 |
+
# Keep the top nodes by importance.
|
| 426 |
+
sorted_nodes = sorted(node_importance.items(), key=lambda x: x[1], reverse=True)
|
| 427 |
+
n_keep = int(len(sorted_nodes) * threshold)
|
| 428 |
+
important_nodes = set([node for node, _ in sorted_nodes[:n_keep]])
|
| 429 |
+
|
| 430 |
+
# Always keep the output and embedding nodes.
|
| 431 |
+
for node, type_ in self.node_types.items():
|
| 432 |
+
if type_ in ["output", "embedding"]:
|
| 433 |
+
important_nodes.add(node)
|
| 434 |
+
|
| 435 |
+
# Create the pruned graph.
|
| 436 |
+
pruned_graph = self.graph.subgraph(important_nodes).copy()
|
| 437 |
+
|
| 438 |
+
return pruned_graph
|
| 439 |
+
|
| 440 |
+
def visualize_graph(self, graph: nx.DiGraph = None, save_path: str = None):
|
| 441 |
+
# Visualizes the attribution graph.
|
| 442 |
+
if graph is None:
|
| 443 |
+
graph = self.graph
|
| 444 |
+
|
| 445 |
+
plt.figure(figsize=(12, 8))
|
| 446 |
+
|
| 447 |
+
# Create a layout for the graph.
|
| 448 |
+
pos = nx.spring_layout(graph, k=1, iterations=50)
|
| 449 |
+
|
| 450 |
+
# Color the nodes by type.
|
| 451 |
+
node_colors = []
|
| 452 |
+
for node in graph.nodes():
|
| 453 |
+
node_type = self.node_types.get(node, "unknown")
|
| 454 |
+
if node_type == "embedding":
|
| 455 |
+
node_colors.append('lightblue')
|
| 456 |
+
elif node_type == "feature":
|
| 457 |
+
node_colors.append('lightgreen')
|
| 458 |
+
elif node_type == "output":
|
| 459 |
+
node_colors.append('orange')
|
| 460 |
+
else:
|
| 461 |
+
node_colors.append('gray')
|
| 462 |
+
|
| 463 |
+
# Draw the nodes.
|
| 464 |
+
nx.draw_networkx_nodes(graph, pos, node_color=node_colors,
|
| 465 |
+
node_size=300, alpha=0.8)
|
| 466 |
+
|
| 467 |
+
# Draw the edges with thickness based on weight.
|
| 468 |
+
edges = graph.edges()
|
| 469 |
+
edge_weights = [abs(self.edge_weights.get((u, v), 0.1)) for u, v in edges]
|
| 470 |
+
max_weight = max(edge_weights) if edge_weights else 1
|
| 471 |
+
edge_widths = [w / max_weight * 3 for w in edge_weights]
|
| 472 |
+
|
| 473 |
+
nx.draw_networkx_edges(graph, pos, width=edge_widths, alpha=0.6,
|
| 474 |
+
edge_color='gray', arrows=True)
|
| 475 |
+
|
| 476 |
+
# Draw the labels.
|
| 477 |
+
nx.draw_networkx_labels(graph, pos, font_size=8)
|
| 478 |
+
|
| 479 |
+
plt.title("Attribution Graph")
|
| 480 |
+
plt.axis('off')
|
| 481 |
+
|
| 482 |
+
if save_path:
|
| 483 |
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
| 484 |
+
plt.show()
|
| 485 |
+
|
| 486 |
+
class PerturbationExperiments:
|
| 487 |
+
# Conducts perturbation experiments to validate hypotheses.
|
| 488 |
+
|
| 489 |
+
def __init__(self, model, clt: CrossLayerTranscoder, tokenizer):
|
| 490 |
+
self.model = model
|
| 491 |
+
self.clt = clt
|
| 492 |
+
self.tokenizer = tokenizer
|
| 493 |
+
self._transformer_blocks: Optional[List[nn.Module]] = None
|
| 494 |
+
|
| 495 |
+
def _get_transformer_blocks(self) -> List[nn.Module]:
|
| 496 |
+
if self._transformer_blocks is not None:
|
| 497 |
+
return self._transformer_blocks
|
| 498 |
+
|
| 499 |
+
n_layers = getattr(self.model.config, "num_hidden_layers", None)
|
| 500 |
+
if n_layers is None:
|
| 501 |
+
raise ValueError("Model config does not expose num_hidden_layers; cannot resolve transformer blocks.")
|
| 502 |
+
|
| 503 |
+
candidate_lists: List[Tuple[str, nn.ModuleList]] = []
|
| 504 |
+
for name, module in self.model.named_modules():
|
| 505 |
+
if isinstance(module, nn.ModuleList) and len(module) == n_layers:
|
| 506 |
+
candidate_lists.append((name, module))
|
| 507 |
+
|
| 508 |
+
if not candidate_lists:
|
| 509 |
+
raise ValueError("Unable to locate transformer block ModuleList in model.")
|
| 510 |
+
|
| 511 |
+
# Prefer names that look like transformer blocks.
|
| 512 |
+
def _score(name: str) -> Tuple[int, str]:
|
| 513 |
+
preferred_suffixes = ("layers", "blocks", "h")
|
| 514 |
+
for idx, suffix in enumerate(preferred_suffixes):
|
| 515 |
+
if name.endswith(suffix):
|
| 516 |
+
return (idx, name)
|
| 517 |
+
return (len(preferred_suffixes), name)
|
| 518 |
+
|
| 519 |
+
selected_name, selected_list = sorted(candidate_lists, key=lambda item: _score(item[0]))[0]
|
| 520 |
+
self._transformer_blocks = list(selected_list)
|
| 521 |
+
logger.debug(f"Resolved transformer blocks from ModuleList '{selected_name}'.")
|
| 522 |
+
return self._transformer_blocks
|
| 523 |
+
|
| 524 |
+
def _format_top_tokens(self, top_tokens: torch.return_types.topk) -> List[Tuple[str, float]]:
|
| 525 |
+
return [
|
| 526 |
+
(self.tokenizer.decode([idx]), prob.item())
|
| 527 |
+
for idx, prob in zip(top_tokens.indices, top_tokens.values)
|
| 528 |
+
]
|
| 529 |
+
|
| 530 |
+
def _prepare_inputs(self, input_text: str, top_k: int) -> Dict[str, Any]:
|
| 531 |
+
if torch.backends.mps.is_available():
|
| 532 |
+
torch.mps.empty_cache()
|
| 533 |
+
|
| 534 |
+
device = next(self.model.parameters()).device
|
| 535 |
+
inputs = self.tokenizer(
|
| 536 |
+
input_text,
|
| 537 |
+
return_tensors="pt",
|
| 538 |
+
padding=True,
|
| 539 |
+
truncation=True,
|
| 540 |
+
max_length=512
|
| 541 |
+
)
|
| 542 |
+
if inputs["input_ids"].size(0) != 1:
|
| 543 |
+
raise ValueError("Perturbation experiments currently support only batch size 1.")
|
| 544 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 545 |
+
|
| 546 |
+
with torch.no_grad():
|
| 547 |
+
baseline_outputs = self.model(**inputs, output_hidden_states=True, return_dict=True)
|
| 548 |
+
|
| 549 |
+
baseline_logits = baseline_outputs.logits[0]
|
| 550 |
+
target_position = baseline_logits.size(0) - 1
|
| 551 |
+
baseline_last_token_logits = baseline_logits[target_position]
|
| 552 |
+
baseline_probs = F.softmax(baseline_last_token_logits, dim=-1)
|
| 553 |
+
baseline_top_tokens = torch.topk(baseline_probs, k=top_k)
|
| 554 |
+
|
| 555 |
+
hidden_states: List[torch.Tensor] = list(baseline_outputs.hidden_states[1:])
|
| 556 |
+
with torch.no_grad():
|
| 557 |
+
feature_activations, _ = self.clt(hidden_states)
|
| 558 |
+
|
| 559 |
+
return {
|
| 560 |
+
'inputs': inputs,
|
| 561 |
+
'baseline_outputs': baseline_outputs,
|
| 562 |
+
'baseline_logits': baseline_logits,
|
| 563 |
+
'baseline_last_token_logits': baseline_last_token_logits,
|
| 564 |
+
'baseline_probs': baseline_probs,
|
| 565 |
+
'baseline_top_tokens': baseline_top_tokens,
|
| 566 |
+
'target_position': target_position,
|
| 567 |
+
'hidden_states': hidden_states,
|
| 568 |
+
'feature_activations': feature_activations,
|
| 569 |
+
'default_target_token_id': baseline_top_tokens.indices[0].item()
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
def _compute_feature_contributions(
|
| 573 |
+
self,
|
| 574 |
+
feature_activations: List[torch.Tensor],
|
| 575 |
+
feature_set: List[Tuple[int, int]]
|
| 576 |
+
) -> Dict[int, torch.Tensor]:
|
| 577 |
+
contributions: Dict[int, torch.Tensor] = {}
|
| 578 |
+
with torch.no_grad():
|
| 579 |
+
for layer_idx, feature_idx in feature_set:
|
| 580 |
+
if layer_idx >= len(feature_activations):
|
| 581 |
+
continue
|
| 582 |
+
features = feature_activations[layer_idx]
|
| 583 |
+
if feature_idx >= features.size(-1):
|
| 584 |
+
continue
|
| 585 |
+
feature_values = features[:, :, feature_idx].detach()
|
| 586 |
+
|
| 587 |
+
for dest_layer in range(layer_idx, self.clt.n_layers):
|
| 588 |
+
decoder_key = f"{layer_idx}_to_{dest_layer}"
|
| 589 |
+
if decoder_key not in self.clt.decoders:
|
| 590 |
+
continue
|
| 591 |
+
decoder = self.clt.decoders[decoder_key]
|
| 592 |
+
weight_column = decoder.weight[:, feature_idx]
|
| 593 |
+
contrib = torch.einsum('bs,h->bsh', feature_values, weight_column).detach()
|
| 594 |
+
if dest_layer in contributions:
|
| 595 |
+
contributions[dest_layer] += contrib
|
| 596 |
+
else:
|
| 597 |
+
contributions[dest_layer] = contrib
|
| 598 |
+
return contributions
|
| 599 |
+
|
| 600 |
+
def _run_with_hooks(
|
| 601 |
+
self,
|
| 602 |
+
inputs: Dict[str, torch.Tensor],
|
| 603 |
+
contributions: Dict[int, torch.Tensor],
|
| 604 |
+
intervention_strength: float
|
| 605 |
+
):
|
| 606 |
+
blocks = self._get_transformer_blocks()
|
| 607 |
+
handles: List[Any] = []
|
| 608 |
+
|
| 609 |
+
def _make_hook(cached_contrib: torch.Tensor):
|
| 610 |
+
def hook(module, module_input, module_output):
|
| 611 |
+
if isinstance(module_output, torch.Tensor):
|
| 612 |
+
target_tensor = module_output
|
| 613 |
+
elif isinstance(module_output, (tuple, list)):
|
| 614 |
+
target_tensor = module_output[0]
|
| 615 |
+
elif hasattr(module_output, "last_hidden_state"):
|
| 616 |
+
target_tensor = module_output.last_hidden_state
|
| 617 |
+
else:
|
| 618 |
+
raise TypeError(
|
| 619 |
+
f"Unsupported module output type '{type(module_output)}' for perturbation hook."
|
| 620 |
+
)
|
| 621 |
+
|
| 622 |
+
tensor_contrib = cached_contrib.to(target_tensor.device).to(target_tensor.dtype)
|
| 623 |
+
scaled = intervention_strength * tensor_contrib
|
| 624 |
+
|
| 625 |
+
if isinstance(module_output, torch.Tensor):
|
| 626 |
+
return module_output - scaled
|
| 627 |
+
elif isinstance(module_output, tuple):
|
| 628 |
+
modified = module_output[0] - scaled
|
| 629 |
+
return (modified,) + tuple(module_output[1:])
|
| 630 |
+
elif isinstance(module_output, list):
|
| 631 |
+
modified = [module_output[0] - scaled, *module_output[1:]]
|
| 632 |
+
return modified
|
| 633 |
+
else:
|
| 634 |
+
module_output.last_hidden_state = module_output.last_hidden_state - scaled
|
| 635 |
+
return module_output
|
| 636 |
+
return hook
|
| 637 |
+
|
| 638 |
+
try:
|
| 639 |
+
for dest_layer, contrib in contributions.items():
|
| 640 |
+
if dest_layer >= len(blocks):
|
| 641 |
+
continue
|
| 642 |
+
handles.append(blocks[dest_layer].register_forward_hook(_make_hook(contrib)))
|
| 643 |
+
|
| 644 |
+
with torch.no_grad():
|
| 645 |
+
outputs = self.model(**inputs, output_hidden_states=True, return_dict=True)
|
| 646 |
+
finally:
|
| 647 |
+
for handle in handles:
|
| 648 |
+
handle.remove()
|
| 649 |
+
|
| 650 |
+
return outputs
|
| 651 |
+
|
| 652 |
+
def feature_set_ablation_experiment(
|
| 653 |
+
self,
|
| 654 |
+
input_text: str,
|
| 655 |
+
feature_set: List[Tuple[int, int]],
|
| 656 |
+
intervention_strength: float = 5.0,
|
| 657 |
+
target_token_id: Optional[int] = None,
|
| 658 |
+
top_k: int = 5,
|
| 659 |
+
ablation_label: str = "feature_set",
|
| 660 |
+
extra_metadata: Optional[Dict[str, Any]] = None
|
| 661 |
+
) -> Dict[str, Any]:
|
| 662 |
+
try:
|
| 663 |
+
baseline_data = self._prepare_inputs(input_text, top_k)
|
| 664 |
+
if target_token_id is None:
|
| 665 |
+
target_token_id = baseline_data['default_target_token_id']
|
| 666 |
+
|
| 667 |
+
feature_set_normalized = [
|
| 668 |
+
(int(layer_idx), int(feature_idx)) for layer_idx, feature_idx in feature_set
|
| 669 |
+
]
|
| 670 |
+
contributions = self._compute_feature_contributions(
|
| 671 |
+
baseline_data['feature_activations'],
|
| 672 |
+
feature_set_normalized
|
| 673 |
+
)
|
| 674 |
+
|
| 675 |
+
baseline_probs = baseline_data['baseline_probs']
|
| 676 |
+
baseline_top_tokens = baseline_data['baseline_top_tokens']
|
| 677 |
+
baseline_last_token_logits = baseline_data['baseline_last_token_logits']
|
| 678 |
+
target_position = baseline_data['target_position']
|
| 679 |
+
hidden_states = baseline_data['hidden_states']
|
| 680 |
+
|
| 681 |
+
baseline_prob = baseline_probs[target_token_id].item()
|
| 682 |
+
baseline_logit = baseline_last_token_logits[target_token_id].item()
|
| 683 |
+
baseline_summary = {
|
| 684 |
+
'baseline_top_tokens': self._format_top_tokens(baseline_top_tokens),
|
| 685 |
+
'baseline_probability': baseline_prob,
|
| 686 |
+
'baseline_logit': baseline_logit
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
if not contributions:
|
| 690 |
+
result = {
|
| 691 |
+
**baseline_summary,
|
| 692 |
+
'ablated_top_tokens': baseline_summary['baseline_top_tokens'],
|
| 693 |
+
'ablated_probability': baseline_prob,
|
| 694 |
+
'ablated_logit': baseline_logit,
|
| 695 |
+
'probability_change': 0.0,
|
| 696 |
+
'logit_change': 0.0,
|
| 697 |
+
'kl_divergence': 0.0,
|
| 698 |
+
'entropy_change': 0.0,
|
| 699 |
+
'hidden_state_delta_norm': 0.0,
|
| 700 |
+
'hidden_state_relative_change': 0.0,
|
| 701 |
+
'ablation_flips_top_prediction': False,
|
| 702 |
+
'feature_set': [
|
| 703 |
+
{'layer': layer_idx, 'feature': feature_idx}
|
| 704 |
+
for layer_idx, feature_idx in feature_set_normalized
|
| 705 |
+
],
|
| 706 |
+
'feature_set_size': len(feature_set_normalized),
|
| 707 |
+
'intervention_strength': intervention_strength,
|
| 708 |
+
'target_token_id': target_token_id,
|
| 709 |
+
'target_token': self.tokenizer.decode([target_token_id]),
|
| 710 |
+
'contributing_layers': [],
|
| 711 |
+
'ablation_applied': False,
|
| 712 |
+
'ablation_type': ablation_label,
|
| 713 |
+
'warning': 'no_contributions_found'
|
| 714 |
+
}
|
| 715 |
+
if extra_metadata:
|
| 716 |
+
result.update(extra_metadata)
|
| 717 |
+
return result
|
| 718 |
+
|
| 719 |
+
ablated_outputs = self._run_with_hooks(
|
| 720 |
+
baseline_data['inputs'],
|
| 721 |
+
contributions,
|
| 722 |
+
intervention_strength
|
| 723 |
+
)
|
| 724 |
+
|
| 725 |
+
ablated_logits = ablated_outputs.logits[0, target_position]
|
| 726 |
+
ablated_probs = F.softmax(ablated_logits, dim=-1)
|
| 727 |
+
ablated_top_tokens = torch.topk(ablated_probs, k=top_k)
|
| 728 |
+
|
| 729 |
+
ablated_prob = ablated_probs[target_token_id].item()
|
| 730 |
+
ablated_logit = ablated_logits[target_token_id].item()
|
| 731 |
+
|
| 732 |
+
epsilon = 1e-9
|
| 733 |
+
kl_divergence = torch.sum(
|
| 734 |
+
baseline_probs * (torch.log(baseline_probs + epsilon) - torch.log(ablated_probs + epsilon))
|
| 735 |
+
).item()
|
| 736 |
+
entropy_baseline = -(baseline_probs * torch.log(baseline_probs + epsilon)).sum().item()
|
| 737 |
+
entropy_ablated = -(ablated_probs * torch.log(ablated_probs + epsilon)).sum().item()
|
| 738 |
+
|
| 739 |
+
baseline_hidden = hidden_states[-1][:, target_position, :]
|
| 740 |
+
ablated_hidden = ablated_outputs.hidden_states[-1][:, target_position, :]
|
| 741 |
+
hidden_delta_norm = torch.norm(baseline_hidden - ablated_hidden, dim=-1).item()
|
| 742 |
+
hidden_baseline_norm = torch.norm(baseline_hidden, dim=-1).item()
|
| 743 |
+
hidden_relative_change = hidden_delta_norm / (hidden_baseline_norm + 1e-9)
|
| 744 |
+
|
| 745 |
+
result = {
|
| 746 |
+
**baseline_summary,
|
| 747 |
+
'ablated_top_tokens': self._format_top_tokens(ablated_top_tokens),
|
| 748 |
+
'ablated_probability': ablated_prob,
|
| 749 |
+
'ablated_logit': ablated_logit,
|
| 750 |
+
'probability_change': baseline_prob - ablated_prob,
|
| 751 |
+
'logit_change': baseline_logit - ablated_logit,
|
| 752 |
+
'kl_divergence': kl_divergence,
|
| 753 |
+
'entropy_change': entropy_ablated - entropy_baseline,
|
| 754 |
+
'hidden_state_delta_norm': hidden_delta_norm,
|
| 755 |
+
'hidden_state_relative_change': hidden_relative_change,
|
| 756 |
+
'ablation_flips_top_prediction': bool(
|
| 757 |
+
baseline_top_tokens.indices[0].item() != ablated_top_tokens.indices[0].item()
|
| 758 |
+
),
|
| 759 |
+
'feature_set': [
|
| 760 |
+
{'layer': layer_idx, 'feature': feature_idx}
|
| 761 |
+
for layer_idx, feature_idx in feature_set_normalized
|
| 762 |
+
],
|
| 763 |
+
'feature_set_size': len(feature_set_normalized),
|
| 764 |
+
'intervention_strength': intervention_strength,
|
| 765 |
+
'target_token_id': target_token_id,
|
| 766 |
+
'target_token': self.tokenizer.decode([target_token_id]),
|
| 767 |
+
'contributing_layers': sorted(list(contributions.keys())),
|
| 768 |
+
'ablation_applied': True,
|
| 769 |
+
'ablation_type': ablation_label
|
| 770 |
+
}
|
| 771 |
+
if extra_metadata:
|
| 772 |
+
result.update(extra_metadata)
|
| 773 |
+
return result
|
| 774 |
+
|
| 775 |
+
except Exception as e:
|
| 776 |
+
logger.warning(f"Perturbation experiment failed: {e}")
|
| 777 |
+
return {
|
| 778 |
+
'baseline_top_tokens': [],
|
| 779 |
+
'ablated_top_tokens': [],
|
| 780 |
+
'feature_set': [
|
| 781 |
+
{'layer': layer_idx, 'feature': feature_idx}
|
| 782 |
+
for layer_idx, feature_idx in feature_set
|
| 783 |
+
],
|
| 784 |
+
'feature_set_size': len(feature_set),
|
| 785 |
+
'intervention_strength': intervention_strength,
|
| 786 |
+
'probability_change': 0.0,
|
| 787 |
+
'logit_change': 0.0,
|
| 788 |
+
'kl_divergence': 0.0,
|
| 789 |
+
'entropy_change': 0.0,
|
| 790 |
+
'hidden_state_delta_norm': 0.0,
|
| 791 |
+
'hidden_state_relative_change': 0.0,
|
| 792 |
+
'ablation_flips_top_prediction': False,
|
| 793 |
+
'ablation_applied': False,
|
| 794 |
+
'ablation_type': ablation_label,
|
| 795 |
+
'error': str(e)
|
| 796 |
+
}
|
| 797 |
+
|
| 798 |
+
def feature_ablation_experiment(
|
| 799 |
+
self,
|
| 800 |
+
input_text: str,
|
| 801 |
+
target_layer: int,
|
| 802 |
+
target_feature: int,
|
| 803 |
+
intervention_strength: float = 5.0,
|
| 804 |
+
target_token_id: Optional[int] = None,
|
| 805 |
+
top_k: int = 5,
|
| 806 |
+
) -> Dict[str, Any]:
|
| 807 |
+
return self.feature_set_ablation_experiment(
|
| 808 |
+
input_text=input_text,
|
| 809 |
+
feature_set=[(target_layer, target_feature)],
|
| 810 |
+
intervention_strength=intervention_strength,
|
| 811 |
+
target_token_id=target_token_id,
|
| 812 |
+
top_k=top_k,
|
| 813 |
+
ablation_label="targeted_feature"
|
| 814 |
+
)
|
| 815 |
+
|
| 816 |
+
def random_feature_ablation_experiment(
|
| 817 |
+
self,
|
| 818 |
+
input_text: str,
|
| 819 |
+
num_features: int = 1,
|
| 820 |
+
intervention_strength: float = 5.0,
|
| 821 |
+
target_token_id: Optional[int] = None,
|
| 822 |
+
top_k: int = 5,
|
| 823 |
+
seed: Optional[int] = None
|
| 824 |
+
) -> Dict[str, Any]:
|
| 825 |
+
rng = random.Random(seed)
|
| 826 |
+
num_features = max(1, int(num_features))
|
| 827 |
+
feature_set: List[Tuple[int, int]] = []
|
| 828 |
+
for _ in range(num_features):
|
| 829 |
+
layer_idx = rng.randrange(self.clt.n_layers)
|
| 830 |
+
feature_idx = rng.randrange(self.clt.n_features)
|
| 831 |
+
feature_set.append((layer_idx, feature_idx))
|
| 832 |
+
|
| 833 |
+
result = self.feature_set_ablation_experiment(
|
| 834 |
+
input_text=input_text,
|
| 835 |
+
feature_set=feature_set,
|
| 836 |
+
intervention_strength=intervention_strength,
|
| 837 |
+
target_token_id=target_token_id,
|
| 838 |
+
top_k=top_k,
|
| 839 |
+
ablation_label="random_baseline",
|
| 840 |
+
extra_metadata={'random_seed': seed}
|
| 841 |
+
)
|
| 842 |
+
return result
|
| 843 |
+
|
| 844 |
+
class AttributionGraphsPipeline:
|
| 845 |
+
# The main pipeline for the attribution graph analysis.
|
| 846 |
+
|
| 847 |
+
def __init__(self, config: AttributionGraphConfig):
|
| 848 |
+
self.config = config
|
| 849 |
+
self.device = torch.device(config.device)
|
| 850 |
+
|
| 851 |
+
# Load the model and tokenizer.
|
| 852 |
+
logger.info(f"Loading OLMo2 7B model from {config.model_path}")
|
| 853 |
+
self.tokenizer = AutoTokenizer.from_pretrained(config.model_path)
|
| 854 |
+
|
| 855 |
+
# Configure model loading based on the device.
|
| 856 |
+
if "mps" in config.device:
|
| 857 |
+
# MPS supports float16 but not device_map.
|
| 858 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 859 |
+
config.model_path,
|
| 860 |
+
torch_dtype=torch.float16,
|
| 861 |
+
device_map=None
|
| 862 |
+
).to(self.device)
|
| 863 |
+
elif "cuda" in config.device:
|
| 864 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 865 |
+
config.model_path,
|
| 866 |
+
torch_dtype=torch.float16,
|
| 867 |
+
device_map="auto"
|
| 868 |
+
)
|
| 869 |
+
else:
|
| 870 |
+
# CPU
|
| 871 |
+
self.model = AutoModelForCausalLM.from_pretrained(
|
| 872 |
+
config.model_path,
|
| 873 |
+
torch_dtype=torch.float32,
|
| 874 |
+
device_map=None
|
| 875 |
+
).to(self.device)
|
| 876 |
+
|
| 877 |
+
if self.tokenizer.pad_token is None:
|
| 878 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 879 |
+
|
| 880 |
+
# Initialize the CLT.
|
| 881 |
+
model_config = self.model.config.to_dict()
|
| 882 |
+
self.clt = CrossLayerTranscoder(model_config, config).to(self.device)
|
| 883 |
+
|
| 884 |
+
# Initialize the other components.
|
| 885 |
+
cache_dir = Path(RESULTS_DIR) / "feature_interpretations_cache"
|
| 886 |
+
self.feature_visualizer = FeatureVisualizer(self.tokenizer, cache_dir=cache_dir)
|
| 887 |
+
self.attribution_graph = AttributionGraph(self.clt, self.tokenizer, config)
|
| 888 |
+
self.perturbation_experiments = PerturbationExperiments(self.model, self.clt, self.tokenizer)
|
| 889 |
+
|
| 890 |
+
logger.info("Attribution Graphs Pipeline initialized successfully")
|
| 891 |
+
|
| 892 |
+
def train_clt(self, training_texts: List[str]) -> Dict:
|
| 893 |
+
# Trains the Cross-Layer Transcoder.
|
| 894 |
+
logger.info("Starting CLT training...")
|
| 895 |
+
|
| 896 |
+
optimizer = torch.optim.Adam(self.clt.parameters(), lr=self.config.learning_rate)
|
| 897 |
+
|
| 898 |
+
training_stats = {
|
| 899 |
+
'reconstruction_losses': [],
|
| 900 |
+
'sparsity_losses': [],
|
| 901 |
+
'total_losses': []
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
for step in tqdm(range(self.config.training_steps), desc="Training CLT"):
|
| 905 |
+
# Sample a batch of texts.
|
| 906 |
+
batch_texts = np.random.choice(training_texts, size=self.config.batch_size)
|
| 907 |
+
|
| 908 |
+
total_loss = 0.0
|
| 909 |
+
total_recon_loss = 0.0
|
| 910 |
+
total_sparsity_loss = 0.0
|
| 911 |
+
|
| 912 |
+
for text in batch_texts:
|
| 913 |
+
# Tokenize the text.
|
| 914 |
+
inputs = self.tokenizer(text, return_tensors="pt", max_length=self.config.max_seq_length,
|
| 915 |
+
truncation=True, padding=True).to(self.device)
|
| 916 |
+
|
| 917 |
+
# Get the model activations.
|
| 918 |
+
with torch.no_grad():
|
| 919 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 920 |
+
hidden_states = outputs.hidden_states[1:]
|
| 921 |
+
|
| 922 |
+
# Forward pass through the CLT.
|
| 923 |
+
feature_activations, reconstructed_outputs = self.clt(hidden_states)
|
| 924 |
+
|
| 925 |
+
# Compute the reconstruction loss.
|
| 926 |
+
recon_loss = 0.0
|
| 927 |
+
for i, (target, pred) in enumerate(zip(hidden_states, reconstructed_outputs)):
|
| 928 |
+
recon_loss += F.mse_loss(pred, target)
|
| 929 |
+
|
| 930 |
+
# Compute the sparsity loss.
|
| 931 |
+
sparsity_loss = 0.0
|
| 932 |
+
for features in feature_activations:
|
| 933 |
+
sparsity_loss += torch.mean(torch.tanh(self.config.sparsity_lambda * features))
|
| 934 |
+
|
| 935 |
+
# Total loss.
|
| 936 |
+
loss = (self.config.reconstruction_loss_weight * recon_loss +
|
| 937 |
+
self.config.sparsity_lambda * sparsity_loss)
|
| 938 |
+
|
| 939 |
+
total_loss += loss
|
| 940 |
+
total_recon_loss += recon_loss
|
| 941 |
+
total_sparsity_loss += sparsity_loss
|
| 942 |
+
|
| 943 |
+
# Average the losses.
|
| 944 |
+
total_loss /= self.config.batch_size
|
| 945 |
+
total_recon_loss /= self.config.batch_size
|
| 946 |
+
total_sparsity_loss /= self.config.batch_size
|
| 947 |
+
|
| 948 |
+
# Backward pass.
|
| 949 |
+
optimizer.zero_grad()
|
| 950 |
+
total_loss.backward()
|
| 951 |
+
optimizer.step()
|
| 952 |
+
|
| 953 |
+
# Log the progress.
|
| 954 |
+
training_stats['total_losses'].append(total_loss.item())
|
| 955 |
+
training_stats['reconstruction_losses'].append(total_recon_loss.item())
|
| 956 |
+
training_stats['sparsity_losses'].append(total_sparsity_loss.item())
|
| 957 |
+
|
| 958 |
+
if step % 100 == 0:
|
| 959 |
+
logger.info(f"Step {step}: Total Loss = {total_loss.item():.4f}, "
|
| 960 |
+
f"Recon Loss = {total_recon_loss.item():.4f}, "
|
| 961 |
+
f"Sparsity Loss = {total_sparsity_loss.item():.4f}")
|
| 962 |
+
|
| 963 |
+
logger.info("CLT training completed")
|
| 964 |
+
return training_stats
|
| 965 |
+
|
| 966 |
+
def analyze_prompt(self, prompt: str, target_token_idx: int = -1) -> Dict:
|
| 967 |
+
# Performs a complete analysis for a single prompt.
|
| 968 |
+
logger.info(f"Analyzing prompt: '{prompt[:50]}...'")
|
| 969 |
+
|
| 970 |
+
# Tokenize the prompt.
|
| 971 |
+
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
|
| 972 |
+
input_tokens = self.tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
|
| 973 |
+
|
| 974 |
+
# Get the model activations.
|
| 975 |
+
with torch.no_grad():
|
| 976 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 977 |
+
hidden_states = outputs.hidden_states[1:]
|
| 978 |
+
|
| 979 |
+
# Forward pass through the CLT.
|
| 980 |
+
feature_activations, reconstructed_outputs = self.clt(hidden_states)
|
| 981 |
+
|
| 982 |
+
logger.info(" > Starting feature visualization and interpretation...")
|
| 983 |
+
feature_visualizations = {}
|
| 984 |
+
for layer_idx, features in enumerate(tqdm(feature_activations, desc="Interpreting features")):
|
| 985 |
+
logger.info(f" - Processing Layer {layer_idx}...")
|
| 986 |
+
layer_viz = {}
|
| 987 |
+
# Analyze the top features for this layer.
|
| 988 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 989 |
+
feature_importance = torch.mean(features, dim=(0, 1)) # Average over batch and sequence
|
| 990 |
+
top_k = min(self.config.feature_visualization_top_k, feature_importance.size(0))
|
| 991 |
+
top_features = torch.topk(feature_importance, k=top_k).indices
|
| 992 |
+
|
| 993 |
+
for feat_idx in top_features:
|
| 994 |
+
viz = self.feature_visualizer.visualize_feature(
|
| 995 |
+
feat_idx.item(), layer_idx, features[0], input_tokens
|
| 996 |
+
)
|
| 997 |
+
interpretation = self.feature_visualizer.interpret_feature(
|
| 998 |
+
feat_idx.item(), layer_idx, viz, self.config.qwen_api_config
|
| 999 |
+
)
|
| 1000 |
+
viz['interpretation'] = interpretation
|
| 1001 |
+
layer_viz[f"feature_{feat_idx.item()}"] = viz
|
| 1002 |
+
|
| 1003 |
+
feature_visualizations[f"layer_{layer_idx}"] = layer_viz
|
| 1004 |
+
|
| 1005 |
+
# Construct the attribution graph.
|
| 1006 |
+
graph = self.attribution_graph.construct_graph(
|
| 1007 |
+
input_tokens, feature_activations, target_token_idx
|
| 1008 |
+
)
|
| 1009 |
+
|
| 1010 |
+
# Prune the graph.
|
| 1011 |
+
pruned_graph = self.attribution_graph.prune_graph(self.config.pruning_threshold)
|
| 1012 |
+
|
| 1013 |
+
# Analyze the most important paths.
|
| 1014 |
+
important_paths = []
|
| 1015 |
+
if len(pruned_graph.nodes()) > 0:
|
| 1016 |
+
embedding_nodes = [
|
| 1017 |
+
node for node, type_ in self.attribution_graph.node_types.items()
|
| 1018 |
+
if type_ == "embedding" and node in pruned_graph
|
| 1019 |
+
]
|
| 1020 |
+
output_nodes = [
|
| 1021 |
+
node for node, type_ in self.attribution_graph.node_types.items()
|
| 1022 |
+
if type_ == "output" and node in pruned_graph
|
| 1023 |
+
]
|
| 1024 |
+
path_loop = tqdm(embedding_nodes[:3], desc="Enumerating paths")
|
| 1025 |
+
for emb_node in path_loop:
|
| 1026 |
+
for out_node in output_nodes:
|
| 1027 |
+
try:
|
| 1028 |
+
cutoff = max(2, self.config.path_search_cutoff or 5)
|
| 1029 |
+
paths = list(nx.all_simple_paths(pruned_graph, emb_node, out_node, cutoff=cutoff))
|
| 1030 |
+
for path in paths[:2]:
|
| 1031 |
+
path_weight = 1.0
|
| 1032 |
+
for i in range(len(path) - 1):
|
| 1033 |
+
edge_weight = self.attribution_graph.edge_weights.get(
|
| 1034 |
+
(path[i], path[i + 1]), 0.0
|
| 1035 |
+
)
|
| 1036 |
+
path_weight *= abs(edge_weight)
|
| 1037 |
+
important_paths.append({
|
| 1038 |
+
'path': path,
|
| 1039 |
+
'weight': path_weight,
|
| 1040 |
+
'description': self._describe_path(path)
|
| 1041 |
+
})
|
| 1042 |
+
except nx.NetworkXNoPath:
|
| 1043 |
+
continue
|
| 1044 |
+
|
| 1045 |
+
# Sort paths by importance.
|
| 1046 |
+
important_paths.sort(key=lambda x: x['weight'], reverse=True)
|
| 1047 |
+
|
| 1048 |
+
# Run targeted perturbation experiments for highlighted features.
|
| 1049 |
+
targeted_feature_ablation_results: List[Dict[str, Any]] = []
|
| 1050 |
+
max_total_experiments = self.config.max_ablation_experiments
|
| 1051 |
+
per_layer_limit = self.config.ablation_features_per_layer
|
| 1052 |
+
total_run = 0
|
| 1053 |
+
stop_all = False
|
| 1054 |
+
layer_items = list(feature_visualizations.items())
|
| 1055 |
+
for layer_name, layer_features in tqdm(layer_items, desc="Targeted ablations"):
|
| 1056 |
+
if stop_all:
|
| 1057 |
+
break
|
| 1058 |
+
try:
|
| 1059 |
+
layer_idx = int(layer_name.split('_')[1])
|
| 1060 |
+
except (IndexError, ValueError):
|
| 1061 |
+
logger.warning(f"Unable to parse layer index from key '{layer_name}'. Skipping perturbation experiments for this layer.")
|
| 1062 |
+
continue
|
| 1063 |
+
|
| 1064 |
+
feature_items = list(layer_features.items())
|
| 1065 |
+
if per_layer_limit is not None:
|
| 1066 |
+
feature_items = feature_items[:per_layer_limit]
|
| 1067 |
+
|
| 1068 |
+
feature_bar = tqdm(feature_items, desc=f"{layer_name} features", leave=False)
|
| 1069 |
+
for feature_name, feature_payload in feature_bar:
|
| 1070 |
+
if max_total_experiments is not None and total_run >= max_total_experiments:
|
| 1071 |
+
stop_all = True
|
| 1072 |
+
break
|
| 1073 |
+
try:
|
| 1074 |
+
feature_idx = int(feature_name.split('_')[1])
|
| 1075 |
+
except (IndexError, ValueError):
|
| 1076 |
+
logger.warning(f"Unable to parse feature index from key '{feature_name}'. Skipping perturbation experiment.")
|
| 1077 |
+
continue
|
| 1078 |
+
|
| 1079 |
+
ablation = self.perturbation_experiments.feature_ablation_experiment(
|
| 1080 |
+
prompt,
|
| 1081 |
+
layer_idx,
|
| 1082 |
+
feature_idx,
|
| 1083 |
+
intervention_strength=self.config.intervention_strength,
|
| 1084 |
+
target_token_id=None,
|
| 1085 |
+
top_k=self.config.ablation_top_k_tokens,
|
| 1086 |
+
)
|
| 1087 |
+
ablation.update({
|
| 1088 |
+
'layer_name': layer_name,
|
| 1089 |
+
'feature_name': feature_name,
|
| 1090 |
+
'feature_interpretation': feature_payload.get('interpretation'),
|
| 1091 |
+
'feature_max_activation': feature_payload.get('max_activation'),
|
| 1092 |
+
})
|
| 1093 |
+
targeted_feature_ablation_results.append(ablation)
|
| 1094 |
+
total_run += 1
|
| 1095 |
+
|
| 1096 |
+
# Random baseline perturbations for comparison.
|
| 1097 |
+
random_baseline_results: List[Dict[str, Any]] = []
|
| 1098 |
+
baseline_trials = self.config.random_baseline_trials
|
| 1099 |
+
if baseline_trials and baseline_trials > 0:
|
| 1100 |
+
num_features = self.config.random_baseline_features or 1
|
| 1101 |
+
for trial_idx in tqdm(range(baseline_trials), desc="Random feature baselines"):
|
| 1102 |
+
seed = None
|
| 1103 |
+
if self.config.random_baseline_seed is not None:
|
| 1104 |
+
seed = self.config.random_baseline_seed + trial_idx
|
| 1105 |
+
random_result = self.perturbation_experiments.random_feature_ablation_experiment(
|
| 1106 |
+
prompt,
|
| 1107 |
+
num_features=num_features,
|
| 1108 |
+
intervention_strength=self.config.intervention_strength,
|
| 1109 |
+
target_token_id=None,
|
| 1110 |
+
top_k=self.config.ablation_top_k_tokens,
|
| 1111 |
+
seed=seed
|
| 1112 |
+
)
|
| 1113 |
+
random_result['trial_index'] = trial_idx
|
| 1114 |
+
random_baseline_results.append(random_result)
|
| 1115 |
+
|
| 1116 |
+
# Path-level ablations for the most important circuits.
|
| 1117 |
+
path_ablation_results: List[Dict[str, Any]] = []
|
| 1118 |
+
max_paths = self.config.path_ablation_top_k or 0
|
| 1119 |
+
extracted_paths: List[Dict[str, Any]] = []
|
| 1120 |
+
if max_paths > 0 and important_paths:
|
| 1121 |
+
for path_info in tqdm(important_paths[:max_paths], desc="Path ablations"):
|
| 1122 |
+
feature_set = self._extract_feature_set_from_path(path_info.get('path', []))
|
| 1123 |
+
if not feature_set:
|
| 1124 |
+
continue
|
| 1125 |
+
path_result = self.perturbation_experiments.feature_set_ablation_experiment(
|
| 1126 |
+
prompt,
|
| 1127 |
+
feature_set=feature_set,
|
| 1128 |
+
intervention_strength=self.config.intervention_strength,
|
| 1129 |
+
target_token_id=None,
|
| 1130 |
+
top_k=self.config.ablation_top_k_tokens,
|
| 1131 |
+
ablation_label="path",
|
| 1132 |
+
extra_metadata={
|
| 1133 |
+
'path_nodes': path_info.get('path'),
|
| 1134 |
+
'path_description': path_info.get('description'),
|
| 1135 |
+
'path_weight': path_info.get('weight')
|
| 1136 |
+
}
|
| 1137 |
+
)
|
| 1138 |
+
path_ablation_results.append(path_result)
|
| 1139 |
+
enriched_path_info = path_info.copy()
|
| 1140 |
+
enriched_path_info['feature_set'] = feature_set
|
| 1141 |
+
extracted_paths.append(enriched_path_info)
|
| 1142 |
+
|
| 1143 |
+
random_path_baseline_results: List[Dict[str, Any]] = []
|
| 1144 |
+
path_baseline_trials = self.config.random_path_baseline_trials
|
| 1145 |
+
if path_baseline_trials and path_baseline_trials > 0 and extracted_paths:
|
| 1146 |
+
rng = random.Random(self.config.random_baseline_seed)
|
| 1147 |
+
available_nodes = [
|
| 1148 |
+
data for data in self.attribution_graph.node_types.items()
|
| 1149 |
+
if data[1] == "feature"
|
| 1150 |
+
]
|
| 1151 |
+
for trial in tqdm(range(path_baseline_trials), desc="Random path baselines"):
|
| 1152 |
+
selected_path = extracted_paths[min(trial % len(extracted_paths), len(extracted_paths) - 1)]
|
| 1153 |
+
target_length = len(selected_path.get('feature_set', []))
|
| 1154 |
+
source_layers = [layer for layer, _ in selected_path.get('feature_set', [])]
|
| 1155 |
+
min_layer = min(source_layers) if source_layers else 0
|
| 1156 |
+
max_layer = max(source_layers) if source_layers else self.clt.n_layers - 1
|
| 1157 |
+
excluded_keys = {
|
| 1158 |
+
(layer, feature)
|
| 1159 |
+
for layer, feature in selected_path.get('feature_set', [])
|
| 1160 |
+
}
|
| 1161 |
+
random_feature_set: List[Tuple[int, int]] = []
|
| 1162 |
+
attempts = 0
|
| 1163 |
+
while len(random_feature_set) < target_length and attempts < target_length * 10:
|
| 1164 |
+
attempts += 1
|
| 1165 |
+
if not available_nodes:
|
| 1166 |
+
break
|
| 1167 |
+
node_name, node_type = rng.choice(available_nodes)
|
| 1168 |
+
metadata = self.attribution_graph.feature_metadata.get(node_name)
|
| 1169 |
+
if metadata is None:
|
| 1170 |
+
continue
|
| 1171 |
+
if metadata['layer'] < min_layer or metadata['layer'] > max_layer:
|
| 1172 |
+
continue
|
| 1173 |
+
key = (metadata['layer'], metadata['feature_index'])
|
| 1174 |
+
if key in excluded_keys:
|
| 1175 |
+
continue
|
| 1176 |
+
if key not in random_feature_set:
|
| 1177 |
+
random_feature_set.append(key)
|
| 1178 |
+
if not random_feature_set:
|
| 1179 |
+
continue
|
| 1180 |
+
if len(random_feature_set) < max(1, target_length):
|
| 1181 |
+
continue
|
| 1182 |
+
random_path_result = self.perturbation_experiments.feature_set_ablation_experiment(
|
| 1183 |
+
prompt,
|
| 1184 |
+
feature_set=random_feature_set,
|
| 1185 |
+
intervention_strength=self.config.intervention_strength,
|
| 1186 |
+
target_token_id=None,
|
| 1187 |
+
top_k=self.config.ablation_top_k_tokens,
|
| 1188 |
+
ablation_label="random_path_baseline",
|
| 1189 |
+
extra_metadata={
|
| 1190 |
+
'trial_index': trial,
|
| 1191 |
+
'sampled_feature_set': random_feature_set,
|
| 1192 |
+
'reference_path_weight': selected_path.get('weight')
|
| 1193 |
+
}
|
| 1194 |
+
)
|
| 1195 |
+
random_path_baseline_results.append(random_path_result)
|
| 1196 |
+
|
| 1197 |
+
targeted_summary = self._summarize_ablation_results(targeted_feature_ablation_results)
|
| 1198 |
+
random_summary = self._summarize_ablation_results(random_baseline_results)
|
| 1199 |
+
path_summary = self._summarize_ablation_results(path_ablation_results)
|
| 1200 |
+
random_path_summary = self._summarize_ablation_results(random_path_baseline_results)
|
| 1201 |
+
summary_statistics = {
|
| 1202 |
+
'targeted': targeted_summary,
|
| 1203 |
+
'random_baseline': random_summary,
|
| 1204 |
+
'path': path_summary,
|
| 1205 |
+
'random_path_baseline': random_path_summary,
|
| 1206 |
+
'target_minus_random_abs_probability_change': targeted_summary.get('avg_abs_probability_change', 0.0) - random_summary.get('avg_abs_probability_change', 0.0),
|
| 1207 |
+
'target_flip_rate_minus_random': targeted_summary.get('flip_rate', 0.0) - random_summary.get('flip_rate', 0.0),
|
| 1208 |
+
'path_minus_random_abs_probability_change': path_summary.get('avg_abs_probability_change', 0.0) - random_path_summary.get('avg_abs_probability_change', 0.0),
|
| 1209 |
+
'path_flip_rate_minus_random': path_summary.get('flip_rate', 0.0) - random_path_summary.get('flip_rate', 0.0)
|
| 1210 |
+
}
|
| 1211 |
+
|
| 1212 |
+
results = {
|
| 1213 |
+
'prompt': prompt,
|
| 1214 |
+
'input_tokens': input_tokens,
|
| 1215 |
+
'feature_visualizations': feature_visualizations,
|
| 1216 |
+
'full_graph_stats': {
|
| 1217 |
+
'n_nodes': len(graph.nodes()),
|
| 1218 |
+
'n_edges': len(graph.edges()),
|
| 1219 |
+
'node_types': dict(self.attribution_graph.node_types)
|
| 1220 |
+
},
|
| 1221 |
+
'pruned_graph_stats': {
|
| 1222 |
+
'n_nodes': len(pruned_graph.nodes()),
|
| 1223 |
+
'n_edges': len(pruned_graph.edges())
|
| 1224 |
+
},
|
| 1225 |
+
'important_paths': important_paths[:5], # Top 5 paths
|
| 1226 |
+
'graph': pruned_graph,
|
| 1227 |
+
'perturbation_experiments': targeted_feature_ablation_results,
|
| 1228 |
+
'random_baseline_experiments': random_baseline_results,
|
| 1229 |
+
'path_ablation_experiments': path_ablation_results,
|
| 1230 |
+
'random_path_baseline_experiments': random_path_baseline_results,
|
| 1231 |
+
'summary_statistics': summary_statistics
|
| 1232 |
+
}
|
| 1233 |
+
|
| 1234 |
+
return results
|
| 1235 |
+
|
| 1236 |
+
def _extract_feature_set_from_path(self, path: List[str]) -> List[Tuple[int, int]]:
|
| 1237 |
+
feature_set: List[Tuple[int, int]] = []
|
| 1238 |
+
seen: Set[Tuple[int, int]] = set()
|
| 1239 |
+
for node in path:
|
| 1240 |
+
if not isinstance(node, str):
|
| 1241 |
+
continue
|
| 1242 |
+
if not node.startswith("feat_"):
|
| 1243 |
+
continue
|
| 1244 |
+
parts = node.split('_')
|
| 1245 |
+
try:
|
| 1246 |
+
layer_str = parts[1] # e.g., "L0"
|
| 1247 |
+
feature_str = parts[3] # e.g., "F123"
|
| 1248 |
+
layer_idx = int(layer_str[1:])
|
| 1249 |
+
feature_idx = int(feature_str[1:])
|
| 1250 |
+
except (IndexError, ValueError):
|
| 1251 |
+
continue
|
| 1252 |
+
key = (layer_idx, feature_idx)
|
| 1253 |
+
if key not in seen:
|
| 1254 |
+
seen.add(key)
|
| 1255 |
+
feature_set.append(key)
|
| 1256 |
+
return feature_set
|
| 1257 |
+
|
| 1258 |
+
def _summarize_ablation_results(self, experiments: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 1259 |
+
summary = {
|
| 1260 |
+
'count': len(experiments),
|
| 1261 |
+
'avg_probability_change': 0.0,
|
| 1262 |
+
'avg_abs_probability_change': 0.0,
|
| 1263 |
+
'std_probability_change': 0.0,
|
| 1264 |
+
'avg_logit_change': 0.0,
|
| 1265 |
+
'avg_abs_logit_change': 0.0,
|
| 1266 |
+
'std_logit_change': 0.0,
|
| 1267 |
+
'avg_kl_divergence': 0.0,
|
| 1268 |
+
'avg_entropy_change': 0.0,
|
| 1269 |
+
'avg_hidden_state_delta_norm': 0.0,
|
| 1270 |
+
'avg_hidden_state_relative_change': 0.0,
|
| 1271 |
+
'flip_rate': 0.0,
|
| 1272 |
+
'count_flipped': 0
|
| 1273 |
+
}
|
| 1274 |
+
if not experiments:
|
| 1275 |
+
return summary
|
| 1276 |
+
|
| 1277 |
+
probability_changes = np.array([exp.get('probability_change', 0.0) for exp in experiments], dtype=float)
|
| 1278 |
+
logit_changes = np.array([exp.get('logit_change', 0.0) for exp in experiments], dtype=float)
|
| 1279 |
+
kl_divergences = np.array([exp.get('kl_divergence', 0.0) for exp in experiments], dtype=float)
|
| 1280 |
+
entropy_changes = np.array([exp.get('entropy_change', 0.0) for exp in experiments], dtype=float)
|
| 1281 |
+
hidden_norms = np.array([exp.get('hidden_state_delta_norm', 0.0) for exp in experiments], dtype=float)
|
| 1282 |
+
hidden_relative = np.array([exp.get('hidden_state_relative_change', 0.0) for exp in experiments], dtype=float)
|
| 1283 |
+
flip_flags = np.array([1.0 if exp.get('ablation_flips_top_prediction') else 0.0 for exp in experiments], dtype=float)
|
| 1284 |
+
|
| 1285 |
+
summary.update({
|
| 1286 |
+
'avg_probability_change': float(np.mean(probability_changes)),
|
| 1287 |
+
'avg_abs_probability_change': float(np.mean(np.abs(probability_changes))),
|
| 1288 |
+
'std_probability_change': float(np.std(probability_changes)),
|
| 1289 |
+
'avg_logit_change': float(np.mean(logit_changes)),
|
| 1290 |
+
'avg_abs_logit_change': float(np.mean(np.abs(logit_changes))),
|
| 1291 |
+
'std_logit_change': float(np.std(logit_changes)),
|
| 1292 |
+
'avg_kl_divergence': float(np.mean(kl_divergences)),
|
| 1293 |
+
'avg_entropy_change': float(np.mean(entropy_changes)),
|
| 1294 |
+
'avg_hidden_state_delta_norm': float(np.mean(hidden_norms)),
|
| 1295 |
+
'avg_hidden_state_relative_change': float(np.mean(hidden_relative)),
|
| 1296 |
+
'flip_rate': float(np.mean(flip_flags)),
|
| 1297 |
+
'count_flipped': int(np.round(np.sum(flip_flags)))
|
| 1298 |
+
})
|
| 1299 |
+
return summary
|
| 1300 |
+
|
| 1301 |
+
def analyze_prompts_batch(self, prompts: List[str]) -> Dict[str, Any]:
|
| 1302 |
+
analyses: Dict[str, Dict[str, Any]] = {}
|
| 1303 |
+
aggregated_targeted: List[Dict[str, Any]] = []
|
| 1304 |
+
aggregated_random: List[Dict[str, Any]] = []
|
| 1305 |
+
aggregated_path: List[Dict[str, Any]] = []
|
| 1306 |
+
|
| 1307 |
+
for idx, prompt in enumerate(tqdm(prompts, desc="Analyzing prompts")):
|
| 1308 |
+
logger.info(f"[Batch Eval] Processing prompt {idx + 1}/{len(prompts)}")
|
| 1309 |
+
analysis = self.analyze_prompt(prompt)
|
| 1310 |
+
key = f"prompt_{idx + 1}"
|
| 1311 |
+
analyses[key] = analysis
|
| 1312 |
+
aggregated_targeted.extend(analysis.get('perturbation_experiments', []))
|
| 1313 |
+
aggregated_random.extend(analysis.get('random_baseline_experiments', []))
|
| 1314 |
+
aggregated_path.extend(analysis.get('path_ablation_experiments', []))
|
| 1315 |
+
|
| 1316 |
+
aggregate_summary = {
|
| 1317 |
+
'targeted': self._summarize_ablation_results(aggregated_targeted),
|
| 1318 |
+
'random_baseline': self._summarize_ablation_results(aggregated_random),
|
| 1319 |
+
'path': self._summarize_ablation_results(aggregated_path),
|
| 1320 |
+
'random_path_baseline': self._summarize_ablation_results(
|
| 1321 |
+
[
|
| 1322 |
+
exp
|
| 1323 |
+
for analysis in analyses.values()
|
| 1324 |
+
for exp in analysis.get('random_path_baseline_experiments', [])
|
| 1325 |
+
]
|
| 1326 |
+
)
|
| 1327 |
+
}
|
| 1328 |
+
aggregate_summary['target_minus_random_abs_probability_change'] = (
|
| 1329 |
+
aggregate_summary['targeted'].get('avg_abs_probability_change', 0.0)
|
| 1330 |
+
- aggregate_summary['random_baseline'].get('avg_abs_probability_change', 0.0)
|
| 1331 |
+
)
|
| 1332 |
+
aggregate_summary['target_flip_rate_minus_random'] = (
|
| 1333 |
+
aggregate_summary['targeted'].get('flip_rate', 0.0)
|
| 1334 |
+
- aggregate_summary['random_baseline'].get('flip_rate', 0.0)
|
| 1335 |
+
)
|
| 1336 |
+
aggregate_summary['path_minus_random_abs_probability_change'] = (
|
| 1337 |
+
aggregate_summary['path'].get('avg_abs_probability_change', 0.0)
|
| 1338 |
+
- aggregate_summary['random_path_baseline'].get('avg_abs_probability_change', 0.0)
|
| 1339 |
+
)
|
| 1340 |
+
aggregate_summary['path_flip_rate_minus_random'] = (
|
| 1341 |
+
aggregate_summary['path'].get('flip_rate', 0.0)
|
| 1342 |
+
- aggregate_summary['random_path_baseline'].get('flip_rate', 0.0)
|
| 1343 |
+
)
|
| 1344 |
+
|
| 1345 |
+
return {
|
| 1346 |
+
'analyses': analyses,
|
| 1347 |
+
'aggregate_summary': aggregate_summary,
|
| 1348 |
+
'prompt_texts': prompts
|
| 1349 |
+
}
|
| 1350 |
+
|
| 1351 |
+
def _describe_path(self, path: List[str]) -> str:
|
| 1352 |
+
# Generates a human-readable description of a path.
|
| 1353 |
+
descriptions = []
|
| 1354 |
+
for node in path:
|
| 1355 |
+
if self.attribution_graph.node_types[node] == "embedding":
|
| 1356 |
+
token = node.split('_')[2]
|
| 1357 |
+
descriptions.append(f"Token '{token}'")
|
| 1358 |
+
elif self.attribution_graph.node_types[node] == "feature":
|
| 1359 |
+
parts = node.split('_')
|
| 1360 |
+
layer = parts[1][1:] # Remove 'L'
|
| 1361 |
+
feature = parts[3][1:] # Remove 'F'
|
| 1362 |
+
# Try to get the interpretation.
|
| 1363 |
+
key = f"L{layer}_F{feature}"
|
| 1364 |
+
interpretation = self.feature_visualizer.feature_interpretations.get(key, "unknown")
|
| 1365 |
+
descriptions.append(f"Feature L{layer}F{feature} ({interpretation})")
|
| 1366 |
+
elif self.attribution_graph.node_types[node] == "output":
|
| 1367 |
+
descriptions.append("Output")
|
| 1368 |
+
|
| 1369 |
+
return " → ".join(descriptions)
|
| 1370 |
+
|
| 1371 |
+
def save_results(self, results: Dict, save_path: str):
|
| 1372 |
+
# Saves the analysis results to a file.
|
| 1373 |
+
serializable_results = copy.deepcopy(results)
|
| 1374 |
+
|
| 1375 |
+
if 'graph' in serializable_results:
|
| 1376 |
+
serializable_results['graph'] = nx.node_link_data(serializable_results['graph'])
|
| 1377 |
+
|
| 1378 |
+
analyses = serializable_results.get('analyses', {})
|
| 1379 |
+
for key, analysis in analyses.items():
|
| 1380 |
+
if 'graph' in analysis:
|
| 1381 |
+
analysis['graph'] = nx.node_link_data(analysis['graph'])
|
| 1382 |
+
|
| 1383 |
+
with open(save_path, 'w') as f:
|
| 1384 |
+
json.dump(serializable_results, f, indent=2, default=str)
|
| 1385 |
+
|
| 1386 |
+
logger.info(f"Results saved to {save_path}")
|
| 1387 |
+
|
| 1388 |
+
def save_clt(self, path: str):
|
| 1389 |
+
# Saves the trained CLT model.
|
| 1390 |
+
torch.save(self.clt.state_dict(), path)
|
| 1391 |
+
logger.info(f"CLT model saved to {path}")
|
| 1392 |
+
|
| 1393 |
+
def load_clt(self, path: str):
|
| 1394 |
+
# Loads a trained CLT model.
|
| 1395 |
+
self.clt.load_state_dict(torch.load(path, map_location=self.device))
|
| 1396 |
+
self.clt.to(self.device)
|
| 1397 |
+
self.clt.eval() # Set the model to evaluation mode
|
| 1398 |
+
logger.info(f"Loaded CLT model from {path}")
|
| 1399 |
+
|
| 1400 |
+
# --- Configuration ---
|
| 1401 |
+
MAX_SEQ_LEN = 256
|
| 1402 |
+
N_FEATURES_PER_LAYER = 512
|
| 1403 |
+
TRAINING_STEPS = 2500
|
| 1404 |
+
BATCH_SIZE = 64
|
| 1405 |
+
LEARNING_RATE = 1e-3
|
| 1406 |
+
|
| 1407 |
+
# Prompts for generating the final analysis.
|
| 1408 |
+
ANALYSIS_PROMPTS = [
|
| 1409 |
+
"The capital of France is",
|
| 1410 |
+
"def factorial(n):",
|
| 1411 |
+
"The literary device in the phrase 'The wind whispered through the trees' is"
|
| 1412 |
+
]
|
| 1413 |
+
|
| 1414 |
+
# A larger set of prompts for training.
|
| 1415 |
+
TRAINING_PROMPTS = [
|
| 1416 |
+
"The capital of France is", "To be or not to be, that is the", "A stitch in time saves",
|
| 1417 |
+
"The first person to walk on the moon was", "The chemical formula for water is H2O.",
|
| 1418 |
+
"Translate to German: 'The cat sits on the mat.'", "def factorial(n):", "import numpy as np",
|
| 1419 |
+
"The main ingredients in a pizza are", "What is the powerhouse of the cell?",
|
| 1420 |
+
"The equation E=mc^2 relates energy to", "Continue the story: Once upon a time, there was a",
|
| 1421 |
+
"Classify the sentiment: 'I am overjoyed!'", "Extract the entities: 'Apple Inc. is in Cupertino.'",
|
| 1422 |
+
"What is the next number: 2, 4, 8, 16, __?", "A rolling stone gathers no",
|
| 1423 |
+
"The opposite of hot is", "import torch", "import pandas as pd", "class MyClass:",
|
| 1424 |
+
"def __init__(self):", "The primary colors are", "What is the capital of Japan?",
|
| 1425 |
+
"Who wrote 'Hamlet'?", "The square root of 64 is", "The sun rises in the",
|
| 1426 |
+
"The Pacific Ocean is the largest ocean on Earth.", "The mitochondria is the powerhouse of the cell.",
|
| 1427 |
+
"What is the capital of Mongolia?", "The movie 'The Matrix' can be classified into the following genre:",
|
| 1428 |
+
"The French translation of 'I would like to order a coffee, please.' is:",
|
| 1429 |
+
"The literary device in the phrase 'The wind whispered through the trees' is",
|
| 1430 |
+
"A Python function that calculates the factorial of a number is:",
|
| 1431 |
+
"The main ingredient in a Negroni cocktail is",
|
| 1432 |
+
"Summarize the plot of 'Hamlet' in one sentence:",
|
| 1433 |
+
"The sentence 'The cake was eaten by the dog' is in the following voice:",
|
| 1434 |
+
"A good headline for an article about a new breakthrough in battery technology would be:"
|
| 1435 |
+
]
|
| 1436 |
+
|
| 1437 |
+
|
| 1438 |
+
# --- Qwen API for Feature Interpretation ---
|
| 1439 |
+
@torch.no_grad()
|
| 1440 |
+
def get_feature_interpretation_with_qwen(
|
| 1441 |
+
api_config: dict,
|
| 1442 |
+
top_tokens: list[str],
|
| 1443 |
+
feature_name: str,
|
| 1444 |
+
layer_index: int,
|
| 1445 |
+
max_retries: int = 3,
|
| 1446 |
+
initial_backoff: float = 2.0
|
| 1447 |
+
) -> str:
|
| 1448 |
+
# Generates a high-quality interpretation for a feature using the Qwen API.
|
| 1449 |
+
if not api_config or not api_config.get('api_key'):
|
| 1450 |
+
logger.warning("Qwen API not configured. Skipping interpretation.")
|
| 1451 |
+
return "API not configured"
|
| 1452 |
+
|
| 1453 |
+
headers = {
|
| 1454 |
+
"Authorization": f"Bearer {api_config['api_key']}",
|
| 1455 |
+
"Content-Type": "application/json"
|
| 1456 |
+
}
|
| 1457 |
+
|
| 1458 |
+
# Create a specialized prompt.
|
| 1459 |
+
prompt_text = f"""
|
| 1460 |
+
You are an expert in transformer interpretability. A feature in a language model (feature '{feature_name}' at layer {layer_index}) is most strongly activated by the following tokens:
|
| 1461 |
+
|
| 1462 |
+
{', '.join(f"'{token}'" for token in top_tokens)}
|
| 1463 |
+
|
| 1464 |
+
Based *only* on these tokens, what is the most likely function or role of this feature?
|
| 1465 |
+
Your answer must be a short, concise phrase (e.g., "Detecting proper nouns", "Identifying JSON syntax", "Completing lists", "Recognizing negative sentiment"). Do not write a full sentence.
|
| 1466 |
+
"""
|
| 1467 |
+
|
| 1468 |
+
data = {
|
| 1469 |
+
"model": api_config["model"],
|
| 1470 |
+
"messages": [
|
| 1471 |
+
{
|
| 1472 |
+
"role": "user",
|
| 1473 |
+
"content": [{"type": "text", "text": prompt_text}]
|
| 1474 |
+
}
|
| 1475 |
+
],
|
| 1476 |
+
"max_tokens": 50,
|
| 1477 |
+
"temperature": 0.1,
|
| 1478 |
+
"top_p": 0.9,
|
| 1479 |
+
"seed": 42
|
| 1480 |
+
}
|
| 1481 |
+
|
| 1482 |
+
logger.info(f" > Interpreting {feature_name} (Layer {layer_index})...")
|
| 1483 |
+
|
| 1484 |
+
for attempt in range(max_retries):
|
| 1485 |
+
try:
|
| 1486 |
+
logger.info(f" - Attempt {attempt + 1}/{max_retries}: Sending request to Qwen API...")
|
| 1487 |
+
response = requests.post(
|
| 1488 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 1489 |
+
headers=headers,
|
| 1490 |
+
json=data,
|
| 1491 |
+
timeout=60
|
| 1492 |
+
)
|
| 1493 |
+
response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx)
|
| 1494 |
+
|
| 1495 |
+
result = response.json()
|
| 1496 |
+
interpretation = result["choices"][0]["message"]["content"].strip()
|
| 1497 |
+
|
| 1498 |
+
# Remove quotes from the output.
|
| 1499 |
+
if interpretation.startswith('"') and interpretation.endswith('"'):
|
| 1500 |
+
interpretation = interpretation[1:-1]
|
| 1501 |
+
|
| 1502 |
+
logger.info(f" - Success! Interpretation: '{interpretation}'")
|
| 1503 |
+
return interpretation
|
| 1504 |
+
|
| 1505 |
+
except requests.exceptions.RequestException as e:
|
| 1506 |
+
logger.warning(f" - Qwen API request failed (Attempt {attempt + 1}/{max_retries}): {e}")
|
| 1507 |
+
if attempt < max_retries - 1:
|
| 1508 |
+
backoff_time = initial_backoff * (2 ** attempt)
|
| 1509 |
+
logger.info(f" - Retrying in {backoff_time:.1f} seconds...")
|
| 1510 |
+
time.sleep(backoff_time)
|
| 1511 |
+
else:
|
| 1512 |
+
logger.error(" - Max retries reached. Failing.")
|
| 1513 |
+
return f"API Error: {e}"
|
| 1514 |
+
except (KeyError, IndexError) as e:
|
| 1515 |
+
logger.error(f" - Failed to parse Qwen API response: {e}")
|
| 1516 |
+
return "API Error: Invalid response format"
|
| 1517 |
+
finally:
|
| 1518 |
+
# Add a delay to respect API rate limits.
|
| 1519 |
+
time.sleep(2.1)
|
| 1520 |
+
|
| 1521 |
+
return "API Error: Max retries exceeded"
|
| 1522 |
+
|
| 1523 |
+
|
| 1524 |
+
def train_transcoder(transcoder, model, tokenizer, training_prompts, device, steps=1000, batch_size=16, optimizer=None):
|
| 1525 |
+
# Trains the Cross-Layer Transcoder.
|
| 1526 |
+
transcoder.train()
|
| 1527 |
+
|
| 1528 |
+
# Use a progress bar for visual feedback.
|
| 1529 |
+
progress_bar = tqdm(range(steps), desc="Training CLT")
|
| 1530 |
+
|
| 1531 |
+
for step in progress_bar:
|
| 1532 |
+
# Get a random batch of prompts.
|
| 1533 |
+
batch_prompts = random.choices(training_prompts, k=batch_size)
|
| 1534 |
+
|
| 1535 |
+
# Tokenize the batch.
|
| 1536 |
+
inputs = tokenizer(
|
| 1537 |
+
batch_prompts,
|
| 1538 |
+
return_tensors="pt",
|
| 1539 |
+
padding=True,
|
| 1540 |
+
truncation=True,
|
| 1541 |
+
max_length=MAX_SEQ_LEN
|
| 1542 |
+
)
|
| 1543 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 1544 |
+
|
| 1545 |
+
# Get the model activations.
|
| 1546 |
+
with torch.no_grad():
|
| 1547 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 1548 |
+
hidden_states = outputs.hidden_states[1:]
|
| 1549 |
+
|
| 1550 |
+
# Forward pass through the CLT.
|
| 1551 |
+
feature_activations, reconstructed_outputs = transcoder(hidden_states)
|
| 1552 |
+
|
| 1553 |
+
# Compute the reconstruction loss.
|
| 1554 |
+
recon_loss = 0.0
|
| 1555 |
+
for i, (target, pred) in enumerate(zip(hidden_states, reconstructed_outputs)):
|
| 1556 |
+
recon_loss += F.mse_loss(pred, target)
|
| 1557 |
+
|
| 1558 |
+
# Compute the sparsity loss.
|
| 1559 |
+
sparsity_loss = 0.0
|
| 1560 |
+
for features in feature_activations:
|
| 1561 |
+
sparsity_loss += torch.mean(torch.tanh(0.01 * features)) # Use config.sparsity_lambda
|
| 1562 |
+
|
| 1563 |
+
# Total loss.
|
| 1564 |
+
loss = (0.8 * recon_loss + 0.2 * sparsity_loss) # Use config.reconstruction_loss_weight
|
| 1565 |
+
|
| 1566 |
+
if optimizer:
|
| 1567 |
+
optimizer.zero_grad()
|
| 1568 |
+
loss.backward()
|
| 1569 |
+
optimizer.step()
|
| 1570 |
+
|
| 1571 |
+
progress_bar.set_postfix({
|
| 1572 |
+
"Recon Loss": f"{recon_loss.item():.4f}",
|
| 1573 |
+
"Sparsity Loss": f"{sparsity_loss.item():.4f}",
|
| 1574 |
+
"Total Loss": f"{loss.item():.4f}"
|
| 1575 |
+
})
|
| 1576 |
+
|
| 1577 |
+
def generate_feature_visualizations(transcoder, model, tokenizer, prompt, device, qwen_api_config=None, graph_config: Optional[AttributionGraphConfig] = None):
|
| 1578 |
+
# Generates feature visualizations and interpretations for a prompt.
|
| 1579 |
+
# Tokenize the prompt.
|
| 1580 |
+
inputs = tokenizer(
|
| 1581 |
+
prompt,
|
| 1582 |
+
return_tensors="pt",
|
| 1583 |
+
padding=True,
|
| 1584 |
+
truncation=True,
|
| 1585 |
+
max_length=MAX_SEQ_LEN
|
| 1586 |
+
)
|
| 1587 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 1588 |
+
|
| 1589 |
+
# Get the model activations.
|
| 1590 |
+
with torch.no_grad():
|
| 1591 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 1592 |
+
hidden_states = outputs.hidden_states[1:]
|
| 1593 |
+
|
| 1594 |
+
# Forward pass through the CLT.
|
| 1595 |
+
feature_activations, reconstructed_outputs = transcoder(hidden_states)
|
| 1596 |
+
|
| 1597 |
+
# Visualize the features.
|
| 1598 |
+
feature_visualizations = {}
|
| 1599 |
+
for layer_idx, features in enumerate(tqdm(feature_activations, desc="Interpreting features")):
|
| 1600 |
+
layer_viz = {}
|
| 1601 |
+
# Analyze the top features for this layer.
|
| 1602 |
+
# features shape: [batch_size, seq_len, n_features]
|
| 1603 |
+
feature_importance = torch.mean(features, dim=(0, 1)) # Average over batch and sequence
|
| 1604 |
+
top_lim = getattr(graph_config, "feature_visualization_top_k", 5) if graph_config else 5
|
| 1605 |
+
top_features = torch.topk(feature_importance, k=min(top_lim, feature_importance.size(0))).indices
|
| 1606 |
+
|
| 1607 |
+
for feat_idx in top_features:
|
| 1608 |
+
viz = FeatureVisualizer(tokenizer).visualize_feature(
|
| 1609 |
+
feat_idx.item(), layer_idx, features[0], tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
|
| 1610 |
+
)
|
| 1611 |
+
interpretation = FeatureVisualizer(tokenizer).interpret_feature(
|
| 1612 |
+
feat_idx.item(), layer_idx, viz, qwen_api_config
|
| 1613 |
+
)
|
| 1614 |
+
viz['interpretation'] = interpretation
|
| 1615 |
+
layer_viz[f"feature_{feat_idx.item()}"] = viz
|
| 1616 |
+
|
| 1617 |
+
feature_visualizations[f"layer_{layer_idx}"] = layer_viz
|
| 1618 |
+
|
| 1619 |
+
# Construct the attribution graph.
|
| 1620 |
+
if graph_config is None:
|
| 1621 |
+
graph_config = AttributionGraphConfig()
|
| 1622 |
+
attribution_graph = AttributionGraph(transcoder, tokenizer, graph_config)
|
| 1623 |
+
graph = attribution_graph.construct_graph(
|
| 1624 |
+
tokenizer.convert_ids_to_tokens(inputs["input_ids"][0]), feature_activations, -1 # No target token for visualization
|
| 1625 |
+
)
|
| 1626 |
+
|
| 1627 |
+
# Prune the graph.
|
| 1628 |
+
pruned_graph = attribution_graph.prune_graph(0.8) # Use config.pruning_threshold
|
| 1629 |
+
|
| 1630 |
+
# Analyze the most important paths.
|
| 1631 |
+
important_paths = []
|
| 1632 |
+
if len(pruned_graph.nodes()) > 0:
|
| 1633 |
+
embedding_nodes = [
|
| 1634 |
+
node for node, type_ in attribution_graph.node_types.items()
|
| 1635 |
+
if type_ == "embedding" and node in pruned_graph
|
| 1636 |
+
]
|
| 1637 |
+
output_nodes = [
|
| 1638 |
+
node for node, type_ in attribution_graph.node_types.items()
|
| 1639 |
+
if type_ == "output" and node in pruned_graph
|
| 1640 |
+
]
|
| 1641 |
+
path_loop = tqdm(embedding_nodes[:3], desc="Enumerating paths")
|
| 1642 |
+
for emb_node in path_loop:
|
| 1643 |
+
for out_node in output_nodes:
|
| 1644 |
+
try:
|
| 1645 |
+
cutoff = getattr(graph_config, "path_search_cutoff", 5) if graph_config else 5
|
| 1646 |
+
cutoff = max(2, cutoff)
|
| 1647 |
+
paths = list(nx.all_simple_paths(pruned_graph, emb_node, out_node, cutoff=cutoff))
|
| 1648 |
+
for path in paths[:2]:
|
| 1649 |
+
path_weight = 1.0
|
| 1650 |
+
for i in range(len(path) - 1):
|
| 1651 |
+
edge_weight = attribution_graph.edge_weights.get(
|
| 1652 |
+
(path[i], path[i + 1]), 0.0
|
| 1653 |
+
)
|
| 1654 |
+
path_weight *= abs(edge_weight)
|
| 1655 |
+
important_paths.append({
|
| 1656 |
+
'path': path,
|
| 1657 |
+
'weight': path_weight,
|
| 1658 |
+
'description': attribution_graph._describe_path(path)
|
| 1659 |
+
})
|
| 1660 |
+
except nx.NetworkXNoPath:
|
| 1661 |
+
continue
|
| 1662 |
+
|
| 1663 |
+
# Sort paths by importance.
|
| 1664 |
+
important_paths.sort(key=lambda x: x['weight'], reverse=True)
|
| 1665 |
+
|
| 1666 |
+
return {
|
| 1667 |
+
"prompt": prompt,
|
| 1668 |
+
"full_graph_stats": {
|
| 1669 |
+
"n_nodes": len(graph.nodes()),
|
| 1670 |
+
"n_edges": len(graph.edges()),
|
| 1671 |
+
"node_types": dict(attribution_graph.node_types)
|
| 1672 |
+
},
|
| 1673 |
+
"pruned_graph_stats": {
|
| 1674 |
+
"n_nodes": len(pruned_graph.nodes()),
|
| 1675 |
+
"n_edges": len(pruned_graph.edges())
|
| 1676 |
+
},
|
| 1677 |
+
"feature_visualizations": feature_visualizations,
|
| 1678 |
+
"important_paths": important_paths[:5] # Top 5 paths
|
| 1679 |
+
}
|
| 1680 |
+
|
| 1681 |
+
def main():
|
| 1682 |
+
# Main function to run the analysis for a single prompt.
|
| 1683 |
+
|
| 1684 |
+
# Set a seed for reproducibility.
|
| 1685 |
+
set_seed()
|
| 1686 |
+
|
| 1687 |
+
# --- Argument Parser ---
|
| 1688 |
+
parser = argparse.ArgumentParser(description="Run Attribution Graph analysis for a single prompt.")
|
| 1689 |
+
parser.add_argument(
|
| 1690 |
+
'--prompt-index',
|
| 1691 |
+
type=int,
|
| 1692 |
+
required=True,
|
| 1693 |
+
help=f"The 0-based index of the prompt to analyze from the ANALYSIS_PROMPTS list (0 to {len(ANALYSIS_PROMPTS) - 1})."
|
| 1694 |
+
)
|
| 1695 |
+
parser.add_argument(
|
| 1696 |
+
'--force-retrain-clt',
|
| 1697 |
+
action='store_true',
|
| 1698 |
+
help="Force re-training of the Cross-Layer Transcoder, even if a saved model exists."
|
| 1699 |
+
)
|
| 1700 |
+
parser.add_argument(
|
| 1701 |
+
'--batch-eval',
|
| 1702 |
+
action='store_true',
|
| 1703 |
+
help="Analyze all predefined prompts and compute aggregate faithfulness metrics."
|
| 1704 |
+
)
|
| 1705 |
+
args = parser.parse_args()
|
| 1706 |
+
|
| 1707 |
+
prompt_idx = args.prompt_index
|
| 1708 |
+
if not (0 <= prompt_idx < len(ANALYSIS_PROMPTS)):
|
| 1709 |
+
print(f"❌ Error: --prompt-index must be between 0 and {len(ANALYSIS_PROMPTS) - 1}.")
|
| 1710 |
+
return
|
| 1711 |
+
|
| 1712 |
+
# Get the API config from the utility function.
|
| 1713 |
+
qwen_api_config = init_qwen_api()
|
| 1714 |
+
|
| 1715 |
+
# Configuration
|
| 1716 |
+
config = AttributionGraphConfig(
|
| 1717 |
+
model_path="./models/OLMo-2-1124-7B",
|
| 1718 |
+
n_features_per_layer=512,
|
| 1719 |
+
training_steps=500,
|
| 1720 |
+
batch_size=4,
|
| 1721 |
+
max_seq_length=256,
|
| 1722 |
+
learning_rate=1e-4,
|
| 1723 |
+
sparsity_lambda=0.01,
|
| 1724 |
+
qwen_api_config=qwen_api_config
|
| 1725 |
+
)
|
| 1726 |
+
|
| 1727 |
+
print("Attribution Graphs for OLMo2 7B - Single Prompt Pipeline")
|
| 1728 |
+
print("=" * 50)
|
| 1729 |
+
print(f"Model path: {config.model_path}")
|
| 1730 |
+
print(f"Device: {config.device}")
|
| 1731 |
+
|
| 1732 |
+
try:
|
| 1733 |
+
# Initialize the full pipeline.
|
| 1734 |
+
print("🚀 Initializing Attribution Graphs Pipeline...")
|
| 1735 |
+
pipeline = AttributionGraphsPipeline(config)
|
| 1736 |
+
print("✓ Pipeline initialized successfully")
|
| 1737 |
+
print()
|
| 1738 |
+
|
| 1739 |
+
# Load an existing CLT model or train a new one.
|
| 1740 |
+
if os.path.exists(CLT_SAVE_PATH) and not args.force_retrain_clt:
|
| 1741 |
+
print(f"🧠 Loading existing CLT model from {CLT_SAVE_PATH}...")
|
| 1742 |
+
pipeline.load_clt(CLT_SAVE_PATH)
|
| 1743 |
+
print("✓ CLT model loaded successfully.")
|
| 1744 |
+
else:
|
| 1745 |
+
if args.force_retrain_clt and os.path.exists(CLT_SAVE_PATH):
|
| 1746 |
+
print("��♂️ --force-retrain-clt flag is set. Overwriting existing model.")
|
| 1747 |
+
|
| 1748 |
+
# Train a new CLT model.
|
| 1749 |
+
print("📚 Training a new CLT model...")
|
| 1750 |
+
print(f" Training on {len(TRAINING_PROMPTS)} example texts...")
|
| 1751 |
+
training_stats = pipeline.train_clt(TRAINING_PROMPTS)
|
| 1752 |
+
print("✓ CLT training completed.")
|
| 1753 |
+
|
| 1754 |
+
# Save the training statistics.
|
| 1755 |
+
stats_save_path = os.path.join(RESULTS_DIR, "clt_training_stats.json")
|
| 1756 |
+
with open(stats_save_path, 'w') as f:
|
| 1757 |
+
json.dump(training_stats, f, indent=2)
|
| 1758 |
+
print(f" Saved training stats to {stats_save_path}")
|
| 1759 |
+
|
| 1760 |
+
# Save the new model.
|
| 1761 |
+
pipeline.save_clt(CLT_SAVE_PATH)
|
| 1762 |
+
print(f" Saved trained model to {CLT_SAVE_PATH} for future use.")
|
| 1763 |
+
|
| 1764 |
+
print()
|
| 1765 |
+
|
| 1766 |
+
if args.batch_eval:
|
| 1767 |
+
print("📊 Running batch faithfulness evaluation across all prompts...")
|
| 1768 |
+
batch_payload = pipeline.analyze_prompts_batch(ANALYSIS_PROMPTS)
|
| 1769 |
+
final_results = copy.deepcopy(batch_payload)
|
| 1770 |
+
final_results['config'] = config.__dict__
|
| 1771 |
+
final_results['timestamp'] = str(time.time())
|
| 1772 |
+
for analysis_entry in final_results['analyses'].values():
|
| 1773 |
+
analysis_entry.pop('graph', None)
|
| 1774 |
+
batch_save_path = os.path.join(RESULTS_DIR, "attribution_graphs_batch_results.json")
|
| 1775 |
+
pipeline.save_results(final_results, batch_save_path)
|
| 1776 |
+
print(f"💾 Batch results saved to {batch_save_path}")
|
| 1777 |
+
|
| 1778 |
+
aggregate_summary = batch_payload['aggregate_summary']
|
| 1779 |
+
targeted_summary = aggregate_summary.get('targeted', {})
|
| 1780 |
+
random_summary = aggregate_summary.get('random_baseline', {})
|
| 1781 |
+
path_summary = aggregate_summary.get('path', {})
|
| 1782 |
+
|
| 1783 |
+
def _format_summary(label: str, summary: Dict[str, Any]) -> str:
|
| 1784 |
+
return (
|
| 1785 |
+
f"{label}: count={summary.get('count', 0)}, "
|
| 1786 |
+
f"avg|Δp|={summary.get('avg_abs_probability_change', 0.0):.4f}, "
|
| 1787 |
+
f"flip_rate={summary.get('flip_rate', 0.0):.2%}"
|
| 1788 |
+
)
|
| 1789 |
+
|
| 1790 |
+
print("📈 Aggregate faithfulness summary")
|
| 1791 |
+
print(f" {_format_summary('Targeted', targeted_summary)}")
|
| 1792 |
+
print(f" {_format_summary('Random baseline', random_summary)}")
|
| 1793 |
+
print(f" {_format_summary('Path', path_summary)}")
|
| 1794 |
+
print(f" {_format_summary('Random path baseline', aggregate_summary.get('random_path_baseline', {}))}")
|
| 1795 |
+
diff_abs = aggregate_summary.get('target_minus_random_abs_probability_change', 0.0)
|
| 1796 |
+
diff_flip = aggregate_summary.get('target_flip_rate_minus_random', 0.0)
|
| 1797 |
+
path_diff_abs = aggregate_summary.get('path_minus_random_abs_probability_change', 0.0)
|
| 1798 |
+
path_diff_flip = aggregate_summary.get('path_flip_rate_minus_random', 0.0)
|
| 1799 |
+
print(f" Targeted vs Random |Δp| difference: {diff_abs:.4f}")
|
| 1800 |
+
print(f" Targeted vs Random flip rate difference: {diff_flip:.4f}")
|
| 1801 |
+
print(f" Path vs Random path |Δp| difference: {path_diff_abs:.4f}")
|
| 1802 |
+
print(f" Path vs Random path flip rate difference: {path_diff_flip:.4f}")
|
| 1803 |
+
print("\n🎉 Batch evaluation completed successfully!")
|
| 1804 |
+
return
|
| 1805 |
+
|
| 1806 |
+
# Analyze the selected prompt.
|
| 1807 |
+
prompt_to_analyze = ANALYSIS_PROMPTS[prompt_idx]
|
| 1808 |
+
print(f"🔍 Analyzing prompt {prompt_idx + 1}/{len(ANALYSIS_PROMPTS)}: '{prompt_to_analyze}'")
|
| 1809 |
+
|
| 1810 |
+
analysis = pipeline.analyze_prompt(prompt_to_analyze, target_token_idx=-1)
|
| 1811 |
+
|
| 1812 |
+
# Display the key results.
|
| 1813 |
+
print(f" ✓ Tokenized into {len(analysis['input_tokens'])} tokens")
|
| 1814 |
+
print(f" ✓ Full graph: {analysis['full_graph_stats']['n_nodes']} nodes, {analysis['full_graph_stats']['n_edges']} edges")
|
| 1815 |
+
print(f" ✓ Pruned graph: {analysis['pruned_graph_stats']['n_nodes']} nodes, {analysis['pruned_graph_stats']['n_edges']} edges")
|
| 1816 |
+
|
| 1817 |
+
# Show the top features.
|
| 1818 |
+
print(" 📊 Top active features:")
|
| 1819 |
+
feature_layers_items = list(analysis['feature_visualizations'].items())
|
| 1820 |
+
if config.summary_max_layers is not None:
|
| 1821 |
+
feature_layers_items = feature_layers_items[:config.summary_max_layers]
|
| 1822 |
+
for layer_name, layer_features in feature_layers_items:
|
| 1823 |
+
print(f" {layer_name}:")
|
| 1824 |
+
feature_items = layer_features.items()
|
| 1825 |
+
if config.summary_features_per_layer is not None:
|
| 1826 |
+
feature_items = list(feature_items)[:config.summary_features_per_layer]
|
| 1827 |
+
for feat_name, feat_data in feature_items:
|
| 1828 |
+
print(f" {feat_name}: {feat_data['interpretation']} (max: {feat_data['max_activation']:.3f})")
|
| 1829 |
+
|
| 1830 |
+
print()
|
| 1831 |
+
|
| 1832 |
+
# Summarize perturbation experiments and baselines.
|
| 1833 |
+
print("🧪 Targeted feature ablations:")
|
| 1834 |
+
targeted_results = analysis.get('perturbation_experiments', [])
|
| 1835 |
+
if targeted_results:
|
| 1836 |
+
for experiment in targeted_results:
|
| 1837 |
+
layer_name = experiment.get('layer_name', f"L{experiment.get('feature_set', [{}])[0].get('layer', '?')}")
|
| 1838 |
+
feature_name = experiment.get('feature_name', f"F{experiment.get('feature_set', [{}])[0].get('feature', '?')}")
|
| 1839 |
+
prob_delta = experiment.get('probability_change', 0.0)
|
| 1840 |
+
logit_delta = experiment.get('logit_change', 0.0)
|
| 1841 |
+
flips = experiment.get('ablation_flips_top_prediction', False)
|
| 1842 |
+
print(f" {layer_name}/{feature_name}: Δp={prob_delta:.4f}, Δlogit={logit_delta:.4f}, flips_top={flips}")
|
| 1843 |
+
else:
|
| 1844 |
+
print(" - No targeted ablations were recorded.")
|
| 1845 |
+
|
| 1846 |
+
print("\n🎲 Random baseline ablations:")
|
| 1847 |
+
random_baseline = analysis.get('random_baseline_experiments', [])
|
| 1848 |
+
if random_baseline:
|
| 1849 |
+
for experiment in random_baseline:
|
| 1850 |
+
prob_delta = experiment.get('probability_change', 0.0)
|
| 1851 |
+
logit_delta = experiment.get('logit_change', 0.0)
|
| 1852 |
+
flips = experiment.get('ablation_flips_top_prediction', False)
|
| 1853 |
+
trial_idx = experiment.get('trial_index', '?')
|
| 1854 |
+
print(f" Trial {trial_idx}: Δp={prob_delta:.4f}, Δlogit={logit_delta:.4f}, flips_top={flips}")
|
| 1855 |
+
else:
|
| 1856 |
+
print(" - No random baseline trials were run.")
|
| 1857 |
+
|
| 1858 |
+
print("\n🛤️ Path ablations:")
|
| 1859 |
+
path_results = analysis.get('path_ablation_experiments', [])
|
| 1860 |
+
if path_results:
|
| 1861 |
+
for path_exp in path_results:
|
| 1862 |
+
description = path_exp.get('path_description', 'Path')
|
| 1863 |
+
prob_delta = path_exp.get('probability_change', 0.0)
|
| 1864 |
+
logit_delta = path_exp.get('logit_change', 0.0)
|
| 1865 |
+
flips = path_exp.get('ablation_flips_top_prediction', False)
|
| 1866 |
+
print(f" {description}: Δp={prob_delta:.4f}, Δlogit={logit_delta:.4f}, flips_top={flips}")
|
| 1867 |
+
else:
|
| 1868 |
+
print(" - No path ablations were run.")
|
| 1869 |
+
|
| 1870 |
+
summary_stats = analysis.get('summary_statistics', {})
|
| 1871 |
+
targeted_summary = summary_stats.get('targeted', {})
|
| 1872 |
+
random_summary = summary_stats.get('random_baseline', {})
|
| 1873 |
+
path_summary = summary_stats.get('path', {})
|
| 1874 |
+
random_path_summary = summary_stats.get('random_path_baseline', {})
|
| 1875 |
+
print("\n📈 Summary statistics:")
|
| 1876 |
+
print(f" Targeted: avg|Δp|={targeted_summary.get('avg_abs_probability_change', 0.0):.4f}, flip_rate={targeted_summary.get('flip_rate', 0.0):.2%}")
|
| 1877 |
+
print(f" Random baseline: avg|Δp|={random_summary.get('avg_abs_probability_change', 0.0):.4f}, flip_rate={random_summary.get('flip_rate', 0.0):.2%}")
|
| 1878 |
+
print(f" Path: avg|Δp|={path_summary.get('avg_abs_probability_change', 0.0):.4f}, flip_rate={path_summary.get('flip_rate', 0.0):.2%}")
|
| 1879 |
+
print(f" Random path baseline: avg|Δp|={random_path_summary.get('avg_abs_probability_change', 0.0):.4f}, flip_rate={random_path_summary.get('flip_rate', 0.0):.2%}")
|
| 1880 |
+
print(f" Targeted vs Random |Δp| diff: {summary_stats.get('target_minus_random_abs_probability_change', 0.0):.4f}")
|
| 1881 |
+
print(f" Targeted vs Random flip diff: {summary_stats.get('target_flip_rate_minus_random', 0.0):.4f}")
|
| 1882 |
+
print(f" Path vs Random path |Δp| diff: {summary_stats.get('path_minus_random_abs_probability_change', 0.0):.4f}")
|
| 1883 |
+
print(f" Path vs Random path flip diff: {summary_stats.get('path_flip_rate_minus_random', 0.0):.4f}")
|
| 1884 |
+
print("\n✓ Faithfulness experiments summarized\n")
|
| 1885 |
+
|
| 1886 |
+
# Generate a visualization for the prompt.
|
| 1887 |
+
print("📈 Generating visualization...")
|
| 1888 |
+
if 'graph' in analysis and analysis['pruned_graph_stats']['n_nodes'] > 0:
|
| 1889 |
+
viz_path = os.path.join(RESULTS_DIR, f"attribution_graph_prompt_{prompt_idx + 1}.png")
|
| 1890 |
+
pipeline.attribution_graph.visualize_graph(analysis['graph'], save_path=viz_path)
|
| 1891 |
+
print(f" ✓ Graph visualization saved to {viz_path}")
|
| 1892 |
+
else:
|
| 1893 |
+
print(" - Skipping visualization as no graph was generated or it was empty.")
|
| 1894 |
+
|
| 1895 |
+
# Save the results in a format for the web app.
|
| 1896 |
+
save_path = os.path.join(RESULTS_DIR, f"attribution_graphs_results_prompt_{prompt_idx + 1}.json")
|
| 1897 |
+
|
| 1898 |
+
# Create a JSON file that can be merged with others.
|
| 1899 |
+
final_results = {
|
| 1900 |
+
"analyses": {
|
| 1901 |
+
f"prompt_{prompt_idx + 1}": analysis
|
| 1902 |
+
},
|
| 1903 |
+
"config": config.__dict__,
|
| 1904 |
+
"timestamp": str(time.time())
|
| 1905 |
+
}
|
| 1906 |
+
|
| 1907 |
+
# The web page doesn't use the graph object, so remove it.
|
| 1908 |
+
if 'graph' in final_results['analyses'][f"prompt_{prompt_idx + 1}"]:
|
| 1909 |
+
del final_results['analyses'][f"prompt_{prompt_idx + 1}"]['graph']
|
| 1910 |
+
|
| 1911 |
+
pipeline.save_results(final_results, save_path)
|
| 1912 |
+
print(f"💾 Results saved to {save_path}")
|
| 1913 |
+
|
| 1914 |
+
print("\n🎉 Analysis for this prompt completed successfully!")
|
| 1915 |
+
|
| 1916 |
+
except Exception as e:
|
| 1917 |
+
print(f"❌ Error during execution: {e}")
|
| 1918 |
+
import traceback
|
| 1919 |
+
traceback.print_exc()
|
| 1920 |
+
|
| 1921 |
+
if __name__ == "__main__":
|
| 1922 |
+
main()
|
circuit_analysis/calculate_cpr_cmd.py
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import numpy as np
|
| 3 |
+
import networkx as nx
|
| 4 |
+
import argparse
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import logging
|
| 9 |
+
from typing import List, Tuple
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
import math
|
| 12 |
+
|
| 13 |
+
# Ensure we can import the pipeline
|
| 14 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 15 |
+
|
| 16 |
+
from circuit_analysis.attribution_graphs_olmo import (
|
| 17 |
+
AttributionGraphsPipeline,
|
| 18 |
+
AttributionGraphConfig,
|
| 19 |
+
ANALYSIS_PROMPTS,
|
| 20 |
+
AttributionGraph
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
# Configure logging
|
| 24 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 25 |
+
logger = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
def compute_cpr(k_values: List[float], f_values: List[float]) -> float:
|
| 28 |
+
"""
|
| 29 |
+
Compute CPR (Integrated Circuit Performance Ratio) using the trapezoidal rule.
|
| 30 |
+
CPR = Integral of f(C_k) dk
|
| 31 |
+
"""
|
| 32 |
+
cpr = 0.0
|
| 33 |
+
for i in range(len(k_values) - 1):
|
| 34 |
+
cpr += 0.5 * (f_values[i] + f_values[i+1]) * (k_values[i+1] - k_values[i])
|
| 35 |
+
return cpr
|
| 36 |
+
|
| 37 |
+
def compute_cmd(k_values: List[float], f_values: List[float]) -> float:
|
| 38 |
+
"""
|
| 39 |
+
Compute CMD (Integrated Circuit-Model Distance) using the trapezoidal rule.
|
| 40 |
+
CMD = Integral of |1 - f(C_k)| dk
|
| 41 |
+
"""
|
| 42 |
+
cmd = 0.0
|
| 43 |
+
for i in range(len(k_values) - 1):
|
| 44 |
+
y0 = abs(1.0 - f_values[i])
|
| 45 |
+
y1 = abs(1.0 - f_values[i+1])
|
| 46 |
+
cmd += 0.5 * (y0 + y1) * (k_values[i+1] - k_values[i])
|
| 47 |
+
return cmd
|
| 48 |
+
|
| 49 |
+
def get_active_features_from_graph(graph: nx.DiGraph) -> List[Tuple[int, int]]:
|
| 50 |
+
"""
|
| 51 |
+
Extracts the list of feature nodes (as layer_idx, feature_idx tuples) from the graph.
|
| 52 |
+
"""
|
| 53 |
+
features = []
|
| 54 |
+
for node in graph.nodes():
|
| 55 |
+
if node.startswith("feat_"):
|
| 56 |
+
parts = node.split('_')
|
| 57 |
+
try:
|
| 58 |
+
# Format: feat_L{layer}_T{token}_F{feature}
|
| 59 |
+
layer_idx = int(parts[1][1:])
|
| 60 |
+
feature_idx = int(parts[3][1:])
|
| 61 |
+
# We only care about unique (layer, feature) pairs for ablation
|
| 62 |
+
features.append((layer_idx, feature_idx))
|
| 63 |
+
except (IndexError, ValueError):
|
| 64 |
+
continue
|
| 65 |
+
return list(set(features))
|
| 66 |
+
|
| 67 |
+
def calculate_graph_importance(attribution_graph_obj: AttributionGraph, graph: nx.DiGraph) -> List[Tuple[str, float]]:
|
| 68 |
+
"""
|
| 69 |
+
Calculates the importance of each feature node in the graph based on edge weights.
|
| 70 |
+
Returns a list of (node_id, importance_score) sorted by importance descending.
|
| 71 |
+
"""
|
| 72 |
+
node_importance = {}
|
| 73 |
+
|
| 74 |
+
# Identify feature nodes
|
| 75 |
+
feature_nodes = [n for n in graph.nodes() if attribution_graph_obj.node_types.get(n) == "feature"]
|
| 76 |
+
|
| 77 |
+
# Calculate importance as sum of absolute weights of connected edges
|
| 78 |
+
for node in feature_nodes:
|
| 79 |
+
importance = 0.0
|
| 80 |
+
# Outgoing edges
|
| 81 |
+
for _, target in graph.out_edges(node):
|
| 82 |
+
weight = attribution_graph_obj.edge_weights.get((node, target), 0.0)
|
| 83 |
+
importance += abs(weight)
|
| 84 |
+
# Incoming edges? MIB usually focuses on "importance" for the task.
|
| 85 |
+
# Using sum of absolute edge weights is a standard proxy.
|
| 86 |
+
# attribution_graphs_olmo.py prune_graph uses sum of all connected edge weights (in and out).
|
| 87 |
+
for source, _ in graph.in_edges(node):
|
| 88 |
+
weight = attribution_graph_obj.edge_weights.get((source, node), 0.0)
|
| 89 |
+
importance += abs(weight)
|
| 90 |
+
|
| 91 |
+
node_importance[node] = importance
|
| 92 |
+
|
| 93 |
+
return sorted(node_importance.items(), key=lambda x: x[1], reverse=True)
|
| 94 |
+
|
| 95 |
+
def get_edges_count(graph: nx.DiGraph, nodes: List[str]) -> int:
|
| 96 |
+
"""
|
| 97 |
+
Returns the number of edges in the subgraph induced by the given nodes
|
| 98 |
+
(plus edges to output/embedding if we consider them part of the circuit context).
|
| 99 |
+
However, strictly following "fraction of total edges":
|
| 100 |
+
We should count edges where BOTH source and target are in the kept set (including embeddings/output).
|
| 101 |
+
"""
|
| 102 |
+
# Assuming embeddings and output are always "kept" or don't count towards the quota
|
| 103 |
+
# if we only ablate features.
|
| 104 |
+
# But for the metric k = |C|/|N|, we need a consistent definition.
|
| 105 |
+
# Let's define |C| as the number of edges in the subgraph induced by (Selected Features + Embeddings + Output).
|
| 106 |
+
|
| 107 |
+
nodes_set = set(nodes)
|
| 108 |
+
count = 0
|
| 109 |
+
for u, v in graph.edges():
|
| 110 |
+
if u in nodes_set and v in nodes_set:
|
| 111 |
+
count += 1
|
| 112 |
+
return count
|
| 113 |
+
|
| 114 |
+
def run_cpr_cmd_analysis(pipeline: AttributionGraphsPipeline, prompt_idx: int):
|
| 115 |
+
"""
|
| 116 |
+
Compute CPR and CMD for a given prompt, using:
|
| 117 |
+
|
| 118 |
+
- Universe: all feature nodes present in the attribution graph
|
| 119 |
+
- Metric m: logit(target) only (no foil)
|
| 120 |
+
- Interventions: ablation of feature sets with intervention_strength=1.0
|
| 121 |
+
"""
|
| 122 |
+
prompt = ANALYSIS_PROMPTS[prompt_idx]
|
| 123 |
+
logger.info(f"Analyzing prompt {prompt_idx}: '{prompt}'")
|
| 124 |
+
|
| 125 |
+
# Build/prune the attribution graph for this prompt
|
| 126 |
+
pipeline.analyze_prompt(prompt)
|
| 127 |
+
full_graph = pipeline.attribution_graph.graph
|
| 128 |
+
|
| 129 |
+
# Baseline: run once to get logits & feature activations
|
| 130 |
+
baseline_data = pipeline.perturbation_experiments._prepare_inputs(prompt, top_k=1)
|
| 131 |
+
target_token_id = baseline_data['baseline_top_tokens'].indices[0].item()
|
| 132 |
+
baseline_logits = baseline_data['baseline_last_token_logits']
|
| 133 |
+
m_N = baseline_logits[target_token_id].item()
|
| 134 |
+
|
| 135 |
+
logger.info(
|
| 136 |
+
f"Baseline m(N) = {m_N:.4f} "
|
| 137 |
+
f"(Token: {pipeline.tokenizer.decode([target_token_id])})"
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# Universe: all feature nodes in the graph
|
| 141 |
+
universe_features = get_active_features_from_graph(full_graph)
|
| 142 |
+
logger.info(f"Graph Universe size: {len(universe_features)} features")
|
| 143 |
+
|
| 144 |
+
if not universe_features:
|
| 145 |
+
logger.warning("No features found in graph. Skipping.")
|
| 146 |
+
return None
|
| 147 |
+
|
| 148 |
+
# Empty circuit: ablate all universe features
|
| 149 |
+
empty_res = pipeline.perturbation_experiments.feature_set_ablation_experiment(
|
| 150 |
+
prompt,
|
| 151 |
+
feature_set=universe_features,
|
| 152 |
+
intervention_strength=1.0,
|
| 153 |
+
target_token_id=target_token_id
|
| 154 |
+
)
|
| 155 |
+
m_empty = empty_res["ablated_logit"]
|
| 156 |
+
logger.info(f"Empty m(Ø) = {m_empty:.4f}")
|
| 157 |
+
|
| 158 |
+
if not math.isfinite(m_empty):
|
| 159 |
+
logger.warning(
|
| 160 |
+
f"m_empty is non-finite ({m_empty}) for prompt {prompt_idx}; "
|
| 161 |
+
"skipping CPR/CMD for this prompt."
|
| 162 |
+
)
|
| 163 |
+
return None
|
| 164 |
+
|
| 165 |
+
# Node importance within the graph
|
| 166 |
+
sorted_nodes = calculate_graph_importance(pipeline.attribution_graph, full_graph)
|
| 167 |
+
|
| 168 |
+
total_edges = full_graph.number_of_edges()
|
| 169 |
+
k_grid = [0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0]
|
| 170 |
+
f_values = []
|
| 171 |
+
actual_k_values = []
|
| 172 |
+
|
| 173 |
+
# Embeddings/output are always kept
|
| 174 |
+
always_kept_nodes = [n for n in full_graph.nodes() if not n.startswith("feat_")]
|
| 175 |
+
|
| 176 |
+
logger.info("Computing faithfulness curve...")
|
| 177 |
+
|
| 178 |
+
for k in k_grid:
|
| 179 |
+
target_edge_count = int(k * total_edges)
|
| 180 |
+
|
| 181 |
+
current_circuit_nodes = list(always_kept_nodes)
|
| 182 |
+
current_feature_tuples = []
|
| 183 |
+
|
| 184 |
+
for node, _ in sorted_nodes:
|
| 185 |
+
current_edge_count = get_edges_count(full_graph, current_circuit_nodes)
|
| 186 |
+
if current_edge_count >= target_edge_count and len(current_feature_tuples) > 0:
|
| 187 |
+
break
|
| 188 |
+
|
| 189 |
+
current_circuit_nodes.append(node)
|
| 190 |
+
parts = node.split("_")
|
| 191 |
+
l = int(parts[1][1:])
|
| 192 |
+
f = int(parts[3][1:])
|
| 193 |
+
current_feature_tuples.append((l, f))
|
| 194 |
+
|
| 195 |
+
actual_edges = get_edges_count(full_graph, current_circuit_nodes)
|
| 196 |
+
actual_k = actual_edges / total_edges if total_edges > 0 else 0.0
|
| 197 |
+
actual_k_values.append(actual_k)
|
| 198 |
+
|
| 199 |
+
# Complement = universe \ current features
|
| 200 |
+
current_set = set(current_feature_tuples)
|
| 201 |
+
complement_set = [ft for ft in universe_features if ft not in current_set]
|
| 202 |
+
|
| 203 |
+
if not complement_set:
|
| 204 |
+
m_Ck = m_N
|
| 205 |
+
else:
|
| 206 |
+
res = pipeline.perturbation_experiments.feature_set_ablation_experiment(
|
| 207 |
+
prompt,
|
| 208 |
+
feature_set=complement_set,
|
| 209 |
+
intervention_strength=1.0,
|
| 210 |
+
target_token_id=target_token_id
|
| 211 |
+
)
|
| 212 |
+
m_Ck = res["ablated_logit"]
|
| 213 |
+
|
| 214 |
+
if not math.isfinite(m_Ck):
|
| 215 |
+
logger.warning(
|
| 216 |
+
f"Non-finite m_Ck={m_Ck} for k={k:.4f} on prompt {prompt_idx}; "
|
| 217 |
+
"skipping this k point."
|
| 218 |
+
)
|
| 219 |
+
continue
|
| 220 |
+
|
| 221 |
+
if abs(m_N - m_empty) < 1e-6:
|
| 222 |
+
f_k = 0.0
|
| 223 |
+
else:
|
| 224 |
+
raw_f = (m_Ck - m_empty) / (m_N - m_empty)
|
| 225 |
+
f_k = max(0.0, min(1.0, raw_f))
|
| 226 |
+
|
| 227 |
+
f_values.append(f_k)
|
| 228 |
+
|
| 229 |
+
if not actual_k_values or not f_values:
|
| 230 |
+
logger.warning(f"No valid k-points for prompt {prompt_idx}; skipping.")
|
| 231 |
+
return None
|
| 232 |
+
|
| 233 |
+
pairs = sorted(zip(actual_k_values, f_values), key=lambda x: x[0])
|
| 234 |
+
sorted_k = [p[0] for p in pairs]
|
| 235 |
+
sorted_f = [p[1] for p in pairs]
|
| 236 |
+
|
| 237 |
+
if sorted_k[0] > 0.0:
|
| 238 |
+
sorted_k.insert(0, 0.0)
|
| 239 |
+
sorted_f.insert(0, 0.0)
|
| 240 |
+
if sorted_k[-1] < 1.0:
|
| 241 |
+
last_f = sorted_f[-1]
|
| 242 |
+
sorted_k.append(1.0)
|
| 243 |
+
sorted_f.append(last_f)
|
| 244 |
+
|
| 245 |
+
cpr = compute_cpr(sorted_k, sorted_f)
|
| 246 |
+
cmd = compute_cmd(sorted_k, sorted_f)
|
| 247 |
+
|
| 248 |
+
logger.info(f"Result: CPR={cpr:.4f}, CMD={cmd:.4f}")
|
| 249 |
+
|
| 250 |
+
return {
|
| 251 |
+
"prompt": prompt,
|
| 252 |
+
"target_token": pipeline.tokenizer.decode([target_token_id]),
|
| 253 |
+
"m_N": m_N,
|
| 254 |
+
"m_empty": m_empty,
|
| 255 |
+
"curve_k": sorted_k,
|
| 256 |
+
"curve_f": sorted_f,
|
| 257 |
+
"CPR": cpr,
|
| 258 |
+
"CMD": cmd
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
def main():
|
| 262 |
+
parser = argparse.ArgumentParser()
|
| 263 |
+
parser.add_argument("--output", type=str, default="circuit_analysis/results/cpr_cmd_results.json")
|
| 264 |
+
args = parser.parse_args()
|
| 265 |
+
|
| 266 |
+
# Initialize Pipeline
|
| 267 |
+
config = AttributionGraphConfig(
|
| 268 |
+
model_path="models/OLMo-2-1124-7B", # Adjust relative path if needed
|
| 269 |
+
n_features_per_layer=512, # Back to 512 due to memory constraints
|
| 270 |
+
# We want a fairly rich graph to start with, so we can prune it down
|
| 271 |
+
graph_feature_activation_threshold=0.01,
|
| 272 |
+
graph_edge_weight_threshold=0.003, # Lower threshold for more edges (prev: 0.005)
|
| 273 |
+
graph_max_features_per_layer=40, # Increased from 24 (prev: 100 was too slow)
|
| 274 |
+
graph_max_edges_per_node=20, # Increased from 12 (prev: 50 was too slow)
|
| 275 |
+
# intervention_strength defaults to 5.0 in AttributionGraphConfig, which was working better
|
| 276 |
+
intervention_strength=1.0,
|
| 277 |
+
)
|
| 278 |
+
|
| 279 |
+
# Check model path
|
| 280 |
+
if not os.path.exists(config.model_path):
|
| 281 |
+
# Try absolute python3 circuit_analysis/calculate_cpr_cmd.pypath or relative to script
|
| 282 |
+
root_path = Path(__file__).resolve().parent.parent
|
| 283 |
+
possible_path = root_path / "models" / "OLMo-2-1124-7B"
|
| 284 |
+
if possible_path.exists():
|
| 285 |
+
config.model_path = str(possible_path)
|
| 286 |
+
else:
|
| 287 |
+
# Try the one in current dir?
|
| 288 |
+
pass
|
| 289 |
+
|
| 290 |
+
pipeline = AttributionGraphsPipeline(config)
|
| 291 |
+
|
| 292 |
+
# Load CLT
|
| 293 |
+
clt_path = "circuit_analysis/models/clt_model.pth"
|
| 294 |
+
if not os.path.exists(clt_path):
|
| 295 |
+
# Try full path
|
| 296 |
+
clt_path = str(Path(__file__).resolve().parent / "models" / "clt_model.pth")
|
| 297 |
+
|
| 298 |
+
if os.path.exists(clt_path):
|
| 299 |
+
pipeline.load_clt(clt_path)
|
| 300 |
+
else:
|
| 301 |
+
logger.error(f"CLT model not found at {clt_path}. Please train it first.")
|
| 302 |
+
return
|
| 303 |
+
|
| 304 |
+
results = []
|
| 305 |
+
for i in range(len(ANALYSIS_PROMPTS)):
|
| 306 |
+
try:
|
| 307 |
+
res = run_cpr_cmd_analysis(pipeline, i)
|
| 308 |
+
if res:
|
| 309 |
+
results.append(res)
|
| 310 |
+
except Exception as e:
|
| 311 |
+
logger.error(f"Failed prompt {i}: {e}", exc_info=True)
|
| 312 |
+
|
| 313 |
+
# Average CPR/CMD
|
| 314 |
+
if results:
|
| 315 |
+
avg_cpr = np.mean([r['CPR'] for r in results])
|
| 316 |
+
avg_cmd = np.mean([r['CMD'] for r in results])
|
| 317 |
+
else:
|
| 318 |
+
avg_cpr = 0.0
|
| 319 |
+
avg_cmd = 0.0
|
| 320 |
+
|
| 321 |
+
final_output = {
|
| 322 |
+
"results": results,
|
| 323 |
+
"average_CPR": avg_cpr,
|
| 324 |
+
"average_CMD": avg_cmd
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
# Save
|
| 328 |
+
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
| 329 |
+
with open(args.output, 'w') as f:
|
| 330 |
+
json.dump(final_output, f, indent=2)
|
| 331 |
+
|
| 332 |
+
print(f"\n\nFinal Average CPR: {avg_cpr:.4f}")
|
| 333 |
+
print(f"Final Average CMD: {avg_cmd:.4f}")
|
| 334 |
+
print(f"Results saved to {args.output}")
|
| 335 |
+
|
| 336 |
+
if __name__ == "__main__":
|
| 337 |
+
main()
|
| 338 |
+
|
circuit_analysis/circuit_trace_page.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/merge_circuit_results.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import sys
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
def merge_json_results(base_file, new_file):
|
| 6 |
+
# Merges the 'analyses' from a new results file into a base file.
|
| 7 |
+
try:
|
| 8 |
+
# Ensure the results directory exists.
|
| 9 |
+
results_dir = "circuit_analysis/results"
|
| 10 |
+
if not os.path.exists(results_dir):
|
| 11 |
+
os.makedirs(results_dir)
|
| 12 |
+
|
| 13 |
+
base_path = os.path.join(results_dir, base_file)
|
| 14 |
+
new_path = os.path.join(results_dir, new_file)
|
| 15 |
+
|
| 16 |
+
# Load the base file, or create a new one if it doesn't exist.
|
| 17 |
+
if os.path.exists(base_path):
|
| 18 |
+
with open(base_path, 'r') as f:
|
| 19 |
+
base_data = json.load(f)
|
| 20 |
+
else:
|
| 21 |
+
print(f"Base file '{base_file}' not found. Creating a new one.")
|
| 22 |
+
base_data = {"analyses": {}}
|
| 23 |
+
|
| 24 |
+
# Load the new results file.
|
| 25 |
+
with open(new_path, 'r') as f:
|
| 26 |
+
new_data = json.load(f)
|
| 27 |
+
|
| 28 |
+
# Ensure both files have the 'analyses' key.
|
| 29 |
+
if 'analyses' not in base_data or 'analyses' not in new_data:
|
| 30 |
+
print("Error: Both files must contain an 'analyses' key.")
|
| 31 |
+
return
|
| 32 |
+
|
| 33 |
+
# Update the analyses from the base file.
|
| 34 |
+
base_data['analyses'].update(new_data['analyses'])
|
| 35 |
+
|
| 36 |
+
# Update the timestamp and config from the new file.
|
| 37 |
+
base_data['timestamp'] = new_data.get('timestamp', base_data.get('timestamp'))
|
| 38 |
+
base_data['config'] = new_data.get('config', base_data.get('config'))
|
| 39 |
+
|
| 40 |
+
# Write the merged data back to the base file.
|
| 41 |
+
with open(base_path, 'w') as f:
|
| 42 |
+
json.dump(base_data, f, indent=2)
|
| 43 |
+
|
| 44 |
+
print(f"Successfully merged '{new_file}' into '{base_file}'.")
|
| 45 |
+
|
| 46 |
+
except FileNotFoundError as e:
|
| 47 |
+
print(f"Error: File not found - {e.filename}")
|
| 48 |
+
except json.JSONDecodeError:
|
| 49 |
+
print("Error: Invalid JSON format in one of the files.")
|
| 50 |
+
except Exception as e:
|
| 51 |
+
print(f"An unexpected error occurred: {e}")
|
| 52 |
+
|
| 53 |
+
if __name__ == "__main__":
|
| 54 |
+
if len(sys.argv) != 3:
|
| 55 |
+
print("Usage: python merge_circuit_results.py <base_json_file> <new_json_file>")
|
| 56 |
+
else:
|
| 57 |
+
merge_json_results(sys.argv[1], sys.argv[2])
|
circuit_analysis/offline_circuit_metrics.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Run attribution-graph ablation experiments outside the Streamlit UI.
|
| 4 |
+
|
| 5 |
+
This script executes the same targeted/random/path perturbations as the
|
| 6 |
+
interactive tool and emits aggregate metrics so we can verify that the
|
| 7 |
+
visual plots actually reflect causal differences.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
import sys
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
from typing import Dict, List, Any
|
| 18 |
+
|
| 19 |
+
# Ensure we can import the pipeline when this script is executed directly.
|
| 20 |
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
| 21 |
+
PROJECT_ROOT = SCRIPT_DIR.parent
|
| 22 |
+
if str(PROJECT_ROOT) not in sys.path:
|
| 23 |
+
sys.path.append(str(PROJECT_ROOT))
|
| 24 |
+
|
| 25 |
+
from circuit_analysis.attribution_graphs_olmo import ( # noqa: E402
|
| 26 |
+
AttributionGraphsPipeline,
|
| 27 |
+
AttributionGraphConfig,
|
| 28 |
+
ANALYSIS_PROMPTS,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
RESULTS_DIR = SCRIPT_DIR / "results"
|
| 32 |
+
DEFAULT_OUTPUT = RESULTS_DIR / "offline_circuit_metrics.json"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _load_prompts(args: argparse.Namespace) -> List[str]:
|
| 36 |
+
if args.prompts_file:
|
| 37 |
+
path = Path(args.prompts_file)
|
| 38 |
+
if not path.exists():
|
| 39 |
+
raise FileNotFoundError(f"Prompts file not found: {path}")
|
| 40 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 41 |
+
prompts = [line.strip() for line in f if line.strip()]
|
| 42 |
+
if not prompts:
|
| 43 |
+
raise ValueError(f"No prompts found in {path}")
|
| 44 |
+
return prompts
|
| 45 |
+
|
| 46 |
+
if args.prompt_text:
|
| 47 |
+
return [args.prompt_text]
|
| 48 |
+
|
| 49 |
+
if args.prompt_index is not None:
|
| 50 |
+
idx = args.prompt_index
|
| 51 |
+
if not (0 <= idx < len(ANALYSIS_PROMPTS)):
|
| 52 |
+
raise ValueError(f"--prompt-index must be between 0 and {len(ANALYSIS_PROMPTS)-1}")
|
| 53 |
+
return [ANALYSIS_PROMPTS[idx]]
|
| 54 |
+
|
| 55 |
+
if args.use_all:
|
| 56 |
+
return ANALYSIS_PROMPTS
|
| 57 |
+
|
| 58 |
+
# Default: run the canonical prompt set.
|
| 59 |
+
return ANALYSIS_PROMPTS
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _format_summary(label: str, summary: Dict[str, Any]) -> str:
|
| 63 |
+
return (
|
| 64 |
+
f"{label:<20} "
|
| 65 |
+
f"count={summary.get('count', 0):3d} "
|
| 66 |
+
f"avg|Δp|={summary.get('avg_abs_probability_change', 0.0):.4f} "
|
| 67 |
+
f"flip_rate={summary.get('flip_rate', 0.0):.2%} "
|
| 68 |
+
f"avg|Δlogit|={summary.get('avg_abs_logit_change', 0.0):.4f}"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _top_experiments(experiments: List[Dict[str, Any]], top_n: int = 5) -> List[Dict[str, Any]]:
|
| 73 |
+
sorted_exps = sorted(
|
| 74 |
+
experiments,
|
| 75 |
+
key=lambda exp: abs(exp.get("probability_change", 0.0)),
|
| 76 |
+
reverse=True,
|
| 77 |
+
)
|
| 78 |
+
summary = []
|
| 79 |
+
for exp in sorted_exps[:top_n]:
|
| 80 |
+
summary.append(
|
| 81 |
+
{
|
| 82 |
+
"label": exp.get("feature_name") or exp.get("path_description") or "feature_set",
|
| 83 |
+
"probability_change": exp.get("probability_change", 0.0),
|
| 84 |
+
"logit_change": exp.get("logit_change", 0.0),
|
| 85 |
+
"flip": bool(exp.get("ablation_flips_top_prediction")),
|
| 86 |
+
}
|
| 87 |
+
)
|
| 88 |
+
return summary
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _sanitize_analysis(analysis: Dict[str, Any]) -> Dict[str, Any]:
|
| 92 |
+
return {
|
| 93 |
+
"prompt": analysis.get("prompt"),
|
| 94 |
+
"summary_statistics": analysis.get("summary_statistics", {}),
|
| 95 |
+
"counts": {
|
| 96 |
+
"targeted": len(analysis.get("perturbation_experiments", []) or []),
|
| 97 |
+
"random": len(analysis.get("random_baseline_experiments", []) or []),
|
| 98 |
+
"path": len(analysis.get("path_ablation_experiments", []) or []),
|
| 99 |
+
"random_path": len(analysis.get("random_path_baseline_experiments", []) or []),
|
| 100 |
+
},
|
| 101 |
+
"top_targeted": _top_experiments(analysis.get("perturbation_experiments", []) or []),
|
| 102 |
+
"top_paths": _top_experiments(analysis.get("path_ablation_experiments", []) or []),
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def main():
|
| 107 |
+
parser = argparse.ArgumentParser(description="Run offline attribution-graph ablation metrics.")
|
| 108 |
+
parser.add_argument("--prompt-index", type=int, help="Run a single prompt by index from ANALYSIS_PROMPTS.")
|
| 109 |
+
parser.add_argument("--prompt-text", type=str, help="Run a single custom prompt.")
|
| 110 |
+
parser.add_argument("--prompts-file", type=str, help="Path to a text file with one prompt per line.")
|
| 111 |
+
parser.add_argument("--use-all", action="store_true", help="Run all predefined analysis prompts.")
|
| 112 |
+
parser.add_argument(
|
| 113 |
+
"--feature-top-k",
|
| 114 |
+
type=int,
|
| 115 |
+
default=12,
|
| 116 |
+
help="Number of top features per layer to analyze for targeted ablations.",
|
| 117 |
+
)
|
| 118 |
+
parser.add_argument(
|
| 119 |
+
"--ablation-features-per-layer",
|
| 120 |
+
type=int,
|
| 121 |
+
default=4,
|
| 122 |
+
help="Limit of targeted feature ablations per layer.",
|
| 123 |
+
)
|
| 124 |
+
parser.add_argument(
|
| 125 |
+
"--output",
|
| 126 |
+
type=str,
|
| 127 |
+
default=str(DEFAULT_OUTPUT),
|
| 128 |
+
help="Where to store the JSON summary of the offline metrics.",
|
| 129 |
+
)
|
| 130 |
+
args = parser.parse_args()
|
| 131 |
+
|
| 132 |
+
prompts = _load_prompts(args)
|
| 133 |
+
|
| 134 |
+
# Use consistent config matching the trained CLT
|
| 135 |
+
# Use the constructed graph (pruned), not the full universe
|
| 136 |
+
config = AttributionGraphConfig(
|
| 137 |
+
n_features_per_layer=512, # Match trained CLT
|
| 138 |
+
sparsity_lambda=1e-3, # Match training
|
| 139 |
+
graph_feature_activation_threshold=0.01,
|
| 140 |
+
graph_edge_weight_threshold=0.003,
|
| 141 |
+
graph_max_features_per_layer=40,
|
| 142 |
+
graph_max_edges_per_node=20,
|
| 143 |
+
ablation_features_per_layer=args.ablation_features_per_layer,
|
| 144 |
+
# Use default pruning_threshold (0.8) to use the constructed graph, not full universe
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
pipeline = AttributionGraphsPipeline(config)
|
| 148 |
+
print(f"Running offline faithfulness experiments for {len(prompts)} prompt(s)...")
|
| 149 |
+
batch_payload = pipeline.analyze_prompts_batch(prompts)
|
| 150 |
+
|
| 151 |
+
aggregate_summary = batch_payload.get("aggregate_summary", {})
|
| 152 |
+
print("\n=== Aggregate Metrics ===")
|
| 153 |
+
print(_format_summary("Targeted", aggregate_summary.get("targeted", {})))
|
| 154 |
+
print(_format_summary("Random baseline", aggregate_summary.get("random_baseline", {})))
|
| 155 |
+
print(_format_summary("Path", aggregate_summary.get("path", {})))
|
| 156 |
+
print(_format_summary("Random path", aggregate_summary.get("random_path_baseline", {})))
|
| 157 |
+
print(
|
| 158 |
+
f"\nTargeted − Random |Δp| = "
|
| 159 |
+
f"{aggregate_summary.get('target_minus_random_abs_probability_change', 0.0):.4f}"
|
| 160 |
+
)
|
| 161 |
+
print(
|
| 162 |
+
f"Path − Random path |Δp| = "
|
| 163 |
+
f"{aggregate_summary.get('path_minus_random_abs_probability_change', 0.0):.4f}"
|
| 164 |
+
)
|
| 165 |
+
print(
|
| 166 |
+
f"Targeted − Random flip rate = "
|
| 167 |
+
f"{aggregate_summary.get('target_flip_rate_minus_random', 0.0):.4f}"
|
| 168 |
+
)
|
| 169 |
+
print(
|
| 170 |
+
f"Path − Random path flip rate = "
|
| 171 |
+
f"{aggregate_summary.get('path_flip_rate_minus_random', 0.0):.4f}"
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
sanitized_per_prompt = {
|
| 175 |
+
key: _sanitize_analysis(analysis) for key, analysis in batch_payload.get("analyses", {}).items()
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
output_payload = {
|
| 179 |
+
"prompts_ran": prompts,
|
| 180 |
+
"aggregate_summary": aggregate_summary,
|
| 181 |
+
"per_prompt": sanitized_per_prompt,
|
| 182 |
+
"config": config.__dict__,
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
os.makedirs(Path(args.output).parent, exist_ok=True)
|
| 186 |
+
with open(args.output, "w", encoding="utf-8") as f:
|
| 187 |
+
json.dump(output_payload, f, indent=2)
|
| 188 |
+
|
| 189 |
+
print(f"\nSaved offline metrics to {args.output}")
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
if __name__ == "__main__":
|
| 193 |
+
main()
|
| 194 |
+
|
circuit_analysis/plot_offline_metrics.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Visualize the aggregate metrics produced by offline_circuit_metrics.py
|
| 4 |
+
both overall and per prompt.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import argparse
|
| 10 |
+
import json
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Dict, Any
|
| 13 |
+
from textwrap import fill
|
| 14 |
+
|
| 15 |
+
import matplotlib.pyplot as plt
|
| 16 |
+
import numpy as np
|
| 17 |
+
import seaborn as sns
|
| 18 |
+
|
| 19 |
+
DEFAULT_RESULTS = Path(__file__).parent / "results" / "offline_circuit_metrics.json"
|
| 20 |
+
DEFAULT_CPR_CMD = Path(__file__).parent / "results" / "cpr_cmd_results.json"
|
| 21 |
+
# Save directly to the paper figures directory
|
| 22 |
+
DEFAULT_FIG = Path(__file__).parent.parent / "writing" / "ELIA__EACL_2026_System_Demonstrations_" / "figures" / "offline_circuit_metrics_combined.png"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _load_payload(path: Path) -> Dict[str, Any]:
|
| 26 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 27 |
+
data = json.load(f)
|
| 28 |
+
if "aggregate_summary" not in data or "per_prompt" not in data:
|
| 29 |
+
raise ValueError(f"Expected 'aggregate_summary' and 'per_prompt' in {path}")
|
| 30 |
+
return data
|
| 31 |
+
|
| 32 |
+
def _configure_plot_style() -> None:
|
| 33 |
+
sns.set_theme(style="ticks", palette="colorblind")
|
| 34 |
+
plt.rcParams["font.family"] = "sans-serif"
|
| 35 |
+
plt.rcParams["font.sans-serif"] = "Arial"
|
| 36 |
+
plt.rcParams["axes.labelweight"] = "normal"
|
| 37 |
+
plt.rcParams["axes.titleweight"] = "bold"
|
| 38 |
+
plt.rcParams["figure.titleweight"] = "bold"
|
| 39 |
+
plt.rcParams["savefig.dpi"] = 300
|
| 40 |
+
plt.rcParams["figure.facecolor"] = "white"
|
| 41 |
+
plt.rcParams["axes.facecolor"] = "white"
|
| 42 |
+
plt.rcParams["grid.alpha"] = 0.2
|
| 43 |
+
plt.rcParams["axes.spines.top"] = False
|
| 44 |
+
plt.rcParams["axes.spines.right"] = False
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _load_cpr_cmd(path: Path) -> Dict[str, Any]:
|
| 48 |
+
"""Load CPR/CMD results if available."""
|
| 49 |
+
if not path.exists():
|
| 50 |
+
return None
|
| 51 |
+
try:
|
| 52 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 53 |
+
data = json.load(f)
|
| 54 |
+
return data
|
| 55 |
+
except Exception as e:
|
| 56 |
+
print(f"Warning: Could not load CPR/CMD results from {path}: {e}")
|
| 57 |
+
return None
|
| 58 |
+
|
| 59 |
+
def plot_combined(summary: Dict[str, Any], per_prompt: Dict[str, Any], output_path: Path, cpr_cmd_data: Dict[str, Any] = None):
|
| 60 |
+
_configure_plot_style()
|
| 61 |
+
|
| 62 |
+
# Prepare data
|
| 63 |
+
labels = [r"$\mathbf{Aggregate}$"]
|
| 64 |
+
targeted_vals = [summary["targeted"]["avg_abs_probability_change"]]
|
| 65 |
+
random_vals = [summary["random_baseline"]["avg_abs_probability_change"]]
|
| 66 |
+
path_vals = [summary["path"]["avg_abs_probability_change"]]
|
| 67 |
+
random_path_vals = [summary["random_path_baseline"]["avg_abs_probability_change"]]
|
| 68 |
+
|
| 69 |
+
# Prepare CPR data if available
|
| 70 |
+
cpr_vals = []
|
| 71 |
+
if cpr_cmd_data:
|
| 72 |
+
# Get average CPR for aggregate
|
| 73 |
+
results = cpr_cmd_data.get("results", [])
|
| 74 |
+
if results:
|
| 75 |
+
avg_cpr = cpr_cmd_data.get("average_CPR", 0.0)
|
| 76 |
+
cpr_vals.append(avg_cpr)
|
| 77 |
+
|
| 78 |
+
# Map prompts to CPR values
|
| 79 |
+
prompt_to_cpr = {}
|
| 80 |
+
for result in results:
|
| 81 |
+
prompt_text = result.get("prompt", "")
|
| 82 |
+
prompt_to_cpr[prompt_text] = result.get("CPR", 0.0)
|
| 83 |
+
|
| 84 |
+
for key, data in per_prompt.items():
|
| 85 |
+
# Clean up prompt label for display (first 5 words or so)
|
| 86 |
+
prompt_text = data.get("prompt", key)
|
| 87 |
+
labels.append(prompt_text)
|
| 88 |
+
|
| 89 |
+
stats = data.get("summary_statistics", {})
|
| 90 |
+
targeted_vals.append(stats.get("targeted", {}).get("avg_abs_probability_change", 0.0))
|
| 91 |
+
random_vals.append(stats.get("random_baseline", {}).get("avg_abs_probability_change", 0.0))
|
| 92 |
+
path_vals.append(stats.get("path", {}).get("avg_abs_probability_change", 0.0))
|
| 93 |
+
random_path_vals.append(stats.get("random_path_baseline", {}).get("avg_abs_probability_change", 0.0))
|
| 94 |
+
|
| 95 |
+
# Add CPR for this prompt if available
|
| 96 |
+
if cpr_cmd_data and prompt_text in prompt_to_cpr:
|
| 97 |
+
cpr_vals.append(prompt_to_cpr[prompt_text])
|
| 98 |
+
elif cpr_cmd_data:
|
| 99 |
+
# If CPR data exists but this prompt isn't in it, add zero
|
| 100 |
+
cpr_vals.append(0.0)
|
| 101 |
+
|
| 102 |
+
x = np.arange(len(labels))
|
| 103 |
+
width = 0.2
|
| 104 |
+
|
| 105 |
+
# Use a aspect ratio that fits well in a paper (e.g. wide enough for column)
|
| 106 |
+
fig, ax = plt.subplots(figsize=(10, 6), constrained_layout=True)
|
| 107 |
+
|
| 108 |
+
# Create second y-axis for CPR if data is available
|
| 109 |
+
ax2 = None
|
| 110 |
+
if cpr_cmd_data and cpr_vals:
|
| 111 |
+
ax2 = ax.twinx()
|
| 112 |
+
|
| 113 |
+
# Color palette - using specific indices from colorblind to ensure contrast
|
| 114 |
+
# 0: Blue, 1: Orange, 2: Green, 3: Red, 4: Purple, etc.
|
| 115 |
+
palette = sns.color_palette("colorblind")
|
| 116 |
+
c_target = palette[0] # Blue
|
| 117 |
+
c_random = palette[7] # Grey-ish or distinct
|
| 118 |
+
c_path = palette[2] # Green
|
| 119 |
+
c_path_rnd = palette[3] # Red
|
| 120 |
+
|
| 121 |
+
# Plot bars
|
| 122 |
+
features_targeted = ax.bar(x - width * 1.5, targeted_vals, width, label="Targeted Features", color=c_target)
|
| 123 |
+
features_random = ax.bar(x - width/2, random_vals, width, label="Random Features", color=c_random, alpha=0.7)
|
| 124 |
+
paths_targeted = ax.bar(x + width/2, path_vals, width, label="Traced Circuits", color=c_path)
|
| 125 |
+
paths_random = ax.bar(x + width * 1.5, random_path_vals, width, label="Random Path Baseline", color=c_path_rnd, alpha=0.7)
|
| 126 |
+
|
| 127 |
+
# Add value labels on top of bars (only if they are significant enough to not clutter)
|
| 128 |
+
def autolabel(rects):
|
| 129 |
+
for rect in rects:
|
| 130 |
+
height = rect.get_height()
|
| 131 |
+
# Threshold logic: Only skip if truly tiny (effectively zero)
|
| 132 |
+
if height > 0.01:
|
| 133 |
+
ax.annotate(
|
| 134 |
+
f"{height:.2f}",
|
| 135 |
+
xy=(rect.get_x() + rect.get_width() / 2, height),
|
| 136 |
+
xytext=(0, 3),
|
| 137 |
+
textcoords="offset points",
|
| 138 |
+
ha="center",
|
| 139 |
+
va="bottom",
|
| 140 |
+
fontsize=14,
|
| 141 |
+
fontweight="normal",
|
| 142 |
+
color="black"
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
autolabel(features_targeted)
|
| 146 |
+
autolabel(features_random)
|
| 147 |
+
autolabel(paths_targeted)
|
| 148 |
+
autolabel(paths_random)
|
| 149 |
+
|
| 150 |
+
# Plot CPR on second axis if available
|
| 151 |
+
if ax2 and cpr_vals:
|
| 152 |
+
# Plot as line with markers
|
| 153 |
+
line1 = ax2.plot(x, cpr_vals, marker='o', linestyle='--', linewidth=2,
|
| 154 |
+
markersize=8, color='purple', label='CPR', zorder=5)
|
| 155 |
+
ax2.set_ylabel("CPR", fontsize=16, fontweight="normal", color='black')
|
| 156 |
+
ax2.tick_params(axis='y', labelcolor='black', labelsize=14)
|
| 157 |
+
ax2.set_ylim(0, 1.1) # CPR is in [0,1]
|
| 158 |
+
|
| 159 |
+
# Add value labels for CPR (below the markers)
|
| 160 |
+
for i, cpr_val in enumerate(cpr_vals):
|
| 161 |
+
if cpr_val > 0.01:
|
| 162 |
+
ax2.annotate(f'{cpr_val:.2f}', xy=(i, cpr_val), xytext=(-20, -5),
|
| 163 |
+
textcoords='offset points', fontsize=11, color='purple',
|
| 164 |
+
fontweight='bold', ha='center')
|
| 165 |
+
|
| 166 |
+
# Add CPR to legend
|
| 167 |
+
lines1, labels1 = ax.get_legend_handles_labels()
|
| 168 |
+
lines2, labels2 = ax2.get_legend_handles_labels()
|
| 169 |
+
ax.legend(lines1 + lines2, labels1 + labels2, loc="upper left", ncol=3,
|
| 170 |
+
frameon=True, framealpha=0.9, edgecolor="white", fontsize=12)
|
| 171 |
+
else:
|
| 172 |
+
# Original legend if no CPR
|
| 173 |
+
ax.legend(loc="upper left", ncol=2, frameon=True, framealpha=0.9, edgecolor="white", fontsize=14)
|
| 174 |
+
|
| 175 |
+
ax.set_ylabel("Avg. |Probability Change| (|Δp|)", fontsize=16, fontweight="normal")
|
| 176 |
+
ax.set_xticks(x)
|
| 177 |
+
|
| 178 |
+
# Wrap labels nicely (but preserve LaTeX formatting for Aggregate)
|
| 179 |
+
wrapped_labels = []
|
| 180 |
+
for label in labels:
|
| 181 |
+
if r"$\mathbf{Aggregate}$" in label:
|
| 182 |
+
wrapped_labels.append(label)
|
| 183 |
+
else:
|
| 184 |
+
wrapped_labels.append(fill(label, 20))
|
| 185 |
+
ax.set_xticklabels(wrapped_labels, rotation=0, ha="center", fontsize=14)
|
| 186 |
+
|
| 187 |
+
# Add subtle grid
|
| 188 |
+
ax.grid(axis='y', linestyle='--', alpha=0.3)
|
| 189 |
+
|
| 190 |
+
# Adjust y-limit to give some headroom for labels
|
| 191 |
+
y_max = max(max(targeted_vals), max(path_vals), max(random_vals), max(random_path_vals))
|
| 192 |
+
ax.set_ylim(0, y_max * 1.30)
|
| 193 |
+
|
| 194 |
+
# Ensure output directory exists
|
| 195 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 196 |
+
|
| 197 |
+
fig.savefig(output_path, dpi=300)
|
| 198 |
+
plt.close(fig)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def main():
|
| 202 |
+
parser = argparse.ArgumentParser(description="Plot offline attribution metrics.")
|
| 203 |
+
parser.add_argument(
|
| 204 |
+
"--input",
|
| 205 |
+
type=str,
|
| 206 |
+
default=str(DEFAULT_RESULTS),
|
| 207 |
+
help="Path to offline_circuit_metrics.json"
|
| 208 |
+
)
|
| 209 |
+
parser.add_argument(
|
| 210 |
+
"--output",
|
| 211 |
+
type=str,
|
| 212 |
+
default=str(DEFAULT_FIG),
|
| 213 |
+
help="Path to save the per-prompt figure (PNG)."
|
| 214 |
+
)
|
| 215 |
+
parser.add_argument(
|
| 216 |
+
"--cpr-cmd",
|
| 217 |
+
type=str,
|
| 218 |
+
default=str(DEFAULT_CPR_CMD),
|
| 219 |
+
help="Path to CPR/CMD results JSON file (optional)."
|
| 220 |
+
)
|
| 221 |
+
args = parser.parse_args()
|
| 222 |
+
|
| 223 |
+
if not Path(args.input).exists():
|
| 224 |
+
print(f"Error: Input file {args.input} not found. Please run offline_circuit_metrics.py first.")
|
| 225 |
+
return
|
| 226 |
+
|
| 227 |
+
payload = _load_payload(Path(args.input))
|
| 228 |
+
summary = payload["aggregate_summary"]
|
| 229 |
+
per_prompt = payload["per_prompt"]
|
| 230 |
+
|
| 231 |
+
# Load CPR/CMD data if available
|
| 232 |
+
cpr_cmd_data = _load_cpr_cmd(Path(args.cpr_cmd))
|
| 233 |
+
|
| 234 |
+
plot_combined(summary, per_prompt, Path(args.output), cpr_cmd_data)
|
| 235 |
+
print(f"Saved combined plot to {args.output}")
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
if __name__ == "__main__":
|
| 239 |
+
main()
|
circuit_analysis/results/attribution_graphs_results.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/attribution_graphs_results_de.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/attribution_graphs_results_de_prompt_1.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/attribution_graphs_results_de_prompt_2.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/attribution_graphs_results_de_prompt_3.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/attribution_graphs_results_prompt_1.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/attribution_graphs_results_prompt_2.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/attribution_graphs_results_prompt_3.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/clt_training_stats.json
ADDED
|
@@ -0,0 +1,4508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"reconstruction_losses": [
|
| 3 |
+
8.078125,
|
| 4 |
+
7.6015625,
|
| 5 |
+
7.625,
|
| 6 |
+
7.3828125,
|
| 7 |
+
7.3671875,
|
| 8 |
+
7.0390625,
|
| 9 |
+
7.171875,
|
| 10 |
+
6.875,
|
| 11 |
+
6.703125,
|
| 12 |
+
6.4375,
|
| 13 |
+
6.46875,
|
| 14 |
+
6.41015625,
|
| 15 |
+
6.56640625,
|
| 16 |
+
6.3671875,
|
| 17 |
+
6.44140625,
|
| 18 |
+
6.265625,
|
| 19 |
+
5.80078125,
|
| 20 |
+
6.21875,
|
| 21 |
+
6.1171875,
|
| 22 |
+
6.0546875,
|
| 23 |
+
6.1015625,
|
| 24 |
+
5.5,
|
| 25 |
+
5.390625,
|
| 26 |
+
5.4609375,
|
| 27 |
+
6.0625,
|
| 28 |
+
5.7890625,
|
| 29 |
+
5.8671875,
|
| 30 |
+
5.7578125,
|
| 31 |
+
5.8046875,
|
| 32 |
+
5.6015625,
|
| 33 |
+
5.37890625,
|
| 34 |
+
5.328125,
|
| 35 |
+
5.40625,
|
| 36 |
+
5.453125,
|
| 37 |
+
5.7265625,
|
| 38 |
+
5.375,
|
| 39 |
+
5.3828125,
|
| 40 |
+
5.11328125,
|
| 41 |
+
5.66015625,
|
| 42 |
+
5.30078125,
|
| 43 |
+
5.46875,
|
| 44 |
+
5.4609375,
|
| 45 |
+
5.60546875,
|
| 46 |
+
5.13671875,
|
| 47 |
+
5.02734375,
|
| 48 |
+
5.18359375,
|
| 49 |
+
4.84375,
|
| 50 |
+
5.05859375,
|
| 51 |
+
4.96875,
|
| 52 |
+
5.05078125,
|
| 53 |
+
4.9765625,
|
| 54 |
+
5.40234375,
|
| 55 |
+
5.25,
|
| 56 |
+
4.89453125,
|
| 57 |
+
4.84375,
|
| 58 |
+
5.1953125,
|
| 59 |
+
4.75,
|
| 60 |
+
4.859375,
|
| 61 |
+
4.2890625,
|
| 62 |
+
4.84765625,
|
| 63 |
+
4.97265625,
|
| 64 |
+
4.73046875,
|
| 65 |
+
4.62890625,
|
| 66 |
+
4.6015625,
|
| 67 |
+
4.796875,
|
| 68 |
+
4.33203125,
|
| 69 |
+
4.81640625,
|
| 70 |
+
4.734375,
|
| 71 |
+
4.49609375,
|
| 72 |
+
4.65625,
|
| 73 |
+
4.4453125,
|
| 74 |
+
4.56640625,
|
| 75 |
+
4.52734375,
|
| 76 |
+
4.09765625,
|
| 77 |
+
4.48046875,
|
| 78 |
+
4.4296875,
|
| 79 |
+
4.61328125,
|
| 80 |
+
4.5625,
|
| 81 |
+
4.39453125,
|
| 82 |
+
4.44140625,
|
| 83 |
+
4.6171875,
|
| 84 |
+
4.39453125,
|
| 85 |
+
4.4765625,
|
| 86 |
+
4.5078125,
|
| 87 |
+
4.05078125,
|
| 88 |
+
4.20703125,
|
| 89 |
+
4.54296875,
|
| 90 |
+
4.53515625,
|
| 91 |
+
4.33984375,
|
| 92 |
+
4.29296875,
|
| 93 |
+
4.2109375,
|
| 94 |
+
4.1171875,
|
| 95 |
+
4.18359375,
|
| 96 |
+
4.3671875,
|
| 97 |
+
4.140625,
|
| 98 |
+
4.16015625,
|
| 99 |
+
4.16015625,
|
| 100 |
+
4.08984375,
|
| 101 |
+
3.865234375,
|
| 102 |
+
4.1796875,
|
| 103 |
+
3.740234375,
|
| 104 |
+
4.3125,
|
| 105 |
+
4.2578125,
|
| 106 |
+
4.203125,
|
| 107 |
+
4.109375,
|
| 108 |
+
4.1484375,
|
| 109 |
+
3.666015625,
|
| 110 |
+
4.21484375,
|
| 111 |
+
3.89453125,
|
| 112 |
+
3.861328125,
|
| 113 |
+
4.05859375,
|
| 114 |
+
3.626953125,
|
| 115 |
+
4.1640625,
|
| 116 |
+
3.84375,
|
| 117 |
+
3.970703125,
|
| 118 |
+
4.09375,
|
| 119 |
+
3.70703125,
|
| 120 |
+
4.359375,
|
| 121 |
+
3.98046875,
|
| 122 |
+
3.89453125,
|
| 123 |
+
3.70703125,
|
| 124 |
+
3.7421875,
|
| 125 |
+
3.880859375,
|
| 126 |
+
3.7734375,
|
| 127 |
+
4.109375,
|
| 128 |
+
3.748046875,
|
| 129 |
+
3.83984375,
|
| 130 |
+
3.640625,
|
| 131 |
+
3.876953125,
|
| 132 |
+
4.0234375,
|
| 133 |
+
3.72265625,
|
| 134 |
+
4.140625,
|
| 135 |
+
3.89453125,
|
| 136 |
+
4.0703125,
|
| 137 |
+
3.853515625,
|
| 138 |
+
3.861328125,
|
| 139 |
+
3.99609375,
|
| 140 |
+
3.8046875,
|
| 141 |
+
3.6640625,
|
| 142 |
+
3.59375,
|
| 143 |
+
3.46875,
|
| 144 |
+
3.88671875,
|
| 145 |
+
3.78515625,
|
| 146 |
+
3.525390625,
|
| 147 |
+
3.9375,
|
| 148 |
+
3.5625,
|
| 149 |
+
3.45703125,
|
| 150 |
+
3.849609375,
|
| 151 |
+
3.58203125,
|
| 152 |
+
3.408203125,
|
| 153 |
+
3.67578125,
|
| 154 |
+
3.84765625,
|
| 155 |
+
3.8203125,
|
| 156 |
+
3.681640625,
|
| 157 |
+
3.67578125,
|
| 158 |
+
3.361328125,
|
| 159 |
+
3.47265625,
|
| 160 |
+
3.734375,
|
| 161 |
+
3.720703125,
|
| 162 |
+
3.369140625,
|
| 163 |
+
3.5546875,
|
| 164 |
+
3.556640625,
|
| 165 |
+
3.357421875,
|
| 166 |
+
3.44140625,
|
| 167 |
+
3.3671875,
|
| 168 |
+
3.46875,
|
| 169 |
+
3.767578125,
|
| 170 |
+
3.5546875,
|
| 171 |
+
3.26171875,
|
| 172 |
+
3.619140625,
|
| 173 |
+
3.837890625,
|
| 174 |
+
3.01171875,
|
| 175 |
+
3.4296875,
|
| 176 |
+
3.396484375,
|
| 177 |
+
3.658203125,
|
| 178 |
+
3.375,
|
| 179 |
+
3.458984375,
|
| 180 |
+
3.46875,
|
| 181 |
+
3.1640625,
|
| 182 |
+
3.412109375,
|
| 183 |
+
3.38671875,
|
| 184 |
+
3.361328125,
|
| 185 |
+
3.70703125,
|
| 186 |
+
3.31640625,
|
| 187 |
+
3.2578125,
|
| 188 |
+
3.32421875,
|
| 189 |
+
3.7578125,
|
| 190 |
+
3.65234375,
|
| 191 |
+
3.16796875,
|
| 192 |
+
3.474609375,
|
| 193 |
+
3.4375,
|
| 194 |
+
3.13671875,
|
| 195 |
+
3.478515625,
|
| 196 |
+
3.357421875,
|
| 197 |
+
3.17578125,
|
| 198 |
+
3.3515625,
|
| 199 |
+
3.32421875,
|
| 200 |
+
3.158203125,
|
| 201 |
+
3.64453125,
|
| 202 |
+
3.802734375,
|
| 203 |
+
3.1953125,
|
| 204 |
+
3.0859375,
|
| 205 |
+
3.474609375,
|
| 206 |
+
3.208984375,
|
| 207 |
+
3.1171875,
|
| 208 |
+
3.3046875,
|
| 209 |
+
3.169921875,
|
| 210 |
+
3.2109375,
|
| 211 |
+
3.296875,
|
| 212 |
+
3.125,
|
| 213 |
+
3.2578125,
|
| 214 |
+
3.328125,
|
| 215 |
+
3.412109375,
|
| 216 |
+
3.353515625,
|
| 217 |
+
3.265625,
|
| 218 |
+
3.419921875,
|
| 219 |
+
3.298828125,
|
| 220 |
+
3.373046875,
|
| 221 |
+
3.302734375,
|
| 222 |
+
3.05859375,
|
| 223 |
+
3.12890625,
|
| 224 |
+
3.248046875,
|
| 225 |
+
2.955078125,
|
| 226 |
+
3.0703125,
|
| 227 |
+
3.048828125,
|
| 228 |
+
3.36328125,
|
| 229 |
+
3.126953125,
|
| 230 |
+
3.0625,
|
| 231 |
+
3.240234375,
|
| 232 |
+
3.052734375,
|
| 233 |
+
3.1796875,
|
| 234 |
+
2.943359375,
|
| 235 |
+
3.080078125,
|
| 236 |
+
3.185546875,
|
| 237 |
+
2.98046875,
|
| 238 |
+
3.234375,
|
| 239 |
+
3.01171875,
|
| 240 |
+
2.9921875,
|
| 241 |
+
3.01953125,
|
| 242 |
+
2.71875,
|
| 243 |
+
3.1328125,
|
| 244 |
+
2.75,
|
| 245 |
+
3.23046875,
|
| 246 |
+
3.01953125,
|
| 247 |
+
2.81640625,
|
| 248 |
+
3.162109375,
|
| 249 |
+
3.080078125,
|
| 250 |
+
3.37890625,
|
| 251 |
+
3.06640625,
|
| 252 |
+
3.03125,
|
| 253 |
+
3.1875,
|
| 254 |
+
2.83984375,
|
| 255 |
+
2.94140625,
|
| 256 |
+
2.8203125,
|
| 257 |
+
3.07421875,
|
| 258 |
+
2.90234375,
|
| 259 |
+
2.87890625,
|
| 260 |
+
3.12890625,
|
| 261 |
+
2.94921875,
|
| 262 |
+
2.97265625,
|
| 263 |
+
2.73046875,
|
| 264 |
+
2.892578125,
|
| 265 |
+
2.79296875,
|
| 266 |
+
3.1640625,
|
| 267 |
+
2.974609375,
|
| 268 |
+
2.681640625,
|
| 269 |
+
2.98046875,
|
| 270 |
+
2.84375,
|
| 271 |
+
2.990234375,
|
| 272 |
+
2.62890625,
|
| 273 |
+
2.953125,
|
| 274 |
+
3.046875,
|
| 275 |
+
2.962890625,
|
| 276 |
+
3.126953125,
|
| 277 |
+
2.88671875,
|
| 278 |
+
2.9765625,
|
| 279 |
+
2.701171875,
|
| 280 |
+
2.7734375,
|
| 281 |
+
3.255859375,
|
| 282 |
+
2.939453125,
|
| 283 |
+
2.98046875,
|
| 284 |
+
2.705078125,
|
| 285 |
+
2.98828125,
|
| 286 |
+
2.869140625,
|
| 287 |
+
2.828125,
|
| 288 |
+
3.025390625,
|
| 289 |
+
2.765625,
|
| 290 |
+
2.91015625,
|
| 291 |
+
2.671875,
|
| 292 |
+
2.892578125,
|
| 293 |
+
3.181640625,
|
| 294 |
+
2.91796875,
|
| 295 |
+
2.908203125,
|
| 296 |
+
2.91796875,
|
| 297 |
+
2.904296875,
|
| 298 |
+
2.93359375,
|
| 299 |
+
2.802734375,
|
| 300 |
+
3.044921875,
|
| 301 |
+
2.9296875,
|
| 302 |
+
2.96875,
|
| 303 |
+
2.859375,
|
| 304 |
+
2.890625,
|
| 305 |
+
2.984375,
|
| 306 |
+
2.7265625,
|
| 307 |
+
2.78515625,
|
| 308 |
+
2.876953125,
|
| 309 |
+
2.798828125,
|
| 310 |
+
2.759765625,
|
| 311 |
+
2.6328125,
|
| 312 |
+
2.765625,
|
| 313 |
+
2.705078125,
|
| 314 |
+
2.7265625,
|
| 315 |
+
2.767578125,
|
| 316 |
+
2.9609375,
|
| 317 |
+
2.5234375,
|
| 318 |
+
2.65625,
|
| 319 |
+
2.701171875,
|
| 320 |
+
2.82421875,
|
| 321 |
+
2.677734375,
|
| 322 |
+
2.57421875,
|
| 323 |
+
2.90234375,
|
| 324 |
+
2.806640625,
|
| 325 |
+
2.94921875,
|
| 326 |
+
2.912109375,
|
| 327 |
+
2.865234375,
|
| 328 |
+
2.6328125,
|
| 329 |
+
2.787109375,
|
| 330 |
+
2.634765625,
|
| 331 |
+
2.66796875,
|
| 332 |
+
2.701171875,
|
| 333 |
+
2.81640625,
|
| 334 |
+
2.646484375,
|
| 335 |
+
2.72265625,
|
| 336 |
+
2.5859375,
|
| 337 |
+
2.8046875,
|
| 338 |
+
2.548828125,
|
| 339 |
+
2.6171875,
|
| 340 |
+
2.59375,
|
| 341 |
+
3.01171875,
|
| 342 |
+
2.828125,
|
| 343 |
+
2.85546875,
|
| 344 |
+
2.525390625,
|
| 345 |
+
2.751953125,
|
| 346 |
+
2.779296875,
|
| 347 |
+
2.77734375,
|
| 348 |
+
2.689453125,
|
| 349 |
+
2.638671875,
|
| 350 |
+
2.64453125,
|
| 351 |
+
2.748046875,
|
| 352 |
+
2.587890625,
|
| 353 |
+
2.634765625,
|
| 354 |
+
2.5625,
|
| 355 |
+
2.9375,
|
| 356 |
+
2.64453125,
|
| 357 |
+
2.8828125,
|
| 358 |
+
2.47265625,
|
| 359 |
+
2.63671875,
|
| 360 |
+
2.60546875,
|
| 361 |
+
2.4453125,
|
| 362 |
+
2.6796875,
|
| 363 |
+
2.56640625,
|
| 364 |
+
2.814453125,
|
| 365 |
+
2.623046875,
|
| 366 |
+
2.6640625,
|
| 367 |
+
2.892578125,
|
| 368 |
+
2.80078125,
|
| 369 |
+
2.67578125,
|
| 370 |
+
2.73046875,
|
| 371 |
+
2.42578125,
|
| 372 |
+
2.73046875,
|
| 373 |
+
2.853515625,
|
| 374 |
+
2.54296875,
|
| 375 |
+
2.70703125,
|
| 376 |
+
2.537109375,
|
| 377 |
+
2.65625,
|
| 378 |
+
2.44140625,
|
| 379 |
+
2.400390625,
|
| 380 |
+
2.76171875,
|
| 381 |
+
2.693359375,
|
| 382 |
+
2.72265625,
|
| 383 |
+
2.62109375,
|
| 384 |
+
2.421875,
|
| 385 |
+
2.4140625,
|
| 386 |
+
2.673828125,
|
| 387 |
+
2.515625,
|
| 388 |
+
2.48828125,
|
| 389 |
+
2.681640625,
|
| 390 |
+
2.40625,
|
| 391 |
+
2.6328125,
|
| 392 |
+
2.322265625,
|
| 393 |
+
2.482421875,
|
| 394 |
+
2.51953125,
|
| 395 |
+
2.486328125,
|
| 396 |
+
2.669921875,
|
| 397 |
+
2.50390625,
|
| 398 |
+
2.576171875,
|
| 399 |
+
2.494140625,
|
| 400 |
+
2.62109375,
|
| 401 |
+
2.72265625,
|
| 402 |
+
2.669921875,
|
| 403 |
+
2.587890625,
|
| 404 |
+
2.587890625,
|
| 405 |
+
2.42578125,
|
| 406 |
+
2.376953125,
|
| 407 |
+
2.466796875,
|
| 408 |
+
2.396484375,
|
| 409 |
+
2.513671875,
|
| 410 |
+
2.42578125,
|
| 411 |
+
2.408203125,
|
| 412 |
+
2.71484375,
|
| 413 |
+
2.482421875,
|
| 414 |
+
2.490234375,
|
| 415 |
+
2.75390625,
|
| 416 |
+
2.47265625,
|
| 417 |
+
2.439453125,
|
| 418 |
+
2.541015625,
|
| 419 |
+
2.466796875,
|
| 420 |
+
2.197265625,
|
| 421 |
+
2.294921875,
|
| 422 |
+
2.515625,
|
| 423 |
+
2.6328125,
|
| 424 |
+
2.37109375,
|
| 425 |
+
2.62109375,
|
| 426 |
+
2.3046875,
|
| 427 |
+
2.48828125,
|
| 428 |
+
2.435546875,
|
| 429 |
+
2.685546875,
|
| 430 |
+
2.28125,
|
| 431 |
+
2.591796875,
|
| 432 |
+
2.41015625,
|
| 433 |
+
2.5703125,
|
| 434 |
+
2.21484375,
|
| 435 |
+
2.4140625,
|
| 436 |
+
2.244140625,
|
| 437 |
+
2.5859375,
|
| 438 |
+
2.58984375,
|
| 439 |
+
2.56640625,
|
| 440 |
+
2.611328125,
|
| 441 |
+
2.3359375,
|
| 442 |
+
2.46875,
|
| 443 |
+
2.484375,
|
| 444 |
+
2.4375,
|
| 445 |
+
2.29296875,
|
| 446 |
+
2.46875,
|
| 447 |
+
2.36328125,
|
| 448 |
+
2.537109375,
|
| 449 |
+
2.525390625,
|
| 450 |
+
2.3515625,
|
| 451 |
+
2.484375,
|
| 452 |
+
2.443359375,
|
| 453 |
+
2.421875,
|
| 454 |
+
2.4375,
|
| 455 |
+
2.46484375,
|
| 456 |
+
2.591796875,
|
| 457 |
+
2.302734375,
|
| 458 |
+
2.3203125,
|
| 459 |
+
2.3046875,
|
| 460 |
+
2.4375,
|
| 461 |
+
2.28125,
|
| 462 |
+
2.37109375,
|
| 463 |
+
2.5859375,
|
| 464 |
+
2.51953125,
|
| 465 |
+
2.33203125,
|
| 466 |
+
2.16796875,
|
| 467 |
+
2.328125,
|
| 468 |
+
2.421875,
|
| 469 |
+
2.388671875,
|
| 470 |
+
2.42578125,
|
| 471 |
+
2.3671875,
|
| 472 |
+
2.48046875,
|
| 473 |
+
2.44140625,
|
| 474 |
+
2.28515625,
|
| 475 |
+
2.25,
|
| 476 |
+
2.279296875,
|
| 477 |
+
2.48828125,
|
| 478 |
+
2.26171875,
|
| 479 |
+
2.37109375,
|
| 480 |
+
2.369140625,
|
| 481 |
+
2.517578125,
|
| 482 |
+
2.419921875,
|
| 483 |
+
2.302734375,
|
| 484 |
+
2.4140625,
|
| 485 |
+
2.333984375,
|
| 486 |
+
2.251953125,
|
| 487 |
+
2.345703125,
|
| 488 |
+
2.259765625,
|
| 489 |
+
2.373046875,
|
| 490 |
+
2.38671875,
|
| 491 |
+
2.404296875,
|
| 492 |
+
2.306640625,
|
| 493 |
+
2.47265625,
|
| 494 |
+
2.12890625,
|
| 495 |
+
2.302734375,
|
| 496 |
+
2.56640625,
|
| 497 |
+
2.279296875,
|
| 498 |
+
2.314453125,
|
| 499 |
+
2.4296875,
|
| 500 |
+
2.470703125,
|
| 501 |
+
2.40234375,
|
| 502 |
+
2.283203125,
|
| 503 |
+
2.1484375,
|
| 504 |
+
2.48046875,
|
| 505 |
+
2.5,
|
| 506 |
+
2.357421875,
|
| 507 |
+
2.158203125,
|
| 508 |
+
2.357421875,
|
| 509 |
+
2.49609375,
|
| 510 |
+
2.375,
|
| 511 |
+
2.26171875,
|
| 512 |
+
2.40625,
|
| 513 |
+
2.2734375,
|
| 514 |
+
2.34375,
|
| 515 |
+
2.173828125,
|
| 516 |
+
2.326171875,
|
| 517 |
+
2.15234375,
|
| 518 |
+
2.125,
|
| 519 |
+
2.431640625,
|
| 520 |
+
2.083984375,
|
| 521 |
+
2.203125,
|
| 522 |
+
2.400390625,
|
| 523 |
+
2.365234375,
|
| 524 |
+
2.29296875,
|
| 525 |
+
2.060546875,
|
| 526 |
+
2.158203125,
|
| 527 |
+
2.150390625,
|
| 528 |
+
2.26171875,
|
| 529 |
+
1.931640625,
|
| 530 |
+
2.099609375,
|
| 531 |
+
2.3203125,
|
| 532 |
+
2.2109375,
|
| 533 |
+
2.236328125,
|
| 534 |
+
2.482421875,
|
| 535 |
+
2.203125,
|
| 536 |
+
2.2109375,
|
| 537 |
+
2.259765625,
|
| 538 |
+
2.10546875,
|
| 539 |
+
2.1953125,
|
| 540 |
+
2.16015625,
|
| 541 |
+
2.50390625,
|
| 542 |
+
2.501953125,
|
| 543 |
+
2.265625,
|
| 544 |
+
2.25390625,
|
| 545 |
+
1.9990234375,
|
| 546 |
+
2.255859375,
|
| 547 |
+
2.158203125,
|
| 548 |
+
2.21484375,
|
| 549 |
+
2.181640625,
|
| 550 |
+
2.2734375,
|
| 551 |
+
2.40234375,
|
| 552 |
+
2.345703125,
|
| 553 |
+
2.296875,
|
| 554 |
+
2.123046875,
|
| 555 |
+
2.337890625,
|
| 556 |
+
2.037109375,
|
| 557 |
+
2.08203125,
|
| 558 |
+
2.28125,
|
| 559 |
+
2.0234375,
|
| 560 |
+
2.169921875,
|
| 561 |
+
2.236328125,
|
| 562 |
+
2.248046875,
|
| 563 |
+
2.541015625,
|
| 564 |
+
2.40625,
|
| 565 |
+
2.111328125,
|
| 566 |
+
1.9775390625,
|
| 567 |
+
2.12109375,
|
| 568 |
+
2.181640625,
|
| 569 |
+
2.177734375,
|
| 570 |
+
1.9755859375,
|
| 571 |
+
2.10546875,
|
| 572 |
+
1.994140625,
|
| 573 |
+
2.044921875,
|
| 574 |
+
2.115234375,
|
| 575 |
+
2.392578125,
|
| 576 |
+
1.873046875,
|
| 577 |
+
2.02734375,
|
| 578 |
+
2.36328125,
|
| 579 |
+
2.056640625,
|
| 580 |
+
2.1640625,
|
| 581 |
+
2.048828125,
|
| 582 |
+
2.009765625,
|
| 583 |
+
2.095703125,
|
| 584 |
+
2.0625,
|
| 585 |
+
2.10546875,
|
| 586 |
+
2.0703125,
|
| 587 |
+
2.306640625,
|
| 588 |
+
2.201171875,
|
| 589 |
+
1.951171875,
|
| 590 |
+
1.9130859375,
|
| 591 |
+
2.20703125,
|
| 592 |
+
2.24609375,
|
| 593 |
+
2.337890625,
|
| 594 |
+
2.0625,
|
| 595 |
+
2.03515625,
|
| 596 |
+
2.259765625,
|
| 597 |
+
2.154296875,
|
| 598 |
+
1.966796875,
|
| 599 |
+
1.9765625,
|
| 600 |
+
2.248046875,
|
| 601 |
+
2.1796875,
|
| 602 |
+
2.099609375,
|
| 603 |
+
2.119140625,
|
| 604 |
+
2.130859375,
|
| 605 |
+
2.19140625,
|
| 606 |
+
2.0,
|
| 607 |
+
2.26953125,
|
| 608 |
+
2.181640625,
|
| 609 |
+
2.072265625,
|
| 610 |
+
2.0703125,
|
| 611 |
+
1.974609375,
|
| 612 |
+
2.1171875,
|
| 613 |
+
2.10546875,
|
| 614 |
+
2.3671875,
|
| 615 |
+
2.130859375,
|
| 616 |
+
2.001953125,
|
| 617 |
+
2.03125,
|
| 618 |
+
2.181640625,
|
| 619 |
+
1.962890625,
|
| 620 |
+
2.015625,
|
| 621 |
+
2.10546875,
|
| 622 |
+
2.32421875,
|
| 623 |
+
2.08984375,
|
| 624 |
+
2.0234375,
|
| 625 |
+
2.029296875,
|
| 626 |
+
1.904296875,
|
| 627 |
+
2.189453125,
|
| 628 |
+
2.2734375,
|
| 629 |
+
2.173828125,
|
| 630 |
+
2.060546875,
|
| 631 |
+
2.015625,
|
| 632 |
+
2.0625,
|
| 633 |
+
2.13671875,
|
| 634 |
+
1.94140625,
|
| 635 |
+
1.91796875,
|
| 636 |
+
1.9462890625,
|
| 637 |
+
1.9599609375,
|
| 638 |
+
2.013671875,
|
| 639 |
+
2.0234375,
|
| 640 |
+
1.96875,
|
| 641 |
+
1.9765625,
|
| 642 |
+
2.119140625,
|
| 643 |
+
2.056640625,
|
| 644 |
+
2.099609375,
|
| 645 |
+
2.06640625,
|
| 646 |
+
1.8115234375,
|
| 647 |
+
2.224609375,
|
| 648 |
+
1.9189453125,
|
| 649 |
+
2.08203125,
|
| 650 |
+
2.01953125,
|
| 651 |
+
1.8779296875,
|
| 652 |
+
1.908203125,
|
| 653 |
+
2.09765625,
|
| 654 |
+
2.22265625,
|
| 655 |
+
1.921875,
|
| 656 |
+
1.884765625,
|
| 657 |
+
1.9912109375,
|
| 658 |
+
2.052734375,
|
| 659 |
+
2.025390625,
|
| 660 |
+
2.185546875,
|
| 661 |
+
2.099609375,
|
| 662 |
+
1.9833984375,
|
| 663 |
+
2.1015625,
|
| 664 |
+
2.220703125,
|
| 665 |
+
2.244140625,
|
| 666 |
+
2.123046875,
|
| 667 |
+
1.9736328125,
|
| 668 |
+
1.828125,
|
| 669 |
+
1.982421875,
|
| 670 |
+
1.8388671875,
|
| 671 |
+
2.1953125,
|
| 672 |
+
1.9150390625,
|
| 673 |
+
1.8994140625,
|
| 674 |
+
1.9296875,
|
| 675 |
+
1.818359375,
|
| 676 |
+
2.06640625,
|
| 677 |
+
1.958984375,
|
| 678 |
+
2.056640625,
|
| 679 |
+
2.11328125,
|
| 680 |
+
1.9423828125,
|
| 681 |
+
2.26953125,
|
| 682 |
+
1.98046875,
|
| 683 |
+
1.806640625,
|
| 684 |
+
2.12890625,
|
| 685 |
+
2.07421875,
|
| 686 |
+
2.05859375,
|
| 687 |
+
2.09375,
|
| 688 |
+
2.14453125,
|
| 689 |
+
2.142578125,
|
| 690 |
+
1.93359375,
|
| 691 |
+
1.9990234375,
|
| 692 |
+
1.900390625,
|
| 693 |
+
1.8837890625,
|
| 694 |
+
1.880859375,
|
| 695 |
+
1.9931640625,
|
| 696 |
+
2.162109375,
|
| 697 |
+
2.064453125,
|
| 698 |
+
1.8603515625,
|
| 699 |
+
2.09375,
|
| 700 |
+
2.1015625,
|
| 701 |
+
2.03125,
|
| 702 |
+
2.154296875,
|
| 703 |
+
2.123046875,
|
| 704 |
+
1.822265625,
|
| 705 |
+
2.001953125,
|
| 706 |
+
2.04296875,
|
| 707 |
+
1.9951171875,
|
| 708 |
+
1.9677734375,
|
| 709 |
+
2.146484375,
|
| 710 |
+
2.060546875,
|
| 711 |
+
2.00390625,
|
| 712 |
+
1.978515625,
|
| 713 |
+
2.005859375,
|
| 714 |
+
1.943359375,
|
| 715 |
+
1.9375,
|
| 716 |
+
1.828125,
|
| 717 |
+
1.90625,
|
| 718 |
+
1.8935546875,
|
| 719 |
+
1.958984375,
|
| 720 |
+
2.009765625,
|
| 721 |
+
1.8984375,
|
| 722 |
+
2.03515625,
|
| 723 |
+
2.0625,
|
| 724 |
+
2.091796875,
|
| 725 |
+
1.8447265625,
|
| 726 |
+
2.11328125,
|
| 727 |
+
1.765625,
|
| 728 |
+
1.9130859375,
|
| 729 |
+
1.8974609375,
|
| 730 |
+
1.87109375,
|
| 731 |
+
2.033203125,
|
| 732 |
+
1.921875,
|
| 733 |
+
1.921875,
|
| 734 |
+
1.615234375,
|
| 735 |
+
1.9169921875,
|
| 736 |
+
1.9609375,
|
| 737 |
+
1.7412109375,
|
| 738 |
+
1.9296875,
|
| 739 |
+
1.8935546875,
|
| 740 |
+
2.109375,
|
| 741 |
+
1.8779296875,
|
| 742 |
+
1.875,
|
| 743 |
+
1.794921875,
|
| 744 |
+
2.076171875,
|
| 745 |
+
2.0546875,
|
| 746 |
+
1.828125,
|
| 747 |
+
1.8076171875,
|
| 748 |
+
1.951171875,
|
| 749 |
+
1.73046875,
|
| 750 |
+
1.9482421875,
|
| 751 |
+
2.109375,
|
| 752 |
+
1.978515625,
|
| 753 |
+
2.169921875,
|
| 754 |
+
1.943359375,
|
| 755 |
+
1.8896484375,
|
| 756 |
+
1.9443359375,
|
| 757 |
+
2.10546875,
|
| 758 |
+
1.986328125,
|
| 759 |
+
2.005859375,
|
| 760 |
+
1.93359375,
|
| 761 |
+
2.078125,
|
| 762 |
+
1.8740234375,
|
| 763 |
+
2.056640625,
|
| 764 |
+
2.015625,
|
| 765 |
+
2.02734375,
|
| 766 |
+
1.9296875,
|
| 767 |
+
1.5615234375,
|
| 768 |
+
2.1171875,
|
| 769 |
+
1.796875,
|
| 770 |
+
1.9716796875,
|
| 771 |
+
1.8515625,
|
| 772 |
+
2.046875,
|
| 773 |
+
1.6640625,
|
| 774 |
+
1.9560546875,
|
| 775 |
+
1.8271484375,
|
| 776 |
+
1.9677734375,
|
| 777 |
+
1.9912109375,
|
| 778 |
+
1.861328125,
|
| 779 |
+
1.935546875,
|
| 780 |
+
1.94140625,
|
| 781 |
+
2.01171875,
|
| 782 |
+
2.173828125,
|
| 783 |
+
2.01953125,
|
| 784 |
+
1.8701171875,
|
| 785 |
+
1.828125,
|
| 786 |
+
2.06640625,
|
| 787 |
+
2.021484375,
|
| 788 |
+
1.9677734375,
|
| 789 |
+
1.9453125,
|
| 790 |
+
1.76171875,
|
| 791 |
+
1.998046875,
|
| 792 |
+
2.103515625,
|
| 793 |
+
1.8115234375,
|
| 794 |
+
2.08984375,
|
| 795 |
+
1.8740234375,
|
| 796 |
+
1.787109375,
|
| 797 |
+
2.08203125,
|
| 798 |
+
2.017578125,
|
| 799 |
+
1.7421875,
|
| 800 |
+
1.865234375,
|
| 801 |
+
2.03515625,
|
| 802 |
+
1.87890625,
|
| 803 |
+
1.7744140625,
|
| 804 |
+
2.01171875,
|
| 805 |
+
1.779296875,
|
| 806 |
+
1.970703125,
|
| 807 |
+
1.79296875,
|
| 808 |
+
1.9130859375,
|
| 809 |
+
1.880859375,
|
| 810 |
+
1.814453125,
|
| 811 |
+
1.833984375,
|
| 812 |
+
1.8671875,
|
| 813 |
+
1.8017578125,
|
| 814 |
+
1.708984375,
|
| 815 |
+
1.9697265625,
|
| 816 |
+
1.9130859375,
|
| 817 |
+
2.033203125,
|
| 818 |
+
1.728515625,
|
| 819 |
+
1.7158203125,
|
| 820 |
+
1.8798828125,
|
| 821 |
+
1.9765625,
|
| 822 |
+
1.63671875,
|
| 823 |
+
1.9140625,
|
| 824 |
+
1.857421875,
|
| 825 |
+
1.8037109375,
|
| 826 |
+
1.75,
|
| 827 |
+
1.7978515625,
|
| 828 |
+
1.6875,
|
| 829 |
+
1.88671875,
|
| 830 |
+
1.84765625,
|
| 831 |
+
1.8828125,
|
| 832 |
+
1.8515625,
|
| 833 |
+
1.8359375,
|
| 834 |
+
1.931640625,
|
| 835 |
+
1.939453125,
|
| 836 |
+
1.970703125,
|
| 837 |
+
1.8662109375,
|
| 838 |
+
1.88671875,
|
| 839 |
+
1.6826171875,
|
| 840 |
+
1.87890625,
|
| 841 |
+
1.748046875,
|
| 842 |
+
1.779296875,
|
| 843 |
+
1.9384765625,
|
| 844 |
+
1.88671875,
|
| 845 |
+
1.8154296875,
|
| 846 |
+
1.767578125,
|
| 847 |
+
1.8798828125,
|
| 848 |
+
1.962890625,
|
| 849 |
+
1.89453125,
|
| 850 |
+
1.970703125,
|
| 851 |
+
1.966796875,
|
| 852 |
+
1.86328125,
|
| 853 |
+
1.947265625,
|
| 854 |
+
1.900390625,
|
| 855 |
+
1.8271484375,
|
| 856 |
+
1.9453125,
|
| 857 |
+
1.818359375,
|
| 858 |
+
1.8994140625,
|
| 859 |
+
1.8623046875,
|
| 860 |
+
1.8046875,
|
| 861 |
+
1.7509765625,
|
| 862 |
+
1.8525390625,
|
| 863 |
+
2.01171875,
|
| 864 |
+
1.7734375,
|
| 865 |
+
1.68359375,
|
| 866 |
+
2.01171875,
|
| 867 |
+
2.0,
|
| 868 |
+
1.880859375,
|
| 869 |
+
1.775390625,
|
| 870 |
+
1.828125,
|
| 871 |
+
1.716796875,
|
| 872 |
+
1.849609375,
|
| 873 |
+
1.806640625,
|
| 874 |
+
1.8271484375,
|
| 875 |
+
1.8193359375,
|
| 876 |
+
1.955078125,
|
| 877 |
+
1.970703125,
|
| 878 |
+
1.7529296875,
|
| 879 |
+
1.62890625,
|
| 880 |
+
1.861328125,
|
| 881 |
+
1.669921875,
|
| 882 |
+
1.888671875,
|
| 883 |
+
1.859375,
|
| 884 |
+
1.8427734375,
|
| 885 |
+
1.751953125,
|
| 886 |
+
1.7109375,
|
| 887 |
+
1.7470703125,
|
| 888 |
+
1.8095703125,
|
| 889 |
+
1.84765625,
|
| 890 |
+
1.771484375,
|
| 891 |
+
1.728515625,
|
| 892 |
+
1.818359375,
|
| 893 |
+
1.7841796875,
|
| 894 |
+
2.01953125,
|
| 895 |
+
1.94140625,
|
| 896 |
+
1.81640625,
|
| 897 |
+
1.974609375,
|
| 898 |
+
1.8525390625,
|
| 899 |
+
1.748046875,
|
| 900 |
+
1.962890625,
|
| 901 |
+
1.91796875,
|
| 902 |
+
1.822265625,
|
| 903 |
+
1.7099609375,
|
| 904 |
+
1.9775390625,
|
| 905 |
+
1.75390625,
|
| 906 |
+
1.775390625,
|
| 907 |
+
1.8955078125,
|
| 908 |
+
1.728515625,
|
| 909 |
+
1.8369140625,
|
| 910 |
+
2.068359375,
|
| 911 |
+
1.890625,
|
| 912 |
+
1.6982421875,
|
| 913 |
+
1.7509765625,
|
| 914 |
+
1.8125,
|
| 915 |
+
1.716796875,
|
| 916 |
+
1.8544921875,
|
| 917 |
+
1.6630859375,
|
| 918 |
+
1.646484375,
|
| 919 |
+
1.7802734375,
|
| 920 |
+
1.513671875,
|
| 921 |
+
1.92578125,
|
| 922 |
+
1.560546875,
|
| 923 |
+
1.8212890625,
|
| 924 |
+
1.7490234375,
|
| 925 |
+
1.8564453125,
|
| 926 |
+
1.765625,
|
| 927 |
+
1.8037109375,
|
| 928 |
+
1.7470703125,
|
| 929 |
+
1.60546875,
|
| 930 |
+
1.869140625,
|
| 931 |
+
1.7421875,
|
| 932 |
+
1.814453125,
|
| 933 |
+
1.6513671875,
|
| 934 |
+
1.7353515625,
|
| 935 |
+
1.8828125,
|
| 936 |
+
1.7529296875,
|
| 937 |
+
1.70703125,
|
| 938 |
+
1.927734375,
|
| 939 |
+
1.7099609375,
|
| 940 |
+
1.650390625,
|
| 941 |
+
1.857421875,
|
| 942 |
+
1.78125,
|
| 943 |
+
1.7998046875,
|
| 944 |
+
1.623046875,
|
| 945 |
+
1.7998046875,
|
| 946 |
+
1.8955078125,
|
| 947 |
+
1.9072265625,
|
| 948 |
+
1.662109375,
|
| 949 |
+
1.64453125,
|
| 950 |
+
1.7119140625,
|
| 951 |
+
1.85546875,
|
| 952 |
+
1.8505859375,
|
| 953 |
+
1.806640625,
|
| 954 |
+
1.5927734375,
|
| 955 |
+
1.90234375,
|
| 956 |
+
1.7626953125,
|
| 957 |
+
1.8935546875,
|
| 958 |
+
1.8115234375,
|
| 959 |
+
1.7109375,
|
| 960 |
+
1.994140625,
|
| 961 |
+
1.8896484375,
|
| 962 |
+
1.732421875,
|
| 963 |
+
1.6640625,
|
| 964 |
+
1.74609375,
|
| 965 |
+
1.6875,
|
| 966 |
+
1.71875,
|
| 967 |
+
1.80078125,
|
| 968 |
+
1.9140625,
|
| 969 |
+
1.6865234375,
|
| 970 |
+
1.646484375,
|
| 971 |
+
1.7646484375,
|
| 972 |
+
1.765625,
|
| 973 |
+
1.509765625,
|
| 974 |
+
1.7548828125,
|
| 975 |
+
1.9052734375,
|
| 976 |
+
1.615234375,
|
| 977 |
+
1.5146484375,
|
| 978 |
+
1.7548828125,
|
| 979 |
+
1.7451171875,
|
| 980 |
+
1.7626953125,
|
| 981 |
+
1.7353515625,
|
| 982 |
+
1.7607421875,
|
| 983 |
+
1.669921875,
|
| 984 |
+
1.7734375,
|
| 985 |
+
1.7900390625,
|
| 986 |
+
1.75390625,
|
| 987 |
+
1.9267578125,
|
| 988 |
+
1.8232421875,
|
| 989 |
+
1.6748046875,
|
| 990 |
+
1.5771484375,
|
| 991 |
+
1.740234375,
|
| 992 |
+
1.6904296875,
|
| 993 |
+
1.90625,
|
| 994 |
+
1.59375,
|
| 995 |
+
1.677734375,
|
| 996 |
+
1.6259765625,
|
| 997 |
+
1.658203125,
|
| 998 |
+
1.751953125,
|
| 999 |
+
1.6982421875,
|
| 1000 |
+
1.7294921875,
|
| 1001 |
+
1.8388671875,
|
| 1002 |
+
1.73046875,
|
| 1003 |
+
1.775390625,
|
| 1004 |
+
1.818359375,
|
| 1005 |
+
1.7734375,
|
| 1006 |
+
1.779296875,
|
| 1007 |
+
1.541015625,
|
| 1008 |
+
1.7744140625,
|
| 1009 |
+
1.5859375,
|
| 1010 |
+
1.896484375,
|
| 1011 |
+
1.6298828125,
|
| 1012 |
+
1.6962890625,
|
| 1013 |
+
1.666015625,
|
| 1014 |
+
2.01953125,
|
| 1015 |
+
1.65234375,
|
| 1016 |
+
1.7041015625,
|
| 1017 |
+
1.626953125,
|
| 1018 |
+
1.611328125,
|
| 1019 |
+
1.8544921875,
|
| 1020 |
+
1.8515625,
|
| 1021 |
+
1.8662109375,
|
| 1022 |
+
1.7353515625,
|
| 1023 |
+
1.787109375,
|
| 1024 |
+
1.791015625,
|
| 1025 |
+
1.8642578125,
|
| 1026 |
+
1.71875,
|
| 1027 |
+
1.703125,
|
| 1028 |
+
1.681640625,
|
| 1029 |
+
1.666015625,
|
| 1030 |
+
1.8740234375,
|
| 1031 |
+
1.7587890625,
|
| 1032 |
+
1.736328125,
|
| 1033 |
+
1.599609375,
|
| 1034 |
+
1.677734375,
|
| 1035 |
+
1.853515625,
|
| 1036 |
+
1.66796875,
|
| 1037 |
+
1.5537109375,
|
| 1038 |
+
1.8505859375,
|
| 1039 |
+
1.833984375,
|
| 1040 |
+
1.744140625,
|
| 1041 |
+
1.64453125,
|
| 1042 |
+
1.701171875,
|
| 1043 |
+
1.6796875,
|
| 1044 |
+
1.8955078125,
|
| 1045 |
+
1.8505859375,
|
| 1046 |
+
1.66015625,
|
| 1047 |
+
1.8330078125,
|
| 1048 |
+
1.6171875,
|
| 1049 |
+
1.7861328125,
|
| 1050 |
+
1.5546875,
|
| 1051 |
+
1.9013671875,
|
| 1052 |
+
1.763671875,
|
| 1053 |
+
1.6474609375,
|
| 1054 |
+
1.509765625,
|
| 1055 |
+
1.6513671875,
|
| 1056 |
+
1.791015625,
|
| 1057 |
+
1.8134765625,
|
| 1058 |
+
1.70703125,
|
| 1059 |
+
1.740234375,
|
| 1060 |
+
1.72265625,
|
| 1061 |
+
1.703125,
|
| 1062 |
+
1.63671875,
|
| 1063 |
+
1.5693359375,
|
| 1064 |
+
1.611328125,
|
| 1065 |
+
1.76953125,
|
| 1066 |
+
1.818359375,
|
| 1067 |
+
1.732421875,
|
| 1068 |
+
1.5029296875,
|
| 1069 |
+
1.583984375,
|
| 1070 |
+
1.64453125,
|
| 1071 |
+
1.5634765625,
|
| 1072 |
+
1.71484375,
|
| 1073 |
+
1.572265625,
|
| 1074 |
+
1.62109375,
|
| 1075 |
+
1.58203125,
|
| 1076 |
+
1.7080078125,
|
| 1077 |
+
1.6689453125,
|
| 1078 |
+
1.5244140625,
|
| 1079 |
+
1.732421875,
|
| 1080 |
+
1.64453125,
|
| 1081 |
+
1.67578125,
|
| 1082 |
+
1.669921875,
|
| 1083 |
+
1.76953125,
|
| 1084 |
+
1.767578125,
|
| 1085 |
+
1.6552734375,
|
| 1086 |
+
1.654296875,
|
| 1087 |
+
1.8671875,
|
| 1088 |
+
1.5791015625,
|
| 1089 |
+
1.572265625,
|
| 1090 |
+
1.9609375,
|
| 1091 |
+
1.5625,
|
| 1092 |
+
1.91015625,
|
| 1093 |
+
1.7001953125,
|
| 1094 |
+
1.90625,
|
| 1095 |
+
1.767578125,
|
| 1096 |
+
1.611328125,
|
| 1097 |
+
1.80078125,
|
| 1098 |
+
1.6865234375,
|
| 1099 |
+
1.73046875,
|
| 1100 |
+
1.6640625,
|
| 1101 |
+
1.611328125,
|
| 1102 |
+
1.560546875,
|
| 1103 |
+
1.75390625,
|
| 1104 |
+
1.9609375,
|
| 1105 |
+
1.720703125,
|
| 1106 |
+
1.7177734375,
|
| 1107 |
+
1.689453125,
|
| 1108 |
+
1.744140625,
|
| 1109 |
+
1.72265625,
|
| 1110 |
+
1.59375,
|
| 1111 |
+
1.634765625,
|
| 1112 |
+
1.5947265625,
|
| 1113 |
+
1.6748046875,
|
| 1114 |
+
1.53515625,
|
| 1115 |
+
1.8359375,
|
| 1116 |
+
1.70703125,
|
| 1117 |
+
1.666015625,
|
| 1118 |
+
1.626953125,
|
| 1119 |
+
1.560546875,
|
| 1120 |
+
1.6337890625,
|
| 1121 |
+
1.5947265625,
|
| 1122 |
+
1.626953125,
|
| 1123 |
+
1.6953125,
|
| 1124 |
+
1.421875,
|
| 1125 |
+
1.8046875,
|
| 1126 |
+
1.7890625,
|
| 1127 |
+
1.658203125,
|
| 1128 |
+
1.6796875,
|
| 1129 |
+
1.693359375,
|
| 1130 |
+
1.49609375,
|
| 1131 |
+
1.693359375,
|
| 1132 |
+
1.642578125,
|
| 1133 |
+
1.541015625,
|
| 1134 |
+
1.9150390625,
|
| 1135 |
+
1.8095703125,
|
| 1136 |
+
1.69140625,
|
| 1137 |
+
1.5439453125,
|
| 1138 |
+
1.6328125,
|
| 1139 |
+
1.6474609375,
|
| 1140 |
+
1.6640625,
|
| 1141 |
+
1.45703125,
|
| 1142 |
+
1.5166015625,
|
| 1143 |
+
1.552734375,
|
| 1144 |
+
1.912109375,
|
| 1145 |
+
1.646484375,
|
| 1146 |
+
1.791015625,
|
| 1147 |
+
1.4482421875,
|
| 1148 |
+
1.75390625,
|
| 1149 |
+
1.572265625,
|
| 1150 |
+
1.619140625,
|
| 1151 |
+
1.6591796875,
|
| 1152 |
+
1.5302734375,
|
| 1153 |
+
1.56640625,
|
| 1154 |
+
1.685546875,
|
| 1155 |
+
1.525390625,
|
| 1156 |
+
1.7041015625,
|
| 1157 |
+
1.6787109375,
|
| 1158 |
+
1.6943359375,
|
| 1159 |
+
1.8330078125,
|
| 1160 |
+
1.6142578125,
|
| 1161 |
+
1.720703125,
|
| 1162 |
+
1.791015625,
|
| 1163 |
+
1.6005859375,
|
| 1164 |
+
1.568359375,
|
| 1165 |
+
1.6318359375,
|
| 1166 |
+
1.5546875,
|
| 1167 |
+
1.6533203125,
|
| 1168 |
+
1.71484375,
|
| 1169 |
+
1.498046875,
|
| 1170 |
+
1.6220703125,
|
| 1171 |
+
1.7724609375,
|
| 1172 |
+
1.66796875,
|
| 1173 |
+
1.79296875,
|
| 1174 |
+
1.8359375,
|
| 1175 |
+
1.74609375,
|
| 1176 |
+
1.822265625,
|
| 1177 |
+
1.751953125,
|
| 1178 |
+
1.609375,
|
| 1179 |
+
1.63671875,
|
| 1180 |
+
1.6376953125,
|
| 1181 |
+
1.5,
|
| 1182 |
+
1.76171875,
|
| 1183 |
+
1.744140625,
|
| 1184 |
+
1.728515625,
|
| 1185 |
+
1.6533203125,
|
| 1186 |
+
1.6474609375,
|
| 1187 |
+
1.6689453125,
|
| 1188 |
+
1.771484375,
|
| 1189 |
+
1.59765625,
|
| 1190 |
+
1.7763671875,
|
| 1191 |
+
1.7158203125,
|
| 1192 |
+
1.4404296875,
|
| 1193 |
+
1.412109375,
|
| 1194 |
+
1.4833984375,
|
| 1195 |
+
1.6396484375,
|
| 1196 |
+
1.712890625,
|
| 1197 |
+
1.6171875,
|
| 1198 |
+
1.45703125,
|
| 1199 |
+
1.78515625,
|
| 1200 |
+
1.662109375,
|
| 1201 |
+
1.83984375,
|
| 1202 |
+
1.44921875,
|
| 1203 |
+
1.7392578125,
|
| 1204 |
+
1.7861328125,
|
| 1205 |
+
1.62109375,
|
| 1206 |
+
1.5791015625,
|
| 1207 |
+
1.52734375,
|
| 1208 |
+
1.724609375,
|
| 1209 |
+
1.7216796875,
|
| 1210 |
+
1.7041015625,
|
| 1211 |
+
1.73046875,
|
| 1212 |
+
1.7373046875,
|
| 1213 |
+
1.556640625,
|
| 1214 |
+
1.736328125,
|
| 1215 |
+
1.8095703125,
|
| 1216 |
+
1.701171875,
|
| 1217 |
+
1.48046875,
|
| 1218 |
+
1.7373046875,
|
| 1219 |
+
1.64453125,
|
| 1220 |
+
1.669921875,
|
| 1221 |
+
1.6162109375,
|
| 1222 |
+
1.6357421875,
|
| 1223 |
+
1.6181640625,
|
| 1224 |
+
1.7265625,
|
| 1225 |
+
1.69921875,
|
| 1226 |
+
1.6796875,
|
| 1227 |
+
1.6767578125,
|
| 1228 |
+
1.6171875,
|
| 1229 |
+
1.5341796875,
|
| 1230 |
+
1.8037109375,
|
| 1231 |
+
1.6298828125,
|
| 1232 |
+
1.59765625,
|
| 1233 |
+
1.689453125,
|
| 1234 |
+
1.595703125,
|
| 1235 |
+
1.693359375,
|
| 1236 |
+
1.6474609375,
|
| 1237 |
+
1.76171875,
|
| 1238 |
+
1.7265625,
|
| 1239 |
+
1.353515625,
|
| 1240 |
+
1.572265625,
|
| 1241 |
+
1.69921875,
|
| 1242 |
+
1.6484375,
|
| 1243 |
+
1.63671875,
|
| 1244 |
+
1.525390625,
|
| 1245 |
+
1.5830078125,
|
| 1246 |
+
1.6923828125,
|
| 1247 |
+
1.8232421875,
|
| 1248 |
+
1.685546875,
|
| 1249 |
+
1.529296875,
|
| 1250 |
+
1.6630859375,
|
| 1251 |
+
1.796875,
|
| 1252 |
+
1.6328125,
|
| 1253 |
+
1.626953125,
|
| 1254 |
+
1.7353515625,
|
| 1255 |
+
1.7109375,
|
| 1256 |
+
1.6220703125,
|
| 1257 |
+
1.5185546875,
|
| 1258 |
+
1.5615234375,
|
| 1259 |
+
1.5732421875,
|
| 1260 |
+
1.77734375,
|
| 1261 |
+
1.662109375,
|
| 1262 |
+
1.7158203125,
|
| 1263 |
+
1.703125,
|
| 1264 |
+
1.7119140625,
|
| 1265 |
+
1.7392578125,
|
| 1266 |
+
1.552734375,
|
| 1267 |
+
1.6787109375,
|
| 1268 |
+
1.59375,
|
| 1269 |
+
1.51953125,
|
| 1270 |
+
1.4970703125,
|
| 1271 |
+
1.7021484375,
|
| 1272 |
+
1.533203125,
|
| 1273 |
+
1.5673828125,
|
| 1274 |
+
1.5439453125,
|
| 1275 |
+
1.5390625,
|
| 1276 |
+
1.6162109375,
|
| 1277 |
+
1.7041015625,
|
| 1278 |
+
1.5029296875,
|
| 1279 |
+
1.6484375,
|
| 1280 |
+
1.62890625,
|
| 1281 |
+
1.6494140625,
|
| 1282 |
+
1.509765625,
|
| 1283 |
+
1.830078125,
|
| 1284 |
+
1.6845703125,
|
| 1285 |
+
1.68359375,
|
| 1286 |
+
1.330078125,
|
| 1287 |
+
1.58203125,
|
| 1288 |
+
1.7197265625,
|
| 1289 |
+
1.515625,
|
| 1290 |
+
1.70703125,
|
| 1291 |
+
1.603515625,
|
| 1292 |
+
1.583984375,
|
| 1293 |
+
1.5947265625,
|
| 1294 |
+
1.5478515625,
|
| 1295 |
+
1.572265625,
|
| 1296 |
+
1.5625,
|
| 1297 |
+
1.546875,
|
| 1298 |
+
1.5830078125,
|
| 1299 |
+
1.787109375,
|
| 1300 |
+
1.6435546875,
|
| 1301 |
+
1.6689453125,
|
| 1302 |
+
1.6796875,
|
| 1303 |
+
1.771484375,
|
| 1304 |
+
1.630859375,
|
| 1305 |
+
1.6923828125,
|
| 1306 |
+
1.72265625,
|
| 1307 |
+
1.5888671875,
|
| 1308 |
+
1.693359375,
|
| 1309 |
+
1.677734375,
|
| 1310 |
+
1.5205078125,
|
| 1311 |
+
1.64453125,
|
| 1312 |
+
1.748046875,
|
| 1313 |
+
1.84375,
|
| 1314 |
+
1.6357421875,
|
| 1315 |
+
1.623046875,
|
| 1316 |
+
1.705078125,
|
| 1317 |
+
1.763671875,
|
| 1318 |
+
1.6044921875,
|
| 1319 |
+
1.6640625,
|
| 1320 |
+
1.7421875,
|
| 1321 |
+
1.67578125,
|
| 1322 |
+
1.841796875,
|
| 1323 |
+
1.79296875,
|
| 1324 |
+
1.8046875,
|
| 1325 |
+
1.4951171875,
|
| 1326 |
+
1.5712890625,
|
| 1327 |
+
1.61328125,
|
| 1328 |
+
1.6015625,
|
| 1329 |
+
1.6298828125,
|
| 1330 |
+
1.701171875,
|
| 1331 |
+
1.59765625,
|
| 1332 |
+
1.71484375,
|
| 1333 |
+
1.5634765625,
|
| 1334 |
+
1.65234375,
|
| 1335 |
+
1.759765625,
|
| 1336 |
+
1.4267578125,
|
| 1337 |
+
1.748046875,
|
| 1338 |
+
1.62890625,
|
| 1339 |
+
1.50390625,
|
| 1340 |
+
1.712890625,
|
| 1341 |
+
1.7861328125,
|
| 1342 |
+
1.625,
|
| 1343 |
+
1.69921875,
|
| 1344 |
+
1.654296875,
|
| 1345 |
+
1.771484375,
|
| 1346 |
+
1.5048828125,
|
| 1347 |
+
1.666015625,
|
| 1348 |
+
1.4296875,
|
| 1349 |
+
1.8046875,
|
| 1350 |
+
1.634765625,
|
| 1351 |
+
1.6484375,
|
| 1352 |
+
1.6748046875,
|
| 1353 |
+
1.76953125,
|
| 1354 |
+
1.779296875,
|
| 1355 |
+
1.6669921875,
|
| 1356 |
+
1.814453125,
|
| 1357 |
+
1.677734375,
|
| 1358 |
+
1.7001953125,
|
| 1359 |
+
1.7412109375,
|
| 1360 |
+
1.91015625,
|
| 1361 |
+
1.654296875,
|
| 1362 |
+
1.5703125,
|
| 1363 |
+
1.6103515625,
|
| 1364 |
+
1.634765625,
|
| 1365 |
+
1.689453125,
|
| 1366 |
+
1.521484375,
|
| 1367 |
+
1.6748046875,
|
| 1368 |
+
1.6689453125,
|
| 1369 |
+
1.455078125,
|
| 1370 |
+
1.7490234375,
|
| 1371 |
+
1.5166015625,
|
| 1372 |
+
1.6611328125,
|
| 1373 |
+
1.779296875,
|
| 1374 |
+
1.640625,
|
| 1375 |
+
1.669921875,
|
| 1376 |
+
1.724609375,
|
| 1377 |
+
1.6201171875,
|
| 1378 |
+
1.677734375,
|
| 1379 |
+
1.654296875,
|
| 1380 |
+
1.7724609375,
|
| 1381 |
+
1.6396484375,
|
| 1382 |
+
1.689453125,
|
| 1383 |
+
1.58203125,
|
| 1384 |
+
1.4560546875,
|
| 1385 |
+
1.4609375,
|
| 1386 |
+
1.65234375,
|
| 1387 |
+
1.759765625,
|
| 1388 |
+
1.814453125,
|
| 1389 |
+
1.6455078125,
|
| 1390 |
+
1.783203125,
|
| 1391 |
+
1.58984375,
|
| 1392 |
+
1.734375,
|
| 1393 |
+
1.548828125,
|
| 1394 |
+
1.51953125,
|
| 1395 |
+
1.8203125,
|
| 1396 |
+
1.615234375,
|
| 1397 |
+
1.6044921875,
|
| 1398 |
+
1.673828125,
|
| 1399 |
+
1.6953125,
|
| 1400 |
+
1.771484375,
|
| 1401 |
+
1.6455078125,
|
| 1402 |
+
1.5439453125,
|
| 1403 |
+
1.73828125,
|
| 1404 |
+
1.7119140625,
|
| 1405 |
+
1.58203125,
|
| 1406 |
+
1.61328125,
|
| 1407 |
+
1.7109375,
|
| 1408 |
+
1.751953125,
|
| 1409 |
+
1.2490234375,
|
| 1410 |
+
1.529296875,
|
| 1411 |
+
1.48828125,
|
| 1412 |
+
1.7431640625,
|
| 1413 |
+
1.5986328125,
|
| 1414 |
+
1.6796875,
|
| 1415 |
+
1.6787109375,
|
| 1416 |
+
1.521484375,
|
| 1417 |
+
1.6875,
|
| 1418 |
+
1.716796875,
|
| 1419 |
+
1.5546875,
|
| 1420 |
+
1.8046875,
|
| 1421 |
+
1.7626953125,
|
| 1422 |
+
1.7099609375,
|
| 1423 |
+
1.60546875,
|
| 1424 |
+
1.6904296875,
|
| 1425 |
+
1.65234375,
|
| 1426 |
+
1.5693359375,
|
| 1427 |
+
1.791015625,
|
| 1428 |
+
1.5048828125,
|
| 1429 |
+
1.673828125,
|
| 1430 |
+
1.576171875,
|
| 1431 |
+
1.763671875,
|
| 1432 |
+
1.677734375,
|
| 1433 |
+
1.412109375,
|
| 1434 |
+
1.7265625,
|
| 1435 |
+
1.634765625,
|
| 1436 |
+
1.5732421875,
|
| 1437 |
+
1.5703125,
|
| 1438 |
+
1.5849609375,
|
| 1439 |
+
1.611328125,
|
| 1440 |
+
1.5732421875,
|
| 1441 |
+
1.6640625,
|
| 1442 |
+
1.6318359375,
|
| 1443 |
+
1.615234375,
|
| 1444 |
+
1.677734375,
|
| 1445 |
+
1.67578125,
|
| 1446 |
+
1.6142578125,
|
| 1447 |
+
1.62890625,
|
| 1448 |
+
1.611328125,
|
| 1449 |
+
1.634765625,
|
| 1450 |
+
1.4755859375,
|
| 1451 |
+
1.537109375,
|
| 1452 |
+
1.634765625,
|
| 1453 |
+
1.443359375,
|
| 1454 |
+
1.720703125,
|
| 1455 |
+
1.5390625,
|
| 1456 |
+
1.55078125,
|
| 1457 |
+
1.6884765625,
|
| 1458 |
+
1.4248046875,
|
| 1459 |
+
1.6201171875,
|
| 1460 |
+
1.681640625,
|
| 1461 |
+
1.623046875,
|
| 1462 |
+
1.5947265625,
|
| 1463 |
+
1.5703125,
|
| 1464 |
+
1.4716796875,
|
| 1465 |
+
1.5986328125,
|
| 1466 |
+
1.7255859375,
|
| 1467 |
+
1.505859375,
|
| 1468 |
+
1.6982421875,
|
| 1469 |
+
1.677734375,
|
| 1470 |
+
1.6962890625,
|
| 1471 |
+
1.662109375,
|
| 1472 |
+
1.677734375,
|
| 1473 |
+
1.6044921875,
|
| 1474 |
+
1.77734375,
|
| 1475 |
+
1.71484375,
|
| 1476 |
+
1.634765625,
|
| 1477 |
+
1.677734375,
|
| 1478 |
+
1.560546875,
|
| 1479 |
+
1.6865234375,
|
| 1480 |
+
1.5986328125,
|
| 1481 |
+
1.6767578125,
|
| 1482 |
+
1.564453125,
|
| 1483 |
+
1.7138671875,
|
| 1484 |
+
1.70703125,
|
| 1485 |
+
1.6298828125,
|
| 1486 |
+
1.583984375,
|
| 1487 |
+
1.462890625,
|
| 1488 |
+
1.681640625,
|
| 1489 |
+
1.515625,
|
| 1490 |
+
1.5625,
|
| 1491 |
+
1.70703125,
|
| 1492 |
+
1.74609375,
|
| 1493 |
+
1.783203125,
|
| 1494 |
+
1.6572265625,
|
| 1495 |
+
1.65234375,
|
| 1496 |
+
1.60546875,
|
| 1497 |
+
1.58203125,
|
| 1498 |
+
1.4873046875,
|
| 1499 |
+
1.6689453125,
|
| 1500 |
+
1.619140625,
|
| 1501 |
+
1.65625,
|
| 1502 |
+
1.6572265625
|
| 1503 |
+
],
|
| 1504 |
+
"sparsity_losses": [
|
| 1505 |
+
0.6328125,
|
| 1506 |
+
0.7421875,
|
| 1507 |
+
1.294921875,
|
| 1508 |
+
1.56640625,
|
| 1509 |
+
1.5380859375,
|
| 1510 |
+
1.6962890625,
|
| 1511 |
+
2.01171875,
|
| 1512 |
+
2.505859375,
|
| 1513 |
+
2.822265625,
|
| 1514 |
+
3.525390625,
|
| 1515 |
+
3.46875,
|
| 1516 |
+
3.8359375,
|
| 1517 |
+
3.884765625,
|
| 1518 |
+
3.958984375,
|
| 1519 |
+
4.16015625,
|
| 1520 |
+
3.935546875,
|
| 1521 |
+
4.234375,
|
| 1522 |
+
4.1796875,
|
| 1523 |
+
4.09375,
|
| 1524 |
+
3.986328125,
|
| 1525 |
+
3.931640625,
|
| 1526 |
+
4.05859375,
|
| 1527 |
+
4.1484375,
|
| 1528 |
+
4.09375,
|
| 1529 |
+
3.98828125,
|
| 1530 |
+
4.078125,
|
| 1531 |
+
4.0390625,
|
| 1532 |
+
4.265625,
|
| 1533 |
+
4.140625,
|
| 1534 |
+
4.296875,
|
| 1535 |
+
4.26953125,
|
| 1536 |
+
4.2265625,
|
| 1537 |
+
4.28125,
|
| 1538 |
+
4.19921875,
|
| 1539 |
+
4.1875,
|
| 1540 |
+
4.26953125,
|
| 1541 |
+
4.3359375,
|
| 1542 |
+
4.31640625,
|
| 1543 |
+
4.3046875,
|
| 1544 |
+
4.5078125,
|
| 1545 |
+
4.3203125,
|
| 1546 |
+
4.3671875,
|
| 1547 |
+
4.21484375,
|
| 1548 |
+
4.3046875,
|
| 1549 |
+
4.3046875,
|
| 1550 |
+
4.3125,
|
| 1551 |
+
4.55859375,
|
| 1552 |
+
4.3203125,
|
| 1553 |
+
4.359375,
|
| 1554 |
+
4.484375,
|
| 1555 |
+
4.625,
|
| 1556 |
+
4.484375,
|
| 1557 |
+
4.50390625,
|
| 1558 |
+
4.5,
|
| 1559 |
+
4.61328125,
|
| 1560 |
+
4.54296875,
|
| 1561 |
+
4.58984375,
|
| 1562 |
+
4.5078125,
|
| 1563 |
+
4.53125,
|
| 1564 |
+
4.59375,
|
| 1565 |
+
4.54296875,
|
| 1566 |
+
4.6640625,
|
| 1567 |
+
4.53125,
|
| 1568 |
+
4.65234375,
|
| 1569 |
+
4.48046875,
|
| 1570 |
+
4.66796875,
|
| 1571 |
+
4.6640625,
|
| 1572 |
+
4.65625,
|
| 1573 |
+
4.78125,
|
| 1574 |
+
4.671875,
|
| 1575 |
+
4.6640625,
|
| 1576 |
+
4.73828125,
|
| 1577 |
+
4.7578125,
|
| 1578 |
+
4.7734375,
|
| 1579 |
+
4.828125,
|
| 1580 |
+
4.81640625,
|
| 1581 |
+
4.7265625,
|
| 1582 |
+
4.59375,
|
| 1583 |
+
4.75390625,
|
| 1584 |
+
4.72265625,
|
| 1585 |
+
4.62890625,
|
| 1586 |
+
4.8203125,
|
| 1587 |
+
4.74609375,
|
| 1588 |
+
4.94921875,
|
| 1589 |
+
4.8984375,
|
| 1590 |
+
4.79296875,
|
| 1591 |
+
4.58203125,
|
| 1592 |
+
4.76171875,
|
| 1593 |
+
4.9921875,
|
| 1594 |
+
5.09765625,
|
| 1595 |
+
4.796875,
|
| 1596 |
+
4.85546875,
|
| 1597 |
+
4.9140625,
|
| 1598 |
+
4.84765625,
|
| 1599 |
+
5.140625,
|
| 1600 |
+
4.90625,
|
| 1601 |
+
5.0390625,
|
| 1602 |
+
4.9453125,
|
| 1603 |
+
5.13671875,
|
| 1604 |
+
5.1171875,
|
| 1605 |
+
5.3046875,
|
| 1606 |
+
4.94921875,
|
| 1607 |
+
5.02734375,
|
| 1608 |
+
4.875,
|
| 1609 |
+
5.01953125,
|
| 1610 |
+
5.12890625,
|
| 1611 |
+
5.171875,
|
| 1612 |
+
5.18359375,
|
| 1613 |
+
5.234375,
|
| 1614 |
+
5.30859375,
|
| 1615 |
+
5.23828125,
|
| 1616 |
+
5.265625,
|
| 1617 |
+
5.3203125,
|
| 1618 |
+
5.390625,
|
| 1619 |
+
5.4140625,
|
| 1620 |
+
5.29296875,
|
| 1621 |
+
5.3515625,
|
| 1622 |
+
5.24609375,
|
| 1623 |
+
5.39453125,
|
| 1624 |
+
5.28125,
|
| 1625 |
+
5.46875,
|
| 1626 |
+
5.4375,
|
| 1627 |
+
5.41015625,
|
| 1628 |
+
5.4296875,
|
| 1629 |
+
5.37890625,
|
| 1630 |
+
5.44140625,
|
| 1631 |
+
5.36328125,
|
| 1632 |
+
5.5078125,
|
| 1633 |
+
5.34375,
|
| 1634 |
+
5.28515625,
|
| 1635 |
+
5.47265625,
|
| 1636 |
+
5.4921875,
|
| 1637 |
+
5.5390625,
|
| 1638 |
+
5.36328125,
|
| 1639 |
+
5.5078125,
|
| 1640 |
+
5.421875,
|
| 1641 |
+
5.578125,
|
| 1642 |
+
5.484375,
|
| 1643 |
+
5.60546875,
|
| 1644 |
+
5.6953125,
|
| 1645 |
+
5.5234375,
|
| 1646 |
+
5.5390625,
|
| 1647 |
+
5.62890625,
|
| 1648 |
+
5.58203125,
|
| 1649 |
+
5.79296875,
|
| 1650 |
+
5.76171875,
|
| 1651 |
+
5.734375,
|
| 1652 |
+
5.6328125,
|
| 1653 |
+
5.4921875,
|
| 1654 |
+
5.734375,
|
| 1655 |
+
5.87109375,
|
| 1656 |
+
5.54296875,
|
| 1657 |
+
5.73046875,
|
| 1658 |
+
5.7265625,
|
| 1659 |
+
5.72265625,
|
| 1660 |
+
5.8359375,
|
| 1661 |
+
5.6875,
|
| 1662 |
+
5.6953125,
|
| 1663 |
+
5.92578125,
|
| 1664 |
+
5.9296875,
|
| 1665 |
+
5.9296875,
|
| 1666 |
+
5.6328125,
|
| 1667 |
+
5.96484375,
|
| 1668 |
+
5.8125,
|
| 1669 |
+
5.9765625,
|
| 1670 |
+
5.875,
|
| 1671 |
+
5.984375,
|
| 1672 |
+
6.0,
|
| 1673 |
+
5.96875,
|
| 1674 |
+
5.8046875,
|
| 1675 |
+
5.8359375,
|
| 1676 |
+
6.0,
|
| 1677 |
+
6.08984375,
|
| 1678 |
+
6.03125,
|
| 1679 |
+
6.2265625,
|
| 1680 |
+
6.08984375,
|
| 1681 |
+
6.06640625,
|
| 1682 |
+
6.08984375,
|
| 1683 |
+
6.0546875,
|
| 1684 |
+
6.05859375,
|
| 1685 |
+
6.2265625,
|
| 1686 |
+
6.1015625,
|
| 1687 |
+
6.203125,
|
| 1688 |
+
6.1171875,
|
| 1689 |
+
6.1328125,
|
| 1690 |
+
6.296875,
|
| 1691 |
+
6.203125,
|
| 1692 |
+
6.2265625,
|
| 1693 |
+
6.078125,
|
| 1694 |
+
6.20703125,
|
| 1695 |
+
6.28125,
|
| 1696 |
+
6.3671875,
|
| 1697 |
+
6.375,
|
| 1698 |
+
6.24609375,
|
| 1699 |
+
6.3671875,
|
| 1700 |
+
6.34375,
|
| 1701 |
+
6.375,
|
| 1702 |
+
6.30859375,
|
| 1703 |
+
6.3046875,
|
| 1704 |
+
6.44140625,
|
| 1705 |
+
6.5703125,
|
| 1706 |
+
6.51953125,
|
| 1707 |
+
6.43359375,
|
| 1708 |
+
6.5390625,
|
| 1709 |
+
6.57421875,
|
| 1710 |
+
6.89453125,
|
| 1711 |
+
6.6875,
|
| 1712 |
+
6.71875,
|
| 1713 |
+
6.80859375,
|
| 1714 |
+
6.62890625,
|
| 1715 |
+
6.640625,
|
| 1716 |
+
6.640625,
|
| 1717 |
+
6.71875,
|
| 1718 |
+
6.640625,
|
| 1719 |
+
6.75390625,
|
| 1720 |
+
6.63671875,
|
| 1721 |
+
6.59765625,
|
| 1722 |
+
6.5,
|
| 1723 |
+
6.84375,
|
| 1724 |
+
6.8125,
|
| 1725 |
+
6.7734375,
|
| 1726 |
+
6.796875,
|
| 1727 |
+
6.640625,
|
| 1728 |
+
6.875,
|
| 1729 |
+
6.671875,
|
| 1730 |
+
6.75390625,
|
| 1731 |
+
6.73828125,
|
| 1732 |
+
6.921875,
|
| 1733 |
+
6.85546875,
|
| 1734 |
+
6.9921875,
|
| 1735 |
+
6.9296875,
|
| 1736 |
+
6.6796875,
|
| 1737 |
+
7.0859375,
|
| 1738 |
+
7.03125,
|
| 1739 |
+
6.9140625,
|
| 1740 |
+
6.796875,
|
| 1741 |
+
6.9765625,
|
| 1742 |
+
6.87890625,
|
| 1743 |
+
6.9375,
|
| 1744 |
+
7.0234375,
|
| 1745 |
+
6.93359375,
|
| 1746 |
+
7.03515625,
|
| 1747 |
+
6.98828125,
|
| 1748 |
+
7.0546875,
|
| 1749 |
+
7.265625,
|
| 1750 |
+
6.8515625,
|
| 1751 |
+
6.9375,
|
| 1752 |
+
7.2109375,
|
| 1753 |
+
7.05078125,
|
| 1754 |
+
7.0546875,
|
| 1755 |
+
6.98828125,
|
| 1756 |
+
7.171875,
|
| 1757 |
+
7.140625,
|
| 1758 |
+
7.359375,
|
| 1759 |
+
7.109375,
|
| 1760 |
+
7.12109375,
|
| 1761 |
+
7.33984375,
|
| 1762 |
+
7.2890625,
|
| 1763 |
+
7.26171875,
|
| 1764 |
+
7.0703125,
|
| 1765 |
+
7.2421875,
|
| 1766 |
+
6.99609375,
|
| 1767 |
+
7.0625,
|
| 1768 |
+
7.34765625,
|
| 1769 |
+
7.48046875,
|
| 1770 |
+
7.31640625,
|
| 1771 |
+
7.265625,
|
| 1772 |
+
7.3125,
|
| 1773 |
+
7.26171875,
|
| 1774 |
+
7.1640625,
|
| 1775 |
+
7.30078125,
|
| 1776 |
+
7.234375,
|
| 1777 |
+
7.203125,
|
| 1778 |
+
7.5,
|
| 1779 |
+
7.34375,
|
| 1780 |
+
7.515625,
|
| 1781 |
+
7.375,
|
| 1782 |
+
7.4609375,
|
| 1783 |
+
7.48046875,
|
| 1784 |
+
7.359375,
|
| 1785 |
+
7.51171875,
|
| 1786 |
+
7.48828125,
|
| 1787 |
+
7.5,
|
| 1788 |
+
7.59375,
|
| 1789 |
+
7.765625,
|
| 1790 |
+
7.671875,
|
| 1791 |
+
7.6875,
|
| 1792 |
+
7.671875,
|
| 1793 |
+
7.65625,
|
| 1794 |
+
7.609375,
|
| 1795 |
+
7.8359375,
|
| 1796 |
+
7.62109375,
|
| 1797 |
+
7.5078125,
|
| 1798 |
+
7.5234375,
|
| 1799 |
+
7.546875,
|
| 1800 |
+
7.65625,
|
| 1801 |
+
7.75,
|
| 1802 |
+
7.84375,
|
| 1803 |
+
7.7265625,
|
| 1804 |
+
7.6875,
|
| 1805 |
+
7.78125,
|
| 1806 |
+
7.7734375,
|
| 1807 |
+
7.484375,
|
| 1808 |
+
7.81640625,
|
| 1809 |
+
7.5390625,
|
| 1810 |
+
7.9609375,
|
| 1811 |
+
7.69140625,
|
| 1812 |
+
7.72265625,
|
| 1813 |
+
7.765625,
|
| 1814 |
+
7.875,
|
| 1815 |
+
7.7421875,
|
| 1816 |
+
7.88671875,
|
| 1817 |
+
7.9453125,
|
| 1818 |
+
7.73046875,
|
| 1819 |
+
7.99609375,
|
| 1820 |
+
8.046875,
|
| 1821 |
+
7.94921875,
|
| 1822 |
+
7.9375,
|
| 1823 |
+
7.71875,
|
| 1824 |
+
7.7578125,
|
| 1825 |
+
7.8125,
|
| 1826 |
+
8.0234375,
|
| 1827 |
+
8.03125,
|
| 1828 |
+
8.03125,
|
| 1829 |
+
8.0859375,
|
| 1830 |
+
8.0546875,
|
| 1831 |
+
7.97265625,
|
| 1832 |
+
7.92578125,
|
| 1833 |
+
8.140625,
|
| 1834 |
+
8.0703125,
|
| 1835 |
+
8.3203125,
|
| 1836 |
+
8.1640625,
|
| 1837 |
+
8.140625,
|
| 1838 |
+
8.0703125,
|
| 1839 |
+
8.359375,
|
| 1840 |
+
8.3125,
|
| 1841 |
+
8.390625,
|
| 1842 |
+
8.25,
|
| 1843 |
+
8.25,
|
| 1844 |
+
8.2109375,
|
| 1845 |
+
8.34375,
|
| 1846 |
+
8.0625,
|
| 1847 |
+
8.578125,
|
| 1848 |
+
8.2265625,
|
| 1849 |
+
8.453125,
|
| 1850 |
+
8.421875,
|
| 1851 |
+
8.5,
|
| 1852 |
+
8.59375,
|
| 1853 |
+
8.3203125,
|
| 1854 |
+
8.6171875,
|
| 1855 |
+
8.390625,
|
| 1856 |
+
8.4375,
|
| 1857 |
+
8.46875,
|
| 1858 |
+
8.296875,
|
| 1859 |
+
8.4765625,
|
| 1860 |
+
8.578125,
|
| 1861 |
+
8.3359375,
|
| 1862 |
+
8.453125,
|
| 1863 |
+
8.515625,
|
| 1864 |
+
8.59375,
|
| 1865 |
+
8.5859375,
|
| 1866 |
+
8.71875,
|
| 1867 |
+
8.65625,
|
| 1868 |
+
8.7421875,
|
| 1869 |
+
8.5703125,
|
| 1870 |
+
8.4296875,
|
| 1871 |
+
8.765625,
|
| 1872 |
+
8.46875,
|
| 1873 |
+
8.8515625,
|
| 1874 |
+
8.8125,
|
| 1875 |
+
8.953125,
|
| 1876 |
+
8.7265625,
|
| 1877 |
+
8.890625,
|
| 1878 |
+
8.5703125,
|
| 1879 |
+
8.828125,
|
| 1880 |
+
8.734375,
|
| 1881 |
+
8.734375,
|
| 1882 |
+
8.6875,
|
| 1883 |
+
8.7578125,
|
| 1884 |
+
8.9375,
|
| 1885 |
+
8.84375,
|
| 1886 |
+
8.78125,
|
| 1887 |
+
8.75,
|
| 1888 |
+
8.9765625,
|
| 1889 |
+
8.9375,
|
| 1890 |
+
8.8359375,
|
| 1891 |
+
8.921875,
|
| 1892 |
+
8.9921875,
|
| 1893 |
+
8.671875,
|
| 1894 |
+
8.953125,
|
| 1895 |
+
8.8828125,
|
| 1896 |
+
9.0546875,
|
| 1897 |
+
8.9609375,
|
| 1898 |
+
9.1640625,
|
| 1899 |
+
9.140625,
|
| 1900 |
+
8.96875,
|
| 1901 |
+
9.09375,
|
| 1902 |
+
9.2265625,
|
| 1903 |
+
9.2578125,
|
| 1904 |
+
9.125,
|
| 1905 |
+
9.2890625,
|
| 1906 |
+
9.078125,
|
| 1907 |
+
9.3203125,
|
| 1908 |
+
9.109375,
|
| 1909 |
+
9.2890625,
|
| 1910 |
+
8.921875,
|
| 1911 |
+
9.1328125,
|
| 1912 |
+
9.25,
|
| 1913 |
+
9.09375,
|
| 1914 |
+
8.9375,
|
| 1915 |
+
9.1328125,
|
| 1916 |
+
9.234375,
|
| 1917 |
+
9.1875,
|
| 1918 |
+
9.4765625,
|
| 1919 |
+
9.1640625,
|
| 1920 |
+
9.1796875,
|
| 1921 |
+
9.25,
|
| 1922 |
+
8.90625,
|
| 1923 |
+
9.09375,
|
| 1924 |
+
9.46875,
|
| 1925 |
+
9.25,
|
| 1926 |
+
9.1875,
|
| 1927 |
+
9.359375,
|
| 1928 |
+
9.328125,
|
| 1929 |
+
9.46875,
|
| 1930 |
+
9.3125,
|
| 1931 |
+
9.640625,
|
| 1932 |
+
9.34375,
|
| 1933 |
+
9.53125,
|
| 1934 |
+
9.4609375,
|
| 1935 |
+
9.4921875,
|
| 1936 |
+
9.25,
|
| 1937 |
+
9.359375,
|
| 1938 |
+
9.3203125,
|
| 1939 |
+
9.578125,
|
| 1940 |
+
9.46875,
|
| 1941 |
+
9.546875,
|
| 1942 |
+
9.5078125,
|
| 1943 |
+
9.6796875,
|
| 1944 |
+
9.7421875,
|
| 1945 |
+
9.328125,
|
| 1946 |
+
9.6015625,
|
| 1947 |
+
9.703125,
|
| 1948 |
+
9.4375,
|
| 1949 |
+
9.578125,
|
| 1950 |
+
9.625,
|
| 1951 |
+
9.6875,
|
| 1952 |
+
9.5859375,
|
| 1953 |
+
9.640625,
|
| 1954 |
+
9.7578125,
|
| 1955 |
+
9.78125,
|
| 1956 |
+
9.765625,
|
| 1957 |
+
9.8515625,
|
| 1958 |
+
9.65625,
|
| 1959 |
+
9.734375,
|
| 1960 |
+
9.5625,
|
| 1961 |
+
9.8515625,
|
| 1962 |
+
9.6640625,
|
| 1963 |
+
9.5859375,
|
| 1964 |
+
9.828125,
|
| 1965 |
+
9.8203125,
|
| 1966 |
+
9.9375,
|
| 1967 |
+
10.125,
|
| 1968 |
+
9.859375,
|
| 1969 |
+
9.9375,
|
| 1970 |
+
10.28125,
|
| 1971 |
+
10.0078125,
|
| 1972 |
+
10.1953125,
|
| 1973 |
+
10.1875,
|
| 1974 |
+
10.046875,
|
| 1975 |
+
10.0546875,
|
| 1976 |
+
9.875,
|
| 1977 |
+
9.765625,
|
| 1978 |
+
9.875,
|
| 1979 |
+
10.234375,
|
| 1980 |
+
10.125,
|
| 1981 |
+
10.0078125,
|
| 1982 |
+
10.0,
|
| 1983 |
+
10.1953125,
|
| 1984 |
+
10.078125,
|
| 1985 |
+
10.2734375,
|
| 1986 |
+
10.265625,
|
| 1987 |
+
9.9375,
|
| 1988 |
+
10.0234375,
|
| 1989 |
+
10.3046875,
|
| 1990 |
+
10.1640625,
|
| 1991 |
+
10.3125,
|
| 1992 |
+
10.015625,
|
| 1993 |
+
10.09375,
|
| 1994 |
+
10.0703125,
|
| 1995 |
+
10.1171875,
|
| 1996 |
+
10.171875,
|
| 1997 |
+
10.484375,
|
| 1998 |
+
10.2578125,
|
| 1999 |
+
10.0234375,
|
| 2000 |
+
10.359375,
|
| 2001 |
+
10.109375,
|
| 2002 |
+
10.390625,
|
| 2003 |
+
10.3671875,
|
| 2004 |
+
10.4765625,
|
| 2005 |
+
10.28125,
|
| 2006 |
+
10.2890625,
|
| 2007 |
+
10.453125,
|
| 2008 |
+
10.2890625,
|
| 2009 |
+
10.46875,
|
| 2010 |
+
10.40625,
|
| 2011 |
+
10.546875,
|
| 2012 |
+
10.46875,
|
| 2013 |
+
10.1953125,
|
| 2014 |
+
10.390625,
|
| 2015 |
+
10.46875,
|
| 2016 |
+
10.375,
|
| 2017 |
+
10.4375,
|
| 2018 |
+
10.3125,
|
| 2019 |
+
10.4453125,
|
| 2020 |
+
10.40625,
|
| 2021 |
+
10.84375,
|
| 2022 |
+
10.8125,
|
| 2023 |
+
10.6484375,
|
| 2024 |
+
10.8984375,
|
| 2025 |
+
10.65625,
|
| 2026 |
+
10.7578125,
|
| 2027 |
+
10.6875,
|
| 2028 |
+
10.625,
|
| 2029 |
+
10.515625,
|
| 2030 |
+
10.765625,
|
| 2031 |
+
10.4375,
|
| 2032 |
+
10.609375,
|
| 2033 |
+
10.875,
|
| 2034 |
+
10.921875,
|
| 2035 |
+
10.796875,
|
| 2036 |
+
10.8359375,
|
| 2037 |
+
10.765625,
|
| 2038 |
+
10.8984375,
|
| 2039 |
+
10.921875,
|
| 2040 |
+
10.75,
|
| 2041 |
+
10.6875,
|
| 2042 |
+
10.7265625,
|
| 2043 |
+
11.015625,
|
| 2044 |
+
10.8046875,
|
| 2045 |
+
10.640625,
|
| 2046 |
+
10.890625,
|
| 2047 |
+
10.6640625,
|
| 2048 |
+
11.03125,
|
| 2049 |
+
10.8359375,
|
| 2050 |
+
11.0859375,
|
| 2051 |
+
10.96875,
|
| 2052 |
+
10.984375,
|
| 2053 |
+
10.9375,
|
| 2054 |
+
11.265625,
|
| 2055 |
+
10.96875,
|
| 2056 |
+
10.9453125,
|
| 2057 |
+
11.0625,
|
| 2058 |
+
11.1328125,
|
| 2059 |
+
11.0859375,
|
| 2060 |
+
11.0234375,
|
| 2061 |
+
11.0234375,
|
| 2062 |
+
11.09375,
|
| 2063 |
+
11.0234375,
|
| 2064 |
+
11.09375,
|
| 2065 |
+
11.0546875,
|
| 2066 |
+
11.2265625,
|
| 2067 |
+
10.9296875,
|
| 2068 |
+
10.84375,
|
| 2069 |
+
11.203125,
|
| 2070 |
+
10.9375,
|
| 2071 |
+
11.40625,
|
| 2072 |
+
11.046875,
|
| 2073 |
+
11.2265625,
|
| 2074 |
+
11.1328125,
|
| 2075 |
+
11.1171875,
|
| 2076 |
+
11.0546875,
|
| 2077 |
+
11.28125,
|
| 2078 |
+
11.0703125,
|
| 2079 |
+
11.140625,
|
| 2080 |
+
11.515625,
|
| 2081 |
+
10.8359375,
|
| 2082 |
+
11.328125,
|
| 2083 |
+
11.546875,
|
| 2084 |
+
11.3203125,
|
| 2085 |
+
11.109375,
|
| 2086 |
+
11.390625,
|
| 2087 |
+
11.4140625,
|
| 2088 |
+
11.5546875,
|
| 2089 |
+
11.5546875,
|
| 2090 |
+
11.515625,
|
| 2091 |
+
11.390625,
|
| 2092 |
+
11.125,
|
| 2093 |
+
11.7890625,
|
| 2094 |
+
11.4375,
|
| 2095 |
+
11.6015625,
|
| 2096 |
+
11.359375,
|
| 2097 |
+
11.5390625,
|
| 2098 |
+
11.6953125,
|
| 2099 |
+
11.546875,
|
| 2100 |
+
11.2265625,
|
| 2101 |
+
11.3984375,
|
| 2102 |
+
11.984375,
|
| 2103 |
+
11.71875,
|
| 2104 |
+
11.625,
|
| 2105 |
+
11.6171875,
|
| 2106 |
+
11.7734375,
|
| 2107 |
+
11.46875,
|
| 2108 |
+
11.671875,
|
| 2109 |
+
11.5390625,
|
| 2110 |
+
11.71875,
|
| 2111 |
+
11.6484375,
|
| 2112 |
+
11.7265625,
|
| 2113 |
+
11.5859375,
|
| 2114 |
+
11.6875,
|
| 2115 |
+
11.84375,
|
| 2116 |
+
11.9765625,
|
| 2117 |
+
11.640625,
|
| 2118 |
+
11.625,
|
| 2119 |
+
11.765625,
|
| 2120 |
+
11.6796875,
|
| 2121 |
+
11.8125,
|
| 2122 |
+
11.8046875,
|
| 2123 |
+
11.9296875,
|
| 2124 |
+
11.921875,
|
| 2125 |
+
11.59375,
|
| 2126 |
+
11.8515625,
|
| 2127 |
+
11.84375,
|
| 2128 |
+
11.7421875,
|
| 2129 |
+
12.0078125,
|
| 2130 |
+
12.09375,
|
| 2131 |
+
11.9375,
|
| 2132 |
+
11.7890625,
|
| 2133 |
+
11.6953125,
|
| 2134 |
+
11.765625,
|
| 2135 |
+
12.15625,
|
| 2136 |
+
11.9609375,
|
| 2137 |
+
12.03125,
|
| 2138 |
+
12.2421875,
|
| 2139 |
+
12.0,
|
| 2140 |
+
11.828125,
|
| 2141 |
+
12.0625,
|
| 2142 |
+
11.921875,
|
| 2143 |
+
12.15625,
|
| 2144 |
+
12.1484375,
|
| 2145 |
+
11.78125,
|
| 2146 |
+
12.265625,
|
| 2147 |
+
12.328125,
|
| 2148 |
+
11.84375,
|
| 2149 |
+
12.125,
|
| 2150 |
+
12.1484375,
|
| 2151 |
+
12.2109375,
|
| 2152 |
+
12.4375,
|
| 2153 |
+
11.984375,
|
| 2154 |
+
12.1328125,
|
| 2155 |
+
12.2265625,
|
| 2156 |
+
12.09375,
|
| 2157 |
+
12.484375,
|
| 2158 |
+
12.046875,
|
| 2159 |
+
12.53125,
|
| 2160 |
+
12.0078125,
|
| 2161 |
+
12.453125,
|
| 2162 |
+
12.3125,
|
| 2163 |
+
12.3203125,
|
| 2164 |
+
12.0546875,
|
| 2165 |
+
12.25,
|
| 2166 |
+
12.2421875,
|
| 2167 |
+
12.3671875,
|
| 2168 |
+
12.28125,
|
| 2169 |
+
12.3671875,
|
| 2170 |
+
12.1171875,
|
| 2171 |
+
12.296875,
|
| 2172 |
+
12.0390625,
|
| 2173 |
+
12.640625,
|
| 2174 |
+
12.1015625,
|
| 2175 |
+
11.9921875,
|
| 2176 |
+
12.6953125,
|
| 2177 |
+
12.28125,
|
| 2178 |
+
12.4140625,
|
| 2179 |
+
12.203125,
|
| 2180 |
+
12.2421875,
|
| 2181 |
+
12.0703125,
|
| 2182 |
+
12.1640625,
|
| 2183 |
+
12.5234375,
|
| 2184 |
+
12.15625,
|
| 2185 |
+
12.4375,
|
| 2186 |
+
12.90625,
|
| 2187 |
+
12.7109375,
|
| 2188 |
+
12.3515625,
|
| 2189 |
+
12.4375,
|
| 2190 |
+
12.546875,
|
| 2191 |
+
12.703125,
|
| 2192 |
+
12.5078125,
|
| 2193 |
+
12.78125,
|
| 2194 |
+
12.453125,
|
| 2195 |
+
12.59375,
|
| 2196 |
+
12.6328125,
|
| 2197 |
+
12.5625,
|
| 2198 |
+
12.890625,
|
| 2199 |
+
12.7734375,
|
| 2200 |
+
12.59375,
|
| 2201 |
+
12.765625,
|
| 2202 |
+
13.0078125,
|
| 2203 |
+
12.90625,
|
| 2204 |
+
13.109375,
|
| 2205 |
+
13.1875,
|
| 2206 |
+
12.6015625,
|
| 2207 |
+
13.03125,
|
| 2208 |
+
12.9296875,
|
| 2209 |
+
12.875,
|
| 2210 |
+
12.875,
|
| 2211 |
+
12.9375,
|
| 2212 |
+
12.71875,
|
| 2213 |
+
13.0234375,
|
| 2214 |
+
12.8984375,
|
| 2215 |
+
13.046875,
|
| 2216 |
+
13.0546875,
|
| 2217 |
+
13.015625,
|
| 2218 |
+
12.90625,
|
| 2219 |
+
12.7578125,
|
| 2220 |
+
12.828125,
|
| 2221 |
+
13.2890625,
|
| 2222 |
+
13.1953125,
|
| 2223 |
+
13.2734375,
|
| 2224 |
+
13.1796875,
|
| 2225 |
+
12.9375,
|
| 2226 |
+
13.265625,
|
| 2227 |
+
13.109375,
|
| 2228 |
+
13.3671875,
|
| 2229 |
+
12.8671875,
|
| 2230 |
+
13.0625,
|
| 2231 |
+
13.21875,
|
| 2232 |
+
13.0078125,
|
| 2233 |
+
13.109375,
|
| 2234 |
+
12.859375,
|
| 2235 |
+
13.171875,
|
| 2236 |
+
12.6875,
|
| 2237 |
+
13.2890625,
|
| 2238 |
+
13.46875,
|
| 2239 |
+
12.75,
|
| 2240 |
+
13.046875,
|
| 2241 |
+
13.65625,
|
| 2242 |
+
13.46875,
|
| 2243 |
+
13.328125,
|
| 2244 |
+
13.3359375,
|
| 2245 |
+
13.515625,
|
| 2246 |
+
13.40625,
|
| 2247 |
+
13.609375,
|
| 2248 |
+
13.3046875,
|
| 2249 |
+
13.5390625,
|
| 2250 |
+
13.8046875,
|
| 2251 |
+
13.4296875,
|
| 2252 |
+
13.234375,
|
| 2253 |
+
13.625,
|
| 2254 |
+
13.6875,
|
| 2255 |
+
13.46875,
|
| 2256 |
+
13.703125,
|
| 2257 |
+
13.703125,
|
| 2258 |
+
13.359375,
|
| 2259 |
+
13.6015625,
|
| 2260 |
+
13.7734375,
|
| 2261 |
+
13.515625,
|
| 2262 |
+
13.4375,
|
| 2263 |
+
13.875,
|
| 2264 |
+
13.5234375,
|
| 2265 |
+
13.890625,
|
| 2266 |
+
13.7109375,
|
| 2267 |
+
13.5859375,
|
| 2268 |
+
13.578125,
|
| 2269 |
+
13.09375,
|
| 2270 |
+
13.578125,
|
| 2271 |
+
13.9375,
|
| 2272 |
+
13.625,
|
| 2273 |
+
13.7265625,
|
| 2274 |
+
13.9140625,
|
| 2275 |
+
13.28125,
|
| 2276 |
+
13.6484375,
|
| 2277 |
+
13.8046875,
|
| 2278 |
+
13.96875,
|
| 2279 |
+
13.84375,
|
| 2280 |
+
13.6640625,
|
| 2281 |
+
13.53125,
|
| 2282 |
+
13.96875,
|
| 2283 |
+
13.6875,
|
| 2284 |
+
13.9921875,
|
| 2285 |
+
13.7265625,
|
| 2286 |
+
13.75,
|
| 2287 |
+
13.9375,
|
| 2288 |
+
14.0078125,
|
| 2289 |
+
14.0078125,
|
| 2290 |
+
13.8515625,
|
| 2291 |
+
14.1015625,
|
| 2292 |
+
13.78125,
|
| 2293 |
+
13.7734375,
|
| 2294 |
+
14.1015625,
|
| 2295 |
+
13.8046875,
|
| 2296 |
+
14.015625,
|
| 2297 |
+
13.828125,
|
| 2298 |
+
13.671875,
|
| 2299 |
+
14.0625,
|
| 2300 |
+
14.078125,
|
| 2301 |
+
13.515625,
|
| 2302 |
+
13.84375,
|
| 2303 |
+
14.0390625,
|
| 2304 |
+
14.046875,
|
| 2305 |
+
14.0625,
|
| 2306 |
+
13.953125,
|
| 2307 |
+
13.8125,
|
| 2308 |
+
14.1484375,
|
| 2309 |
+
14.09375,
|
| 2310 |
+
14.2109375,
|
| 2311 |
+
14.34375,
|
| 2312 |
+
14.015625,
|
| 2313 |
+
13.9140625,
|
| 2314 |
+
14.03125,
|
| 2315 |
+
14.265625,
|
| 2316 |
+
14.0234375,
|
| 2317 |
+
13.9921875,
|
| 2318 |
+
14.484375,
|
| 2319 |
+
14.359375,
|
| 2320 |
+
14.265625,
|
| 2321 |
+
13.84375,
|
| 2322 |
+
14.21875,
|
| 2323 |
+
14.3671875,
|
| 2324 |
+
13.734375,
|
| 2325 |
+
14.125,
|
| 2326 |
+
13.890625,
|
| 2327 |
+
14.203125,
|
| 2328 |
+
14.3671875,
|
| 2329 |
+
14.2734375,
|
| 2330 |
+
14.0234375,
|
| 2331 |
+
14.59375,
|
| 2332 |
+
14.484375,
|
| 2333 |
+
14.53125,
|
| 2334 |
+
14.6171875,
|
| 2335 |
+
14.1953125,
|
| 2336 |
+
14.296875,
|
| 2337 |
+
14.734375,
|
| 2338 |
+
14.796875,
|
| 2339 |
+
14.5390625,
|
| 2340 |
+
14.6015625,
|
| 2341 |
+
13.8984375,
|
| 2342 |
+
14.46875,
|
| 2343 |
+
14.671875,
|
| 2344 |
+
14.6640625,
|
| 2345 |
+
14.4609375,
|
| 2346 |
+
14.421875,
|
| 2347 |
+
14.53125,
|
| 2348 |
+
14.7734375,
|
| 2349 |
+
14.5703125,
|
| 2350 |
+
14.25,
|
| 2351 |
+
14.609375,
|
| 2352 |
+
14.5859375,
|
| 2353 |
+
14.9375,
|
| 2354 |
+
14.9609375,
|
| 2355 |
+
14.7734375,
|
| 2356 |
+
14.7265625,
|
| 2357 |
+
14.6875,
|
| 2358 |
+
14.765625,
|
| 2359 |
+
14.6875,
|
| 2360 |
+
15.0625,
|
| 2361 |
+
15.1328125,
|
| 2362 |
+
14.96875,
|
| 2363 |
+
14.546875,
|
| 2364 |
+
14.7265625,
|
| 2365 |
+
14.6015625,
|
| 2366 |
+
14.703125,
|
| 2367 |
+
14.546875,
|
| 2368 |
+
15.3515625,
|
| 2369 |
+
15.046875,
|
| 2370 |
+
14.890625,
|
| 2371 |
+
14.890625,
|
| 2372 |
+
14.6875,
|
| 2373 |
+
15.125,
|
| 2374 |
+
14.7890625,
|
| 2375 |
+
14.90625,
|
| 2376 |
+
14.5,
|
| 2377 |
+
15.1328125,
|
| 2378 |
+
15.09375,
|
| 2379 |
+
15.1796875,
|
| 2380 |
+
14.59375,
|
| 2381 |
+
14.7578125,
|
| 2382 |
+
14.9765625,
|
| 2383 |
+
15.125,
|
| 2384 |
+
15.28125,
|
| 2385 |
+
15.0546875,
|
| 2386 |
+
15.0703125,
|
| 2387 |
+
15.21875,
|
| 2388 |
+
14.4609375,
|
| 2389 |
+
15.015625,
|
| 2390 |
+
15.1875,
|
| 2391 |
+
15.390625,
|
| 2392 |
+
15.09375,
|
| 2393 |
+
14.75,
|
| 2394 |
+
15.1328125,
|
| 2395 |
+
15.359375,
|
| 2396 |
+
15.2890625,
|
| 2397 |
+
15.2734375,
|
| 2398 |
+
15.1484375,
|
| 2399 |
+
15.1328125,
|
| 2400 |
+
15.15625,
|
| 2401 |
+
15.1328125,
|
| 2402 |
+
15.421875,
|
| 2403 |
+
15.15625,
|
| 2404 |
+
15.2578125,
|
| 2405 |
+
14.9375,
|
| 2406 |
+
15.015625,
|
| 2407 |
+
15.46875,
|
| 2408 |
+
15.2734375,
|
| 2409 |
+
15.2734375,
|
| 2410 |
+
15.40625,
|
| 2411 |
+
15.6484375,
|
| 2412 |
+
15.8671875,
|
| 2413 |
+
15.3125,
|
| 2414 |
+
15.015625,
|
| 2415 |
+
15.90625,
|
| 2416 |
+
15.203125,
|
| 2417 |
+
15.328125,
|
| 2418 |
+
15.46875,
|
| 2419 |
+
15.0078125,
|
| 2420 |
+
15.546875,
|
| 2421 |
+
15.359375,
|
| 2422 |
+
14.8515625,
|
| 2423 |
+
15.875,
|
| 2424 |
+
15.15625,
|
| 2425 |
+
15.296875,
|
| 2426 |
+
15.671875,
|
| 2427 |
+
15.3984375,
|
| 2428 |
+
15.296875,
|
| 2429 |
+
15.6171875,
|
| 2430 |
+
15.25,
|
| 2431 |
+
15.0625,
|
| 2432 |
+
15.671875,
|
| 2433 |
+
15.7109375,
|
| 2434 |
+
15.5546875,
|
| 2435 |
+
15.640625,
|
| 2436 |
+
15.625,
|
| 2437 |
+
15.984375,
|
| 2438 |
+
15.8125,
|
| 2439 |
+
15.71875,
|
| 2440 |
+
15.6875,
|
| 2441 |
+
15.5,
|
| 2442 |
+
15.3359375,
|
| 2443 |
+
15.15625,
|
| 2444 |
+
15.78125,
|
| 2445 |
+
15.6015625,
|
| 2446 |
+
15.625,
|
| 2447 |
+
15.71875,
|
| 2448 |
+
15.921875,
|
| 2449 |
+
15.6171875,
|
| 2450 |
+
15.5703125,
|
| 2451 |
+
15.34375,
|
| 2452 |
+
15.546875,
|
| 2453 |
+
16.0,
|
| 2454 |
+
15.921875,
|
| 2455 |
+
15.546875,
|
| 2456 |
+
15.7578125,
|
| 2457 |
+
16.0,
|
| 2458 |
+
15.78125,
|
| 2459 |
+
16.390625,
|
| 2460 |
+
16.25,
|
| 2461 |
+
15.953125,
|
| 2462 |
+
15.9765625,
|
| 2463 |
+
16.390625,
|
| 2464 |
+
16.046875,
|
| 2465 |
+
15.8046875,
|
| 2466 |
+
15.9375,
|
| 2467 |
+
15.875,
|
| 2468 |
+
15.9296875,
|
| 2469 |
+
16.21875,
|
| 2470 |
+
16.1875,
|
| 2471 |
+
16.125,
|
| 2472 |
+
15.96875,
|
| 2473 |
+
15.765625,
|
| 2474 |
+
16.3125,
|
| 2475 |
+
15.7890625,
|
| 2476 |
+
16.1875,
|
| 2477 |
+
16.40625,
|
| 2478 |
+
15.5390625,
|
| 2479 |
+
15.84375,
|
| 2480 |
+
16.265625,
|
| 2481 |
+
16.109375,
|
| 2482 |
+
16.03125,
|
| 2483 |
+
16.375,
|
| 2484 |
+
16.40625,
|
| 2485 |
+
15.3828125,
|
| 2486 |
+
15.8046875,
|
| 2487 |
+
16.671875,
|
| 2488 |
+
16.40625,
|
| 2489 |
+
16.34375,
|
| 2490 |
+
16.375,
|
| 2491 |
+
16.078125,
|
| 2492 |
+
16.015625,
|
| 2493 |
+
16.359375,
|
| 2494 |
+
16.515625,
|
| 2495 |
+
15.8828125,
|
| 2496 |
+
16.109375,
|
| 2497 |
+
16.1875,
|
| 2498 |
+
16.359375,
|
| 2499 |
+
16.34375,
|
| 2500 |
+
16.25,
|
| 2501 |
+
16.203125,
|
| 2502 |
+
16.546875,
|
| 2503 |
+
16.28125,
|
| 2504 |
+
16.390625,
|
| 2505 |
+
16.859375,
|
| 2506 |
+
16.125,
|
| 2507 |
+
16.84375,
|
| 2508 |
+
16.453125,
|
| 2509 |
+
15.96875,
|
| 2510 |
+
16.65625,
|
| 2511 |
+
16.296875,
|
| 2512 |
+
16.5,
|
| 2513 |
+
16.546875,
|
| 2514 |
+
16.109375,
|
| 2515 |
+
16.546875,
|
| 2516 |
+
16.53125,
|
| 2517 |
+
16.1875,
|
| 2518 |
+
16.65625,
|
| 2519 |
+
16.3125,
|
| 2520 |
+
16.625,
|
| 2521 |
+
16.53125,
|
| 2522 |
+
16.21875,
|
| 2523 |
+
16.96875,
|
| 2524 |
+
16.84375,
|
| 2525 |
+
16.734375,
|
| 2526 |
+
16.65625,
|
| 2527 |
+
16.671875,
|
| 2528 |
+
16.640625,
|
| 2529 |
+
16.3125,
|
| 2530 |
+
16.90625,
|
| 2531 |
+
16.4375,
|
| 2532 |
+
16.5625,
|
| 2533 |
+
16.671875,
|
| 2534 |
+
16.859375,
|
| 2535 |
+
16.875,
|
| 2536 |
+
16.546875,
|
| 2537 |
+
16.90625,
|
| 2538 |
+
16.796875,
|
| 2539 |
+
16.546875,
|
| 2540 |
+
16.828125,
|
| 2541 |
+
16.65625,
|
| 2542 |
+
16.8125,
|
| 2543 |
+
16.703125,
|
| 2544 |
+
16.71875,
|
| 2545 |
+
16.9375,
|
| 2546 |
+
16.96875,
|
| 2547 |
+
16.40625,
|
| 2548 |
+
16.65625,
|
| 2549 |
+
16.328125,
|
| 2550 |
+
16.53125,
|
| 2551 |
+
17.0625,
|
| 2552 |
+
16.53125,
|
| 2553 |
+
16.625,
|
| 2554 |
+
16.859375,
|
| 2555 |
+
17.09375,
|
| 2556 |
+
16.390625,
|
| 2557 |
+
16.9375,
|
| 2558 |
+
17.015625,
|
| 2559 |
+
17.0,
|
| 2560 |
+
16.53125,
|
| 2561 |
+
16.953125,
|
| 2562 |
+
16.578125,
|
| 2563 |
+
17.4375,
|
| 2564 |
+
16.703125,
|
| 2565 |
+
16.6875,
|
| 2566 |
+
16.78125,
|
| 2567 |
+
17.0,
|
| 2568 |
+
17.15625,
|
| 2569 |
+
16.59375,
|
| 2570 |
+
16.53125,
|
| 2571 |
+
16.84375,
|
| 2572 |
+
16.921875,
|
| 2573 |
+
16.9375,
|
| 2574 |
+
17.125,
|
| 2575 |
+
17.0,
|
| 2576 |
+
17.40625,
|
| 2577 |
+
16.9375,
|
| 2578 |
+
16.90625,
|
| 2579 |
+
17.109375,
|
| 2580 |
+
17.03125,
|
| 2581 |
+
16.671875,
|
| 2582 |
+
17.203125,
|
| 2583 |
+
17.203125,
|
| 2584 |
+
16.90625,
|
| 2585 |
+
16.828125,
|
| 2586 |
+
17.125,
|
| 2587 |
+
17.15625,
|
| 2588 |
+
17.109375,
|
| 2589 |
+
16.953125,
|
| 2590 |
+
16.890625,
|
| 2591 |
+
17.0,
|
| 2592 |
+
17.265625,
|
| 2593 |
+
17.46875,
|
| 2594 |
+
17.515625,
|
| 2595 |
+
17.015625,
|
| 2596 |
+
17.296875,
|
| 2597 |
+
17.109375,
|
| 2598 |
+
17.171875,
|
| 2599 |
+
17.3125,
|
| 2600 |
+
17.1875,
|
| 2601 |
+
17.671875,
|
| 2602 |
+
16.9375,
|
| 2603 |
+
17.265625,
|
| 2604 |
+
17.0625,
|
| 2605 |
+
17.578125,
|
| 2606 |
+
16.828125,
|
| 2607 |
+
17.21875,
|
| 2608 |
+
17.421875,
|
| 2609 |
+
17.265625,
|
| 2610 |
+
17.375,
|
| 2611 |
+
17.203125,
|
| 2612 |
+
17.21875,
|
| 2613 |
+
17.578125,
|
| 2614 |
+
17.1875,
|
| 2615 |
+
17.359375,
|
| 2616 |
+
17.234375,
|
| 2617 |
+
17.96875,
|
| 2618 |
+
17.546875,
|
| 2619 |
+
17.59375,
|
| 2620 |
+
17.34375,
|
| 2621 |
+
16.984375,
|
| 2622 |
+
17.40625,
|
| 2623 |
+
17.234375,
|
| 2624 |
+
17.015625,
|
| 2625 |
+
17.265625,
|
| 2626 |
+
17.25,
|
| 2627 |
+
17.1875,
|
| 2628 |
+
17.625,
|
| 2629 |
+
17.8125,
|
| 2630 |
+
17.59375,
|
| 2631 |
+
17.1875,
|
| 2632 |
+
17.140625,
|
| 2633 |
+
17.328125,
|
| 2634 |
+
17.390625,
|
| 2635 |
+
17.5625,
|
| 2636 |
+
17.3125,
|
| 2637 |
+
17.75,
|
| 2638 |
+
17.484375,
|
| 2639 |
+
16.984375,
|
| 2640 |
+
17.640625,
|
| 2641 |
+
17.75,
|
| 2642 |
+
17.734375,
|
| 2643 |
+
16.65625,
|
| 2644 |
+
17.125,
|
| 2645 |
+
17.78125,
|
| 2646 |
+
17.59375,
|
| 2647 |
+
17.453125,
|
| 2648 |
+
17.5625,
|
| 2649 |
+
17.484375,
|
| 2650 |
+
17.875,
|
| 2651 |
+
17.53125,
|
| 2652 |
+
17.5625,
|
| 2653 |
+
17.5625,
|
| 2654 |
+
17.140625,
|
| 2655 |
+
17.78125,
|
| 2656 |
+
17.5625,
|
| 2657 |
+
17.625,
|
| 2658 |
+
17.671875,
|
| 2659 |
+
17.75,
|
| 2660 |
+
17.75,
|
| 2661 |
+
18.09375,
|
| 2662 |
+
17.8125,
|
| 2663 |
+
17.5,
|
| 2664 |
+
17.96875,
|
| 2665 |
+
17.46875,
|
| 2666 |
+
18.0,
|
| 2667 |
+
17.9375,
|
| 2668 |
+
17.8125,
|
| 2669 |
+
17.46875,
|
| 2670 |
+
17.5,
|
| 2671 |
+
17.4375,
|
| 2672 |
+
17.09375,
|
| 2673 |
+
17.875,
|
| 2674 |
+
17.796875,
|
| 2675 |
+
18.046875,
|
| 2676 |
+
17.90625,
|
| 2677 |
+
17.796875,
|
| 2678 |
+
17.65625,
|
| 2679 |
+
18.0,
|
| 2680 |
+
17.765625,
|
| 2681 |
+
17.859375,
|
| 2682 |
+
17.859375,
|
| 2683 |
+
17.5625,
|
| 2684 |
+
17.796875,
|
| 2685 |
+
17.828125,
|
| 2686 |
+
17.6875,
|
| 2687 |
+
17.515625,
|
| 2688 |
+
17.546875,
|
| 2689 |
+
18.203125,
|
| 2690 |
+
17.828125,
|
| 2691 |
+
17.546875,
|
| 2692 |
+
18.296875,
|
| 2693 |
+
18.28125,
|
| 2694 |
+
17.53125,
|
| 2695 |
+
17.625,
|
| 2696 |
+
17.578125,
|
| 2697 |
+
18.0625,
|
| 2698 |
+
17.984375,
|
| 2699 |
+
18.125,
|
| 2700 |
+
17.984375,
|
| 2701 |
+
17.828125,
|
| 2702 |
+
17.984375,
|
| 2703 |
+
18.140625,
|
| 2704 |
+
17.78125,
|
| 2705 |
+
18.03125,
|
| 2706 |
+
17.765625,
|
| 2707 |
+
18.234375,
|
| 2708 |
+
17.90625,
|
| 2709 |
+
18.21875,
|
| 2710 |
+
18.09375,
|
| 2711 |
+
18.375,
|
| 2712 |
+
17.953125,
|
| 2713 |
+
18.140625,
|
| 2714 |
+
18.3125,
|
| 2715 |
+
17.984375,
|
| 2716 |
+
18.0625,
|
| 2717 |
+
18.359375,
|
| 2718 |
+
18.5,
|
| 2719 |
+
17.90625,
|
| 2720 |
+
18.265625,
|
| 2721 |
+
18.421875,
|
| 2722 |
+
17.890625,
|
| 2723 |
+
18.015625,
|
| 2724 |
+
17.828125,
|
| 2725 |
+
18.21875,
|
| 2726 |
+
18.15625,
|
| 2727 |
+
18.15625,
|
| 2728 |
+
18.484375,
|
| 2729 |
+
18.484375,
|
| 2730 |
+
18.03125,
|
| 2731 |
+
18.078125,
|
| 2732 |
+
17.9375,
|
| 2733 |
+
17.921875,
|
| 2734 |
+
17.96875,
|
| 2735 |
+
18.03125,
|
| 2736 |
+
17.84375,
|
| 2737 |
+
18.375,
|
| 2738 |
+
18.359375,
|
| 2739 |
+
18.46875,
|
| 2740 |
+
17.96875,
|
| 2741 |
+
17.734375,
|
| 2742 |
+
18.421875,
|
| 2743 |
+
17.984375,
|
| 2744 |
+
18.421875,
|
| 2745 |
+
18.203125,
|
| 2746 |
+
18.46875,
|
| 2747 |
+
18.28125,
|
| 2748 |
+
18.40625,
|
| 2749 |
+
17.8125,
|
| 2750 |
+
18.21875,
|
| 2751 |
+
18.59375,
|
| 2752 |
+
18.59375,
|
| 2753 |
+
18.765625,
|
| 2754 |
+
18.40625,
|
| 2755 |
+
18.65625,
|
| 2756 |
+
17.984375,
|
| 2757 |
+
18.375,
|
| 2758 |
+
18.421875,
|
| 2759 |
+
18.25,
|
| 2760 |
+
18.140625,
|
| 2761 |
+
18.203125,
|
| 2762 |
+
18.265625,
|
| 2763 |
+
18.125,
|
| 2764 |
+
18.203125,
|
| 2765 |
+
18.359375,
|
| 2766 |
+
18.5,
|
| 2767 |
+
18.6875,
|
| 2768 |
+
18.125,
|
| 2769 |
+
18.46875,
|
| 2770 |
+
18.359375,
|
| 2771 |
+
18.140625,
|
| 2772 |
+
18.125,
|
| 2773 |
+
18.421875,
|
| 2774 |
+
18.15625,
|
| 2775 |
+
17.734375,
|
| 2776 |
+
18.296875,
|
| 2777 |
+
18.09375,
|
| 2778 |
+
18.375,
|
| 2779 |
+
18.1875,
|
| 2780 |
+
17.734375,
|
| 2781 |
+
18.1875,
|
| 2782 |
+
18.65625,
|
| 2783 |
+
18.234375,
|
| 2784 |
+
17.890625,
|
| 2785 |
+
18.484375,
|
| 2786 |
+
19.03125,
|
| 2787 |
+
18.34375,
|
| 2788 |
+
17.859375,
|
| 2789 |
+
18.5,
|
| 2790 |
+
18.75,
|
| 2791 |
+
18.390625,
|
| 2792 |
+
18.59375,
|
| 2793 |
+
18.15625,
|
| 2794 |
+
18.21875,
|
| 2795 |
+
18.625,
|
| 2796 |
+
18.46875,
|
| 2797 |
+
18.734375,
|
| 2798 |
+
18.625,
|
| 2799 |
+
18.1875,
|
| 2800 |
+
18.046875,
|
| 2801 |
+
18.578125,
|
| 2802 |
+
18.625,
|
| 2803 |
+
18.546875,
|
| 2804 |
+
18.1875,
|
| 2805 |
+
18.78125,
|
| 2806 |
+
18.65625,
|
| 2807 |
+
18.796875,
|
| 2808 |
+
18.65625,
|
| 2809 |
+
18.5,
|
| 2810 |
+
18.40625,
|
| 2811 |
+
18.859375,
|
| 2812 |
+
18.046875,
|
| 2813 |
+
18.65625,
|
| 2814 |
+
18.46875,
|
| 2815 |
+
18.875,
|
| 2816 |
+
18.6875,
|
| 2817 |
+
18.171875,
|
| 2818 |
+
18.5,
|
| 2819 |
+
18.15625,
|
| 2820 |
+
18.28125,
|
| 2821 |
+
18.078125,
|
| 2822 |
+
19.03125,
|
| 2823 |
+
18.375,
|
| 2824 |
+
18.609375,
|
| 2825 |
+
18.96875,
|
| 2826 |
+
18.609375,
|
| 2827 |
+
18.25,
|
| 2828 |
+
18.890625,
|
| 2829 |
+
18.46875,
|
| 2830 |
+
18.703125,
|
| 2831 |
+
18.53125,
|
| 2832 |
+
18.34375,
|
| 2833 |
+
18.6875,
|
| 2834 |
+
18.5,
|
| 2835 |
+
18.53125,
|
| 2836 |
+
18.75,
|
| 2837 |
+
18.453125,
|
| 2838 |
+
18.515625,
|
| 2839 |
+
19.0625,
|
| 2840 |
+
18.46875,
|
| 2841 |
+
18.578125,
|
| 2842 |
+
19.125,
|
| 2843 |
+
18.734375,
|
| 2844 |
+
18.40625,
|
| 2845 |
+
18.875,
|
| 2846 |
+
18.5,
|
| 2847 |
+
18.34375,
|
| 2848 |
+
18.21875,
|
| 2849 |
+
18.40625,
|
| 2850 |
+
18.0,
|
| 2851 |
+
18.234375,
|
| 2852 |
+
18.21875,
|
| 2853 |
+
18.75,
|
| 2854 |
+
18.828125,
|
| 2855 |
+
18.34375,
|
| 2856 |
+
18.40625,
|
| 2857 |
+
18.671875,
|
| 2858 |
+
18.5625,
|
| 2859 |
+
18.875,
|
| 2860 |
+
18.875,
|
| 2861 |
+
18.59375,
|
| 2862 |
+
18.328125,
|
| 2863 |
+
18.59375,
|
| 2864 |
+
18.703125,
|
| 2865 |
+
18.8125,
|
| 2866 |
+
18.8125,
|
| 2867 |
+
18.625,
|
| 2868 |
+
18.8125,
|
| 2869 |
+
18.84375,
|
| 2870 |
+
18.859375,
|
| 2871 |
+
18.734375,
|
| 2872 |
+
18.234375,
|
| 2873 |
+
18.765625,
|
| 2874 |
+
19.09375,
|
| 2875 |
+
18.796875,
|
| 2876 |
+
19.109375,
|
| 2877 |
+
18.484375,
|
| 2878 |
+
18.796875,
|
| 2879 |
+
18.53125,
|
| 2880 |
+
18.953125,
|
| 2881 |
+
18.828125,
|
| 2882 |
+
18.484375,
|
| 2883 |
+
19.0625,
|
| 2884 |
+
18.765625,
|
| 2885 |
+
18.5625,
|
| 2886 |
+
18.21875,
|
| 2887 |
+
18.3125,
|
| 2888 |
+
18.5625,
|
| 2889 |
+
18.640625,
|
| 2890 |
+
18.96875,
|
| 2891 |
+
18.515625,
|
| 2892 |
+
18.46875,
|
| 2893 |
+
18.609375,
|
| 2894 |
+
18.828125,
|
| 2895 |
+
18.46875,
|
| 2896 |
+
18.5,
|
| 2897 |
+
18.5625,
|
| 2898 |
+
18.53125,
|
| 2899 |
+
18.703125,
|
| 2900 |
+
18.296875,
|
| 2901 |
+
18.59375,
|
| 2902 |
+
19.0625,
|
| 2903 |
+
18.125,
|
| 2904 |
+
18.53125,
|
| 2905 |
+
18.890625,
|
| 2906 |
+
18.765625,
|
| 2907 |
+
18.296875,
|
| 2908 |
+
18.84375,
|
| 2909 |
+
18.90625,
|
| 2910 |
+
18.15625,
|
| 2911 |
+
17.8125,
|
| 2912 |
+
18.953125,
|
| 2913 |
+
18.296875,
|
| 2914 |
+
18.703125,
|
| 2915 |
+
18.703125,
|
| 2916 |
+
18.46875,
|
| 2917 |
+
19.03125,
|
| 2918 |
+
18.546875,
|
| 2919 |
+
18.59375,
|
| 2920 |
+
18.875,
|
| 2921 |
+
19.015625,
|
| 2922 |
+
18.609375,
|
| 2923 |
+
18.59375,
|
| 2924 |
+
18.65625,
|
| 2925 |
+
18.828125,
|
| 2926 |
+
18.875,
|
| 2927 |
+
18.359375,
|
| 2928 |
+
18.46875,
|
| 2929 |
+
18.96875,
|
| 2930 |
+
18.8125,
|
| 2931 |
+
17.75,
|
| 2932 |
+
18.390625,
|
| 2933 |
+
18.875,
|
| 2934 |
+
18.125,
|
| 2935 |
+
18.34375,
|
| 2936 |
+
18.703125,
|
| 2937 |
+
18.90625,
|
| 2938 |
+
19.09375,
|
| 2939 |
+
18.765625,
|
| 2940 |
+
18.515625,
|
| 2941 |
+
18.421875,
|
| 2942 |
+
18.828125,
|
| 2943 |
+
18.609375,
|
| 2944 |
+
18.28125,
|
| 2945 |
+
18.875,
|
| 2946 |
+
18.578125,
|
| 2947 |
+
18.90625,
|
| 2948 |
+
18.75,
|
| 2949 |
+
18.609375,
|
| 2950 |
+
19.0,
|
| 2951 |
+
18.84375,
|
| 2952 |
+
18.546875,
|
| 2953 |
+
18.5,
|
| 2954 |
+
18.734375,
|
| 2955 |
+
18.703125,
|
| 2956 |
+
18.5625,
|
| 2957 |
+
18.765625,
|
| 2958 |
+
18.28125,
|
| 2959 |
+
19.078125,
|
| 2960 |
+
18.359375,
|
| 2961 |
+
18.515625,
|
| 2962 |
+
19.0625,
|
| 2963 |
+
18.84375,
|
| 2964 |
+
18.5,
|
| 2965 |
+
18.765625,
|
| 2966 |
+
18.625,
|
| 2967 |
+
18.5,
|
| 2968 |
+
18.953125,
|
| 2969 |
+
18.609375,
|
| 2970 |
+
18.71875,
|
| 2971 |
+
18.453125,
|
| 2972 |
+
18.625,
|
| 2973 |
+
18.546875,
|
| 2974 |
+
18.8125,
|
| 2975 |
+
18.890625,
|
| 2976 |
+
18.84375,
|
| 2977 |
+
19.171875,
|
| 2978 |
+
18.84375,
|
| 2979 |
+
18.96875,
|
| 2980 |
+
18.484375,
|
| 2981 |
+
19.09375,
|
| 2982 |
+
18.890625,
|
| 2983 |
+
19.265625,
|
| 2984 |
+
18.40625,
|
| 2985 |
+
18.9375,
|
| 2986 |
+
18.6875,
|
| 2987 |
+
18.734375,
|
| 2988 |
+
18.578125,
|
| 2989 |
+
18.421875,
|
| 2990 |
+
19.296875,
|
| 2991 |
+
18.84375,
|
| 2992 |
+
19.015625,
|
| 2993 |
+
18.828125,
|
| 2994 |
+
19.09375,
|
| 2995 |
+
18.84375,
|
| 2996 |
+
19.015625,
|
| 2997 |
+
19.0625,
|
| 2998 |
+
18.96875,
|
| 2999 |
+
19.21875,
|
| 3000 |
+
18.421875,
|
| 3001 |
+
18.875,
|
| 3002 |
+
18.546875,
|
| 3003 |
+
18.59375,
|
| 3004 |
+
18.984375
|
| 3005 |
+
],
|
| 3006 |
+
"total_losses": [
|
| 3007 |
+
8.078125,
|
| 3008 |
+
7.6015625,
|
| 3009 |
+
7.625,
|
| 3010 |
+
7.3828125,
|
| 3011 |
+
7.3671875,
|
| 3012 |
+
7.0390625,
|
| 3013 |
+
7.17578125,
|
| 3014 |
+
6.87890625,
|
| 3015 |
+
6.70703125,
|
| 3016 |
+
6.44140625,
|
| 3017 |
+
6.47265625,
|
| 3018 |
+
6.4140625,
|
| 3019 |
+
6.5703125,
|
| 3020 |
+
6.37109375,
|
| 3021 |
+
6.4453125,
|
| 3022 |
+
6.26953125,
|
| 3023 |
+
5.8046875,
|
| 3024 |
+
6.22265625,
|
| 3025 |
+
6.12109375,
|
| 3026 |
+
6.05859375,
|
| 3027 |
+
6.10546875,
|
| 3028 |
+
5.50390625,
|
| 3029 |
+
5.39453125,
|
| 3030 |
+
5.46484375,
|
| 3031 |
+
6.06640625,
|
| 3032 |
+
5.79296875,
|
| 3033 |
+
5.87109375,
|
| 3034 |
+
5.76171875,
|
| 3035 |
+
5.80859375,
|
| 3036 |
+
5.60546875,
|
| 3037 |
+
5.3828125,
|
| 3038 |
+
5.33203125,
|
| 3039 |
+
5.41015625,
|
| 3040 |
+
5.45703125,
|
| 3041 |
+
5.73046875,
|
| 3042 |
+
5.37890625,
|
| 3043 |
+
5.38671875,
|
| 3044 |
+
5.1171875,
|
| 3045 |
+
5.6640625,
|
| 3046 |
+
5.3046875,
|
| 3047 |
+
5.47265625,
|
| 3048 |
+
5.46484375,
|
| 3049 |
+
5.609375,
|
| 3050 |
+
5.140625,
|
| 3051 |
+
5.03125,
|
| 3052 |
+
5.1875,
|
| 3053 |
+
4.84765625,
|
| 3054 |
+
5.0625,
|
| 3055 |
+
4.97265625,
|
| 3056 |
+
5.0546875,
|
| 3057 |
+
4.98046875,
|
| 3058 |
+
5.40625,
|
| 3059 |
+
5.25390625,
|
| 3060 |
+
4.8984375,
|
| 3061 |
+
4.84765625,
|
| 3062 |
+
5.19921875,
|
| 3063 |
+
4.75390625,
|
| 3064 |
+
4.86328125,
|
| 3065 |
+
4.29296875,
|
| 3066 |
+
4.8515625,
|
| 3067 |
+
4.9765625,
|
| 3068 |
+
4.734375,
|
| 3069 |
+
4.6328125,
|
| 3070 |
+
4.60546875,
|
| 3071 |
+
4.80078125,
|
| 3072 |
+
4.3359375,
|
| 3073 |
+
4.8203125,
|
| 3074 |
+
4.73828125,
|
| 3075 |
+
4.5,
|
| 3076 |
+
4.66015625,
|
| 3077 |
+
4.44921875,
|
| 3078 |
+
4.5703125,
|
| 3079 |
+
4.53125,
|
| 3080 |
+
4.1015625,
|
| 3081 |
+
4.484375,
|
| 3082 |
+
4.43359375,
|
| 3083 |
+
4.6171875,
|
| 3084 |
+
4.56640625,
|
| 3085 |
+
4.3984375,
|
| 3086 |
+
4.4453125,
|
| 3087 |
+
4.62109375,
|
| 3088 |
+
4.3984375,
|
| 3089 |
+
4.48046875,
|
| 3090 |
+
4.51171875,
|
| 3091 |
+
4.0546875,
|
| 3092 |
+
4.2109375,
|
| 3093 |
+
4.546875,
|
| 3094 |
+
4.5390625,
|
| 3095 |
+
4.34375,
|
| 3096 |
+
4.296875,
|
| 3097 |
+
4.21484375,
|
| 3098 |
+
4.12109375,
|
| 3099 |
+
4.1875,
|
| 3100 |
+
4.37109375,
|
| 3101 |
+
4.14453125,
|
| 3102 |
+
4.1640625,
|
| 3103 |
+
4.1640625,
|
| 3104 |
+
4.09375,
|
| 3105 |
+
3.87109375,
|
| 3106 |
+
4.18359375,
|
| 3107 |
+
3.74609375,
|
| 3108 |
+
4.31640625,
|
| 3109 |
+
4.26171875,
|
| 3110 |
+
4.20703125,
|
| 3111 |
+
4.11328125,
|
| 3112 |
+
4.15234375,
|
| 3113 |
+
3.671875,
|
| 3114 |
+
4.21875,
|
| 3115 |
+
3.900390625,
|
| 3116 |
+
3.8671875,
|
| 3117 |
+
4.0625,
|
| 3118 |
+
3.6328125,
|
| 3119 |
+
4.16796875,
|
| 3120 |
+
3.849609375,
|
| 3121 |
+
3.9765625,
|
| 3122 |
+
4.09765625,
|
| 3123 |
+
3.712890625,
|
| 3124 |
+
4.36328125,
|
| 3125 |
+
3.986328125,
|
| 3126 |
+
3.900390625,
|
| 3127 |
+
3.712890625,
|
| 3128 |
+
3.748046875,
|
| 3129 |
+
3.88671875,
|
| 3130 |
+
3.779296875,
|
| 3131 |
+
4.11328125,
|
| 3132 |
+
3.75390625,
|
| 3133 |
+
3.845703125,
|
| 3134 |
+
3.646484375,
|
| 3135 |
+
3.8828125,
|
| 3136 |
+
4.02734375,
|
| 3137 |
+
3.728515625,
|
| 3138 |
+
4.14453125,
|
| 3139 |
+
3.900390625,
|
| 3140 |
+
4.07421875,
|
| 3141 |
+
3.859375,
|
| 3142 |
+
3.8671875,
|
| 3143 |
+
4.0,
|
| 3144 |
+
3.810546875,
|
| 3145 |
+
3.669921875,
|
| 3146 |
+
3.599609375,
|
| 3147 |
+
3.474609375,
|
| 3148 |
+
3.892578125,
|
| 3149 |
+
3.791015625,
|
| 3150 |
+
3.53125,
|
| 3151 |
+
3.943359375,
|
| 3152 |
+
3.568359375,
|
| 3153 |
+
3.462890625,
|
| 3154 |
+
3.85546875,
|
| 3155 |
+
3.587890625,
|
| 3156 |
+
3.4140625,
|
| 3157 |
+
3.681640625,
|
| 3158 |
+
3.853515625,
|
| 3159 |
+
3.826171875,
|
| 3160 |
+
3.6875,
|
| 3161 |
+
3.681640625,
|
| 3162 |
+
3.3671875,
|
| 3163 |
+
3.478515625,
|
| 3164 |
+
3.740234375,
|
| 3165 |
+
3.7265625,
|
| 3166 |
+
3.375,
|
| 3167 |
+
3.560546875,
|
| 3168 |
+
3.5625,
|
| 3169 |
+
3.36328125,
|
| 3170 |
+
3.447265625,
|
| 3171 |
+
3.373046875,
|
| 3172 |
+
3.474609375,
|
| 3173 |
+
3.7734375,
|
| 3174 |
+
3.560546875,
|
| 3175 |
+
3.267578125,
|
| 3176 |
+
3.625,
|
| 3177 |
+
3.84375,
|
| 3178 |
+
3.017578125,
|
| 3179 |
+
3.435546875,
|
| 3180 |
+
3.40234375,
|
| 3181 |
+
3.6640625,
|
| 3182 |
+
3.380859375,
|
| 3183 |
+
3.46484375,
|
| 3184 |
+
3.474609375,
|
| 3185 |
+
3.169921875,
|
| 3186 |
+
3.41796875,
|
| 3187 |
+
3.392578125,
|
| 3188 |
+
3.3671875,
|
| 3189 |
+
3.712890625,
|
| 3190 |
+
3.322265625,
|
| 3191 |
+
3.263671875,
|
| 3192 |
+
3.330078125,
|
| 3193 |
+
3.763671875,
|
| 3194 |
+
3.658203125,
|
| 3195 |
+
3.173828125,
|
| 3196 |
+
3.48046875,
|
| 3197 |
+
3.443359375,
|
| 3198 |
+
3.142578125,
|
| 3199 |
+
3.484375,
|
| 3200 |
+
3.36328125,
|
| 3201 |
+
3.181640625,
|
| 3202 |
+
3.357421875,
|
| 3203 |
+
3.330078125,
|
| 3204 |
+
3.1640625,
|
| 3205 |
+
3.650390625,
|
| 3206 |
+
3.80859375,
|
| 3207 |
+
3.201171875,
|
| 3208 |
+
3.091796875,
|
| 3209 |
+
3.48046875,
|
| 3210 |
+
3.21484375,
|
| 3211 |
+
3.123046875,
|
| 3212 |
+
3.3125,
|
| 3213 |
+
3.17578125,
|
| 3214 |
+
3.216796875,
|
| 3215 |
+
3.302734375,
|
| 3216 |
+
3.130859375,
|
| 3217 |
+
3.263671875,
|
| 3218 |
+
3.333984375,
|
| 3219 |
+
3.41796875,
|
| 3220 |
+
3.359375,
|
| 3221 |
+
3.271484375,
|
| 3222 |
+
3.42578125,
|
| 3223 |
+
3.3046875,
|
| 3224 |
+
3.37890625,
|
| 3225 |
+
3.310546875,
|
| 3226 |
+
3.064453125,
|
| 3227 |
+
3.134765625,
|
| 3228 |
+
3.25390625,
|
| 3229 |
+
2.9609375,
|
| 3230 |
+
3.078125,
|
| 3231 |
+
3.0546875,
|
| 3232 |
+
3.369140625,
|
| 3233 |
+
3.1328125,
|
| 3234 |
+
3.0703125,
|
| 3235 |
+
3.248046875,
|
| 3236 |
+
3.060546875,
|
| 3237 |
+
3.1875,
|
| 3238 |
+
2.94921875,
|
| 3239 |
+
3.087890625,
|
| 3240 |
+
3.193359375,
|
| 3241 |
+
2.98828125,
|
| 3242 |
+
3.240234375,
|
| 3243 |
+
3.01953125,
|
| 3244 |
+
3.0,
|
| 3245 |
+
3.02734375,
|
| 3246 |
+
2.7265625,
|
| 3247 |
+
3.140625,
|
| 3248 |
+
2.7578125,
|
| 3249 |
+
3.23828125,
|
| 3250 |
+
3.02734375,
|
| 3251 |
+
2.82421875,
|
| 3252 |
+
3.169921875,
|
| 3253 |
+
3.087890625,
|
| 3254 |
+
3.38671875,
|
| 3255 |
+
3.07421875,
|
| 3256 |
+
3.0390625,
|
| 3257 |
+
3.1953125,
|
| 3258 |
+
2.84765625,
|
| 3259 |
+
2.94921875,
|
| 3260 |
+
2.828125,
|
| 3261 |
+
3.08203125,
|
| 3262 |
+
2.91015625,
|
| 3263 |
+
2.88671875,
|
| 3264 |
+
3.13671875,
|
| 3265 |
+
2.95703125,
|
| 3266 |
+
2.98046875,
|
| 3267 |
+
2.73828125,
|
| 3268 |
+
2.900390625,
|
| 3269 |
+
2.80078125,
|
| 3270 |
+
3.171875,
|
| 3271 |
+
2.982421875,
|
| 3272 |
+
2.689453125,
|
| 3273 |
+
2.98828125,
|
| 3274 |
+
2.8515625,
|
| 3275 |
+
2.998046875,
|
| 3276 |
+
2.63671875,
|
| 3277 |
+
2.9609375,
|
| 3278 |
+
3.0546875,
|
| 3279 |
+
2.970703125,
|
| 3280 |
+
3.134765625,
|
| 3281 |
+
2.89453125,
|
| 3282 |
+
2.984375,
|
| 3283 |
+
2.708984375,
|
| 3284 |
+
2.78125,
|
| 3285 |
+
3.263671875,
|
| 3286 |
+
2.947265625,
|
| 3287 |
+
2.98828125,
|
| 3288 |
+
2.712890625,
|
| 3289 |
+
2.99609375,
|
| 3290 |
+
2.876953125,
|
| 3291 |
+
2.8359375,
|
| 3292 |
+
3.033203125,
|
| 3293 |
+
2.7734375,
|
| 3294 |
+
2.91796875,
|
| 3295 |
+
2.6796875,
|
| 3296 |
+
2.900390625,
|
| 3297 |
+
3.189453125,
|
| 3298 |
+
2.92578125,
|
| 3299 |
+
2.916015625,
|
| 3300 |
+
2.92578125,
|
| 3301 |
+
2.912109375,
|
| 3302 |
+
2.94140625,
|
| 3303 |
+
2.810546875,
|
| 3304 |
+
3.052734375,
|
| 3305 |
+
2.9375,
|
| 3306 |
+
2.9765625,
|
| 3307 |
+
2.8671875,
|
| 3308 |
+
2.8984375,
|
| 3309 |
+
2.9921875,
|
| 3310 |
+
2.734375,
|
| 3311 |
+
2.79296875,
|
| 3312 |
+
2.884765625,
|
| 3313 |
+
2.806640625,
|
| 3314 |
+
2.767578125,
|
| 3315 |
+
2.640625,
|
| 3316 |
+
2.7734375,
|
| 3317 |
+
2.712890625,
|
| 3318 |
+
2.734375,
|
| 3319 |
+
2.775390625,
|
| 3320 |
+
2.96875,
|
| 3321 |
+
2.53125,
|
| 3322 |
+
2.6640625,
|
| 3323 |
+
2.708984375,
|
| 3324 |
+
2.83203125,
|
| 3325 |
+
2.685546875,
|
| 3326 |
+
2.58203125,
|
| 3327 |
+
2.91015625,
|
| 3328 |
+
2.814453125,
|
| 3329 |
+
2.95703125,
|
| 3330 |
+
2.919921875,
|
| 3331 |
+
2.873046875,
|
| 3332 |
+
2.640625,
|
| 3333 |
+
2.794921875,
|
| 3334 |
+
2.642578125,
|
| 3335 |
+
2.67578125,
|
| 3336 |
+
2.708984375,
|
| 3337 |
+
2.82421875,
|
| 3338 |
+
2.654296875,
|
| 3339 |
+
2.73046875,
|
| 3340 |
+
2.59375,
|
| 3341 |
+
2.8125,
|
| 3342 |
+
2.556640625,
|
| 3343 |
+
2.625,
|
| 3344 |
+
2.6015625,
|
| 3345 |
+
3.01953125,
|
| 3346 |
+
2.8359375,
|
| 3347 |
+
2.86328125,
|
| 3348 |
+
2.533203125,
|
| 3349 |
+
2.759765625,
|
| 3350 |
+
2.787109375,
|
| 3351 |
+
2.78515625,
|
| 3352 |
+
2.697265625,
|
| 3353 |
+
2.646484375,
|
| 3354 |
+
2.65234375,
|
| 3355 |
+
2.755859375,
|
| 3356 |
+
2.595703125,
|
| 3357 |
+
2.642578125,
|
| 3358 |
+
2.5703125,
|
| 3359 |
+
2.9453125,
|
| 3360 |
+
2.65234375,
|
| 3361 |
+
2.890625,
|
| 3362 |
+
2.48046875,
|
| 3363 |
+
2.64453125,
|
| 3364 |
+
2.61328125,
|
| 3365 |
+
2.453125,
|
| 3366 |
+
2.6875,
|
| 3367 |
+
2.57421875,
|
| 3368 |
+
2.822265625,
|
| 3369 |
+
2.630859375,
|
| 3370 |
+
2.671875,
|
| 3371 |
+
2.900390625,
|
| 3372 |
+
2.80859375,
|
| 3373 |
+
2.68359375,
|
| 3374 |
+
2.73828125,
|
| 3375 |
+
2.435546875,
|
| 3376 |
+
2.740234375,
|
| 3377 |
+
2.86328125,
|
| 3378 |
+
2.55078125,
|
| 3379 |
+
2.716796875,
|
| 3380 |
+
2.544921875,
|
| 3381 |
+
2.666015625,
|
| 3382 |
+
2.44921875,
|
| 3383 |
+
2.408203125,
|
| 3384 |
+
2.76953125,
|
| 3385 |
+
2.701171875,
|
| 3386 |
+
2.732421875,
|
| 3387 |
+
2.630859375,
|
| 3388 |
+
2.4296875,
|
| 3389 |
+
2.421875,
|
| 3390 |
+
2.68359375,
|
| 3391 |
+
2.525390625,
|
| 3392 |
+
2.498046875,
|
| 3393 |
+
2.69140625,
|
| 3394 |
+
2.416015625,
|
| 3395 |
+
2.640625,
|
| 3396 |
+
2.33203125,
|
| 3397 |
+
2.4921875,
|
| 3398 |
+
2.529296875,
|
| 3399 |
+
2.49609375,
|
| 3400 |
+
2.6796875,
|
| 3401 |
+
2.513671875,
|
| 3402 |
+
2.5859375,
|
| 3403 |
+
2.50390625,
|
| 3404 |
+
2.630859375,
|
| 3405 |
+
2.732421875,
|
| 3406 |
+
2.6796875,
|
| 3407 |
+
2.59765625,
|
| 3408 |
+
2.59765625,
|
| 3409 |
+
2.435546875,
|
| 3410 |
+
2.38671875,
|
| 3411 |
+
2.4765625,
|
| 3412 |
+
2.40625,
|
| 3413 |
+
2.5234375,
|
| 3414 |
+
2.435546875,
|
| 3415 |
+
2.41796875,
|
| 3416 |
+
2.724609375,
|
| 3417 |
+
2.4921875,
|
| 3418 |
+
2.5,
|
| 3419 |
+
2.763671875,
|
| 3420 |
+
2.482421875,
|
| 3421 |
+
2.44921875,
|
| 3422 |
+
2.55078125,
|
| 3423 |
+
2.4765625,
|
| 3424 |
+
2.20703125,
|
| 3425 |
+
2.3046875,
|
| 3426 |
+
2.525390625,
|
| 3427 |
+
2.642578125,
|
| 3428 |
+
2.380859375,
|
| 3429 |
+
2.630859375,
|
| 3430 |
+
2.314453125,
|
| 3431 |
+
2.498046875,
|
| 3432 |
+
2.4453125,
|
| 3433 |
+
2.6953125,
|
| 3434 |
+
2.291015625,
|
| 3435 |
+
2.6015625,
|
| 3436 |
+
2.419921875,
|
| 3437 |
+
2.580078125,
|
| 3438 |
+
2.224609375,
|
| 3439 |
+
2.423828125,
|
| 3440 |
+
2.25390625,
|
| 3441 |
+
2.595703125,
|
| 3442 |
+
2.599609375,
|
| 3443 |
+
2.576171875,
|
| 3444 |
+
2.62109375,
|
| 3445 |
+
2.345703125,
|
| 3446 |
+
2.478515625,
|
| 3447 |
+
2.494140625,
|
| 3448 |
+
2.447265625,
|
| 3449 |
+
2.302734375,
|
| 3450 |
+
2.478515625,
|
| 3451 |
+
2.373046875,
|
| 3452 |
+
2.546875,
|
| 3453 |
+
2.53515625,
|
| 3454 |
+
2.361328125,
|
| 3455 |
+
2.494140625,
|
| 3456 |
+
2.453125,
|
| 3457 |
+
2.431640625,
|
| 3458 |
+
2.447265625,
|
| 3459 |
+
2.474609375,
|
| 3460 |
+
2.6015625,
|
| 3461 |
+
2.3125,
|
| 3462 |
+
2.330078125,
|
| 3463 |
+
2.314453125,
|
| 3464 |
+
2.447265625,
|
| 3465 |
+
2.291015625,
|
| 3466 |
+
2.380859375,
|
| 3467 |
+
2.595703125,
|
| 3468 |
+
2.529296875,
|
| 3469 |
+
2.341796875,
|
| 3470 |
+
2.177734375,
|
| 3471 |
+
2.337890625,
|
| 3472 |
+
2.431640625,
|
| 3473 |
+
2.3984375,
|
| 3474 |
+
2.435546875,
|
| 3475 |
+
2.376953125,
|
| 3476 |
+
2.490234375,
|
| 3477 |
+
2.451171875,
|
| 3478 |
+
2.294921875,
|
| 3479 |
+
2.259765625,
|
| 3480 |
+
2.2890625,
|
| 3481 |
+
2.498046875,
|
| 3482 |
+
2.271484375,
|
| 3483 |
+
2.380859375,
|
| 3484 |
+
2.37890625,
|
| 3485 |
+
2.52734375,
|
| 3486 |
+
2.4296875,
|
| 3487 |
+
2.3125,
|
| 3488 |
+
2.423828125,
|
| 3489 |
+
2.34375,
|
| 3490 |
+
2.26171875,
|
| 3491 |
+
2.35546875,
|
| 3492 |
+
2.26953125,
|
| 3493 |
+
2.3828125,
|
| 3494 |
+
2.396484375,
|
| 3495 |
+
2.4140625,
|
| 3496 |
+
2.31640625,
|
| 3497 |
+
2.482421875,
|
| 3498 |
+
2.138671875,
|
| 3499 |
+
2.3125,
|
| 3500 |
+
2.576171875,
|
| 3501 |
+
2.2890625,
|
| 3502 |
+
2.32421875,
|
| 3503 |
+
2.439453125,
|
| 3504 |
+
2.48046875,
|
| 3505 |
+
2.412109375,
|
| 3506 |
+
2.29296875,
|
| 3507 |
+
2.158203125,
|
| 3508 |
+
2.490234375,
|
| 3509 |
+
2.509765625,
|
| 3510 |
+
2.3671875,
|
| 3511 |
+
2.16796875,
|
| 3512 |
+
2.3671875,
|
| 3513 |
+
2.505859375,
|
| 3514 |
+
2.384765625,
|
| 3515 |
+
2.271484375,
|
| 3516 |
+
2.416015625,
|
| 3517 |
+
2.283203125,
|
| 3518 |
+
2.353515625,
|
| 3519 |
+
2.18359375,
|
| 3520 |
+
2.3359375,
|
| 3521 |
+
2.162109375,
|
| 3522 |
+
2.134765625,
|
| 3523 |
+
2.443359375,
|
| 3524 |
+
2.095703125,
|
| 3525 |
+
2.212890625,
|
| 3526 |
+
2.412109375,
|
| 3527 |
+
2.375,
|
| 3528 |
+
2.3046875,
|
| 3529 |
+
2.0703125,
|
| 3530 |
+
2.16796875,
|
| 3531 |
+
2.16015625,
|
| 3532 |
+
2.2734375,
|
| 3533 |
+
1.9423828125,
|
| 3534 |
+
2.109375,
|
| 3535 |
+
2.33203125,
|
| 3536 |
+
2.22265625,
|
| 3537 |
+
2.248046875,
|
| 3538 |
+
2.494140625,
|
| 3539 |
+
2.21484375,
|
| 3540 |
+
2.22265625,
|
| 3541 |
+
2.271484375,
|
| 3542 |
+
2.1171875,
|
| 3543 |
+
2.205078125,
|
| 3544 |
+
2.169921875,
|
| 3545 |
+
2.515625,
|
| 3546 |
+
2.513671875,
|
| 3547 |
+
2.275390625,
|
| 3548 |
+
2.265625,
|
| 3549 |
+
2.009765625,
|
| 3550 |
+
2.267578125,
|
| 3551 |
+
2.169921875,
|
| 3552 |
+
2.2265625,
|
| 3553 |
+
2.193359375,
|
| 3554 |
+
2.28515625,
|
| 3555 |
+
2.4140625,
|
| 3556 |
+
2.357421875,
|
| 3557 |
+
2.30859375,
|
| 3558 |
+
2.134765625,
|
| 3559 |
+
2.349609375,
|
| 3560 |
+
2.048828125,
|
| 3561 |
+
2.09375,
|
| 3562 |
+
2.29296875,
|
| 3563 |
+
2.03515625,
|
| 3564 |
+
2.181640625,
|
| 3565 |
+
2.248046875,
|
| 3566 |
+
2.259765625,
|
| 3567 |
+
2.552734375,
|
| 3568 |
+
2.41796875,
|
| 3569 |
+
2.123046875,
|
| 3570 |
+
1.98828125,
|
| 3571 |
+
2.1328125,
|
| 3572 |
+
2.193359375,
|
| 3573 |
+
2.189453125,
|
| 3574 |
+
1.986328125,
|
| 3575 |
+
2.1171875,
|
| 3576 |
+
2.005859375,
|
| 3577 |
+
2.056640625,
|
| 3578 |
+
2.126953125,
|
| 3579 |
+
2.404296875,
|
| 3580 |
+
1.8837890625,
|
| 3581 |
+
2.0390625,
|
| 3582 |
+
2.375,
|
| 3583 |
+
2.068359375,
|
| 3584 |
+
2.17578125,
|
| 3585 |
+
2.060546875,
|
| 3586 |
+
2.021484375,
|
| 3587 |
+
2.107421875,
|
| 3588 |
+
2.07421875,
|
| 3589 |
+
2.1171875,
|
| 3590 |
+
2.08203125,
|
| 3591 |
+
2.318359375,
|
| 3592 |
+
2.212890625,
|
| 3593 |
+
1.962890625,
|
| 3594 |
+
1.923828125,
|
| 3595 |
+
2.21875,
|
| 3596 |
+
2.2578125,
|
| 3597 |
+
2.349609375,
|
| 3598 |
+
2.07421875,
|
| 3599 |
+
2.046875,
|
| 3600 |
+
2.271484375,
|
| 3601 |
+
2.166015625,
|
| 3602 |
+
1.9775390625,
|
| 3603 |
+
1.98828125,
|
| 3604 |
+
2.259765625,
|
| 3605 |
+
2.19140625,
|
| 3606 |
+
2.111328125,
|
| 3607 |
+
2.130859375,
|
| 3608 |
+
2.142578125,
|
| 3609 |
+
2.203125,
|
| 3610 |
+
2.01171875,
|
| 3611 |
+
2.28125,
|
| 3612 |
+
2.193359375,
|
| 3613 |
+
2.083984375,
|
| 3614 |
+
2.08203125,
|
| 3615 |
+
1.986328125,
|
| 3616 |
+
2.12890625,
|
| 3617 |
+
2.1171875,
|
| 3618 |
+
2.37890625,
|
| 3619 |
+
2.142578125,
|
| 3620 |
+
2.013671875,
|
| 3621 |
+
2.04296875,
|
| 3622 |
+
2.193359375,
|
| 3623 |
+
1.974609375,
|
| 3624 |
+
2.02734375,
|
| 3625 |
+
2.1171875,
|
| 3626 |
+
2.3359375,
|
| 3627 |
+
2.1015625,
|
| 3628 |
+
2.03515625,
|
| 3629 |
+
2.041015625,
|
| 3630 |
+
1.916015625,
|
| 3631 |
+
2.201171875,
|
| 3632 |
+
2.28515625,
|
| 3633 |
+
2.185546875,
|
| 3634 |
+
2.072265625,
|
| 3635 |
+
2.02734375,
|
| 3636 |
+
2.07421875,
|
| 3637 |
+
2.1484375,
|
| 3638 |
+
1.953125,
|
| 3639 |
+
1.9296875,
|
| 3640 |
+
1.958984375,
|
| 3641 |
+
1.9716796875,
|
| 3642 |
+
2.025390625,
|
| 3643 |
+
2.03515625,
|
| 3644 |
+
1.98046875,
|
| 3645 |
+
1.98828125,
|
| 3646 |
+
2.130859375,
|
| 3647 |
+
2.068359375,
|
| 3648 |
+
2.111328125,
|
| 3649 |
+
2.078125,
|
| 3650 |
+
1.8232421875,
|
| 3651 |
+
2.236328125,
|
| 3652 |
+
1.9306640625,
|
| 3653 |
+
2.09375,
|
| 3654 |
+
2.03125,
|
| 3655 |
+
1.8896484375,
|
| 3656 |
+
1.919921875,
|
| 3657 |
+
2.109375,
|
| 3658 |
+
2.234375,
|
| 3659 |
+
1.9345703125,
|
| 3660 |
+
1.896484375,
|
| 3661 |
+
2.00390625,
|
| 3662 |
+
2.064453125,
|
| 3663 |
+
2.037109375,
|
| 3664 |
+
2.197265625,
|
| 3665 |
+
2.111328125,
|
| 3666 |
+
1.9951171875,
|
| 3667 |
+
2.11328125,
|
| 3668 |
+
2.232421875,
|
| 3669 |
+
2.255859375,
|
| 3670 |
+
2.134765625,
|
| 3671 |
+
1.986328125,
|
| 3672 |
+
1.83984375,
|
| 3673 |
+
1.9951171875,
|
| 3674 |
+
1.8505859375,
|
| 3675 |
+
2.20703125,
|
| 3676 |
+
1.9267578125,
|
| 3677 |
+
1.9111328125,
|
| 3678 |
+
1.9423828125,
|
| 3679 |
+
1.8310546875,
|
| 3680 |
+
2.078125,
|
| 3681 |
+
1.970703125,
|
| 3682 |
+
2.068359375,
|
| 3683 |
+
2.125,
|
| 3684 |
+
1.9541015625,
|
| 3685 |
+
2.28125,
|
| 3686 |
+
1.9921875,
|
| 3687 |
+
1.8193359375,
|
| 3688 |
+
2.142578125,
|
| 3689 |
+
2.087890625,
|
| 3690 |
+
2.0703125,
|
| 3691 |
+
2.10546875,
|
| 3692 |
+
2.15625,
|
| 3693 |
+
2.15625,
|
| 3694 |
+
1.9462890625,
|
| 3695 |
+
2.01171875,
|
| 3696 |
+
1.9130859375,
|
| 3697 |
+
1.896484375,
|
| 3698 |
+
1.8935546875,
|
| 3699 |
+
2.005859375,
|
| 3700 |
+
2.17578125,
|
| 3701 |
+
2.078125,
|
| 3702 |
+
1.873046875,
|
| 3703 |
+
2.107421875,
|
| 3704 |
+
2.115234375,
|
| 3705 |
+
2.044921875,
|
| 3706 |
+
2.16796875,
|
| 3707 |
+
2.13671875,
|
| 3708 |
+
1.8349609375,
|
| 3709 |
+
2.015625,
|
| 3710 |
+
2.056640625,
|
| 3711 |
+
2.0078125,
|
| 3712 |
+
1.98046875,
|
| 3713 |
+
2.16015625,
|
| 3714 |
+
2.07421875,
|
| 3715 |
+
2.017578125,
|
| 3716 |
+
1.9912109375,
|
| 3717 |
+
2.01953125,
|
| 3718 |
+
1.9560546875,
|
| 3719 |
+
1.9501953125,
|
| 3720 |
+
1.8408203125,
|
| 3721 |
+
1.9189453125,
|
| 3722 |
+
1.90625,
|
| 3723 |
+
1.97265625,
|
| 3724 |
+
2.0234375,
|
| 3725 |
+
1.912109375,
|
| 3726 |
+
2.048828125,
|
| 3727 |
+
2.076171875,
|
| 3728 |
+
2.10546875,
|
| 3729 |
+
1.857421875,
|
| 3730 |
+
2.126953125,
|
| 3731 |
+
1.7783203125,
|
| 3732 |
+
1.92578125,
|
| 3733 |
+
1.9111328125,
|
| 3734 |
+
1.8837890625,
|
| 3735 |
+
2.046875,
|
| 3736 |
+
1.9345703125,
|
| 3737 |
+
1.9345703125,
|
| 3738 |
+
1.6279296875,
|
| 3739 |
+
1.9306640625,
|
| 3740 |
+
1.974609375,
|
| 3741 |
+
1.75390625,
|
| 3742 |
+
1.9423828125,
|
| 3743 |
+
1.9072265625,
|
| 3744 |
+
2.123046875,
|
| 3745 |
+
1.8916015625,
|
| 3746 |
+
1.888671875,
|
| 3747 |
+
1.80859375,
|
| 3748 |
+
2.08984375,
|
| 3749 |
+
2.068359375,
|
| 3750 |
+
1.841796875,
|
| 3751 |
+
1.8212890625,
|
| 3752 |
+
1.96484375,
|
| 3753 |
+
1.744140625,
|
| 3754 |
+
1.9619140625,
|
| 3755 |
+
2.123046875,
|
| 3756 |
+
1.9921875,
|
| 3757 |
+
2.18359375,
|
| 3758 |
+
1.95703125,
|
| 3759 |
+
1.9033203125,
|
| 3760 |
+
1.9580078125,
|
| 3761 |
+
2.119140625,
|
| 3762 |
+
2.0,
|
| 3763 |
+
2.01953125,
|
| 3764 |
+
1.947265625,
|
| 3765 |
+
2.091796875,
|
| 3766 |
+
1.8876953125,
|
| 3767 |
+
2.0703125,
|
| 3768 |
+
2.029296875,
|
| 3769 |
+
2.041015625,
|
| 3770 |
+
1.943359375,
|
| 3771 |
+
1.57421875,
|
| 3772 |
+
2.130859375,
|
| 3773 |
+
1.810546875,
|
| 3774 |
+
1.9853515625,
|
| 3775 |
+
1.865234375,
|
| 3776 |
+
2.060546875,
|
| 3777 |
+
1.677734375,
|
| 3778 |
+
1.9697265625,
|
| 3779 |
+
1.8408203125,
|
| 3780 |
+
1.9814453125,
|
| 3781 |
+
2.005859375,
|
| 3782 |
+
1.875,
|
| 3783 |
+
1.94921875,
|
| 3784 |
+
1.955078125,
|
| 3785 |
+
2.025390625,
|
| 3786 |
+
2.1875,
|
| 3787 |
+
2.033203125,
|
| 3788 |
+
1.8837890625,
|
| 3789 |
+
1.841796875,
|
| 3790 |
+
2.080078125,
|
| 3791 |
+
2.03515625,
|
| 3792 |
+
1.9814453125,
|
| 3793 |
+
1.958984375,
|
| 3794 |
+
1.775390625,
|
| 3795 |
+
2.01171875,
|
| 3796 |
+
2.1171875,
|
| 3797 |
+
1.8251953125,
|
| 3798 |
+
2.103515625,
|
| 3799 |
+
1.8876953125,
|
| 3800 |
+
1.80078125,
|
| 3801 |
+
2.095703125,
|
| 3802 |
+
2.03125,
|
| 3803 |
+
1.755859375,
|
| 3804 |
+
1.87890625,
|
| 3805 |
+
2.048828125,
|
| 3806 |
+
1.892578125,
|
| 3807 |
+
1.7880859375,
|
| 3808 |
+
2.025390625,
|
| 3809 |
+
1.79296875,
|
| 3810 |
+
1.984375,
|
| 3811 |
+
1.806640625,
|
| 3812 |
+
1.927734375,
|
| 3813 |
+
1.8955078125,
|
| 3814 |
+
1.828125,
|
| 3815 |
+
1.84765625,
|
| 3816 |
+
1.880859375,
|
| 3817 |
+
1.81640625,
|
| 3818 |
+
1.72265625,
|
| 3819 |
+
1.9833984375,
|
| 3820 |
+
1.927734375,
|
| 3821 |
+
2.046875,
|
| 3822 |
+
1.7431640625,
|
| 3823 |
+
1.7294921875,
|
| 3824 |
+
1.89453125,
|
| 3825 |
+
1.9912109375,
|
| 3826 |
+
1.650390625,
|
| 3827 |
+
1.927734375,
|
| 3828 |
+
1.87109375,
|
| 3829 |
+
1.818359375,
|
| 3830 |
+
1.7646484375,
|
| 3831 |
+
1.8125,
|
| 3832 |
+
1.701171875,
|
| 3833 |
+
1.9013671875,
|
| 3834 |
+
1.8623046875,
|
| 3835 |
+
1.8974609375,
|
| 3836 |
+
1.8662109375,
|
| 3837 |
+
1.8505859375,
|
| 3838 |
+
1.9462890625,
|
| 3839 |
+
1.9541015625,
|
| 3840 |
+
1.9853515625,
|
| 3841 |
+
1.880859375,
|
| 3842 |
+
1.9013671875,
|
| 3843 |
+
1.6962890625,
|
| 3844 |
+
1.8935546875,
|
| 3845 |
+
1.7626953125,
|
| 3846 |
+
1.7939453125,
|
| 3847 |
+
1.953125,
|
| 3848 |
+
1.9013671875,
|
| 3849 |
+
1.830078125,
|
| 3850 |
+
1.7822265625,
|
| 3851 |
+
1.89453125,
|
| 3852 |
+
1.9775390625,
|
| 3853 |
+
1.9091796875,
|
| 3854 |
+
1.9853515625,
|
| 3855 |
+
1.9814453125,
|
| 3856 |
+
1.8779296875,
|
| 3857 |
+
1.9619140625,
|
| 3858 |
+
1.9150390625,
|
| 3859 |
+
1.841796875,
|
| 3860 |
+
1.9599609375,
|
| 3861 |
+
1.8330078125,
|
| 3862 |
+
1.9140625,
|
| 3863 |
+
1.876953125,
|
| 3864 |
+
1.8193359375,
|
| 3865 |
+
1.765625,
|
| 3866 |
+
1.8671875,
|
| 3867 |
+
2.025390625,
|
| 3868 |
+
1.7880859375,
|
| 3869 |
+
1.6982421875,
|
| 3870 |
+
2.02734375,
|
| 3871 |
+
2.015625,
|
| 3872 |
+
1.8955078125,
|
| 3873 |
+
1.7900390625,
|
| 3874 |
+
1.8427734375,
|
| 3875 |
+
1.7314453125,
|
| 3876 |
+
1.8642578125,
|
| 3877 |
+
1.8212890625,
|
| 3878 |
+
1.841796875,
|
| 3879 |
+
1.833984375,
|
| 3880 |
+
1.9697265625,
|
| 3881 |
+
1.986328125,
|
| 3882 |
+
1.767578125,
|
| 3883 |
+
1.6435546875,
|
| 3884 |
+
1.8759765625,
|
| 3885 |
+
1.6845703125,
|
| 3886 |
+
1.904296875,
|
| 3887 |
+
1.8740234375,
|
| 3888 |
+
1.857421875,
|
| 3889 |
+
1.767578125,
|
| 3890 |
+
1.7255859375,
|
| 3891 |
+
1.76171875,
|
| 3892 |
+
1.8251953125,
|
| 3893 |
+
1.86328125,
|
| 3894 |
+
1.7861328125,
|
| 3895 |
+
1.7431640625,
|
| 3896 |
+
1.8330078125,
|
| 3897 |
+
1.7998046875,
|
| 3898 |
+
2.03515625,
|
| 3899 |
+
1.95703125,
|
| 3900 |
+
1.83203125,
|
| 3901 |
+
1.9892578125,
|
| 3902 |
+
1.8681640625,
|
| 3903 |
+
1.7626953125,
|
| 3904 |
+
1.978515625,
|
| 3905 |
+
1.93359375,
|
| 3906 |
+
1.837890625,
|
| 3907 |
+
1.724609375,
|
| 3908 |
+
1.9921875,
|
| 3909 |
+
1.76953125,
|
| 3910 |
+
1.791015625,
|
| 3911 |
+
1.9111328125,
|
| 3912 |
+
1.744140625,
|
| 3913 |
+
1.8525390625,
|
| 3914 |
+
2.083984375,
|
| 3915 |
+
1.90625,
|
| 3916 |
+
1.712890625,
|
| 3917 |
+
1.7666015625,
|
| 3918 |
+
1.828125,
|
| 3919 |
+
1.732421875,
|
| 3920 |
+
1.8701171875,
|
| 3921 |
+
1.677734375,
|
| 3922 |
+
1.662109375,
|
| 3923 |
+
1.7958984375,
|
| 3924 |
+
1.5283203125,
|
| 3925 |
+
1.94140625,
|
| 3926 |
+
1.576171875,
|
| 3927 |
+
1.8369140625,
|
| 3928 |
+
1.7646484375,
|
| 3929 |
+
1.8720703125,
|
| 3930 |
+
1.78125,
|
| 3931 |
+
1.8193359375,
|
| 3932 |
+
1.7626953125,
|
| 3933 |
+
1.6201171875,
|
| 3934 |
+
1.884765625,
|
| 3935 |
+
1.7578125,
|
| 3936 |
+
1.830078125,
|
| 3937 |
+
1.6669921875,
|
| 3938 |
+
1.7509765625,
|
| 3939 |
+
1.8984375,
|
| 3940 |
+
1.7685546875,
|
| 3941 |
+
1.72265625,
|
| 3942 |
+
1.943359375,
|
| 3943 |
+
1.7255859375,
|
| 3944 |
+
1.666015625,
|
| 3945 |
+
1.873046875,
|
| 3946 |
+
1.796875,
|
| 3947 |
+
1.8154296875,
|
| 3948 |
+
1.638671875,
|
| 3949 |
+
1.8154296875,
|
| 3950 |
+
1.9111328125,
|
| 3951 |
+
1.9228515625,
|
| 3952 |
+
1.677734375,
|
| 3953 |
+
1.66015625,
|
| 3954 |
+
1.7275390625,
|
| 3955 |
+
1.87109375,
|
| 3956 |
+
1.8662109375,
|
| 3957 |
+
1.822265625,
|
| 3958 |
+
1.6083984375,
|
| 3959 |
+
1.91796875,
|
| 3960 |
+
1.7783203125,
|
| 3961 |
+
1.91015625,
|
| 3962 |
+
1.828125,
|
| 3963 |
+
1.7265625,
|
| 3964 |
+
2.009765625,
|
| 3965 |
+
1.90625,
|
| 3966 |
+
1.748046875,
|
| 3967 |
+
1.6796875,
|
| 3968 |
+
1.76171875,
|
| 3969 |
+
1.703125,
|
| 3970 |
+
1.734375,
|
| 3971 |
+
1.8173828125,
|
| 3972 |
+
1.9306640625,
|
| 3973 |
+
1.703125,
|
| 3974 |
+
1.662109375,
|
| 3975 |
+
1.7802734375,
|
| 3976 |
+
1.7822265625,
|
| 3977 |
+
1.525390625,
|
| 3978 |
+
1.771484375,
|
| 3979 |
+
1.921875,
|
| 3980 |
+
1.630859375,
|
| 3981 |
+
1.5302734375,
|
| 3982 |
+
1.771484375,
|
| 3983 |
+
1.76171875,
|
| 3984 |
+
1.7783203125,
|
| 3985 |
+
1.751953125,
|
| 3986 |
+
1.77734375,
|
| 3987 |
+
1.685546875,
|
| 3988 |
+
1.7890625,
|
| 3989 |
+
1.806640625,
|
| 3990 |
+
1.7705078125,
|
| 3991 |
+
1.943359375,
|
| 3992 |
+
1.83984375,
|
| 3993 |
+
1.6904296875,
|
| 3994 |
+
1.5927734375,
|
| 3995 |
+
1.7568359375,
|
| 3996 |
+
1.70703125,
|
| 3997 |
+
1.921875,
|
| 3998 |
+
1.609375,
|
| 3999 |
+
1.6943359375,
|
| 4000 |
+
1.642578125,
|
| 4001 |
+
1.6748046875,
|
| 4002 |
+
1.7685546875,
|
| 4003 |
+
1.71484375,
|
| 4004 |
+
1.74609375,
|
| 4005 |
+
1.85546875,
|
| 4006 |
+
1.7470703125,
|
| 4007 |
+
1.7919921875,
|
| 4008 |
+
1.8349609375,
|
| 4009 |
+
1.7900390625,
|
| 4010 |
+
1.7958984375,
|
| 4011 |
+
1.556640625,
|
| 4012 |
+
1.791015625,
|
| 4013 |
+
1.6025390625,
|
| 4014 |
+
1.9130859375,
|
| 4015 |
+
1.646484375,
|
| 4016 |
+
1.712890625,
|
| 4017 |
+
1.6826171875,
|
| 4018 |
+
2.03515625,
|
| 4019 |
+
1.6689453125,
|
| 4020 |
+
1.720703125,
|
| 4021 |
+
1.6435546875,
|
| 4022 |
+
1.6279296875,
|
| 4023 |
+
1.87109375,
|
| 4024 |
+
1.8681640625,
|
| 4025 |
+
1.8828125,
|
| 4026 |
+
1.751953125,
|
| 4027 |
+
1.8037109375,
|
| 4028 |
+
1.8076171875,
|
| 4029 |
+
1.880859375,
|
| 4030 |
+
1.7353515625,
|
| 4031 |
+
1.7197265625,
|
| 4032 |
+
1.6982421875,
|
| 4033 |
+
1.6826171875,
|
| 4034 |
+
1.890625,
|
| 4035 |
+
1.775390625,
|
| 4036 |
+
1.7529296875,
|
| 4037 |
+
1.6162109375,
|
| 4038 |
+
1.6943359375,
|
| 4039 |
+
1.8701171875,
|
| 4040 |
+
1.6845703125,
|
| 4041 |
+
1.5703125,
|
| 4042 |
+
1.8671875,
|
| 4043 |
+
1.8505859375,
|
| 4044 |
+
1.7607421875,
|
| 4045 |
+
1.6611328125,
|
| 4046 |
+
1.7177734375,
|
| 4047 |
+
1.6962890625,
|
| 4048 |
+
1.912109375,
|
| 4049 |
+
1.8671875,
|
| 4050 |
+
1.6767578125,
|
| 4051 |
+
1.849609375,
|
| 4052 |
+
1.6337890625,
|
| 4053 |
+
1.802734375,
|
| 4054 |
+
1.5712890625,
|
| 4055 |
+
1.91796875,
|
| 4056 |
+
1.7802734375,
|
| 4057 |
+
1.6640625,
|
| 4058 |
+
1.5263671875,
|
| 4059 |
+
1.66796875,
|
| 4060 |
+
1.8076171875,
|
| 4061 |
+
1.830078125,
|
| 4062 |
+
1.7236328125,
|
| 4063 |
+
1.7568359375,
|
| 4064 |
+
1.7392578125,
|
| 4065 |
+
1.720703125,
|
| 4066 |
+
1.6533203125,
|
| 4067 |
+
1.5859375,
|
| 4068 |
+
1.6279296875,
|
| 4069 |
+
1.7861328125,
|
| 4070 |
+
1.8359375,
|
| 4071 |
+
1.7490234375,
|
| 4072 |
+
1.51953125,
|
| 4073 |
+
1.6005859375,
|
| 4074 |
+
1.6611328125,
|
| 4075 |
+
1.580078125,
|
| 4076 |
+
1.732421875,
|
| 4077 |
+
1.5888671875,
|
| 4078 |
+
1.638671875,
|
| 4079 |
+
1.5986328125,
|
| 4080 |
+
1.724609375,
|
| 4081 |
+
1.6865234375,
|
| 4082 |
+
1.541015625,
|
| 4083 |
+
1.7490234375,
|
| 4084 |
+
1.662109375,
|
| 4085 |
+
1.693359375,
|
| 4086 |
+
1.6865234375,
|
| 4087 |
+
1.7861328125,
|
| 4088 |
+
1.78515625,
|
| 4089 |
+
1.6728515625,
|
| 4090 |
+
1.671875,
|
| 4091 |
+
1.8837890625,
|
| 4092 |
+
1.595703125,
|
| 4093 |
+
1.5888671875,
|
| 4094 |
+
1.978515625,
|
| 4095 |
+
1.580078125,
|
| 4096 |
+
1.927734375,
|
| 4097 |
+
1.716796875,
|
| 4098 |
+
1.923828125,
|
| 4099 |
+
1.78515625,
|
| 4100 |
+
1.62890625,
|
| 4101 |
+
1.818359375,
|
| 4102 |
+
1.7041015625,
|
| 4103 |
+
1.748046875,
|
| 4104 |
+
1.6806640625,
|
| 4105 |
+
1.62890625,
|
| 4106 |
+
1.5771484375,
|
| 4107 |
+
1.771484375,
|
| 4108 |
+
1.9775390625,
|
| 4109 |
+
1.73828125,
|
| 4110 |
+
1.7353515625,
|
| 4111 |
+
1.70703125,
|
| 4112 |
+
1.76171875,
|
| 4113 |
+
1.740234375,
|
| 4114 |
+
1.611328125,
|
| 4115 |
+
1.65234375,
|
| 4116 |
+
1.6123046875,
|
| 4117 |
+
1.6923828125,
|
| 4118 |
+
1.552734375,
|
| 4119 |
+
1.853515625,
|
| 4120 |
+
1.724609375,
|
| 4121 |
+
1.68359375,
|
| 4122 |
+
1.64453125,
|
| 4123 |
+
1.5771484375,
|
| 4124 |
+
1.6513671875,
|
| 4125 |
+
1.6123046875,
|
| 4126 |
+
1.6435546875,
|
| 4127 |
+
1.712890625,
|
| 4128 |
+
1.439453125,
|
| 4129 |
+
1.822265625,
|
| 4130 |
+
1.806640625,
|
| 4131 |
+
1.67578125,
|
| 4132 |
+
1.697265625,
|
| 4133 |
+
1.7109375,
|
| 4134 |
+
1.513671875,
|
| 4135 |
+
1.7109375,
|
| 4136 |
+
1.66015625,
|
| 4137 |
+
1.55859375,
|
| 4138 |
+
1.9326171875,
|
| 4139 |
+
1.8271484375,
|
| 4140 |
+
1.708984375,
|
| 4141 |
+
1.560546875,
|
| 4142 |
+
1.650390625,
|
| 4143 |
+
1.6650390625,
|
| 4144 |
+
1.681640625,
|
| 4145 |
+
1.4736328125,
|
| 4146 |
+
1.5341796875,
|
| 4147 |
+
1.5703125,
|
| 4148 |
+
1.9296875,
|
| 4149 |
+
1.6640625,
|
| 4150 |
+
1.80859375,
|
| 4151 |
+
1.4658203125,
|
| 4152 |
+
1.771484375,
|
| 4153 |
+
1.58984375,
|
| 4154 |
+
1.63671875,
|
| 4155 |
+
1.6767578125,
|
| 4156 |
+
1.5478515625,
|
| 4157 |
+
1.583984375,
|
| 4158 |
+
1.703125,
|
| 4159 |
+
1.54296875,
|
| 4160 |
+
1.7216796875,
|
| 4161 |
+
1.6962890625,
|
| 4162 |
+
1.7119140625,
|
| 4163 |
+
1.8515625,
|
| 4164 |
+
1.6318359375,
|
| 4165 |
+
1.73828125,
|
| 4166 |
+
1.80859375,
|
| 4167 |
+
1.6181640625,
|
| 4168 |
+
1.5859375,
|
| 4169 |
+
1.6494140625,
|
| 4170 |
+
1.572265625,
|
| 4171 |
+
1.6708984375,
|
| 4172 |
+
1.732421875,
|
| 4173 |
+
1.515625,
|
| 4174 |
+
1.638671875,
|
| 4175 |
+
1.7900390625,
|
| 4176 |
+
1.685546875,
|
| 4177 |
+
1.810546875,
|
| 4178 |
+
1.853515625,
|
| 4179 |
+
1.763671875,
|
| 4180 |
+
1.83984375,
|
| 4181 |
+
1.76953125,
|
| 4182 |
+
1.626953125,
|
| 4183 |
+
1.654296875,
|
| 4184 |
+
1.6552734375,
|
| 4185 |
+
1.517578125,
|
| 4186 |
+
1.779296875,
|
| 4187 |
+
1.76171875,
|
| 4188 |
+
1.74609375,
|
| 4189 |
+
1.6708984375,
|
| 4190 |
+
1.6650390625,
|
| 4191 |
+
1.6875,
|
| 4192 |
+
1.7890625,
|
| 4193 |
+
1.615234375,
|
| 4194 |
+
1.794921875,
|
| 4195 |
+
1.734375,
|
| 4196 |
+
1.4580078125,
|
| 4197 |
+
1.4296875,
|
| 4198 |
+
1.5009765625,
|
| 4199 |
+
1.658203125,
|
| 4200 |
+
1.73046875,
|
| 4201 |
+
1.6357421875,
|
| 4202 |
+
1.474609375,
|
| 4203 |
+
1.802734375,
|
| 4204 |
+
1.6796875,
|
| 4205 |
+
1.8583984375,
|
| 4206 |
+
1.466796875,
|
| 4207 |
+
1.7568359375,
|
| 4208 |
+
1.8037109375,
|
| 4209 |
+
1.6396484375,
|
| 4210 |
+
1.5966796875,
|
| 4211 |
+
1.5458984375,
|
| 4212 |
+
1.7431640625,
|
| 4213 |
+
1.740234375,
|
| 4214 |
+
1.7216796875,
|
| 4215 |
+
1.7490234375,
|
| 4216 |
+
1.755859375,
|
| 4217 |
+
1.57421875,
|
| 4218 |
+
1.75390625,
|
| 4219 |
+
1.828125,
|
| 4220 |
+
1.7197265625,
|
| 4221 |
+
1.498046875,
|
| 4222 |
+
1.755859375,
|
| 4223 |
+
1.6630859375,
|
| 4224 |
+
1.6875,
|
| 4225 |
+
1.6337890625,
|
| 4226 |
+
1.6533203125,
|
| 4227 |
+
1.63671875,
|
| 4228 |
+
1.7451171875,
|
| 4229 |
+
1.7177734375,
|
| 4230 |
+
1.6982421875,
|
| 4231 |
+
1.6953125,
|
| 4232 |
+
1.634765625,
|
| 4233 |
+
1.552734375,
|
| 4234 |
+
1.8212890625,
|
| 4235 |
+
1.6474609375,
|
| 4236 |
+
1.615234375,
|
| 4237 |
+
1.70703125,
|
| 4238 |
+
1.61328125,
|
| 4239 |
+
1.7119140625,
|
| 4240 |
+
1.666015625,
|
| 4241 |
+
1.7802734375,
|
| 4242 |
+
1.744140625,
|
| 4243 |
+
1.37109375,
|
| 4244 |
+
1.5908203125,
|
| 4245 |
+
1.716796875,
|
| 4246 |
+
1.6669921875,
|
| 4247 |
+
1.6552734375,
|
| 4248 |
+
1.5439453125,
|
| 4249 |
+
1.6015625,
|
| 4250 |
+
1.7109375,
|
| 4251 |
+
1.8408203125,
|
| 4252 |
+
1.7041015625,
|
| 4253 |
+
1.5478515625,
|
| 4254 |
+
1.681640625,
|
| 4255 |
+
1.8154296875,
|
| 4256 |
+
1.6513671875,
|
| 4257 |
+
1.6455078125,
|
| 4258 |
+
1.7529296875,
|
| 4259 |
+
1.7294921875,
|
| 4260 |
+
1.640625,
|
| 4261 |
+
1.537109375,
|
| 4262 |
+
1.580078125,
|
| 4263 |
+
1.591796875,
|
| 4264 |
+
1.7958984375,
|
| 4265 |
+
1.6806640625,
|
| 4266 |
+
1.734375,
|
| 4267 |
+
1.7216796875,
|
| 4268 |
+
1.73046875,
|
| 4269 |
+
1.7578125,
|
| 4270 |
+
1.5712890625,
|
| 4271 |
+
1.697265625,
|
| 4272 |
+
1.6123046875,
|
| 4273 |
+
1.5380859375,
|
| 4274 |
+
1.515625,
|
| 4275 |
+
1.720703125,
|
| 4276 |
+
1.5517578125,
|
| 4277 |
+
1.5849609375,
|
| 4278 |
+
1.5625,
|
| 4279 |
+
1.5576171875,
|
| 4280 |
+
1.634765625,
|
| 4281 |
+
1.72265625,
|
| 4282 |
+
1.5205078125,
|
| 4283 |
+
1.6669921875,
|
| 4284 |
+
1.6474609375,
|
| 4285 |
+
1.66796875,
|
| 4286 |
+
1.52734375,
|
| 4287 |
+
1.8486328125,
|
| 4288 |
+
1.703125,
|
| 4289 |
+
1.7021484375,
|
| 4290 |
+
1.34765625,
|
| 4291 |
+
1.6005859375,
|
| 4292 |
+
1.73828125,
|
| 4293 |
+
1.5341796875,
|
| 4294 |
+
1.7255859375,
|
| 4295 |
+
1.6220703125,
|
| 4296 |
+
1.6025390625,
|
| 4297 |
+
1.61328125,
|
| 4298 |
+
1.56640625,
|
| 4299 |
+
1.5908203125,
|
| 4300 |
+
1.5810546875,
|
| 4301 |
+
1.5654296875,
|
| 4302 |
+
1.6005859375,
|
| 4303 |
+
1.8056640625,
|
| 4304 |
+
1.662109375,
|
| 4305 |
+
1.6875,
|
| 4306 |
+
1.6982421875,
|
| 4307 |
+
1.7900390625,
|
| 4308 |
+
1.6494140625,
|
| 4309 |
+
1.7109375,
|
| 4310 |
+
1.7412109375,
|
| 4311 |
+
1.607421875,
|
| 4312 |
+
1.7119140625,
|
| 4313 |
+
1.6962890625,
|
| 4314 |
+
1.5380859375,
|
| 4315 |
+
1.6630859375,
|
| 4316 |
+
1.7666015625,
|
| 4317 |
+
1.8623046875,
|
| 4318 |
+
1.654296875,
|
| 4319 |
+
1.6416015625,
|
| 4320 |
+
1.7236328125,
|
| 4321 |
+
1.7822265625,
|
| 4322 |
+
1.623046875,
|
| 4323 |
+
1.6826171875,
|
| 4324 |
+
1.7607421875,
|
| 4325 |
+
1.6943359375,
|
| 4326 |
+
1.8603515625,
|
| 4327 |
+
1.8115234375,
|
| 4328 |
+
1.8232421875,
|
| 4329 |
+
1.513671875,
|
| 4330 |
+
1.58984375,
|
| 4331 |
+
1.6318359375,
|
| 4332 |
+
1.6201171875,
|
| 4333 |
+
1.6484375,
|
| 4334 |
+
1.7197265625,
|
| 4335 |
+
1.6162109375,
|
| 4336 |
+
1.7333984375,
|
| 4337 |
+
1.58203125,
|
| 4338 |
+
1.6708984375,
|
| 4339 |
+
1.7783203125,
|
| 4340 |
+
1.4453125,
|
| 4341 |
+
1.767578125,
|
| 4342 |
+
1.6474609375,
|
| 4343 |
+
1.5224609375,
|
| 4344 |
+
1.732421875,
|
| 4345 |
+
1.8046875,
|
| 4346 |
+
1.6435546875,
|
| 4347 |
+
1.7177734375,
|
| 4348 |
+
1.6728515625,
|
| 4349 |
+
1.7900390625,
|
| 4350 |
+
1.5234375,
|
| 4351 |
+
1.6845703125,
|
| 4352 |
+
1.447265625,
|
| 4353 |
+
1.8232421875,
|
| 4354 |
+
1.6533203125,
|
| 4355 |
+
1.6669921875,
|
| 4356 |
+
1.693359375,
|
| 4357 |
+
1.7880859375,
|
| 4358 |
+
1.7978515625,
|
| 4359 |
+
1.685546875,
|
| 4360 |
+
1.8330078125,
|
| 4361 |
+
1.6962890625,
|
| 4362 |
+
1.71875,
|
| 4363 |
+
1.759765625,
|
| 4364 |
+
1.9287109375,
|
| 4365 |
+
1.6728515625,
|
| 4366 |
+
1.5888671875,
|
| 4367 |
+
1.62890625,
|
| 4368 |
+
1.6533203125,
|
| 4369 |
+
1.7080078125,
|
| 4370 |
+
1.5400390625,
|
| 4371 |
+
1.693359375,
|
| 4372 |
+
1.6875,
|
| 4373 |
+
1.4736328125,
|
| 4374 |
+
1.767578125,
|
| 4375 |
+
1.53515625,
|
| 4376 |
+
1.6806640625,
|
| 4377 |
+
1.7978515625,
|
| 4378 |
+
1.66015625,
|
| 4379 |
+
1.6884765625,
|
| 4380 |
+
1.7431640625,
|
| 4381 |
+
1.638671875,
|
| 4382 |
+
1.6962890625,
|
| 4383 |
+
1.6728515625,
|
| 4384 |
+
1.791015625,
|
| 4385 |
+
1.6591796875,
|
| 4386 |
+
1.7080078125,
|
| 4387 |
+
1.6005859375,
|
| 4388 |
+
1.474609375,
|
| 4389 |
+
1.4794921875,
|
| 4390 |
+
1.6708984375,
|
| 4391 |
+
1.7783203125,
|
| 4392 |
+
1.8330078125,
|
| 4393 |
+
1.6640625,
|
| 4394 |
+
1.8017578125,
|
| 4395 |
+
1.6083984375,
|
| 4396 |
+
1.7529296875,
|
| 4397 |
+
1.5673828125,
|
| 4398 |
+
1.5380859375,
|
| 4399 |
+
1.8388671875,
|
| 4400 |
+
1.6337890625,
|
| 4401 |
+
1.623046875,
|
| 4402 |
+
1.6923828125,
|
| 4403 |
+
1.7138671875,
|
| 4404 |
+
1.791015625,
|
| 4405 |
+
1.6640625,
|
| 4406 |
+
1.5625,
|
| 4407 |
+
1.7568359375,
|
| 4408 |
+
1.73046875,
|
| 4409 |
+
1.6005859375,
|
| 4410 |
+
1.6318359375,
|
| 4411 |
+
1.7294921875,
|
| 4412 |
+
1.7705078125,
|
| 4413 |
+
1.2666015625,
|
| 4414 |
+
1.5478515625,
|
| 4415 |
+
1.5068359375,
|
| 4416 |
+
1.76171875,
|
| 4417 |
+
1.6171875,
|
| 4418 |
+
1.6982421875,
|
| 4419 |
+
1.697265625,
|
| 4420 |
+
1.5400390625,
|
| 4421 |
+
1.7060546875,
|
| 4422 |
+
1.7353515625,
|
| 4423 |
+
1.5732421875,
|
| 4424 |
+
1.8232421875,
|
| 4425 |
+
1.78125,
|
| 4426 |
+
1.728515625,
|
| 4427 |
+
1.6240234375,
|
| 4428 |
+
1.708984375,
|
| 4429 |
+
1.6708984375,
|
| 4430 |
+
1.587890625,
|
| 4431 |
+
1.8095703125,
|
| 4432 |
+
1.5234375,
|
| 4433 |
+
1.69140625,
|
| 4434 |
+
1.5947265625,
|
| 4435 |
+
1.7822265625,
|
| 4436 |
+
1.6962890625,
|
| 4437 |
+
1.4306640625,
|
| 4438 |
+
1.7451171875,
|
| 4439 |
+
1.6533203125,
|
| 4440 |
+
1.5927734375,
|
| 4441 |
+
1.5888671875,
|
| 4442 |
+
1.603515625,
|
| 4443 |
+
1.6298828125,
|
| 4444 |
+
1.591796875,
|
| 4445 |
+
1.6826171875,
|
| 4446 |
+
1.650390625,
|
| 4447 |
+
1.6337890625,
|
| 4448 |
+
1.6962890625,
|
| 4449 |
+
1.6943359375,
|
| 4450 |
+
1.6328125,
|
| 4451 |
+
1.6474609375,
|
| 4452 |
+
1.6298828125,
|
| 4453 |
+
1.6533203125,
|
| 4454 |
+
1.494140625,
|
| 4455 |
+
1.5556640625,
|
| 4456 |
+
1.6533203125,
|
| 4457 |
+
1.4619140625,
|
| 4458 |
+
1.7392578125,
|
| 4459 |
+
1.5576171875,
|
| 4460 |
+
1.5693359375,
|
| 4461 |
+
1.7080078125,
|
| 4462 |
+
1.443359375,
|
| 4463 |
+
1.638671875,
|
| 4464 |
+
1.701171875,
|
| 4465 |
+
1.6416015625,
|
| 4466 |
+
1.61328125,
|
| 4467 |
+
1.5888671875,
|
| 4468 |
+
1.490234375,
|
| 4469 |
+
1.6171875,
|
| 4470 |
+
1.744140625,
|
| 4471 |
+
1.5244140625,
|
| 4472 |
+
1.716796875,
|
| 4473 |
+
1.6962890625,
|
| 4474 |
+
1.71484375,
|
| 4475 |
+
1.6806640625,
|
| 4476 |
+
1.6962890625,
|
| 4477 |
+
1.623046875,
|
| 4478 |
+
1.7958984375,
|
| 4479 |
+
1.734375,
|
| 4480 |
+
1.6533203125,
|
| 4481 |
+
1.6962890625,
|
| 4482 |
+
1.5791015625,
|
| 4483 |
+
1.7060546875,
|
| 4484 |
+
1.6171875,
|
| 4485 |
+
1.6962890625,
|
| 4486 |
+
1.5830078125,
|
| 4487 |
+
1.732421875,
|
| 4488 |
+
1.7255859375,
|
| 4489 |
+
1.6484375,
|
| 4490 |
+
1.6025390625,
|
| 4491 |
+
1.4814453125,
|
| 4492 |
+
1.701171875,
|
| 4493 |
+
1.5341796875,
|
| 4494 |
+
1.5810546875,
|
| 4495 |
+
1.7255859375,
|
| 4496 |
+
1.765625,
|
| 4497 |
+
1.8017578125,
|
| 4498 |
+
1.67578125,
|
| 4499 |
+
1.671875,
|
| 4500 |
+
1.6240234375,
|
| 4501 |
+
1.6015625,
|
| 4502 |
+
1.505859375,
|
| 4503 |
+
1.6875,
|
| 4504 |
+
1.6376953125,
|
| 4505 |
+
1.6748046875,
|
| 4506 |
+
1.67578125
|
| 4507 |
+
]
|
| 4508 |
+
}
|
circuit_analysis/results/cpr_cmd_results.json
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"results": [
|
| 3 |
+
{
|
| 4 |
+
"prompt": "The capital of France is",
|
| 5 |
+
"target_token": " Paris",
|
| 6 |
+
"m_N": -0.153564453125,
|
| 7 |
+
"m_empty": -10.2265625,
|
| 8 |
+
"curve_k": [
|
| 9 |
+
0.0,
|
| 10 |
+
0.0009930096056473734,
|
| 11 |
+
0.002035178102663429,
|
| 12 |
+
0.005083029367521703,
|
| 13 |
+
0.010116899843674726,
|
| 14 |
+
0.020007668787053515,
|
| 15 |
+
0.05001425607849692,
|
| 16 |
+
0.10017598883109988,
|
| 17 |
+
0.20007668787053515,
|
| 18 |
+
0.5000639065587793,
|
| 19 |
+
1.0
|
| 20 |
+
],
|
| 21 |
+
"curve_f": [
|
| 22 |
+
0.0,
|
| 23 |
+
0.020940885624954556,
|
| 24 |
+
0.04188177124990911,
|
| 25 |
+
0.08066118907389903,
|
| 26 |
+
0.11944060689788895,
|
| 27 |
+
0.2322887127656996,
|
| 28 |
+
0.7083060665551758,
|
| 29 |
+
0.8935747352092877,
|
| 30 |
+
0.8023461547783514,
|
| 31 |
+
0.8479119707215396,
|
| 32 |
+
1.0
|
| 33 |
+
],
|
| 34 |
+
"CPR": 0.8509204971370973,
|
| 35 |
+
"CMD": 0.14907950286290272
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"prompt": "def factorial(n):",
|
| 39 |
+
"target_token": " #",
|
| 40 |
+
"m_N": -0.498779296875,
|
| 41 |
+
"m_empty": -1.625,
|
| 42 |
+
"curve_k": [
|
| 43 |
+
0.0,
|
| 44 |
+
0.0010854585147512593,
|
| 45 |
+
0.0021831131925896113,
|
| 46 |
+
0.0050492115180564194,
|
| 47 |
+
0.010086226873025746,
|
| 48 |
+
0.02017245374605149,
|
| 49 |
+
0.05017501494029978,
|
| 50 |
+
0.10000853731416097,
|
| 51 |
+
0.20012684009610576,
|
| 52 |
+
0.5000304904077177,
|
| 53 |
+
1.0
|
| 54 |
+
],
|
| 55 |
+
"curve_f": [
|
| 56 |
+
0.0,
|
| 57 |
+
0.0,
|
| 58 |
+
0.0,
|
| 59 |
+
0.0,
|
| 60 |
+
0.0,
|
| 61 |
+
0.0,
|
| 62 |
+
0.0,
|
| 63 |
+
0.4985909386516367,
|
| 64 |
+
0.716236722306525,
|
| 65 |
+
0.0,
|
| 66 |
+
1.0
|
| 67 |
+
],
|
| 68 |
+
"CPR": 0.43062227169181266,
|
| 69 |
+
"CMD": 0.5693777283081873
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"prompt": "The literary device in the phrase 'The wind whispered through the trees' is",
|
| 73 |
+
"target_token": " person",
|
| 74 |
+
"m_N": -0.452392578125,
|
| 75 |
+
"m_empty": -1.4228515625,
|
| 76 |
+
"curve_k": [
|
| 77 |
+
0.0,
|
| 78 |
+
0.0010364040898343655,
|
| 79 |
+
0.002022097276190597,
|
| 80 |
+
0.005020379444335275,
|
| 81 |
+
0.010018572868398867,
|
| 82 |
+
0.0200054514221239,
|
| 83 |
+
0.050054831164385735,
|
| 84 |
+
0.10001140995328257,
|
| 85 |
+
0.20002915876949992,
|
| 86 |
+
0.5000538803349455,
|
| 87 |
+
1.0
|
| 88 |
+
],
|
| 89 |
+
"curve_f": [
|
| 90 |
+
0.0,
|
| 91 |
+
0.0,
|
| 92 |
+
0.0,
|
| 93 |
+
0.0,
|
| 94 |
+
0.0,
|
| 95 |
+
0.0,
|
| 96 |
+
0.0,
|
| 97 |
+
0.0,
|
| 98 |
+
0.0,
|
| 99 |
+
0.0,
|
| 100 |
+
1.0
|
| 101 |
+
],
|
| 102 |
+
"CPR": 0.24997305983252727,
|
| 103 |
+
"CMD": 0.7500269401674727
|
| 104 |
+
}
|
| 105 |
+
],
|
| 106 |
+
"average_CPR": 0.510505276220479,
|
| 107 |
+
"average_CMD": 0.4894947237795209
|
| 108 |
+
}
|
circuit_analysis/results/feature_interpretations_cache/feature_interpretations.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
circuit_analysis/results/offline_circuit_metrics.json
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"prompts_ran": [
|
| 3 |
+
"The capital of France is",
|
| 4 |
+
"def factorial(n):",
|
| 5 |
+
"The literary device in the phrase 'The wind whispered through the trees' is"
|
| 6 |
+
],
|
| 7 |
+
"aggregate_summary": {
|
| 8 |
+
"targeted": {
|
| 9 |
+
"count": 384,
|
| 10 |
+
"avg_probability_change": 0.0037064552307128906,
|
| 11 |
+
"avg_abs_probability_change": 0.017832597096761067,
|
| 12 |
+
"std_probability_change": 0.024085208735490346,
|
| 13 |
+
"avg_logit_change": 0.015276943643887838,
|
| 14 |
+
"avg_abs_logit_change": 0.10990743339061737,
|
| 15 |
+
"std_logit_change": 0.14790562906131785,
|
| 16 |
+
"avg_kl_divergence": 0.0,
|
| 17 |
+
"avg_entropy_change": 0.0,
|
| 18 |
+
"avg_hidden_state_delta_norm": 7.304225921630859,
|
| 19 |
+
"avg_hidden_state_relative_change": 0.07329477507168775,
|
| 20 |
+
"flip_rate": 0.1640625,
|
| 21 |
+
"count_flipped": 63
|
| 22 |
+
},
|
| 23 |
+
"random_baseline": {
|
| 24 |
+
"count": 15,
|
| 25 |
+
"avg_probability_change": 0.00411376953125,
|
| 26 |
+
"avg_abs_probability_change": 0.0045206705729166664,
|
| 27 |
+
"std_probability_change": 0.008715364389044073,
|
| 28 |
+
"avg_logit_change": 0.025040690104166666,
|
| 29 |
+
"avg_abs_logit_change": 0.0387451171875,
|
| 30 |
+
"std_logit_change": 0.056757731194411305,
|
| 31 |
+
"avg_kl_divergence": 0.0,
|
| 32 |
+
"avg_entropy_change": 0.0,
|
| 33 |
+
"avg_hidden_state_delta_norm": 2.098746744791667,
|
| 34 |
+
"avg_hidden_state_relative_change": 0.020347025628623817,
|
| 35 |
+
"flip_rate": 0.13333333333333333,
|
| 36 |
+
"count_flipped": 2
|
| 37 |
+
},
|
| 38 |
+
"path": {
|
| 39 |
+
"count": 9,
|
| 40 |
+
"avg_probability_change": 0.05379909939236111,
|
| 41 |
+
"avg_abs_probability_change": 0.05379909939236111,
|
| 42 |
+
"std_probability_change": 0.03592616044133087,
|
| 43 |
+
"avg_logit_change": 0.1922607421875,
|
| 44 |
+
"avg_abs_logit_change": 0.1967095269097222,
|
| 45 |
+
"std_logit_change": 0.21084131002100345,
|
| 46 |
+
"avg_kl_divergence": 0.0,
|
| 47 |
+
"avg_entropy_change": 0.0,
|
| 48 |
+
"avg_hidden_state_delta_norm": 9.847222222222221,
|
| 49 |
+
"avg_hidden_state_relative_change": 0.09728426029029577,
|
| 50 |
+
"flip_rate": 0.2222222222222222,
|
| 51 |
+
"count_flipped": 2
|
| 52 |
+
},
|
| 53 |
+
"random_path_baseline": {
|
| 54 |
+
"count": 10,
|
| 55 |
+
"avg_probability_change": -0.00140380859375,
|
| 56 |
+
"avg_abs_probability_change": 0.01318359375,
|
| 57 |
+
"std_probability_change": 0.017461901132504055,
|
| 58 |
+
"avg_logit_change": 0.012884521484375,
|
| 59 |
+
"avg_abs_logit_change": 0.075469970703125,
|
| 60 |
+
"std_logit_change": 0.08951169079108637,
|
| 61 |
+
"avg_kl_divergence": 0.0,
|
| 62 |
+
"avg_entropy_change": 0.0,
|
| 63 |
+
"avg_hidden_state_delta_norm": 6.292578125,
|
| 64 |
+
"avg_hidden_state_relative_change": 0.06215471324319018,
|
| 65 |
+
"flip_rate": 0.2,
|
| 66 |
+
"count_flipped": 2
|
| 67 |
+
},
|
| 68 |
+
"target_minus_random_abs_probability_change": 0.0133119265238444,
|
| 69 |
+
"target_flip_rate_minus_random": 0.03072916666666667,
|
| 70 |
+
"path_minus_random_abs_probability_change": 0.04061550564236111,
|
| 71 |
+
"path_flip_rate_minus_random": 0.0222222222222222
|
| 72 |
+
},
|
| 73 |
+
"per_prompt": {
|
| 74 |
+
"prompt_1": {
|
| 75 |
+
"prompt": "The capital of France is",
|
| 76 |
+
"summary_statistics": {
|
| 77 |
+
"targeted": {
|
| 78 |
+
"count": 128,
|
| 79 |
+
"avg_probability_change": 0.004764556884765625,
|
| 80 |
+
"avg_abs_probability_change": 0.025264739990234375,
|
| 81 |
+
"std_probability_change": 0.03195406092166983,
|
| 82 |
+
"avg_logit_change": 0.005943402647972107,
|
| 83 |
+
"avg_abs_logit_change": 0.08632268011569977,
|
| 84 |
+
"std_logit_change": 0.1132019008155424,
|
| 85 |
+
"avg_kl_divergence": 0.0,
|
| 86 |
+
"avg_entropy_change": 0.0,
|
| 87 |
+
"avg_hidden_state_delta_norm": 6.6026153564453125,
|
| 88 |
+
"avg_hidden_state_relative_change": 0.070100760253486,
|
| 89 |
+
"flip_rate": 0.0,
|
| 90 |
+
"count_flipped": 0
|
| 91 |
+
},
|
| 92 |
+
"random_baseline": {
|
| 93 |
+
"count": 5,
|
| 94 |
+
"avg_probability_change": 0.006494140625,
|
| 95 |
+
"avg_abs_probability_change": 0.006884765625,
|
| 96 |
+
"std_probability_change": 0.012063215323471172,
|
| 97 |
+
"avg_logit_change": 0.0234619140625,
|
| 98 |
+
"avg_abs_logit_change": 0.0234619140625,
|
| 99 |
+
"std_logit_change": 0.042261115942436644,
|
| 100 |
+
"avg_kl_divergence": 0.0,
|
| 101 |
+
"avg_entropy_change": 0.0,
|
| 102 |
+
"avg_hidden_state_delta_norm": 1.45966796875,
|
| 103 |
+
"avg_hidden_state_relative_change": 0.015497470139185163,
|
| 104 |
+
"flip_rate": 0.0,
|
| 105 |
+
"count_flipped": 0
|
| 106 |
+
},
|
| 107 |
+
"path": {
|
| 108 |
+
"count": 3,
|
| 109 |
+
"avg_probability_change": 0.07967122395833333,
|
| 110 |
+
"avg_abs_probability_change": 0.07967122395833333,
|
| 111 |
+
"std_probability_change": 0.013695590325716009,
|
| 112 |
+
"avg_logit_change": 0.07661946614583333,
|
| 113 |
+
"avg_abs_logit_change": 0.07661946614583333,
|
| 114 |
+
"std_logit_change": 0.03182210693328131,
|
| 115 |
+
"avg_kl_divergence": 0.0,
|
| 116 |
+
"avg_entropy_change": 0.0,
|
| 117 |
+
"avg_hidden_state_delta_norm": 7.229166666666667,
|
| 118 |
+
"avg_hidden_state_relative_change": 0.0767529307667144,
|
| 119 |
+
"flip_rate": 0.0,
|
| 120 |
+
"count_flipped": 0
|
| 121 |
+
},
|
| 122 |
+
"random_path_baseline": {
|
| 123 |
+
"count": 4,
|
| 124 |
+
"avg_probability_change": -0.00994873046875,
|
| 125 |
+
"avg_abs_probability_change": 0.01690673828125,
|
| 126 |
+
"std_probability_change": 0.02002948420533245,
|
| 127 |
+
"avg_logit_change": 0.0011444091796875,
|
| 128 |
+
"avg_abs_logit_change": 0.0316314697265625,
|
| 129 |
+
"std_logit_change": 0.0377090926751411,
|
| 130 |
+
"avg_kl_divergence": 0.0,
|
| 131 |
+
"avg_entropy_change": 0.0,
|
| 132 |
+
"avg_hidden_state_delta_norm": 4.0029296875,
|
| 133 |
+
"avg_hidden_state_relative_change": 0.04249958526829463,
|
| 134 |
+
"flip_rate": 0.0,
|
| 135 |
+
"count_flipped": 0
|
| 136 |
+
},
|
| 137 |
+
"target_minus_random_abs_probability_change": 0.018379974365234374,
|
| 138 |
+
"target_flip_rate_minus_random": 0.0,
|
| 139 |
+
"path_minus_random_abs_probability_change": 0.06276448567708333,
|
| 140 |
+
"path_flip_rate_minus_random": 0.0
|
| 141 |
+
},
|
| 142 |
+
"counts": {
|
| 143 |
+
"targeted": 128,
|
| 144 |
+
"random": 5,
|
| 145 |
+
"path": 3,
|
| 146 |
+
"random_path": 4
|
| 147 |
+
},
|
| 148 |
+
"top_targeted": [
|
| 149 |
+
{
|
| 150 |
+
"label": "feature_208",
|
| 151 |
+
"probability_change": -0.1171875,
|
| 152 |
+
"logit_change": -0.22528076171875,
|
| 153 |
+
"flip": false
|
| 154 |
+
},
|
| 155 |
+
{
|
| 156 |
+
"label": "feature_378",
|
| 157 |
+
"probability_change": 0.0859375,
|
| 158 |
+
"logit_change": 0.137939453125,
|
| 159 |
+
"flip": false
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"label": "feature_467",
|
| 163 |
+
"probability_change": 0.077880859375,
|
| 164 |
+
"logit_change": 0.0877685546875,
|
| 165 |
+
"flip": false
|
| 166 |
+
},
|
| 167 |
+
{
|
| 168 |
+
"label": "feature_134",
|
| 169 |
+
"probability_change": 0.072021484375,
|
| 170 |
+
"logit_change": 0.224365234375,
|
| 171 |
+
"flip": false
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"label": "feature_142",
|
| 175 |
+
"probability_change": 0.07177734375,
|
| 176 |
+
"logit_change": 0.24560546875,
|
| 177 |
+
"flip": false
|
| 178 |
+
}
|
| 179 |
+
],
|
| 180 |
+
"top_paths": [
|
| 181 |
+
{
|
| 182 |
+
"label": "Token '\u0120capital' \u2192 Feature L0F322 (Word/alphabetic tokens) \u2192 Feature L1F44 (Word/alphabetic tokens) \u2192 Feature L2F320 (Word/alphabetic tokens) \u2192 Feature L3F338 (Word/alphabetic tokens) \u2192 Output",
|
| 183 |
+
"probability_change": 0.08935546875,
|
| 184 |
+
"logit_change": 0.09912109375,
|
| 185 |
+
"flip": false
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"label": "Token 'The' \u2192 Feature L0F322 (Word/alphabetic tokens) \u2192 Feature L1F44 (Word/alphabetic tokens) \u2192 Feature L2F320 (Word/alphabetic tokens) \u2192 Feature L3F338 (Word/alphabetic tokens) \u2192 Output",
|
| 189 |
+
"probability_change": 0.08935546875,
|
| 190 |
+
"logit_change": 0.09912109375,
|
| 191 |
+
"flip": false
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"label": "Token '\u0120capital' \u2192 Feature L0F322 (Word/alphabetic tokens) \u2192 Feature L1F44 (Word/alphabetic tokens) \u2192 Feature L2F320 (Word/alphabetic tokens) \u2192 Feature L3F367 (Word/alphabetic tokens) \u2192 Output",
|
| 195 |
+
"probability_change": 0.060302734375,
|
| 196 |
+
"logit_change": 0.0316162109375,
|
| 197 |
+
"flip": false
|
| 198 |
+
}
|
| 199 |
+
]
|
| 200 |
+
},
|
| 201 |
+
"prompt_2": {
|
| 202 |
+
"prompt": "def factorial(n):",
|
| 203 |
+
"summary_statistics": {
|
| 204 |
+
"targeted": {
|
| 205 |
+
"count": 128,
|
| 206 |
+
"avg_probability_change": 0.003498077392578125,
|
| 207 |
+
"avg_abs_probability_change": 0.016864776611328125,
|
| 208 |
+
"std_probability_change": 0.02251701216357027,
|
| 209 |
+
"avg_logit_change": -0.004237174987792969,
|
| 210 |
+
"avg_abs_logit_change": 0.10776042938232422,
|
| 211 |
+
"std_logit_change": 0.14078546527424055,
|
| 212 |
+
"avg_kl_divergence": 0.0,
|
| 213 |
+
"avg_entropy_change": 0.0,
|
| 214 |
+
"avg_hidden_state_delta_norm": 8.8001708984375,
|
| 215 |
+
"avg_hidden_state_relative_change": 0.08148306387366683,
|
| 216 |
+
"flip_rate": 0.0,
|
| 217 |
+
"count_flipped": 0
|
| 218 |
+
},
|
| 219 |
+
"random_baseline": {
|
| 220 |
+
"count": 5,
|
| 221 |
+
"avg_probability_change": 0.00400390625,
|
| 222 |
+
"avg_abs_probability_change": 0.00478515625,
|
| 223 |
+
"std_probability_change": 0.008191782057277022,
|
| 224 |
+
"avg_logit_change": 0.030224609375,
|
| 225 |
+
"avg_abs_logit_change": 0.071337890625,
|
| 226 |
+
"std_logit_change": 0.08533968984750907,
|
| 227 |
+
"avg_kl_divergence": 0.0,
|
| 228 |
+
"avg_entropy_change": 0.0,
|
| 229 |
+
"avg_hidden_state_delta_norm": 4.21953125,
|
| 230 |
+
"avg_hidden_state_relative_change": 0.039069733795934536,
|
| 231 |
+
"flip_rate": 0.0,
|
| 232 |
+
"count_flipped": 0
|
| 233 |
+
},
|
| 234 |
+
"path": {
|
| 235 |
+
"count": 3,
|
| 236 |
+
"avg_probability_change": 0.07674153645833333,
|
| 237 |
+
"avg_abs_probability_change": 0.07674153645833333,
|
| 238 |
+
"std_probability_change": 0.009609931026867956,
|
| 239 |
+
"avg_logit_change": 0.4598795572916667,
|
| 240 |
+
"avg_abs_logit_change": 0.4598795572916667,
|
| 241 |
+
"std_logit_change": 0.13856714917783255,
|
| 242 |
+
"avg_kl_divergence": 0.0,
|
| 243 |
+
"avg_entropy_change": 0.0,
|
| 244 |
+
"avg_hidden_state_delta_norm": 15.4140625,
|
| 245 |
+
"avg_hidden_state_relative_change": 0.1427228009246044,
|
| 246 |
+
"flip_rate": 0.3333333333333333,
|
| 247 |
+
"count_flipped": 1
|
| 248 |
+
},
|
| 249 |
+
"random_path_baseline": {
|
| 250 |
+
"count": 3,
|
| 251 |
+
"avg_probability_change": 0.00439453125,
|
| 252 |
+
"avg_abs_probability_change": 0.013671875,
|
| 253 |
+
"std_probability_change": 0.016523589485978502,
|
| 254 |
+
"avg_logit_change": -0.005452473958333333,
|
| 255 |
+
"avg_abs_logit_change": 0.11726888020833333,
|
| 256 |
+
"std_logit_change": 0.12771601543377464,
|
| 257 |
+
"avg_kl_divergence": 0.0,
|
| 258 |
+
"avg_entropy_change": 0.0,
|
| 259 |
+
"avg_hidden_state_delta_norm": 10.997395833333334,
|
| 260 |
+
"avg_hidden_state_relative_change": 0.10182773919658801,
|
| 261 |
+
"flip_rate": 0.0,
|
| 262 |
+
"count_flipped": 0
|
| 263 |
+
},
|
| 264 |
+
"target_minus_random_abs_probability_change": 0.012079620361328125,
|
| 265 |
+
"target_flip_rate_minus_random": 0.0,
|
| 266 |
+
"path_minus_random_abs_probability_change": 0.06306966145833333,
|
| 267 |
+
"path_flip_rate_minus_random": 0.3333333333333333
|
| 268 |
+
},
|
| 269 |
+
"counts": {
|
| 270 |
+
"targeted": 128,
|
| 271 |
+
"random": 5,
|
| 272 |
+
"path": 3,
|
| 273 |
+
"random_path": 3
|
| 274 |
+
},
|
| 275 |
+
"top_targeted": [
|
| 276 |
+
{
|
| 277 |
+
"label": "feature_72",
|
| 278 |
+
"probability_change": -0.102294921875,
|
| 279 |
+
"logit_change": -0.555908203125,
|
| 280 |
+
"flip": false
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"label": "feature_240",
|
| 284 |
+
"probability_change": 0.056884765625,
|
| 285 |
+
"logit_change": 0.272216796875,
|
| 286 |
+
"flip": false
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"label": "feature_79",
|
| 290 |
+
"probability_change": -0.0556640625,
|
| 291 |
+
"logit_change": -0.1513671875,
|
| 292 |
+
"flip": false
|
| 293 |
+
},
|
| 294 |
+
{
|
| 295 |
+
"label": "feature_235",
|
| 296 |
+
"probability_change": 0.05224609375,
|
| 297 |
+
"logit_change": 0.252685546875,
|
| 298 |
+
"flip": false
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"label": "feature_439",
|
| 302 |
+
"probability_change": 0.050048828125,
|
| 303 |
+
"logit_change": 0.223388671875,
|
| 304 |
+
"flip": false
|
| 305 |
+
}
|
| 306 |
+
],
|
| 307 |
+
"top_paths": [
|
| 308 |
+
{
|
| 309 |
+
"label": "Token '\u0120factorial' \u2192 Feature L0F246 (Identifying punctuation and articles) \u2192 Feature L1F439 (Word/alphabetic tokens) \u2192 Feature L2F203 (Mixed/polysemantic feature) \u2192 Feature L3F24 (Mixed/polysemantic feature) \u2192 Output",
|
| 310 |
+
"probability_change": 0.09033203125,
|
| 311 |
+
"logit_change": 0.263916015625,
|
| 312 |
+
"flip": true
|
| 313 |
+
},
|
| 314 |
+
{
|
| 315 |
+
"label": "Token '\u0120factorial' \u2192 Feature L0F246 (Identifying punctuation and articles) \u2192 Feature L1F439 (Word/alphabetic tokens) \u2192 Feature L2F203 (Mixed/polysemantic feature) \u2192 Feature L3F187 (Mixed/polysemantic feature) \u2192 Output",
|
| 316 |
+
"probability_change": 0.0699462890625,
|
| 317 |
+
"logit_change": 0.557861328125,
|
| 318 |
+
"flip": false
|
| 319 |
+
},
|
| 320 |
+
{
|
| 321 |
+
"label": "Token 'def' \u2192 Feature L0F246 (Identifying punctuation and articles) \u2192 Feature L1F439 (Word/alphabetic tokens) \u2192 Feature L2F203 (Mixed/polysemantic feature) \u2192 Feature L3F187 (Mixed/polysemantic feature) \u2192 Output",
|
| 322 |
+
"probability_change": 0.0699462890625,
|
| 323 |
+
"logit_change": 0.557861328125,
|
| 324 |
+
"flip": false
|
| 325 |
+
}
|
| 326 |
+
]
|
| 327 |
+
},
|
| 328 |
+
"prompt_3": {
|
| 329 |
+
"prompt": "The literary device in the phrase 'The wind whispered through the trees' is",
|
| 330 |
+
"summary_statistics": {
|
| 331 |
+
"targeted": {
|
| 332 |
+
"count": 128,
|
| 333 |
+
"avg_probability_change": 0.002856731414794922,
|
| 334 |
+
"avg_abs_probability_change": 0.011368274688720703,
|
| 335 |
+
"std_probability_change": 0.014502722583680286,
|
| 336 |
+
"avg_logit_change": 0.044124603271484375,
|
| 337 |
+
"avg_abs_logit_change": 0.13563919067382812,
|
| 338 |
+
"std_logit_change": 0.17802501078177962,
|
| 339 |
+
"avg_kl_divergence": 0.0,
|
| 340 |
+
"avg_entropy_change": 0.0,
|
| 341 |
+
"avg_hidden_state_delta_norm": 6.509891510009766,
|
| 342 |
+
"avg_hidden_state_relative_change": 0.06830050108791044,
|
| 343 |
+
"flip_rate": 0.4921875,
|
| 344 |
+
"count_flipped": 63
|
| 345 |
+
},
|
| 346 |
+
"random_baseline": {
|
| 347 |
+
"count": 5,
|
| 348 |
+
"avg_probability_change": 0.00184326171875,
|
| 349 |
+
"avg_abs_probability_change": 0.00189208984375,
|
| 350 |
+
"std_probability_change": 0.00210067367193147,
|
| 351 |
+
"avg_logit_change": 0.021435546875,
|
| 352 |
+
"avg_abs_logit_change": 0.021435546875,
|
| 353 |
+
"std_logit_change": 0.02351792840670642,
|
| 354 |
+
"avg_kl_divergence": 0.0,
|
| 355 |
+
"avg_entropy_change": 0.0,
|
| 356 |
+
"avg_hidden_state_delta_norm": 0.617041015625,
|
| 357 |
+
"avg_hidden_state_relative_change": 0.006473872950751749,
|
| 358 |
+
"flip_rate": 0.4,
|
| 359 |
+
"count_flipped": 2
|
| 360 |
+
},
|
| 361 |
+
"path": {
|
| 362 |
+
"count": 3,
|
| 363 |
+
"avg_probability_change": 0.004984537760416667,
|
| 364 |
+
"avg_abs_probability_change": 0.004984537760416667,
|
| 365 |
+
"std_probability_change": 0.003682847818679935,
|
| 366 |
+
"avg_logit_change": 0.040283203125,
|
| 367 |
+
"avg_abs_logit_change": 0.053629557291666664,
|
| 368 |
+
"std_logit_change": 0.07112499849825625,
|
| 369 |
+
"avg_kl_divergence": 0.0,
|
| 370 |
+
"avg_entropy_change": 0.0,
|
| 371 |
+
"avg_hidden_state_delta_norm": 6.8984375,
|
| 372 |
+
"avg_hidden_state_relative_change": 0.0723770491795685,
|
| 373 |
+
"flip_rate": 0.3333333333333333,
|
| 374 |
+
"count_flipped": 1
|
| 375 |
+
},
|
| 376 |
+
"random_path_baseline": {
|
| 377 |
+
"count": 3,
|
| 378 |
+
"avg_probability_change": 0.004191080729166667,
|
| 379 |
+
"avg_abs_probability_change": 0.007731119791666667,
|
| 380 |
+
"std_probability_change": 0.0067955519556561735,
|
| 381 |
+
"avg_logit_change": 0.046875,
|
| 382 |
+
"avg_abs_logit_change": 0.09212239583333333,
|
| 383 |
+
"std_logit_change": 0.08261409961169389,
|
| 384 |
+
"avg_kl_divergence": 0.0,
|
| 385 |
+
"avg_entropy_change": 0.0,
|
| 386 |
+
"avg_hidden_state_delta_norm": 4.640625,
|
| 387 |
+
"avg_hidden_state_relative_change": 0.04868852458965311,
|
| 388 |
+
"flip_rate": 0.6666666666666666,
|
| 389 |
+
"count_flipped": 2
|
| 390 |
+
},
|
| 391 |
+
"target_minus_random_abs_probability_change": 0.009476184844970703,
|
| 392 |
+
"target_flip_rate_minus_random": 0.09218749999999998,
|
| 393 |
+
"path_minus_random_abs_probability_change": -0.00274658203125,
|
| 394 |
+
"path_flip_rate_minus_random": -0.3333333333333333
|
| 395 |
+
},
|
| 396 |
+
"counts": {
|
| 397 |
+
"targeted": 128,
|
| 398 |
+
"random": 5,
|
| 399 |
+
"path": 3,
|
| 400 |
+
"random_path": 3
|
| 401 |
+
},
|
| 402 |
+
"top_targeted": [
|
| 403 |
+
{
|
| 404 |
+
"label": "feature_26",
|
| 405 |
+
"probability_change": 0.0413818359375,
|
| 406 |
+
"logit_change": 0.641357421875,
|
| 407 |
+
"flip": true
|
| 408 |
+
},
|
| 409 |
+
{
|
| 410 |
+
"label": "feature_103",
|
| 411 |
+
"probability_change": -0.03997802734375,
|
| 412 |
+
"logit_change": -0.37274169921875,
|
| 413 |
+
"flip": false
|
| 414 |
+
},
|
| 415 |
+
{
|
| 416 |
+
"label": "feature_32",
|
| 417 |
+
"probability_change": 0.03857421875,
|
| 418 |
+
"logit_change": 0.449462890625,
|
| 419 |
+
"flip": true
|
| 420 |
+
},
|
| 421 |
+
{
|
| 422 |
+
"label": "feature_52",
|
| 423 |
+
"probability_change": 0.03411865234375,
|
| 424 |
+
"logit_change": 0.370849609375,
|
| 425 |
+
"flip": true
|
| 426 |
+
},
|
| 427 |
+
{
|
| 428 |
+
"label": "feature_294",
|
| 429 |
+
"probability_change": 0.03387451171875,
|
| 430 |
+
"logit_change": 0.537353515625,
|
| 431 |
+
"flip": true
|
| 432 |
+
}
|
| 433 |
+
],
|
| 434 |
+
"top_paths": [
|
| 435 |
+
{
|
| 436 |
+
"label": "Token '\u0120literary' \u2192 Feature L0F434 (Mixed/polysemantic feature) \u2192 Feature L1F56 (Mixed/polysemantic feature) \u2192 Feature L2F62 (Mixed/polysemantic feature) \u2192 Feature L3F386 (Mixed/polysemantic feature) \u2192 Output",
|
| 437 |
+
"probability_change": 0.01019287109375,
|
| 438 |
+
"logit_change": 0.140869140625,
|
| 439 |
+
"flip": true
|
| 440 |
+
},
|
| 441 |
+
{
|
| 442 |
+
"label": "Token '\u0120literary' \u2192 Feature L0F434 (Mixed/polysemantic feature) \u2192 Feature L1F56 (Mixed/polysemantic feature) \u2192 Feature L2F62 (Mixed/polysemantic feature) \u2192 Feature L3F157 (Mixed/polysemantic feature) \u2192 Output",
|
| 443 |
+
"probability_change": 0.00238037109375,
|
| 444 |
+
"logit_change": -0.010009765625,
|
| 445 |
+
"flip": false
|
| 446 |
+
},
|
| 447 |
+
{
|
| 448 |
+
"label": "Token '\u0120device' \u2192 Feature L0F434 (Mixed/polysemantic feature) \u2192 Feature L1F56 (Mixed/polysemantic feature) \u2192 Feature L2F62 (Mixed/polysemantic feature) \u2192 Feature L3F157 (Mixed/polysemantic feature) \u2192 Output",
|
| 449 |
+
"probability_change": 0.00238037109375,
|
| 450 |
+
"logit_change": -0.010009765625,
|
| 451 |
+
"flip": false
|
| 452 |
+
}
|
| 453 |
+
]
|
| 454 |
+
}
|
| 455 |
+
},
|
| 456 |
+
"config": {
|
| 457 |
+
"model_path": "./models/OLMo-2-1124-7B",
|
| 458 |
+
"max_seq_length": 512,
|
| 459 |
+
"n_features_per_layer": 512,
|
| 460 |
+
"sparsity_lambda": 0.001,
|
| 461 |
+
"reconstruction_loss_weight": 1.0,
|
| 462 |
+
"batch_size": 8,
|
| 463 |
+
"learning_rate": 0.0001,
|
| 464 |
+
"training_steps": 1000,
|
| 465 |
+
"device": "mps",
|
| 466 |
+
"pruning_threshold": 0.8,
|
| 467 |
+
"intervention_strength": 5.0,
|
| 468 |
+
"qwen_api_config": null,
|
| 469 |
+
"max_ablation_experiments": null,
|
| 470 |
+
"ablation_top_k_tokens": 5,
|
| 471 |
+
"ablation_features_per_layer": 4,
|
| 472 |
+
"summary_max_layers": null,
|
| 473 |
+
"summary_features_per_layer": 2,
|
| 474 |
+
"random_baseline_trials": 5,
|
| 475 |
+
"random_baseline_features": 1,
|
| 476 |
+
"random_baseline_seed": 1234,
|
| 477 |
+
"path_ablation_top_k": 3,
|
| 478 |
+
"random_path_baseline_trials": 5,
|
| 479 |
+
"graph_max_features_per_layer": 40,
|
| 480 |
+
"graph_feature_activation_threshold": 0.01,
|
| 481 |
+
"graph_edge_weight_threshold": 0.003,
|
| 482 |
+
"graph_max_edges_per_node": 20
|
| 483 |
+
}
|
| 484 |
+
}
|
circuit_analysis/train_clt_and_plot.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This script trains the Cross-Layer Transcoder (CLT) and plots its training loss.
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
import numpy as np
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
import json
|
| 9 |
+
import logging
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 12 |
+
from typing import Dict, List, Tuple
|
| 13 |
+
from dataclasses import dataclass
|
| 14 |
+
from tqdm import tqdm
|
| 15 |
+
import os
|
| 16 |
+
import random
|
| 17 |
+
import argparse
|
| 18 |
+
import glob
|
| 19 |
+
import itertools
|
| 20 |
+
from torch.optim.lr_scheduler import CosineAnnealingLR
|
| 21 |
+
|
| 22 |
+
# --- Fix import path ---
|
| 23 |
+
import sys
|
| 24 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 25 |
+
from utilities.utils import set_seed
|
| 26 |
+
|
| 27 |
+
# --- Constants ---
|
| 28 |
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
| 29 |
+
RESULTS_DIR = Path(__file__).parent / "results"
|
| 30 |
+
CLT_SAVE_PATH = Path(__file__).parent / "models" / "clt_model.pth"
|
| 31 |
+
STATS_SAVE_PATH = RESULTS_DIR / "clt_training_stats.json"
|
| 32 |
+
PLOT_SAVE_PATH = RESULTS_DIR / "clt_training_loss.png"
|
| 33 |
+
DOLMA_DIR = PROJECT_ROOT / "influence_tracer" / "dolma_dataset_sample_1.6v"
|
| 34 |
+
|
| 35 |
+
# Configure logging.
|
| 36 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 37 |
+
logger = logging.getLogger(__name__)
|
| 38 |
+
|
| 39 |
+
# Set the device for training.
|
| 40 |
+
if torch.backends.mps.is_available():
|
| 41 |
+
DEVICE = torch.device("mps")
|
| 42 |
+
logger.info("Using MPS (Metal Performance Shaders) for GPU acceleration")
|
| 43 |
+
elif torch.cuda.is_available():
|
| 44 |
+
DEVICE = torch.device("cuda")
|
| 45 |
+
logger.info("Using CUDA for GPU acceleration")
|
| 46 |
+
else:
|
| 47 |
+
DEVICE = torch.device("cpu")
|
| 48 |
+
logger.info("Using CPU")
|
| 49 |
+
|
| 50 |
+
@dataclass
|
| 51 |
+
class AttributionGraphConfig:
|
| 52 |
+
# Configuration for building the attribution graph.
|
| 53 |
+
model_path: str = "./models/OLMo-2-1124-7B"
|
| 54 |
+
max_seq_length: int = 128
|
| 55 |
+
n_features_per_layer: int = 512 # Back to 512 due to memory constraints
|
| 56 |
+
sparsity_lambda: float = 1e-3 # Reduced from 0.01 for L1
|
| 57 |
+
reconstruction_loss_weight: float = 1.0
|
| 58 |
+
batch_size: int = 16 # Can be higher with 512 features
|
| 59 |
+
learning_rate: float = 3e-4 # Increased from 1e-4
|
| 60 |
+
training_steps: int = 1500 # Increased from 500
|
| 61 |
+
device: str = str(DEVICE)
|
| 62 |
+
|
| 63 |
+
class JumpReLU(nn.Module):
|
| 64 |
+
# JumpReLU activation function.
|
| 65 |
+
def __init__(self, threshold: float = 0.0):
|
| 66 |
+
super().__init__()
|
| 67 |
+
self.threshold = threshold
|
| 68 |
+
def forward(self, x):
|
| 69 |
+
return F.relu(x - self.threshold)
|
| 70 |
+
|
| 71 |
+
class CrossLayerTranscoder(nn.Module):
|
| 72 |
+
# The Cross-Layer Transcoder (CLT) model.
|
| 73 |
+
def __init__(self, model_config: Dict, clt_config: AttributionGraphConfig):
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.config = clt_config
|
| 76 |
+
self.model_config = model_config
|
| 77 |
+
self.n_layers = model_config['num_hidden_layers']
|
| 78 |
+
self.hidden_size = model_config['hidden_size']
|
| 79 |
+
self.n_features = clt_config.n_features_per_layer
|
| 80 |
+
|
| 81 |
+
self.encoders = nn.ModuleList([
|
| 82 |
+
nn.Linear(self.hidden_size, self.n_features, bias=False)
|
| 83 |
+
for _ in range(self.n_layers)
|
| 84 |
+
])
|
| 85 |
+
self.decoders = nn.ModuleDict()
|
| 86 |
+
for source_layer in range(self.n_layers):
|
| 87 |
+
for target_layer in range(source_layer, self.n_layers):
|
| 88 |
+
key = f"{source_layer}_to_{target_layer}"
|
| 89 |
+
self.decoders[key] = nn.Linear(self.n_features, self.hidden_size, bias=False)
|
| 90 |
+
self.activation = JumpReLU(threshold=0.0)
|
| 91 |
+
self._init_weights()
|
| 92 |
+
|
| 93 |
+
def _init_weights(self):
|
| 94 |
+
for module in self.modules():
|
| 95 |
+
if isinstance(module, nn.Linear):
|
| 96 |
+
# Improved initialization (Xavier/Glorot)
|
| 97 |
+
nn.init.xavier_uniform_(module.weight, gain=0.1)
|
| 98 |
+
|
| 99 |
+
def encode(self, layer_idx: int, residual_activations: torch.Tensor) -> torch.Tensor:
|
| 100 |
+
return self.activation(self.encoders[layer_idx](residual_activations))
|
| 101 |
+
|
| 102 |
+
def decode(self, source_layer: int, target_layer: int, feature_activations: torch.Tensor) -> torch.Tensor:
|
| 103 |
+
key = f"{source_layer}_to_{target_layer}"
|
| 104 |
+
return self.decoders[key](feature_activations)
|
| 105 |
+
|
| 106 |
+
def forward(self, residual_activations: List[torch.Tensor]) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:
|
| 107 |
+
feature_activations = [self.encode(i, r) for i, r in enumerate(residual_activations)]
|
| 108 |
+
reconstructed_mlp_outputs = []
|
| 109 |
+
for target_layer in range(self.n_layers):
|
| 110 |
+
reconstruction = torch.zeros_like(residual_activations[target_layer])
|
| 111 |
+
for source_layer in range(target_layer + 1):
|
| 112 |
+
reconstruction += self.decode(source_layer, target_layer, feature_activations[source_layer])
|
| 113 |
+
reconstructed_mlp_outputs.append(reconstruction)
|
| 114 |
+
return feature_activations, reconstructed_mlp_outputs
|
| 115 |
+
|
| 116 |
+
class TrainingPipeline:
|
| 117 |
+
# A pipeline for training the CLT model.
|
| 118 |
+
def __init__(self, config: AttributionGraphConfig):
|
| 119 |
+
self.config = config
|
| 120 |
+
self.device = torch.device(config.device)
|
| 121 |
+
logger.info(f"Loading OLMo model from {config.model_path}")
|
| 122 |
+
self.tokenizer = AutoTokenizer.from_pretrained(config.model_path)
|
| 123 |
+
|
| 124 |
+
# Configure model loading based on the device.
|
| 125 |
+
model_args = {'torch_dtype': torch.float16 if "cpu" not in config.device else torch.float32}
|
| 126 |
+
if "cuda" in config.device:
|
| 127 |
+
model_args['device_map'] = "auto"
|
| 128 |
+
|
| 129 |
+
self.model = AutoModelForCausalLM.from_pretrained(config.model_path, **model_args).to(self.device)
|
| 130 |
+
|
| 131 |
+
if self.tokenizer.pad_token is None:
|
| 132 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 133 |
+
|
| 134 |
+
model_config = self.model.config.to_dict()
|
| 135 |
+
self.clt = CrossLayerTranscoder(model_config, config).to(self.device)
|
| 136 |
+
logger.info("Training Pipeline initialized successfully")
|
| 137 |
+
|
| 138 |
+
def load_dolma_data(self, buffer_size=10000):
|
| 139 |
+
"""Generator that yields text samples from the Dolma dataset with shuffling."""
|
| 140 |
+
json_files = glob.glob(str(DOLMA_DIR / "*.json"))
|
| 141 |
+
if not json_files:
|
| 142 |
+
logger.error(f"No JSON files found in {DOLMA_DIR}")
|
| 143 |
+
raise FileNotFoundError(f"No training data found in {DOLMA_DIR}")
|
| 144 |
+
|
| 145 |
+
logger.info(f"Found {len(json_files)} training files in {DOLMA_DIR}")
|
| 146 |
+
random.shuffle(json_files)
|
| 147 |
+
|
| 148 |
+
buffer = []
|
| 149 |
+
|
| 150 |
+
while True:
|
| 151 |
+
for file_path in json_files:
|
| 152 |
+
try:
|
| 153 |
+
# Use a larger buffer size for reading
|
| 154 |
+
with open(file_path, 'r', buffering=8192*1024) as f:
|
| 155 |
+
for line in f:
|
| 156 |
+
try:
|
| 157 |
+
doc = json.loads(line)
|
| 158 |
+
text = doc.get('text', '')
|
| 159 |
+
if len(text) > 100: # Filter very short texts
|
| 160 |
+
buffer.append(text)
|
| 161 |
+
|
| 162 |
+
if len(buffer) >= buffer_size:
|
| 163 |
+
random.shuffle(buffer)
|
| 164 |
+
yield from buffer
|
| 165 |
+
buffer = []
|
| 166 |
+
except json.JSONDecodeError:
|
| 167 |
+
continue
|
| 168 |
+
except Exception as e:
|
| 169 |
+
logger.warning(f"Error reading {file_path}: {e}")
|
| 170 |
+
|
| 171 |
+
# Yield remaining items in buffer
|
| 172 |
+
if buffer:
|
| 173 |
+
random.shuffle(buffer)
|
| 174 |
+
yield from buffer
|
| 175 |
+
buffer = []
|
| 176 |
+
|
| 177 |
+
# Shuffle and restart for next epoch
|
| 178 |
+
random.shuffle(json_files)
|
| 179 |
+
|
| 180 |
+
def train_clt(self) -> Dict:
|
| 181 |
+
# Trains the Cross-Layer Transcoder.
|
| 182 |
+
logger.info("Starting CLT training...")
|
| 183 |
+
optimizer = torch.optim.Adam(self.clt.parameters(), lr=self.config.learning_rate)
|
| 184 |
+
scheduler = CosineAnnealingLR(optimizer, T_max=self.config.training_steps, eta_min=1e-6)
|
| 185 |
+
|
| 186 |
+
stats = {
|
| 187 |
+
'reconstruction_losses': [],
|
| 188 |
+
'sparsity_losses': [],
|
| 189 |
+
'total_losses': []
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
self.clt.train()
|
| 193 |
+
progress_bar = tqdm(range(self.config.training_steps), desc="Training CLT")
|
| 194 |
+
|
| 195 |
+
data_generator = self.load_dolma_data()
|
| 196 |
+
|
| 197 |
+
for step in progress_bar:
|
| 198 |
+
# Sample a batch of texts.
|
| 199 |
+
batch_texts = []
|
| 200 |
+
try:
|
| 201 |
+
for _ in range(self.config.batch_size):
|
| 202 |
+
batch_texts.append(next(data_generator))
|
| 203 |
+
except StopIteration:
|
| 204 |
+
logger.warning("Data generator ran out of data!")
|
| 205 |
+
break
|
| 206 |
+
|
| 207 |
+
# Tokenize all texts at once (True batch processing)
|
| 208 |
+
inputs = self.tokenizer(
|
| 209 |
+
batch_texts,
|
| 210 |
+
return_tensors="pt",
|
| 211 |
+
padding=True,
|
| 212 |
+
truncation=True,
|
| 213 |
+
max_length=self.config.max_seq_length
|
| 214 |
+
).to(self.device)
|
| 215 |
+
|
| 216 |
+
with torch.no_grad():
|
| 217 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 218 |
+
hidden_states = outputs.hidden_states[1:]
|
| 219 |
+
|
| 220 |
+
feature_activations, reconstructed_outputs = self.clt(hidden_states)
|
| 221 |
+
|
| 222 |
+
# --- Loss calculation ---
|
| 223 |
+
# Recon loss: Sum over batch, then average later implicitly via batch division or explicit mean
|
| 224 |
+
# To match previous scale: sum of MSE per sample
|
| 225 |
+
recon_loss = sum(F.mse_loss(pred, target) for target, pred in zip(hidden_states, reconstructed_outputs))
|
| 226 |
+
|
| 227 |
+
# L1 Sparsity Loss (Better than tanh)
|
| 228 |
+
sparsity_loss = sum(torch.mean(torch.abs(features)) for features in feature_activations)
|
| 229 |
+
|
| 230 |
+
loss = (self.config.reconstruction_loss_weight * recon_loss +
|
| 231 |
+
self.config.sparsity_lambda * sparsity_loss)
|
| 232 |
+
|
| 233 |
+
# Backward pass
|
| 234 |
+
optimizer.zero_grad()
|
| 235 |
+
loss.backward()
|
| 236 |
+
|
| 237 |
+
# Gradient Clipping (New)
|
| 238 |
+
torch.nn.utils.clip_grad_norm_(self.clt.parameters(), max_norm=1.0)
|
| 239 |
+
|
| 240 |
+
optimizer.step()
|
| 241 |
+
scheduler.step() # Learning Rate Schedule
|
| 242 |
+
|
| 243 |
+
# Normalize losses for logging (divide by number of layers approx or keep as sum)
|
| 244 |
+
# Previous code accumulated and then divided by batch size.
|
| 245 |
+
# Here F.mse_loss is mean over batch by default?
|
| 246 |
+
# F.mse_loss(input, target) -> mean over all elements.
|
| 247 |
+
# So recon_loss is sum(mean_mse_per_layer).
|
| 248 |
+
# This is fine, scale is consistent.
|
| 249 |
+
|
| 250 |
+
stats['total_losses'].append(loss.item())
|
| 251 |
+
stats['reconstruction_losses'].append(recon_loss.item())
|
| 252 |
+
stats['sparsity_losses'].append(sparsity_loss.item())
|
| 253 |
+
|
| 254 |
+
if step % 10 == 0:
|
| 255 |
+
progress_bar.set_postfix({
|
| 256 |
+
"Total": f"{loss.item():.4f}",
|
| 257 |
+
"Recon": f"{recon_loss.item():.4f}",
|
| 258 |
+
"Sparsity": f"{sparsity_loss.item():.4f}",
|
| 259 |
+
"LR": f"{scheduler.get_last_lr()[0]:.2e}"
|
| 260 |
+
})
|
| 261 |
+
|
| 262 |
+
logger.info("CLT training completed.")
|
| 263 |
+
return stats
|
| 264 |
+
|
| 265 |
+
def save_clt(self, path: str):
|
| 266 |
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
| 267 |
+
torch.save(self.clt.state_dict(), path)
|
| 268 |
+
logger.info(f"CLT model saved to {path}")
|
| 269 |
+
|
| 270 |
+
def plot_training_stats(stats_path: str, save_path: str):
|
| 271 |
+
# Loads training stats and generates a plot.
|
| 272 |
+
logger.info(f"Loading training stats from {stats_path}")
|
| 273 |
+
with open(stats_path, 'r') as f:
|
| 274 |
+
stats = json.load(f)
|
| 275 |
+
|
| 276 |
+
plt.style.use('seaborn-v0_8-darkgrid')
|
| 277 |
+
fig, ax1 = plt.subplots(figsize=(12, 6))
|
| 278 |
+
|
| 279 |
+
steps = range(len(stats['total_losses']))
|
| 280 |
+
|
| 281 |
+
color = 'tab:red'
|
| 282 |
+
ax1.set_xlabel('Training Steps')
|
| 283 |
+
ax1.set_ylabel('Total & Reconstruction Loss', color=color)
|
| 284 |
+
ax1.plot(steps, stats['total_losses'], color=color, label='Total Loss', alpha=0.9, linewidth=2)
|
| 285 |
+
ax1.plot(steps, stats['reconstruction_losses'], color='tab:blue', linestyle='--', label='Reconstruction Loss', alpha=1.0)
|
| 286 |
+
ax1.tick_params(axis='y', labelcolor=color)
|
| 287 |
+
ax1.grid(True, which='major', linestyle='--', linewidth='0.5', color='grey')
|
| 288 |
+
|
| 289 |
+
ax2 = ax1.twinx()
|
| 290 |
+
color2 = 'tab:green'
|
| 291 |
+
ax2.set_ylabel('Sparsity Loss (L1)', color=color2)
|
| 292 |
+
ax2.plot(steps, stats['sparsity_losses'], color=color2, linestyle=':', label='Sparsity Loss')
|
| 293 |
+
ax2.tick_params(axis='y', labelcolor=color2)
|
| 294 |
+
ax2.grid(True, which='major', linestyle=':', linewidth='0.5', color='darkgrey')
|
| 295 |
+
|
| 296 |
+
# Combine legends into a single box.
|
| 297 |
+
lines, labels = ax1.get_legend_handles_labels()
|
| 298 |
+
lines2, labels2 = ax2.get_legend_handles_labels()
|
| 299 |
+
ax2.legend(lines + lines2, labels + labels2, loc='center right', frameon=True, facecolor='white', framealpha=0.8, edgecolor='grey')
|
| 300 |
+
|
| 301 |
+
logger.info(f"Full training plot saved to {save_path}")
|
| 302 |
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
| 303 |
+
plt.close()
|
| 304 |
+
|
| 305 |
+
def main():
|
| 306 |
+
# Main function to handle training and plotting.
|
| 307 |
+
|
| 308 |
+
# --- Argument Parser ---
|
| 309 |
+
parser = argparse.ArgumentParser(description="Train CLT model and/or plot training stats.")
|
| 310 |
+
parser.add_argument(
|
| 311 |
+
'--skip-training',
|
| 312 |
+
action='store_true',
|
| 313 |
+
help="Skip the training process and only generate the plot from existing stats."
|
| 314 |
+
)
|
| 315 |
+
args = parser.parse_args()
|
| 316 |
+
|
| 317 |
+
# Set a seed for reproducibility.
|
| 318 |
+
set_seed()
|
| 319 |
+
|
| 320 |
+
# Config is now updated with improvements
|
| 321 |
+
config = AttributionGraphConfig()
|
| 322 |
+
|
| 323 |
+
try:
|
| 324 |
+
pipeline = TrainingPipeline(config)
|
| 325 |
+
logger.info("Training Pipeline initialized successfully")
|
| 326 |
+
|
| 327 |
+
if not args.skip_training:
|
| 328 |
+
# Train the Cross-Layer Transcoder using Dolma dataset
|
| 329 |
+
training_stats = pipeline.train_clt()
|
| 330 |
+
|
| 331 |
+
os.makedirs(RESULTS_DIR, exist_ok=True)
|
| 332 |
+
|
| 333 |
+
with open(STATS_SAVE_PATH, 'w') as f:
|
| 334 |
+
json.dump(training_stats, f, indent=2)
|
| 335 |
+
logger.info(f"Saved training stats to {STATS_SAVE_PATH}")
|
| 336 |
+
|
| 337 |
+
pipeline.save_clt(CLT_SAVE_PATH)
|
| 338 |
+
else:
|
| 339 |
+
logger.info("--skip-training flag is set. Loading existing stats for plotting.")
|
| 340 |
+
|
| 341 |
+
# Always plot, using either new or existing stats.
|
| 342 |
+
if os.path.exists(STATS_SAVE_PATH):
|
| 343 |
+
plot_training_stats(STATS_SAVE_PATH, PLOT_SAVE_PATH)
|
| 344 |
+
else:
|
| 345 |
+
logger.error(f"Statistics file not found at {STATS_SAVE_PATH}. Cannot generate plot. Run training first.")
|
| 346 |
+
|
| 347 |
+
print("\n🎉 CLT training and plotting completed successfully!")
|
| 348 |
+
|
| 349 |
+
except Exception as e:
|
| 350 |
+
logger.error(f"❌ Error during execution: {e}", exc_info=True)
|
| 351 |
+
|
| 352 |
+
if __name__ == "__main__":
|
| 353 |
+
main()
|
function_vectors/data/multilingual_function_categories.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
function_vectors/data/visualizations/de_pca_3d_categories_layer_-1.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
function_vectors/data/visualizations/en_pca_3d_categories_layer_-1.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
function_vectors/function_vectors_page.py
ADDED
|
@@ -0,0 +1,1845 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import base64
|
| 5 |
+
import sys
|
| 6 |
+
import numpy as np
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
import torch
|
| 9 |
+
import pandas as pd
|
| 10 |
+
from utilities.localization import tr
|
| 11 |
+
import plotly.graph_objects as go
|
| 12 |
+
from sklearn.decomposition import PCA
|
| 13 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 14 |
+
from typing import Dict, List
|
| 15 |
+
import requests
|
| 16 |
+
import json
|
| 17 |
+
from PIL import Image
|
| 18 |
+
from io import BytesIO
|
| 19 |
+
import base64
|
| 20 |
+
import markdown
|
| 21 |
+
from datetime import datetime
|
| 22 |
+
from utilities.feedback_survey import display_function_vector_feedback
|
| 23 |
+
import gc
|
| 24 |
+
import colorsys
|
| 25 |
+
import re
|
| 26 |
+
from thefuzz import process
|
| 27 |
+
import threading
|
| 28 |
+
|
| 29 |
+
# Directory for visualizations.
|
| 30 |
+
VIZ_DIR = Path(__file__).parent / "data" / "visualizations"
|
| 31 |
+
|
| 32 |
+
# Add the project root to the path.
|
| 33 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 34 |
+
from function_vectors.data.multilingual_function_categories import FUNCTION_TYPES, FUNCTION_CATEGORIES
|
| 35 |
+
from utilities.utils import init_qwen_api
|
| 36 |
+
|
| 37 |
+
# Define colors and symbols for the plots.
|
| 38 |
+
FUNCTION_TYPE_COLORS = {
|
| 39 |
+
"abstractive_tasks": "#87CEEB", # skyblue
|
| 40 |
+
"multiple_choice_qa": "#90EE90", # lightgreen
|
| 41 |
+
"text_classification": "#FA8072", # salmon
|
| 42 |
+
"extractive_tasks": "#DA70D6", # orchid
|
| 43 |
+
"named_entity_recognition": "#FFD700", # gold
|
| 44 |
+
"text_generation": "#F08080" # lightcoral
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
# HTML entities for shapes in the legend.
|
| 48 |
+
PLOTLY_SYMBOLS_HTML = {
|
| 49 |
+
"abstractive_tasks": "●", "multiple_choice_qa": "◆",
|
| 50 |
+
"text_classification": "■", "extractive_tasks": "✚",
|
| 51 |
+
"named_entity_recognition": "◇", "text_generation": "□"
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
# Plotly symbol names for the plot.
|
| 55 |
+
PLOTLY_SYMBOLS = {
|
| 56 |
+
"abstractive_tasks": "circle", "multiple_choice_qa": "diamond",
|
| 57 |
+
"text_classification": "square", "extractive_tasks": "cross",
|
| 58 |
+
"named_entity_recognition": "diamond-open", "text_generation": "square-open"
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
# Helper function to format category names.
|
| 62 |
+
def format_category_name(name):
|
| 63 |
+
# Formats a category key into a readable name.
|
| 64 |
+
# Make the check case-insensitive.
|
| 65 |
+
if name.lower().endswith('_qa'):
|
| 66 |
+
# Format names that end in '_qa'.
|
| 67 |
+
prefix = name[:-3].replace('_', ' ').replace('-', ' ').title()
|
| 68 |
+
formatted_name = f"{prefix} QA"
|
| 69 |
+
else:
|
| 70 |
+
# Default formatting for other names.
|
| 71 |
+
formatted_name = name.replace('_', ' ').replace('-', ' ').title()
|
| 72 |
+
|
| 73 |
+
return tr(formatted_name)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def show_function_vectors_page():
|
| 77 |
+
# Shows the main Function Vector Analysis page.
|
| 78 |
+
# Add CSS for Bootstrap icons.
|
| 79 |
+
st.markdown('<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">', unsafe_allow_html=True)
|
| 80 |
+
|
| 81 |
+
# Initialize a lock in the session state to prevent concurrent API calls.
|
| 82 |
+
if 'api_lock' not in st.session_state:
|
| 83 |
+
st.session_state.api_lock = threading.Lock()
|
| 84 |
+
|
| 85 |
+
st.markdown(f"<h1>{tr('fv_page_title')}</h1>", unsafe_allow_html=True)
|
| 86 |
+
st.markdown(f"""{tr('fv_page_desc')}""", unsafe_allow_html=True)
|
| 87 |
+
|
| 88 |
+
# Check if the visualization directory exists.
|
| 89 |
+
if not VIZ_DIR.exists():
|
| 90 |
+
st.error(tr('viz_dir_not_found_error'))
|
| 91 |
+
return
|
| 92 |
+
|
| 93 |
+
# Show examples of the categories.
|
| 94 |
+
st.header(tr('dataset_overview'))
|
| 95 |
+
st.markdown(tr('dataset_overview_desc_long'))
|
| 96 |
+
display_category_examples()
|
| 97 |
+
|
| 98 |
+
st.markdown("---")
|
| 99 |
+
|
| 100 |
+
# Add a visual explanation of how function vectors are made.
|
| 101 |
+
st.html(f"""
|
| 102 |
+
<div style='color: #ffffff; margin: 2rem 0;'>
|
| 103 |
+
<h4 style='color: #87CEEB; margin-top: 0; text-align: center; margin-bottom: 1.5rem;'>{tr('how_vectors_are_made_header')}</h4>
|
| 104 |
+
<p style="text-align: center; max-width: 600px; margin: auto; margin-bottom: 2rem;">{tr('how_vectors_are_made_desc')}</p>
|
| 105 |
+
|
| 106 |
+
<div style="display: flex; flex-direction: column; align-items: center; font-family: 'SF Mono', 'Consolas', 'Menlo', monospace; gap: 0.2rem;">
|
| 107 |
+
|
| 108 |
+
<!-- STEP 1: INPUT -->
|
| 109 |
+
<div style="background-color: #333; padding: 0.8rem; border-radius: 8px; width: 90%; max-width: 600px; text-align: center; border: 1px solid #444;">
|
| 110 |
+
<h5 style="margin: 0 0 0.5rem 0; color: #87CEEB; font-size: 0.9rem; letter-spacing: 1px; font-weight: bold;"><i class="bi bi-keyboard"></i> {tr('how_vectors_are_made_step1_title')}</h5>
|
| 111 |
+
<code style="background: none; color: #EAEAEA; font-size: 1em;">"{tr('how_vectors_are_made_step1_example')}"</code>
|
| 112 |
+
</div>
|
| 113 |
+
|
| 114 |
+
<i class="bi bi-arrow-down" style="font-size: 2rem; color: #666; margin: 0.5rem 0;"></i>
|
| 115 |
+
|
| 116 |
+
<!-- STEP 2: TOKENIZER -->
|
| 117 |
+
<div style="background-color: #333; padding: 0.8rem; border-radius: 8px; width: 90%; max-width: 600px; text-align: center; border: 1px solid #444;">
|
| 118 |
+
<h5 style="margin: 0 0 0.5rem 0; color: #87CEEB; font-size: 0.9rem; letter-spacing: 1px; font-weight: bold;"><i class="bi bi-segmented-nav"></i> {tr('how_vectors_are_made_step2_title')}</h5>
|
| 119 |
+
<code style="background: none; color: #EAEAEA; font-size: 1em;">{tr('how_vectors_are_made_step2_example')}</code>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
<i class="bi bi-arrow-down" style="font-size: 2rem; color: #666; margin: 0.5rem 0;"></i>
|
| 123 |
+
|
| 124 |
+
<!-- STEP 3: MODEL -->
|
| 125 |
+
<div style="background-color: #333; padding: 0.8rem; border-radius: 8px; width: 90%; max-width: 600px; text-align: center; border: 1px solid #444;">
|
| 126 |
+
<h5 style="margin: 0 0 0.5rem 0; color: #87CEEB; font-size: 0.9rem; letter-spacing: 1px; font-weight: bold;"><i class="bi bi-cpu-fill"></i> {tr('how_vectors_are_made_step3_title')}</h5>
|
| 127 |
+
<code style="background: none; color: #EAEAEA; font-size: 1em;">{tr('how_vectors_are_made_step3_desc')}</code>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
<i class="bi bi-arrow-down" style="font-size: 2rem; color: #666; margin: 0.5rem 0;"></i>
|
| 131 |
+
|
| 132 |
+
<!-- STEP 4: FINAL LAYER -->
|
| 133 |
+
<div style="background-color: #333; padding: 0.8rem; border-radius: 8px; width: 90%; max-width: 600px; text-align: center; border: 1px solid #444;">
|
| 134 |
+
<h5 style="margin: 0 0 0.5rem 0; color: #87CEEB; font-size: 0.9rem; letter-spacing: 1px; font-weight: bold;"><i class="bi bi-layer-forward"></i> {tr('how_vectors_are_made_step4_title')}</h5>
|
| 135 |
+
<code style="background: none; color: #EAEAEA; font-size: 1em;">{tr('how_vectors_are_made_step4_desc')}</code>
|
| 136 |
+
</div>
|
| 137 |
+
|
| 138 |
+
<i class="bi bi-arrow-down" style="font-size: 2rem; color: #666; margin: 0.5rem 0;"></i>
|
| 139 |
+
|
| 140 |
+
<!-- STEP 5: OUTPUT -->
|
| 141 |
+
<div style="background-color: #1e1e1e; padding: 1.2rem; border-radius: 8px; width: 90%; max-width: 600px; text-align: center; border: 2px solid #90EE90;">
|
| 142 |
+
<h5 style="margin: 0 0 0.5rem 0; color: #90EE90; font-size: 1rem; letter-spacing: 1px; font-weight: bold;"><i class="bi bi-check-circle-fill"></i> {tr('how_vectors_are_made_step5_title')}</h5>
|
| 143 |
+
<code style="background: none; color: #90EE90; font-weight: bold; font-size: 1.1em;">[ -0.23, 1.45, -0.89, ... ]</code>
|
| 144 |
+
</div>
|
| 145 |
+
|
| 146 |
+
</div>
|
| 147 |
+
</div>
|
| 148 |
+
""")
|
| 149 |
+
|
| 150 |
+
st.markdown("---")
|
| 151 |
+
|
| 152 |
+
analysis_run = 'analysis_results' in st.session_state and 'user_input' in st.session_state
|
| 153 |
+
|
| 154 |
+
# --- Initial Visualization ---
|
| 155 |
+
# Show the 3D PCA plot before an analysis is run.
|
| 156 |
+
if not analysis_run:
|
| 157 |
+
st.markdown(f"<h2>{tr('pca_3d_section_header')}</h2>", unsafe_allow_html=True)
|
| 158 |
+
display_3d_pca_visualization(show_description=True)
|
| 159 |
+
st.markdown("---")
|
| 160 |
+
|
| 161 |
+
# The interactive analysis section is always visible.
|
| 162 |
+
st.markdown(f"<h2>{tr('interactive_analysis_section_header')}</h2>", unsafe_allow_html=True)
|
| 163 |
+
display_interactive_analysis()
|
| 164 |
+
|
| 165 |
+
# If an analysis was run, show the results.
|
| 166 |
+
if analysis_run:
|
| 167 |
+
st.markdown("---")
|
| 168 |
+
with st.spinner(tr('running_analysis_spinner')):
|
| 169 |
+
display_analysis_results(st.session_state.analysis_results, st.session_state.user_input)
|
| 170 |
+
|
| 171 |
+
#if 'analysis_results' in st.session_state:
|
| 172 |
+
# display_function_vector_feedback()
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def _trigger_and_rerun_analysis(input_text, include_attribution, include_evolution, enable_ai_explanation):
|
| 176 |
+
# Triggers an analysis, saves the results, and reruns the app.
|
| 177 |
+
if not input_text.strip():
|
| 178 |
+
st.warning("Please enter a prompt to analyze.")
|
| 179 |
+
return
|
| 180 |
+
|
| 181 |
+
st.session_state.user_input = input_text.strip()
|
| 182 |
+
st.session_state.enable_ai_explanation = enable_ai_explanation
|
| 183 |
+
|
| 184 |
+
with st.spinner(tr('running_analysis_spinner')):
|
| 185 |
+
try:
|
| 186 |
+
results = run_interactive_analysis(input_text.strip(), True, True, enable_ai_explanation)
|
| 187 |
+
|
| 188 |
+
if results:
|
| 189 |
+
st.session_state.analysis_results = results
|
| 190 |
+
|
| 191 |
+
# Process and store AI explanations if enabled.
|
| 192 |
+
if enable_ai_explanation or "pca_explanation" in results: # Also process if loaded from cache
|
| 193 |
+
if 'api_error' in results:
|
| 194 |
+
st.warning(results['api_error'])
|
| 195 |
+
|
| 196 |
+
if 'pca_explanation' in results and results['pca_explanation']:
|
| 197 |
+
# Split the explanation into parts based on headings.
|
| 198 |
+
explanation_parts = re.split(r'(?=\n####\s)', results['pca_explanation'].strip())
|
| 199 |
+
explanation_parts = [p.strip() for p in explanation_parts if p.strip()]
|
| 200 |
+
st.session_state.explanation_part_1 = explanation_parts[0] if len(explanation_parts) > 0 else ""
|
| 201 |
+
st.session_state.explanation_part_2 = explanation_parts[1] if len(explanation_parts) > 1 else ""
|
| 202 |
+
st.session_state.explanation_part_3 = explanation_parts[2] if len(explanation_parts) > 2 else ""
|
| 203 |
+
|
| 204 |
+
if 'evolution_explanation' in results and results['evolution_explanation']:
|
| 205 |
+
# Split the evolution explanation into parts.
|
| 206 |
+
evo_parts = re.split(r'(?=\n####\s)', results['evolution_explanation'].strip())
|
| 207 |
+
evo_parts = [p.strip() for p in evo_parts if p.strip()]
|
| 208 |
+
st.session_state.evolution_explanation_part_1 = evo_parts[0] if len(evo_parts) > 0 else ""
|
| 209 |
+
st.session_state.evolution_explanation_part_2 = evo_parts[1] if len(evo_parts) > 1 else ""
|
| 210 |
+
|
| 211 |
+
if 'example_text' in st.session_state:
|
| 212 |
+
del st.session_state['example_text']
|
| 213 |
+
st.rerun()
|
| 214 |
+
else:
|
| 215 |
+
st.error(tr('analysis_failed_error'))
|
| 216 |
+
except Exception as e:
|
| 217 |
+
st.error(tr('analysis_error').format(e=str(e)))
|
| 218 |
+
st.info(tr('ensure_model_and_data_info'))
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def display_interactive_analysis():
|
| 222 |
+
# Shows the interactive analysis section of the page.
|
| 223 |
+
|
| 224 |
+
# Show a section with example queries.
|
| 225 |
+
st.markdown(f"**{tr('example_queries_header')}**", unsafe_allow_html=True)
|
| 226 |
+
st.markdown(tr('example_queries_desc'))
|
| 227 |
+
|
| 228 |
+
current_lang = st.session_state.get('lang', 'en')
|
| 229 |
+
examples = {
|
| 230 |
+
'en': [
|
| 231 |
+
"Summarize the plot of 'Hamlet' in one sentence:",
|
| 232 |
+
"The main ingredient in a Negroni cocktail is",
|
| 233 |
+
"A Python function that calculates the factorial of a number is:",
|
| 234 |
+
"The sentence 'The cake was eaten by the dog' is in the following voice:",
|
| 235 |
+
"A good headline for an article about a new breakthrough in battery technology would be:",
|
| 236 |
+
"The capital of Mongolia is",
|
| 237 |
+
"The literary device in the phrase 'The wind whispered through the trees' is",
|
| 238 |
+
"The French translation of 'I would like to order a coffee, please.' is:",
|
| 239 |
+
"The movie 'The Matrix' can be classified into the following genre:"
|
| 240 |
+
],
|
| 241 |
+
'de': [
|
| 242 |
+
"Fassen Sie die Handlung von 'Hamlet' in einem Satz zusammen:",
|
| 243 |
+
"Die Hauptzutat in einem Negroni-Cocktail ist",
|
| 244 |
+
"Eine Python-Funktion, die die Fakultät einer Zahl berechnet, lautet:",
|
| 245 |
+
"Der Satz 'Der Kuchen wurde vom Hund gefressen' steht in folgender Form:",
|
| 246 |
+
"Eine gute Überschrift für einen Artikel über einen neuen Durchbruch in der Batterietechnologie wäre:",
|
| 247 |
+
"Die Hauptstadt der Mongolei ist",
|
| 248 |
+
"Das literarische Stilmittel im Satz 'Der Wind flüsterte durch die Bäume' ist",
|
| 249 |
+
"Die französische Übersetzung von 'Ich möchte bitte einen Kaffee bestellen.' lautet:",
|
| 250 |
+
"Der Film 'Die Matrix' lässt sich in folgendes Genre einteilen:"
|
| 251 |
+
]
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
# Display the examples in a 3-column grid.
|
| 255 |
+
example_cols = st.columns(3)
|
| 256 |
+
for i, example in enumerate(examples[current_lang]):
|
| 257 |
+
with example_cols[i % 3]:
|
| 258 |
+
if st.button(example, key=f"fv_example_{i}", use_container_width=True):
|
| 259 |
+
# Trigger an analysis when an example is clicked.
|
| 260 |
+
_trigger_and_rerun_analysis(example, True, True, True)
|
| 261 |
+
|
| 262 |
+
# Input section
|
| 263 |
+
# Add some custom CSS to style the text area.
|
| 264 |
+
st.markdown("""
|
| 265 |
+
<style>
|
| 266 |
+
.stTextArea > div > div > textarea {
|
| 267 |
+
background-color: #2b2b2b !important;
|
| 268 |
+
border: 2px solid #4a90e2 !important;
|
| 269 |
+
border-radius: 10px !important;
|
| 270 |
+
color: #ffffff !important;
|
| 271 |
+
}
|
| 272 |
+
.stTextArea > div > div > textarea::placeholder {
|
| 273 |
+
color: #888888 !important;
|
| 274 |
+
}
|
| 275 |
+
.stTextArea > div > div > textarea:focus {
|
| 276 |
+
border-color: #4a90e2 !important;
|
| 277 |
+
box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2) !important;
|
| 278 |
+
}
|
| 279 |
+
.custom-label {
|
| 280 |
+
font-size: 1.25rem !important;
|
| 281 |
+
font-weight: bold !important;
|
| 282 |
+
margin-bottom: 0.5rem !important;
|
| 283 |
+
}
|
| 284 |
+
</style>
|
| 285 |
+
""", unsafe_allow_html=True)
|
| 286 |
+
|
| 287 |
+
# Text input area that uses the session state.
|
| 288 |
+
# Use an example as the default value if one was clicked.
|
| 289 |
+
default_value = st.session_state.get('user_input', '')
|
| 290 |
+
|
| 291 |
+
st.markdown(f"<div class='custom-label'>{tr('input_text_label')}</div>", unsafe_allow_html=True)
|
| 292 |
+
input_text = st.text_area(
|
| 293 |
+
"text_area_for_analysis",
|
| 294 |
+
value=default_value,
|
| 295 |
+
placeholder="Sadly no GPU available. Please select an example above.",
|
| 296 |
+
height=100,
|
| 297 |
+
help=tr('input_text_help'),
|
| 298 |
+
label_visibility="collapsed",
|
| 299 |
+
disabled=True
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
# Checkbox for AI explanations.
|
| 303 |
+
enable_ai_explanation = st.checkbox(tr('enable_ai_explanation_checkbox'), value=True, help=tr('enable_ai_explanation_help'))
|
| 304 |
+
|
| 305 |
+
# Analysis button.
|
| 306 |
+
if st.button(tr('analyze_button'), type="primary"):
|
| 307 |
+
_trigger_and_rerun_analysis(input_text, True, True, enable_ai_explanation)
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def load_model_and_tokenizer():
|
| 311 |
+
# Loads and caches the model and tokenizer.
|
| 312 |
+
MODEL_PATH = "./models/OLMo-2-1124-7B"
|
| 313 |
+
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
|
| 314 |
+
|
| 315 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
|
| 316 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 317 |
+
tokenizer.padding_side = "left"
|
| 318 |
+
|
| 319 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 320 |
+
MODEL_PATH,
|
| 321 |
+
torch_dtype=torch.float16,
|
| 322 |
+
low_cpu_mem_usage=True,
|
| 323 |
+
device_map="auto",
|
| 324 |
+
output_hidden_states=True
|
| 325 |
+
)
|
| 326 |
+
return model, tokenizer, device
|
| 327 |
+
|
| 328 |
+
@st.cache_data
|
| 329 |
+
def _load_precomputed_vectors(lang='en', cache_version="function-vectors-2025-11-09"):
|
| 330 |
+
# Loads pre-computed vectors from a file.
|
| 331 |
+
vector_path = Path(__file__).parent / f"data/vectors/{lang}_category_vectors.npz"
|
| 332 |
+
if not vector_path.exists():
|
| 333 |
+
return None, None, f"Vector file not found for language '{lang}': {vector_path}"
|
| 334 |
+
|
| 335 |
+
try:
|
| 336 |
+
loaded_data = np.load(vector_path, allow_pickle=True)
|
| 337 |
+
category_vectors = {key: loaded_data[key] for key in loaded_data.files}
|
| 338 |
+
|
| 339 |
+
function_type_vectors = {}
|
| 340 |
+
for func_type_key, category_keys in FUNCTION_TYPES.items():
|
| 341 |
+
type_vectors = [category_vectors[cat_key] for cat_key in category_keys if cat_key in category_vectors]
|
| 342 |
+
if type_vectors:
|
| 343 |
+
function_type_vectors[func_type_key] = np.mean(type_vectors, axis=0)
|
| 344 |
+
|
| 345 |
+
return function_type_vectors, category_vectors, None
|
| 346 |
+
except Exception as e:
|
| 347 |
+
return None, None, f"Error loading vectors for language '{lang}': {e}"
|
| 348 |
+
|
| 349 |
+
@st.cache_data(persist=True)
|
| 350 |
+
def _perform_analysis(input_text, include_attribution, include_evolution, lang, enable_ai_explanation, cache_version="function-vectors-2025-11-09"):
|
| 351 |
+
# This function is cached and performs the main analysis.
|
| 352 |
+
results = {}
|
| 353 |
+
model, tokenizer, device = None, None, None
|
| 354 |
+
|
| 355 |
+
if include_attribution or include_evolution:
|
| 356 |
+
model, tokenizer, device = load_model_and_tokenizer()
|
| 357 |
+
|
| 358 |
+
if include_attribution:
|
| 359 |
+
function_type_vectors, category_vectors, error = _load_precomputed_vectors(lang)
|
| 360 |
+
if error:
|
| 361 |
+
results['error'] = error
|
| 362 |
+
return results
|
| 363 |
+
|
| 364 |
+
def get_input_activation(text):
|
| 365 |
+
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512)
|
| 366 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 367 |
+
with torch.no_grad():
|
| 368 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 369 |
+
last_token_pos = inputs['attention_mask'].sum(dim=1) - 1
|
| 370 |
+
last_hidden_state = outputs.hidden_states[-1]
|
| 371 |
+
activation = last_hidden_state[0, last_token_pos[0], :].cpu().numpy()
|
| 372 |
+
return activation.astype(np.float64)
|
| 373 |
+
|
| 374 |
+
def calculate_similarity(activation, vectors_dict):
|
| 375 |
+
similarities = {}
|
| 376 |
+
norm_activation = activation / (np.linalg.norm(activation) + 1e-8)
|
| 377 |
+
for label, vector in vectors_dict.items():
|
| 378 |
+
norm_vector = vector / (np.linalg.norm(vector) + 1e-8)
|
| 379 |
+
similarity = np.dot(norm_activation, norm_vector)
|
| 380 |
+
similarities[label] = float(similarity)
|
| 381 |
+
return similarities
|
| 382 |
+
|
| 383 |
+
input_activation = get_input_activation(input_text)
|
| 384 |
+
function_type_scores = calculate_similarity(input_activation, function_type_vectors)
|
| 385 |
+
category_scores = calculate_similarity(input_activation, category_vectors)
|
| 386 |
+
|
| 387 |
+
results['attribution'] = {
|
| 388 |
+
'function_type_scores': dict(sorted(function_type_scores.items(), key=lambda x: x[1], reverse=True)),
|
| 389 |
+
'category_scores': dict(sorted(category_scores.items(), key=lambda x: x[1], reverse=True)),
|
| 390 |
+
'function_types_mapping': FUNCTION_TYPES,
|
| 391 |
+
'input_text': input_text,
|
| 392 |
+
'input_activation': input_activation,
|
| 393 |
+
'category_vectors': category_vectors,
|
| 394 |
+
'function_type_vectors': function_type_vectors
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
if include_evolution:
|
| 398 |
+
try:
|
| 399 |
+
analyzer = LayerEvolutionAnalyzer(model, tokenizer, device)
|
| 400 |
+
evolution_results = analyzer.analyze_text(input_text)
|
| 401 |
+
results['evolution'] = evolution_results
|
| 402 |
+
except Exception as e:
|
| 403 |
+
results['evolution_error'] = str(e)
|
| 404 |
+
|
| 405 |
+
if enable_ai_explanation:
|
| 406 |
+
with st.spinner(tr('generating_ai_explanation_spinner')):
|
| 407 |
+
api_config = init_qwen_api()
|
| 408 |
+
if api_config:
|
| 409 |
+
if 'attribution' in results:
|
| 410 |
+
attribution_results = results['attribution']
|
| 411 |
+
sorted_category_scores = list(attribution_results['category_scores'].items())
|
| 412 |
+
|
| 413 |
+
# Get the top 3 categories.
|
| 414 |
+
top_3_cats_data = sorted_category_scores[:3]
|
| 415 |
+
top_cats_for_prompt = [format_category_name(cat_key) for cat_key, _ in top_3_cats_data]
|
| 416 |
+
|
| 417 |
+
top_types_raw = list(attribution_results['function_type_scores'].keys())[:3]
|
| 418 |
+
top_types_formatted = [format_category_name(t) for t in top_types_raw]
|
| 419 |
+
results['pca_explanation'] = explain_pca_with_llm(api_config, input_text, top_types_formatted, top_cats_for_prompt)
|
| 420 |
+
|
| 421 |
+
if 'evolution' in results:
|
| 422 |
+
results['evolution_explanation'] = explain_evolution_with_llm(api_config, input_text, results['evolution'])
|
| 423 |
+
else:
|
| 424 |
+
results['api_error'] = "Qwen API key not configured. Skipping AI explanation."
|
| 425 |
+
|
| 426 |
+
# Clean up to free memory.
|
| 427 |
+
if model is not None:
|
| 428 |
+
del model
|
| 429 |
+
del tokenizer
|
| 430 |
+
gc.collect()
|
| 431 |
+
if device == 'mps':
|
| 432 |
+
torch.mps.empty_cache()
|
| 433 |
+
elif device == 'cuda':
|
| 434 |
+
torch.cuda.empty_cache()
|
| 435 |
+
|
| 436 |
+
return results
|
| 437 |
+
|
| 438 |
+
class LayerEvolutionAnalyzer:
|
| 439 |
+
def __init__(self, model, tokenizer, device):
|
| 440 |
+
# Initialize the analyzer with a pre-loaded model.
|
| 441 |
+
self.model = model
|
| 442 |
+
self.tokenizer = tokenizer
|
| 443 |
+
self.device = device
|
| 444 |
+
|
| 445 |
+
# Get the number of layers.
|
| 446 |
+
self.num_layers = self.model.config.num_hidden_layers
|
| 447 |
+
|
| 448 |
+
# Set the model to evaluation mode.
|
| 449 |
+
self.model.eval()
|
| 450 |
+
|
| 451 |
+
def extract_layer_vectors(self, text: str) -> Dict[int, np.ndarray]:
|
| 452 |
+
# Extracts function vectors from each layer for a given text.
|
| 453 |
+
import numpy as np
|
| 454 |
+
import torch
|
| 455 |
+
# Tokenize the input text.
|
| 456 |
+
inputs = self.tokenizer(
|
| 457 |
+
text,
|
| 458 |
+
return_tensors="pt",
|
| 459 |
+
padding=True,
|
| 460 |
+
truncation=True,
|
| 461 |
+
max_length=512
|
| 462 |
+
).to(self.device)
|
| 463 |
+
|
| 464 |
+
with torch.no_grad():
|
| 465 |
+
outputs = self.model(**inputs, output_hidden_states=True)
|
| 466 |
+
|
| 467 |
+
hidden_states = outputs.hidden_states
|
| 468 |
+
|
| 469 |
+
layer_vectors = {}
|
| 470 |
+
for i, state in enumerate(hidden_states):
|
| 471 |
+
vec = state[0].mean(dim=0).cpu().numpy()
|
| 472 |
+
vec = vec.astype(np.float64)
|
| 473 |
+
vec = np.nan_to_num(vec, nan=0.0, posinf=1.0, neginf=-1.0)
|
| 474 |
+
layer_vectors[i] = vec
|
| 475 |
+
|
| 476 |
+
return layer_vectors
|
| 477 |
+
|
| 478 |
+
def compute_layer_similarities(self, layer_vectors: Dict[int, np.ndarray]) -> np.ndarray:
|
| 479 |
+
# Computes the cosine similarity between vectors from different layers.
|
| 480 |
+
import numpy as np
|
| 481 |
+
n_layers = len(layer_vectors)
|
| 482 |
+
vectors = np.array([layer_vectors[i] for i in range(n_layers)])
|
| 483 |
+
|
| 484 |
+
normalized_vectors = vectors / (np.linalg.norm(vectors, axis=1, keepdims=True) + 1e-8)
|
| 485 |
+
|
| 486 |
+
similarity_matrix = np.dot(normalized_vectors, normalized_vectors.T)
|
| 487 |
+
|
| 488 |
+
return similarity_matrix
|
| 489 |
+
|
| 490 |
+
def calculate_layer_changes(self, layer_vectors: Dict[int, np.ndarray]) -> List[float]:
|
| 491 |
+
# Calculates the amount of change between consecutive layers.
|
| 492 |
+
import numpy as np
|
| 493 |
+
changes = []
|
| 494 |
+
for i in range(1, len(layer_vectors)):
|
| 495 |
+
vec1 = layer_vectors[i-1]
|
| 496 |
+
vec2 = layer_vectors[i]
|
| 497 |
+
|
| 498 |
+
norm1 = np.linalg.norm(vec1)
|
| 499 |
+
norm2 = np.linalg.norm(vec2)
|
| 500 |
+
|
| 501 |
+
if norm1 == 0 or norm2 == 0:
|
| 502 |
+
sim = 0
|
| 503 |
+
else:
|
| 504 |
+
sim = np.dot(vec1, vec2) / (norm1 * norm2)
|
| 505 |
+
|
| 506 |
+
distance = 1 - sim
|
| 507 |
+
changes.append(distance)
|
| 508 |
+
|
| 509 |
+
return changes
|
| 510 |
+
|
| 511 |
+
def analyze_text(self, text: str):
|
| 512 |
+
# Performs a complete layer evolution analysis on a text.
|
| 513 |
+
layer_vectors = self.extract_layer_vectors(text)
|
| 514 |
+
similarity_matrix = self.compute_layer_similarities(layer_vectors)
|
| 515 |
+
layer_changes = self.calculate_layer_changes(layer_vectors)
|
| 516 |
+
|
| 517 |
+
return {
|
| 518 |
+
'layer_vectors': layer_vectors,
|
| 519 |
+
'similarity_matrix': similarity_matrix,
|
| 520 |
+
'layer_changes': layer_changes
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
def run_interactive_analysis(input_text, include_attribution=True, include_evolution=True, enable_ai_explanation=True):
|
| 524 |
+
# A wrapper function for running the analysis from the UI.
|
| 525 |
+
|
| 526 |
+
# Before running, check if models exist if not using a cached value.
|
| 527 |
+
# This check relies on the fact that caching is attempted first.
|
| 528 |
+
model_path = "./models/OLMo-2-1124-7B"
|
| 529 |
+
model_exists = os.path.exists(model_path)
|
| 530 |
+
# if not os.path.exists(model_path):
|
| 531 |
+
# # We assume if the model path is missing, we are in a static environment.
|
| 532 |
+
# # The calling function should have already checked the cache.
|
| 533 |
+
# st.info("This live demo is running in a static environment. Only the pre-cached example prompts are available. Please select an example to view its analysis.")
|
| 534 |
+
# return None
|
| 535 |
+
|
| 536 |
+
current_lang = st.session_state.get('lang', 'en')
|
| 537 |
+
|
| 538 |
+
try:
|
| 539 |
+
results = _perform_analysis(input_text, include_attribution, include_evolution, current_lang, enable_ai_explanation)
|
| 540 |
+
except Exception as e:
|
| 541 |
+
if not model_exists:
|
| 542 |
+
st.info("This live demo is running in a static environment. Only the pre-cached example prompts are available. Please select an example to view its analysis.")
|
| 543 |
+
return None
|
| 544 |
+
else:
|
| 545 |
+
# If model exists but it failed, it's a real error
|
| 546 |
+
st.error(f"Analysis failed: {e}")
|
| 547 |
+
return None
|
| 548 |
+
|
| 549 |
+
if 'error' in results and results['error']:
|
| 550 |
+
st.error(results['error'])
|
| 551 |
+
return None
|
| 552 |
+
|
| 553 |
+
if 'evolution_error' in results:
|
| 554 |
+
st.warning(f"Layer evolution analysis failed: {results['evolution_error']}")
|
| 555 |
+
|
| 556 |
+
if 'api_error' in results:
|
| 557 |
+
st.error(results['api_error'])
|
| 558 |
+
|
| 559 |
+
if 'attribution' in results:
|
| 560 |
+
st.session_state.user_input_3d_data = results['attribution']
|
| 561 |
+
|
| 562 |
+
return results
|
| 563 |
+
|
| 564 |
+
def explain_pca_with_llm(api_config, input_text, top_types, top_cats):
|
| 565 |
+
# Generates an explanation for the PCA plot with an LLM.
|
| 566 |
+
lang = st.session_state.get('lang', 'en')
|
| 567 |
+
prompt_key = 'pca_explanation_prompt_de' if lang == 'de' else 'pca_explanation_prompt'
|
| 568 |
+
|
| 569 |
+
prompt = tr(prompt_key).format(
|
| 570 |
+
input_text=input_text,
|
| 571 |
+
top_types=", ".join(top_types),
|
| 572 |
+
top_cats=", ".join(top_cats)
|
| 573 |
+
)
|
| 574 |
+
explanation = _explain_with_llm(api_config, prompt)
|
| 575 |
+
if "API request failed" in explanation or "Failed to generate explanation" in explanation:
|
| 576 |
+
st.error(explanation)
|
| 577 |
+
return None
|
| 578 |
+
return explanation
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
def explain_evolution_with_llm(api_config, input_text, evolution_results):
|
| 582 |
+
# Generates an explanation for the layer evolution charts with an LLM.
|
| 583 |
+
# Extract data for the prompt.
|
| 584 |
+
activation_strengths = [float(np.sqrt(np.sum(vec ** 2))) for vec in evolution_results['layer_vectors'].values()]
|
| 585 |
+
layer_changes = evolution_results['layer_changes']
|
| 586 |
+
|
| 587 |
+
peak_activation_layer = np.argmax(activation_strengths)
|
| 588 |
+
peak_activation_strength = activation_strengths[peak_activation_layer]
|
| 589 |
+
|
| 590 |
+
biggest_change_idx = np.argmax(layer_changes)
|
| 591 |
+
biggest_change_start_layer = biggest_change_idx + 1
|
| 592 |
+
biggest_change_end_layer = biggest_change_idx + 2
|
| 593 |
+
biggest_change_magnitude = layer_changes[biggest_change_idx]
|
| 594 |
+
|
| 595 |
+
lang = st.session_state.get('lang', 'en')
|
| 596 |
+
prompt_key = 'evolution_explanation_prompt_de' if lang == 'de' else 'evolution_explanation_prompt'
|
| 597 |
+
|
| 598 |
+
prompt = tr(prompt_key).format(
|
| 599 |
+
input_text=input_text,
|
| 600 |
+
peak_activation_layer=peak_activation_layer,
|
| 601 |
+
peak_activation_strength=peak_activation_strength,
|
| 602 |
+
biggest_change_start_layer=biggest_change_start_layer,
|
| 603 |
+
biggest_change_end_layer=biggest_change_end_layer,
|
| 604 |
+
biggest_change_magnitude=biggest_change_magnitude
|
| 605 |
+
)
|
| 606 |
+
|
| 607 |
+
explanation = _explain_with_llm(api_config, prompt)
|
| 608 |
+
if "API request failed" in explanation or "Failed to generate explanation" in explanation:
|
| 609 |
+
st.error(explanation)
|
| 610 |
+
return None
|
| 611 |
+
return explanation
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
@st.cache_data(persist=True)
|
| 615 |
+
def _explain_with_llm(_api_config, prompt, cache_version="function-vectors-2025-11-09"):
|
| 616 |
+
# Makes a cached API call to the LLM.
|
| 617 |
+
with st.session_state.api_lock:
|
| 618 |
+
headers = {
|
| 619 |
+
"Authorization": f"Bearer {_api_config['api_key']}",
|
| 620 |
+
"Content-Type": "application/json"
|
| 621 |
+
}
|
| 622 |
+
payload = {
|
| 623 |
+
"model": "qwen2.5-vl-72b-instruct",
|
| 624 |
+
"messages": [{"role": "user", "content": prompt}]
|
| 625 |
+
}
|
| 626 |
+
response = requests.post(
|
| 627 |
+
f"{_api_config['api_endpoint']}/chat/completions",
|
| 628 |
+
headers=headers,
|
| 629 |
+
json=payload,
|
| 630 |
+
timeout=300
|
| 631 |
+
)
|
| 632 |
+
# Raise an exception if the API call fails.
|
| 633 |
+
response.raise_for_status()
|
| 634 |
+
return response.json().get('choices', [{}])[0].get('message', {}).get('content', '')
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
# --- Faithfulness Verification for Function Vectors ---
|
| 638 |
+
|
| 639 |
+
def find_closest_match(query, choices):
|
| 640 |
+
# Wrapper for fuzzy matching to find the best choice.
|
| 641 |
+
if not query or not choices:
|
| 642 |
+
return None
|
| 643 |
+
match, score = process.extractOne(query, choices)
|
| 644 |
+
if score > 80: # Using a similarity threshold
|
| 645 |
+
return match
|
| 646 |
+
return None
|
| 647 |
+
|
| 648 |
+
@st.cache_data(persist=True)
|
| 649 |
+
def _cached_extract_fv_claims(api_config, explanation_text, context, cache_version="function-vectors-2025-11-09"):
|
| 650 |
+
# Extracts verifiable claims from an AI explanation on the function vectors page.
|
| 651 |
+
with st.session_state.api_lock:
|
| 652 |
+
headers = {
|
| 653 |
+
"Authorization": f"Bearer {api_config['api_key']}",
|
| 654 |
+
"Content-Type": "application/json"
|
| 655 |
+
}
|
| 656 |
+
|
| 657 |
+
# The prompt is dynamically adjusted based on the context (PCA or Evolution).
|
| 658 |
+
if context == "pca":
|
| 659 |
+
claim_types_details = tr("fv_claim_extraction_prompt_pca_types_details")
|
| 660 |
+
elif context == "evolution":
|
| 661 |
+
claim_types_details = tr("fv_claim_extraction_prompt_evolution_types_details")
|
| 662 |
+
else:
|
| 663 |
+
return []
|
| 664 |
+
|
| 665 |
+
# Dynamically set the example based on context.
|
| 666 |
+
if context == "pca":
|
| 667 |
+
example_block = f"""{tr('fv_claim_extraction_prompt_pca_example_header')}
|
| 668 |
+
{tr('fv_claim_extraction_prompt_pca_example_explanation')}
|
| 669 |
+
{tr('fv_claim_extraction_prompt_pca_example_json')}
|
| 670 |
+
"""
|
| 671 |
+
elif context == "evolution":
|
| 672 |
+
example_block = f"""{tr('fv_claim_extraction_prompt_evolution_example_header')}
|
| 673 |
+
{tr('fv_claim_extraction_prompt_evolution_example_explanation')}
|
| 674 |
+
{tr('fv_claim_extraction_prompt_evolution_example_json')}
|
| 675 |
+
"""
|
| 676 |
+
else:
|
| 677 |
+
example_block = ""
|
| 678 |
+
|
| 679 |
+
claim_extraction_prompt = f"""{tr('fv_claim_extraction_prompt_header')}
|
| 680 |
+
|
| 681 |
+
{tr('fv_claim_extraction_prompt_instruction')}
|
| 682 |
+
|
| 683 |
+
{tr('fv_claim_extraction_prompt_context_header').format(context=context)}
|
| 684 |
+
|
| 685 |
+
{tr('fv_claim_extraction_prompt_types_header')}
|
| 686 |
+
{claim_types_details}
|
| 687 |
+
|
| 688 |
+
{example_block}
|
| 689 |
+
|
| 690 |
+
{tr('fv_claim_extraction_prompt_analyze_header')}
|
| 691 |
+
"{explanation_text}"
|
| 692 |
+
|
| 693 |
+
{tr('fv_claim_extraction_prompt_footer')}
|
| 694 |
+
"""
|
| 695 |
+
|
| 696 |
+
data = {
|
| 697 |
+
"model": "qwen2.5-vl-72b-instruct",
|
| 698 |
+
"messages": [{"role": "user", "content": claim_extraction_prompt}],
|
| 699 |
+
"max_tokens": 1500,
|
| 700 |
+
"temperature": 0.0,
|
| 701 |
+
"seed": 42
|
| 702 |
+
}
|
| 703 |
+
|
| 704 |
+
response = requests.post(
|
| 705 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 706 |
+
headers=headers,
|
| 707 |
+
json=data,
|
| 708 |
+
timeout=300
|
| 709 |
+
)
|
| 710 |
+
response.raise_for_status()
|
| 711 |
+
claims_text = response.json()["choices"][0]["message"]["content"]
|
| 712 |
+
|
| 713 |
+
try:
|
| 714 |
+
if '```json' in claims_text:
|
| 715 |
+
claims_text = re.search(r'```json\n(.*?)\n```', claims_text, re.DOTALL).group(1)
|
| 716 |
+
return json.loads(claims_text)
|
| 717 |
+
except (AttributeError, json.JSONDecodeError):
|
| 718 |
+
return []
|
| 719 |
+
|
| 720 |
+
@st.cache_data(persist=True)
|
| 721 |
+
def _cached_verify_semantic_cluster_claim(api_config, claimed_clusters, actual_top_clusters, cache_version="function-vectors-2025-11-09"):
|
| 722 |
+
# Uses an LLM to verify if a semantic summary of clusters is faithful to the actual top clusters.
|
| 723 |
+
with st.session_state.api_lock:
|
| 724 |
+
headers = {
|
| 725 |
+
"Authorization": f"Bearer {api_config['api_key']}",
|
| 726 |
+
"Content-Type": "application/json"
|
| 727 |
+
}
|
| 728 |
+
|
| 729 |
+
verification_prompt = f"""{tr('fv_semantic_verification_prompt_header')}
|
| 730 |
+
|
| 731 |
+
{tr('fv_semantic_verification_prompt_rule')}
|
| 732 |
+
|
| 733 |
+
{tr('fv_semantic_verification_prompt_actual_header')}
|
| 734 |
+
{actual_top_clusters}
|
| 735 |
+
|
| 736 |
+
{tr('fv_semantic_verification_prompt_claimed_header')}
|
| 737 |
+
"{', '.join(claimed_clusters)}"
|
| 738 |
+
|
| 739 |
+
{tr('fv_semantic_verification_prompt_task_header')}
|
| 740 |
+
{tr('fv_semantic_verification_prompt_task_instruction')}
|
| 741 |
+
|
| 742 |
+
{tr('fv_semantic_verification_prompt_json_instruction')}
|
| 743 |
+
|
| 744 |
+
{tr('fv_semantic_verification_prompt_footer')}
|
| 745 |
+
"""
|
| 746 |
+
|
| 747 |
+
data = {
|
| 748 |
+
"model": "qwen2.5-vl-72b-instruct",
|
| 749 |
+
"messages": [{"role": "user", "content": verification_prompt}],
|
| 750 |
+
"max_tokens": 400,
|
| 751 |
+
"temperature": 0.0,
|
| 752 |
+
"seed": 42,
|
| 753 |
+
"response_format": {"type": "json_object"}
|
| 754 |
+
}
|
| 755 |
+
|
| 756 |
+
response = requests.post(
|
| 757 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 758 |
+
headers=headers,
|
| 759 |
+
json=data,
|
| 760 |
+
timeout=300
|
| 761 |
+
)
|
| 762 |
+
response.raise_for_status()
|
| 763 |
+
|
| 764 |
+
try:
|
| 765 |
+
result_json = response.json()["choices"][0]["message"]["content"]
|
| 766 |
+
return json.loads(result_json)
|
| 767 |
+
except (json.JSONDecodeError, KeyError):
|
| 768 |
+
return {"is_verified": False, "reasoning": "Could not parse the semantic verification result."}
|
| 769 |
+
|
| 770 |
+
@st.cache_data(persist=True)
|
| 771 |
+
def _cached_verify_justification_claim(api_config, input_prompt, category_name, justification, cache_version="function-vectors-2025-11-09"):
|
| 772 |
+
# Uses an LLM to verify if a justification for a category's relevance is sound.
|
| 773 |
+
with st.session_state.api_lock:
|
| 774 |
+
headers = {
|
| 775 |
+
"Authorization": f"Bearer {api_config['api_key']}",
|
| 776 |
+
"Content-Type": "application/json"
|
| 777 |
+
}
|
| 778 |
+
|
| 779 |
+
verification_prompt = f"""{tr('fv_justification_verification_prompt_header')}
|
| 780 |
+
|
| 781 |
+
{tr('fv_justification_verification_prompt_rule')}
|
| 782 |
+
|
| 783 |
+
{tr('fv_justification_verification_prompt_input_header')}
|
| 784 |
+
"{input_prompt}"
|
| 785 |
+
|
| 786 |
+
{tr('fv_justification_verification_prompt_category_header')}
|
| 787 |
+
"{category_name}"
|
| 788 |
+
|
| 789 |
+
{tr('fv_justification_verification_prompt_justification_header')}
|
| 790 |
+
"{justification}"
|
| 791 |
+
|
| 792 |
+
{tr('fv_justification_verification_prompt_task_header')}
|
| 793 |
+
{tr('fv_justification_verification_prompt_task_instruction')}
|
| 794 |
+
|
| 795 |
+
{tr('fv_justification_verification_prompt_json_instruction')}
|
| 796 |
+
|
| 797 |
+
{tr('fv_justification_verification_prompt_footer')}
|
| 798 |
+
"""
|
| 799 |
+
|
| 800 |
+
data = {
|
| 801 |
+
"model": "qwen2.5-vl-72b-instruct",
|
| 802 |
+
"messages": [{"role": "user", "content": verification_prompt}],
|
| 803 |
+
"max_tokens": 600,
|
| 804 |
+
"temperature": 0.0,
|
| 805 |
+
"seed": 42,
|
| 806 |
+
"response_format": {"type": "json_object"}
|
| 807 |
+
}
|
| 808 |
+
|
| 809 |
+
response = requests.post(
|
| 810 |
+
f"{api_config['api_endpoint']}/chat/completions",
|
| 811 |
+
headers=headers,
|
| 812 |
+
json=data,
|
| 813 |
+
timeout=300
|
| 814 |
+
)
|
| 815 |
+
response.raise_for_status()
|
| 816 |
+
|
| 817 |
+
try:
|
| 818 |
+
result_json = response.json()["choices"][0]["message"]["content"]
|
| 819 |
+
return json.loads(result_json)
|
| 820 |
+
except (json.JSONDecodeError, KeyError):
|
| 821 |
+
return {"is_verified": False, "reasoning": "Could not parse the semantic justification result."}
|
| 822 |
+
|
| 823 |
+
def verify_fv_claims(claims, analysis_results, context):
|
| 824 |
+
# Verifies claims for the function vector page.
|
| 825 |
+
verification_results = []
|
| 826 |
+
|
| 827 |
+
if not analysis_results:
|
| 828 |
+
return [{"claim_text": c.get('claim_text', 'N/A'), "verified": False, "evidence": "Analysis results not available."} for c in claims]
|
| 829 |
+
|
| 830 |
+
for claim in claims:
|
| 831 |
+
is_verified = False
|
| 832 |
+
evidence = "Could not be verified."
|
| 833 |
+
details = claim.get('details', {})
|
| 834 |
+
|
| 835 |
+
try:
|
| 836 |
+
if context == "pca" and 'attribution' in analysis_results:
|
| 837 |
+
attribution_data = analysis_results['attribution']
|
| 838 |
+
claim_type = claim.get('claim_type')
|
| 839 |
+
|
| 840 |
+
if claim_type == 'top_k_similarity':
|
| 841 |
+
item_type = details.get('item_type')
|
| 842 |
+
items_claimed = details.get('items', [])
|
| 843 |
+
items_claimed_lower = [str(i).lower() for i in items_claimed]
|
| 844 |
+
rank_description = details.get('rank_description')
|
| 845 |
+
|
| 846 |
+
TOP_K = 3
|
| 847 |
+
|
| 848 |
+
if item_type == 'function_type':
|
| 849 |
+
actual_scores_raw = list(attribution_data['function_type_scores'].keys())
|
| 850 |
+
actual_scores_formatted = [tr(i) for i in actual_scores_raw]
|
| 851 |
+
actual_scores_lower = [name.lower() for name in actual_scores_formatted]
|
| 852 |
+
|
| 853 |
+
if rank_description == 'most':
|
| 854 |
+
num_claimed = len(items_claimed_lower)
|
| 855 |
+
top_n_actual_formatted = actual_scores_formatted[:num_claimed]
|
| 856 |
+
top_n_actual_lower = actual_scores_lower[:num_claimed]
|
| 857 |
+
|
| 858 |
+
is_verified = set(items_claimed_lower) == set(top_n_actual_lower)
|
| 859 |
+
evidence = f"The top {num_claimed} function type(s) are: {top_n_actual_formatted}. "
|
| 860 |
+
if is_verified:
|
| 861 |
+
evidence += "The claim correctly identified them."
|
| 862 |
+
else:
|
| 863 |
+
evidence += f"The claimed type(s) {items_claimed} did not match the top {num_claimed}."
|
| 864 |
+
else:
|
| 865 |
+
# Default: check for presence in top K
|
| 866 |
+
top_k_actual_formatted = actual_scores_formatted[:TOP_K]
|
| 867 |
+
top_k_actual_lower = actual_scores_lower[:TOP_K]
|
| 868 |
+
unverified_items = [item for item in items_claimed_lower if item not in top_k_actual_lower]
|
| 869 |
+
is_verified = not unverified_items
|
| 870 |
+
evidence = f"Top {TOP_K} actual function types are: {top_k_actual_formatted}. "
|
| 871 |
+
if not is_verified:
|
| 872 |
+
unverified_items_original_case = [c for c in items_claimed if c.lower() in unverified_items]
|
| 873 |
+
evidence += f"The following claimed types were not found in the top {TOP_K}: {unverified_items_original_case}."
|
| 874 |
+
else:
|
| 875 |
+
evidence += f"The claimed types {items_claimed} were successfully found within the top {TOP_K}."
|
| 876 |
+
|
| 877 |
+
elif item_type == 'category':
|
| 878 |
+
actual_scores_raw = list(attribution_data['category_scores'].keys())
|
| 879 |
+
actual_scores_formatted = [format_category_name(i) for i in actual_scores_raw]
|
| 880 |
+
actual_scores_lower = [name.lower() for name in actual_scores_formatted]
|
| 881 |
+
|
| 882 |
+
if rank_description == 'most':
|
| 883 |
+
num_claimed = len(items_claimed_lower)
|
| 884 |
+
top_n_actual_formatted = actual_scores_formatted[:num_claimed]
|
| 885 |
+
top_n_actual_lower = actual_scores_lower[:num_claimed]
|
| 886 |
+
|
| 887 |
+
is_verified = set(items_claimed_lower) == set(top_n_actual_lower)
|
| 888 |
+
evidence = f"The top {num_claimed} category/categories are: {top_n_actual_formatted}. "
|
| 889 |
+
if is_verified:
|
| 890 |
+
evidence += "The claim correctly identified them."
|
| 891 |
+
else:
|
| 892 |
+
evidence += f"The claimed category/categories {items_claimed} did not match the top {num_claimed}."
|
| 893 |
+
else:
|
| 894 |
+
# Default: check for presence in top K
|
| 895 |
+
top_k_actual_formatted = actual_scores_formatted[:TOP_K]
|
| 896 |
+
top_k_actual_lower = actual_scores_lower[:TOP_K]
|
| 897 |
+
unverified_items = [item for item in items_claimed_lower if item not in top_k_actual_lower]
|
| 898 |
+
is_verified = not unverified_items
|
| 899 |
+
evidence = f"Top {TOP_K} actual categories are: {top_k_actual_formatted}. "
|
| 900 |
+
if not is_verified:
|
| 901 |
+
unverified_items_original_case = [c for c in items_claimed if c.lower() in unverified_items]
|
| 902 |
+
evidence += f"The following claimed categories were not found in the top {TOP_K}: {unverified_items_original_case}."
|
| 903 |
+
else:
|
| 904 |
+
evidence += f"The claimed categories {items_claimed} were successfully found within the top {TOP_K}."
|
| 905 |
+
|
| 906 |
+
elif claim_type == 'positional_claim':
|
| 907 |
+
cluster_names_claimed = details.get('cluster_names', [])
|
| 908 |
+
position = details.get('position')
|
| 909 |
+
|
| 910 |
+
if position == 'near':
|
| 911 |
+
top_3_types_raw = list(attribution_data['function_type_scores'].keys())[:3]
|
| 912 |
+
top_3_types_formatted = [tr(i) for i in top_3_types_raw]
|
| 913 |
+
|
| 914 |
+
api_config = init_qwen_api()
|
| 915 |
+
if api_config:
|
| 916 |
+
verification = _cached_verify_semantic_cluster_claim(api_config, cluster_names_claimed, top_3_types_formatted)
|
| 917 |
+
is_verified = verification.get('is_verified', False)
|
| 918 |
+
evidence = verification.get('reasoning', "Failed to get reasoning.")
|
| 919 |
+
else:
|
| 920 |
+
is_verified = False
|
| 921 |
+
evidence = "API key not configured for semantic verification."
|
| 922 |
+
|
| 923 |
+
elif claim_type == 'category_justification_claim':
|
| 924 |
+
category_name = details.get('category_name')
|
| 925 |
+
justification = details.get('justification')
|
| 926 |
+
input_prompt = analysis_results.get('attribution', {}).get('input_text', '')
|
| 927 |
+
|
| 928 |
+
if not all([category_name, justification, input_prompt]):
|
| 929 |
+
evidence = "Missing data for justification verification (category, justification, or input prompt)."
|
| 930 |
+
else:
|
| 931 |
+
api_config = init_qwen_api()
|
| 932 |
+
if api_config:
|
| 933 |
+
verification = _cached_verify_justification_claim(api_config, input_prompt, category_name, justification)
|
| 934 |
+
is_verified = verification.get('is_verified', False)
|
| 935 |
+
evidence = verification.get('reasoning', "Failed to get semantic reasoning for justification.")
|
| 936 |
+
else:
|
| 937 |
+
is_verified = False
|
| 938 |
+
evidence = "API key not configured for semantic verification."
|
| 939 |
+
|
| 940 |
+
elif context == "evolution" and 'evolution' in analysis_results:
|
| 941 |
+
evolution_data = analysis_results['evolution']
|
| 942 |
+
claim_type = claim.get('claim_type')
|
| 943 |
+
|
| 944 |
+
if claim_type == 'peak_activation':
|
| 945 |
+
claimed_layer = details.get('layer_index')
|
| 946 |
+
activation_strengths = [float(np.sqrt(np.sum(vec ** 2))) for vec in evolution_data['layer_vectors'].values()]
|
| 947 |
+
actual_peak_layer = np.argmax(activation_strengths)
|
| 948 |
+
is_verified = (claimed_layer == actual_peak_layer)
|
| 949 |
+
evidence = f"Claimed peak activation at layer {claimed_layer}. Actual peak is at layer {actual_peak_layer}."
|
| 950 |
+
|
| 951 |
+
elif claim_type == 'biggest_change':
|
| 952 |
+
claimed_start = details.get('start_layer')
|
| 953 |
+
layer_changes = evolution_data['layer_changes']
|
| 954 |
+
actual_biggest_change_idx = np.argmax(layer_changes)
|
| 955 |
+
actual_start_layer = actual_biggest_change_idx + 1
|
| 956 |
+
is_verified = (claimed_start == actual_start_layer)
|
| 957 |
+
evidence = f"Claimed biggest change starts at layer {claimed_start}. Actual biggest change is at layer {actual_start_layer} -> {actual_start_layer + 1}."
|
| 958 |
+
|
| 959 |
+
elif claim_type == 'specific_value_claim':
|
| 960 |
+
metric = details.get('metric')
|
| 961 |
+
layer_index = details.get('layer_index')
|
| 962 |
+
value = details.get('value')
|
| 963 |
+
|
| 964 |
+
if metric == 'activation_strength':
|
| 965 |
+
activation_strengths = [float(np.sqrt(np.sum(vec ** 2))) for vec in evolution_data['layer_vectors'].values()]
|
| 966 |
+
# Check if layer_index is valid
|
| 967 |
+
if layer_index < len(activation_strengths):
|
| 968 |
+
actual_value = activation_strengths[layer_index]
|
| 969 |
+
is_verified = round(actual_value, 2) == round(value, 2)
|
| 970 |
+
evidence = f"Claimed activation strength for layer {layer_index} was {value}. Actual strength is {actual_value:.2f}."
|
| 971 |
+
else:
|
| 972 |
+
evidence = f"Invalid layer index {layer_index} provided."
|
| 973 |
+
|
| 974 |
+
elif metric == 'change_magnitude':
|
| 975 |
+
layer_changes = evolution_data['layer_changes']
|
| 976 |
+
# change between L and L+1 is at index L-1 in the list
|
| 977 |
+
# So for layer_index 1 (1->2), we need list index 0.
|
| 978 |
+
change_index = layer_index - 1
|
| 979 |
+
if 0 <= change_index < len(layer_changes):
|
| 980 |
+
actual_value = layer_changes[change_index]
|
| 981 |
+
is_verified = round(actual_value, 2) == round(value, 2)
|
| 982 |
+
evidence = f"Claimed change magnitude for transition starting at layer {layer_index} was {value}. Actual magnitude is {actual_value:.2f}."
|
| 983 |
+
else:
|
| 984 |
+
evidence = f"Invalid starting layer index {layer_index} for change magnitude."
|
| 985 |
+
|
| 986 |
+
except Exception as e:
|
| 987 |
+
evidence = f"An error occurred during verification: {str(e)}"
|
| 988 |
+
|
| 989 |
+
verification_results.append({
|
| 990 |
+
'claim_text': claim.get('claim_text', 'N/A'),
|
| 991 |
+
'verified': is_verified,
|
| 992 |
+
'evidence': evidence
|
| 993 |
+
})
|
| 994 |
+
|
| 995 |
+
return verification_results
|
| 996 |
+
|
| 997 |
+
# --- End Faithfulness Verification ---
|
| 998 |
+
|
| 999 |
+
|
| 1000 |
+
def display_category_examples():
|
| 1001 |
+
# Displays an explorer for the function category examples.
|
| 1002 |
+
st.markdown(tr('category_examples_desc'))
|
| 1003 |
+
|
| 1004 |
+
# Add an expander with descriptions for each function type.
|
| 1005 |
+
with st.expander(tr('what_is_this_function_type')):
|
| 1006 |
+
for func_type_key in FUNCTION_TYPES.keys():
|
| 1007 |
+
color = FUNCTION_TYPE_COLORS.get(func_type_key, '#CCCCCC')
|
| 1008 |
+
st.markdown(f"""
|
| 1009 |
+
<div style="border-left: 5px solid {color}; padding: 0.5rem 1rem; margin-top: 1rem; background-color: #2b2b2b; border-radius: 5px;">
|
| 1010 |
+
<h5 style="margin: 0; color: {color};">{tr(func_type_key)}</h5>
|
| 1011 |
+
<p style="margin-top: 0.5rem; color: #EAEAEA;">{tr(f"desc_{func_type_key}")}</p>
|
| 1012 |
+
</div>
|
| 1013 |
+
""", unsafe_allow_html=True)
|
| 1014 |
+
|
| 1015 |
+
if 'show_all_states' not in st.session_state:
|
| 1016 |
+
st.session_state.show_all_states = {}
|
| 1017 |
+
|
| 1018 |
+
current_lang = st.session_state.get('lang', 'en')
|
| 1019 |
+
col1, col2 = st.columns([1, 3])
|
| 1020 |
+
|
| 1021 |
+
with col1:
|
| 1022 |
+
st.subheader(tr('function_types_subheader'))
|
| 1023 |
+
|
| 1024 |
+
# --- Restore st.radio and add CSS for highlighting ---
|
| 1025 |
+
func_type_keys = list(FUNCTION_TYPES.keys())
|
| 1026 |
+
display_names = [tr(key) for key in func_type_keys]
|
| 1027 |
+
|
| 1028 |
+
# Set a default selection.
|
| 1029 |
+
if 'selected_func_type_key' not in st.session_state:
|
| 1030 |
+
st.session_state.selected_func_type_key = func_type_keys[0]
|
| 1031 |
+
|
| 1032 |
+
# Find the index of the current selection.
|
| 1033 |
+
try:
|
| 1034 |
+
current_index = func_type_keys.index(st.session_state.selected_func_type_key)
|
| 1035 |
+
except ValueError:
|
| 1036 |
+
current_index = 0
|
| 1037 |
+
|
| 1038 |
+
def on_radio_change():
|
| 1039 |
+
# A callback to update the session state when the radio button changes.
|
| 1040 |
+
selected_display_name = st.session_state.radio_selector
|
| 1041 |
+
if selected_display_name in display_names:
|
| 1042 |
+
idx = display_names.index(selected_display_name)
|
| 1043 |
+
st.session_state.selected_func_type_key = func_type_keys[idx]
|
| 1044 |
+
|
| 1045 |
+
# Create the radio button selector.
|
| 1046 |
+
st.radio(
|
| 1047 |
+
label="Function Types",
|
| 1048 |
+
options=display_names,
|
| 1049 |
+
index=current_index,
|
| 1050 |
+
on_change=on_radio_change,
|
| 1051 |
+
key='radio_selector',
|
| 1052 |
+
label_visibility="collapsed"
|
| 1053 |
+
)
|
| 1054 |
+
|
| 1055 |
+
# Get the key and color for the selected function type.
|
| 1056 |
+
selected_func_type_key = st.session_state.selected_func_type_key
|
| 1057 |
+
selected_color = FUNCTION_TYPE_COLORS.get(selected_func_type_key, 'lightgrey')
|
| 1058 |
+
|
| 1059 |
+
# Add some CSS to highlight the selected radio button.
|
| 1060 |
+
st.markdown(f"""
|
| 1061 |
+
<style>
|
| 1062 |
+
[data-testid="stAppViewBlockContainer"] div[role="radiogroup"] > label:has(input[type="radio"]:checked) {{
|
| 1063 |
+
background-color: {selected_color} !important;
|
| 1064 |
+
border-radius: 10px;
|
| 1065 |
+
padding: 0.5rem 1rem;
|
| 1066 |
+
color: white !important;
|
| 1067 |
+
font-weight: bold;
|
| 1068 |
+
}}
|
| 1069 |
+
/* Ensure the text itself is white for contrast */
|
| 1070 |
+
[data-testid="stAppViewBlockContainer"] div[role="radiogroup"] > label:has(input[type="radio"]:checked) div {{
|
| 1071 |
+
color: white !important;
|
| 1072 |
+
}}
|
| 1073 |
+
</style>
|
| 1074 |
+
""", unsafe_allow_html=True)
|
| 1075 |
+
|
| 1076 |
+
|
| 1077 |
+
with col2:
|
| 1078 |
+
category_keys = FUNCTION_TYPES[selected_func_type_key]
|
| 1079 |
+
available_cats = [
|
| 1080 |
+
cat_key for cat_key in category_keys
|
| 1081 |
+
if cat_key in FUNCTION_CATEGORIES and current_lang in FUNCTION_CATEGORIES[cat_key]
|
| 1082 |
+
]
|
| 1083 |
+
|
| 1084 |
+
if not available_cats:
|
| 1085 |
+
st.warning(tr('no_examples_for_type'))
|
| 1086 |
+
else:
|
| 1087 |
+
# Get the color and symbol for the selected type.
|
| 1088 |
+
selected_display_name = tr(selected_func_type_key)
|
| 1089 |
+
|
| 1090 |
+
# Display the header.
|
| 1091 |
+
st.markdown(f"<h4 style='color: #3498db; font-weight: bold;'>{tr('prompt_examples_for_category_header').format(category=selected_display_name)}</h4>", unsafe_allow_html=True)
|
| 1092 |
+
|
| 1093 |
+
num_to_show_by_default = 9
|
| 1094 |
+
show_all = st.session_state.show_all_states.get(selected_func_type_key, False)
|
| 1095 |
+
|
| 1096 |
+
if len(available_cats) > num_to_show_by_default and not show_all:
|
| 1097 |
+
cats_to_display = available_cats[:num_to_show_by_default]
|
| 1098 |
+
else:
|
| 1099 |
+
cats_to_display = available_cats
|
| 1100 |
+
|
| 1101 |
+
# --- Display Cards ---
|
| 1102 |
+
num_columns = 3
|
| 1103 |
+
example_cols = st.columns(num_columns)
|
| 1104 |
+
for i, cat_key in enumerate(cats_to_display):
|
| 1105 |
+
examples = FUNCTION_CATEGORIES.get(cat_key, {}).get(current_lang, [])
|
| 1106 |
+
if examples:
|
| 1107 |
+
# Use the formatter for the display name.
|
| 1108 |
+
display_name = format_category_name(cat_key)
|
| 1109 |
+
with example_cols[i % num_columns]:
|
| 1110 |
+
with st.container():
|
| 1111 |
+
st.markdown(f"""
|
| 1112 |
+
<div style="border: 1px solid #e0e0e0; border-radius: 10px; padding: 1rem; height: 140px; margin-bottom: 1rem; display: flex; flex-direction: column; justify-content: space-between;">
|
| 1113 |
+
<div>
|
| 1114 |
+
<p style="font-weight: bold; color: #3498db;">{display_name}</p>
|
| 1115 |
+
</div>
|
| 1116 |
+
<div>
|
| 1117 |
+
<p style="font-style: italic; font-size: 0.9em; color: #6c757d;">"{examples[0]}"</p>
|
| 1118 |
+
</div>
|
| 1119 |
+
</div>
|
| 1120 |
+
""", unsafe_allow_html=True)
|
| 1121 |
+
|
| 1122 |
+
# --- "Show More/Less" Buttons ---
|
| 1123 |
+
if len(available_cats) > num_to_show_by_default:
|
| 1124 |
+
if not show_all:
|
| 1125 |
+
if st.button(tr('show_all_button').format(count=len(available_cats)), key=f"show_all_{selected_func_type_key}"):
|
| 1126 |
+
st.session_state.show_all_states[selected_func_type_key] = True
|
| 1127 |
+
st.rerun()
|
| 1128 |
+
else:
|
| 1129 |
+
if st.button(tr('show_less_button'), key=f"show_less_{selected_func_type_key}"):
|
| 1130 |
+
# Set to False or remove the key.
|
| 1131 |
+
st.session_state.show_all_states[selected_func_type_key] = False
|
| 1132 |
+
st.rerun()
|
| 1133 |
+
|
| 1134 |
+
def display_3d_pca_visualization(user_input_data=None, show_description=True):
|
| 1135 |
+
# Displays the interactive 3D PCA plot.
|
| 1136 |
+
import numpy as np
|
| 1137 |
+
current_lang = st.session_state.get('lang', 'en')
|
| 1138 |
+
|
| 1139 |
+
if show_description:
|
| 1140 |
+
if current_lang == 'de':
|
| 1141 |
+
st.markdown("""
|
| 1142 |
+
<div style='background-color: #2b2b2b; color: #ffffff; padding: 1.5rem; border-radius: 10px; margin: 1rem 0; border-left: 5px solid #4a90e2;'>
|
| 1143 |
+
<h4 style='color: #4a90e2; margin-top: 0;'>Interaktive 3D-PCA von Funktionsvektoren</h4>
|
| 1144 |
+
<p>Diese Visualisierung stellt die hochdimensionalen 'Funktionsvektoren' verschiedener Anweisungs-Prompts in einem vereinfachten 3D-Raum mittels Hauptkomponentenanalyse (PCA) dar. Hier ist eine Aufschlüsselung dessen, was Sie sehen:</p>
|
| 1145 |
+
<ul>
|
| 1146 |
+
<li><strong>Was sind Funktionsvektoren?</strong> Jeder Punkt in diesem Diagramm repräsentiert einen 'Funktionsvektor' – einen numerischen Fingerabdruck (ein Embedding), der den zentralen funktionalen Zweck eines bestimmten Prompts erfasst. Diese Vektoren werden aus dem letzten verborgenen Zustand des OLMo-Modells extrahiert, nachdem es einen Prompt verarbeitet hat. Prompts mit ähnlichen Funktionen haben Vektoren, die im hochdimensionalen Raum nahe beieinander liegen.</li>
|
| 1147 |
+
<li><strong>Wie funktioniert PCA?</strong> PCA ist eine Technik zur Dimensionsreduktion, die komplexe, hochdimensionale Daten in ein neues, kleineres Koordinatensystem (in diesem Fall 3D) umwandelt. Dies geschieht durch die Identifizierung der Richtungen (Hauptkomponenten), in denen die Daten am stärksten variieren. Durch die Darstellung der ersten drei Hauptkomponenten können wir die wichtigsten Beziehungen zwischen den Funktionsvektoren auf eine für uns leicht interpretierbare Weise visualisieren.</li>
|
| 1148 |
+
<li><strong>Worauf ist zu achten?</strong> Suchen Sie nach Punktclustern. Diese Cluster repräsentieren Gruppen von Funktionen, die das Modell als ähnlich wahrnimmt. Der Abstand zwischen den Punkten gibt ihre funktionale Ähnlichkeit an – nähere Punkte sind ähnlicher.</li>
|
| 1149 |
+
</ul>
|
| 1150 |
+
</div>
|
| 1151 |
+
""", unsafe_allow_html=True)
|
| 1152 |
+
else:
|
| 1153 |
+
st.markdown("""
|
| 1154 |
+
<div style='background-color: #2b2b2b; color: #ffffff; padding: 1.5rem; border-radius: 10px; margin: 1rem 0; border-left: 5px solid #4a90e2;'>
|
| 1155 |
+
<h4 style='color: #4a90e2; margin-top: 0;'>Interactive 3D PCA of Function Vectors</h4>
|
| 1156 |
+
<p>This visualization plots the high-dimensional 'function vectors' of different instructional prompts in a simplified 3D space using <strong>Principal Component Analysis (PCA)</strong>. Here's a breakdown of what you're seeing:</p>
|
| 1157 |
+
<ul>
|
| 1158 |
+
<li><strong>What are Function Vectors?</strong> Each point on this plot represents a 'function vector'—a numerical fingerprint (an embedding) that captures the core functional purpose of a specific prompt. These vectors are extracted from the final hidden state of the OLMo model after it processes a prompt. Prompts with similar functions will have vectors that are close to each other in the high-dimensional space.</li>
|
| 1159 |
+
<li><strong>How does PCA work?</strong> PCA is a dimensionality reduction technique that transforms the complex, high-dimensional data into a new, smaller coordinate system (in this case, 3D). It does this by identifying the directions (principal components) where the data varies the most. By plotting the first three principal components, we can visualize the most significant relationships between the function vectors in a way that's easy for us to interpret.</li>
|
| 1160 |
+
<li><strong>What to look for:</strong> Look for clusters of points. These clusters represent groups of functions that the model perceives as similar. The distance between points indicates their functional similarity—closer points are more alike.</li>
|
| 1161 |
+
</ul>
|
| 1162 |
+
</div>
|
| 1163 |
+
""", unsafe_allow_html=True)
|
| 1164 |
+
st.markdown(tr('run_analysis_for_viz_info'), unsafe_allow_html=True)
|
| 1165 |
+
|
| 1166 |
+
# --- Load the base vectors for the selected language ---
|
| 1167 |
+
@st.cache_data
|
| 1168 |
+
def load_base_vectors(lang, cache_version="function-vectors-2025-11-09"):
|
| 1169 |
+
import numpy as np
|
| 1170 |
+
vector_path = Path(__file__).parent / f"data/vectors/{lang}_category_vectors.npz"
|
| 1171 |
+
if not vector_path.exists():
|
| 1172 |
+
st.error(f"Could not find vector file for language '{lang}' at {vector_path}")
|
| 1173 |
+
return None
|
| 1174 |
+
try:
|
| 1175 |
+
loaded_data = np.load(vector_path, allow_pickle=True)
|
| 1176 |
+
return {key: loaded_data[key] for key in loaded_data.files}
|
| 1177 |
+
except Exception as e:
|
| 1178 |
+
st.error(f"Error loading vectors: {e}")
|
| 1179 |
+
return None
|
| 1180 |
+
|
| 1181 |
+
category_vectors = load_base_vectors(current_lang)
|
| 1182 |
+
|
| 1183 |
+
if category_vectors is None:
|
| 1184 |
+
return # Stop if we can't load the necessary data
|
| 1185 |
+
|
| 1186 |
+
try:
|
| 1187 |
+
# Prepare data for PCA using the loaded base vectors
|
| 1188 |
+
categories = list(category_vectors.keys())
|
| 1189 |
+
vectors = np.vstack([category_vectors[cat] for cat in categories])
|
| 1190 |
+
|
| 1191 |
+
# If user input exists, add it to the data
|
| 1192 |
+
if user_input_data is not None:
|
| 1193 |
+
input_activation = user_input_data['input_activation']
|
| 1194 |
+
input_text = user_input_data['input_text']
|
| 1195 |
+
all_vectors = np.vstack([vectors, input_activation.reshape(1, -1)])
|
| 1196 |
+
plot_title = tr('pca_3d_with_input_title')
|
| 1197 |
+
else:
|
| 1198 |
+
all_vectors = vectors
|
| 1199 |
+
plot_title = tr('pca_3d_title').format(lang=current_lang.upper())
|
| 1200 |
+
|
| 1201 |
+
# Perform PCA
|
| 1202 |
+
pca = PCA(n_components=3)
|
| 1203 |
+
reduced_vectors = pca.fit_transform(all_vectors)
|
| 1204 |
+
|
| 1205 |
+
# Create plotly figure
|
| 1206 |
+
fig = go.Figure()
|
| 1207 |
+
|
| 1208 |
+
# Add category points grouped by function type
|
| 1209 |
+
category_points = reduced_vectors[:len(categories)]
|
| 1210 |
+
for func_type_key, cats in FUNCTION_TYPES.items():
|
| 1211 |
+
func_categories = [cat for cat in cats if cat in categories]
|
| 1212 |
+
if func_categories:
|
| 1213 |
+
indices = [categories.index(cat) for cat in func_categories]
|
| 1214 |
+
fig.add_trace(go.Scatter3d(
|
| 1215 |
+
x=category_points[indices, 0], y=category_points[indices, 1], z=category_points[indices, 2],
|
| 1216 |
+
mode='markers',
|
| 1217 |
+
marker=dict(size=8, color=FUNCTION_TYPE_COLORS.get(func_type_key, 'gray'), symbol=PLOTLY_SYMBOLS.get(func_type_key, 'circle'), line=dict(width=1, color='black'), opacity=0.7),
|
| 1218 |
+
name=tr(func_type_key),
|
| 1219 |
+
text=[format_category_name(cat) for cat in func_categories],
|
| 1220 |
+
hovertemplate="<b>%{text}</b><br>PC1: %{x:.3f}<br>PC2: %{y:.3f}<br>PC3: %{z:.3f}<extra></extra>"
|
| 1221 |
+
))
|
| 1222 |
+
|
| 1223 |
+
# If user input exists, add it as a special point
|
| 1224 |
+
if user_input_data is not None:
|
| 1225 |
+
user_point = reduced_vectors[-1]
|
| 1226 |
+
fig.add_trace(go.Scatter3d(
|
| 1227 |
+
x=[user_point[0]], y=[user_point[1]], z=[user_point[2]],
|
| 1228 |
+
mode='markers',
|
| 1229 |
+
marker=dict(size=12, color='red', symbol='diamond', line=dict(width=2, color='darkred')),
|
| 1230 |
+
name=tr('your_input_legend'),
|
| 1231 |
+
text=[f"{tr('your_input_legend')}: {input_text[:50]}..."],
|
| 1232 |
+
hovertemplate=f"<b>{tr('your_input_hover_title')}</b><br>%{{text}}<br>PC1: %{{x:.3f}}<br>PC2: %{{y:.3f}}<br>PC3: %{{z:.3f}}<extra></extra>"
|
| 1233 |
+
))
|
| 1234 |
+
|
| 1235 |
+
fig.update_layout(
|
| 1236 |
+
title=plot_title,
|
| 1237 |
+
width=1400, height=900,
|
| 1238 |
+
scene=dict(xaxis_title='PC1', yaxis_title='PC2', zaxis_title='PC3', camera=dict(eye=dict(x=1.5, y=1.5, z=1.5))),
|
| 1239 |
+
legend=dict(orientation="v", yanchor="top", y=1, xanchor="left", x=1.02, font=dict(size=10), title_text=tr('legend_title'))
|
| 1240 |
+
)
|
| 1241 |
+
|
| 1242 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 1243 |
+
|
| 1244 |
+
if user_input_data is not None:
|
| 1245 |
+
st.markdown(tr('your_input_analysis_desc').format(input_text=input_text))
|
| 1246 |
+
else:
|
| 1247 |
+
st.markdown(f"""{tr('pca_key_insights')}""", unsafe_allow_html=True)
|
| 1248 |
+
|
| 1249 |
+
except Exception as e:
|
| 1250 |
+
st.error(tr('error_creating_enhanced_pca').format(e=str(e)))
|
| 1251 |
+
|
| 1252 |
+
def display_analysis_results(results, input_text):
|
| 1253 |
+
# Displays the results of the analysis.
|
| 1254 |
+
|
| 1255 |
+
st.success(tr('analysis_complete_success'))
|
| 1256 |
+
|
| 1257 |
+
st.markdown(f"""
|
| 1258 |
+
<div style='background: linear-gradient(135deg, #2f3f70 0%, #3a4c86 100%); padding: 1rem; border-radius: 10px; color: #f5f7fb; margin: 1rem 0; border-left: 4px solid #dcae36;'>
|
| 1259 |
+
<h4 style='margin: 0; color: #f5f7fb;'>{tr('analyzed_text_header')}</h4>
|
| 1260 |
+
<p style='margin: 0.5rem 0 0 0; font-size: 1.1rem; font-style: italic; color: #e8ecf8;'>"{input_text}"</p>
|
| 1261 |
+
</div>
|
| 1262 |
+
""", unsafe_allow_html=True)
|
| 1263 |
+
|
| 1264 |
+
# --- Show the 3D plot with the user's data first ---
|
| 1265 |
+
st.markdown(f"<h2>{tr('pca_3d_section_header')}</h2>", unsafe_allow_html=True)
|
| 1266 |
+
user_input_data = st.session_state.get('user_input_3d_data')
|
| 1267 |
+
display_3d_pca_visualization(user_input_data, show_description=False)
|
| 1268 |
+
|
| 1269 |
+
# --- AI Explanation for PCA Plot ---
|
| 1270 |
+
if st.session_state.get('enable_ai_explanation') and 'explanation_part_1' in st.session_state:
|
| 1271 |
+
# Display the first part of the explanation.
|
| 1272 |
+
if st.session_state.explanation_part_1:
|
| 1273 |
+
explanation_html = markdown.markdown(st.session_state.explanation_part_1)
|
| 1274 |
+
st.markdown(
|
| 1275 |
+
f"<div style='background-color: #2b2b2b; color: #ffffff; padding: 1.2rem; border-radius: 10px; margin: 1rem 0; border-left: 5px solid #6EE7B7; font-size: 0.9rem;'>{explanation_html}</div>",
|
| 1276 |
+
unsafe_allow_html=True
|
| 1277 |
+
)
|
| 1278 |
+
|
| 1279 |
+
# Faithfulness Check for PCA plot
|
| 1280 |
+
with st.expander(tr('faithfulness_check_expander')):
|
| 1281 |
+
st.markdown(tr('fv_faithfulness_explanation_pca_html'), unsafe_allow_html=True)
|
| 1282 |
+
|
| 1283 |
+
# Check for pre-cached faithfulness results first
|
| 1284 |
+
if 'pca_faithfulness' in st.session_state.analysis_results:
|
| 1285 |
+
verification_results = st.session_state.analysis_results['pca_faithfulness']
|
| 1286 |
+
else:
|
| 1287 |
+
api_config = init_qwen_api()
|
| 1288 |
+
if api_config:
|
| 1289 |
+
with st.spinner(tr('running_faithfulness_check_spinner')):
|
| 1290 |
+
claims = _cached_extract_fv_claims(api_config, st.session_state.explanation_part_1, "pca")
|
| 1291 |
+
verification_results = verify_fv_claims(claims, results, "pca")
|
| 1292 |
+
else:
|
| 1293 |
+
verification_results = []
|
| 1294 |
+
st.warning(tr('api_key_not_configured_warning'))
|
| 1295 |
+
|
| 1296 |
+
if verification_results:
|
| 1297 |
+
for result in verification_results:
|
| 1298 |
+
status_text = tr('verified_status') if result['verified'] else tr('contradicted_status')
|
| 1299 |
+
st.markdown(f"""
|
| 1300 |
+
<div style="margin-bottom: 1rem; padding: 0.8rem; border-radius: 8px; border-left: 5px solid {'#28a745' if result['verified'] else '#dc3545'}; background-color: #1a1a1a;">
|
| 1301 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('claim_label')}:</strong> <em>"{result['claim_text']}"</em></p>
|
| 1302 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('status_label')}:</strong> {status_text}</p>
|
| 1303 |
+
<p style="margin-bottom: 0;"><strong>{tr('evidence_label')}:</strong> {result['evidence']}</p>
|
| 1304 |
+
</div>
|
| 1305 |
+
""", unsafe_allow_html=True)
|
| 1306 |
+
else:
|
| 1307 |
+
st.info(tr('no_verifiable_claims_info'))
|
| 1308 |
+
|
| 1309 |
+
st.markdown("---")
|
| 1310 |
+
|
| 1311 |
+
# --- Function Type and Category Analysis ---
|
| 1312 |
+
if 'attribution' in results:
|
| 1313 |
+
attribution = results['attribution']
|
| 1314 |
+
|
| 1315 |
+
# --- Section 1: Function Type Attribution ---
|
| 1316 |
+
st.markdown(f"<h2>{tr('function_types_tab')}</h2>", unsafe_allow_html=True)
|
| 1317 |
+
st.markdown(tr('function_type_attribution_header'))
|
| 1318 |
+
|
| 1319 |
+
function_type_scores = attribution['function_type_scores']
|
| 1320 |
+
top_types = list(function_type_scores.items())[:6]
|
| 1321 |
+
|
| 1322 |
+
# Reverse for a horizontal bar chart.
|
| 1323 |
+
top_types.reverse()
|
| 1324 |
+
|
| 1325 |
+
fig = go.Figure()
|
| 1326 |
+
colors = [FUNCTION_TYPE_COLORS.get(name, '#CCCCCC') for name, _ in top_types]
|
| 1327 |
+
|
| 1328 |
+
fig.add_trace(go.Bar(
|
| 1329 |
+
x=[score for _, score in top_types],
|
| 1330 |
+
y=[tr(name) for name, _ in top_types],
|
| 1331 |
+
orientation='h',
|
| 1332 |
+
marker=dict(color=colors),
|
| 1333 |
+
text=[f"{score:.3f}" for _, score in top_types],
|
| 1334 |
+
textposition='outside',
|
| 1335 |
+
hovertemplate='<b>%{y}</b><br>Score: %{x:.3f}<extra></extra>'
|
| 1336 |
+
))
|
| 1337 |
+
|
| 1338 |
+
fig.update_layout(
|
| 1339 |
+
xaxis_title=tr('attribution_score_xaxis'),
|
| 1340 |
+
yaxis=dict(autorange="reversed"), # Ensures y-axis is not reversed
|
| 1341 |
+
height=500,
|
| 1342 |
+
margin=dict(l=200, r=100, t=50, b=50)
|
| 1343 |
+
)
|
| 1344 |
+
|
| 1345 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 1346 |
+
|
| 1347 |
+
# --- AI Explanation for Function Type Plot ---
|
| 1348 |
+
if st.session_state.get('enable_ai_explanation') and 'explanation_part_2' in st.session_state:
|
| 1349 |
+
if st.session_state.explanation_part_2:
|
| 1350 |
+
explanation_html = markdown.markdown(st.session_state.explanation_part_2)
|
| 1351 |
+
st.markdown(
|
| 1352 |
+
f"<div style='background-color: #2b2b2b; color: #ffffff; padding: 1.2rem; border-radius: 10px; margin: 1rem 0; border-left: 5px solid #A78BFA; font-size: 0.9rem;'>{explanation_html}</div>",
|
| 1353 |
+
unsafe_allow_html=True
|
| 1354 |
+
)
|
| 1355 |
+
|
| 1356 |
+
# Faithfulness Check for Function Type plot
|
| 1357 |
+
with st.expander(tr('faithfulness_check_expander')):
|
| 1358 |
+
st.markdown(tr('fv_faithfulness_explanation_pca_html'), unsafe_allow_html=True)
|
| 1359 |
+
|
| 1360 |
+
if 'pca_faithfulness' in st.session_state.analysis_results:
|
| 1361 |
+
verification_results = st.session_state.analysis_results['pca_faithfulness']
|
| 1362 |
+
else:
|
| 1363 |
+
api_config = init_qwen_api()
|
| 1364 |
+
if api_config:
|
| 1365 |
+
with st.spinner(tr('running_faithfulness_check_spinner')):
|
| 1366 |
+
claims = _cached_extract_fv_claims(api_config, st.session_state.explanation_part_2, "pca")
|
| 1367 |
+
verification_results = verify_fv_claims(claims, results, "pca")
|
| 1368 |
+
else:
|
| 1369 |
+
verification_results = []
|
| 1370 |
+
st.warning(tr('api_key_not_configured_warning'))
|
| 1371 |
+
|
| 1372 |
+
if verification_results:
|
| 1373 |
+
for result in verification_results:
|
| 1374 |
+
status_text = tr('verified_status') if result['verified'] else tr('contradicted_status')
|
| 1375 |
+
st.markdown(f"""
|
| 1376 |
+
<div style="margin-bottom: 1rem; padding: 0.8rem; border-radius: 8px; border-left: 5px solid {'#28a745' if result['verified'] else '#dc3545'}; background-color: #1a1a1a;">
|
| 1377 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('claim_label')}:</strong> <em>"{result['claim_text']}"</em></p>
|
| 1378 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('status_label')}:</strong> {status_text}</p>
|
| 1379 |
+
<p style="margin-bottom: 0;"><strong>{tr('evidence_label')}:</strong> {result['evidence']}</p>
|
| 1380 |
+
</div>
|
| 1381 |
+
""", unsafe_allow_html=True)
|
| 1382 |
+
else:
|
| 1383 |
+
st.info(tr('no_verifiable_claims_info'))
|
| 1384 |
+
|
| 1385 |
+
st.markdown("---")
|
| 1386 |
+
|
| 1387 |
+
# --- Section 2: Category Analysis ---
|
| 1388 |
+
st.markdown(f"<h2>{tr('category_analysis_tab')}</h2>", unsafe_allow_html=True)
|
| 1389 |
+
st.markdown(tr('top_category_attribution_header'))
|
| 1390 |
+
|
| 1391 |
+
category_scores = attribution['category_scores']
|
| 1392 |
+
top_categories = list(category_scores.items())[:20]
|
| 1393 |
+
|
| 1394 |
+
if top_categories:
|
| 1395 |
+
# Get the function type for each category to color the chart.
|
| 1396 |
+
function_type_mapping = attribution.get('function_types_mapping', FUNCTION_TYPES)
|
| 1397 |
+
category_to_func_type = {
|
| 1398 |
+
cat: func_type
|
| 1399 |
+
for func_type, cats in function_type_mapping.items()
|
| 1400 |
+
for cat in cats
|
| 1401 |
+
}
|
| 1402 |
+
|
| 1403 |
+
missing_categories = [cat for cat, _ in top_categories if cat not in category_to_func_type]
|
| 1404 |
+
if missing_categories:
|
| 1405 |
+
st.warning(tr('missing_category_mapping_warning').format(categories=", ".join(missing_categories)))
|
| 1406 |
+
|
| 1407 |
+
filtered_categories = [(cat, score) for cat, score in top_categories if cat in category_to_func_type]
|
| 1408 |
+
|
| 1409 |
+
if not filtered_categories:
|
| 1410 |
+
st.info(tr('no_mapped_categories_info'))
|
| 1411 |
+
else:
|
| 1412 |
+
# Restructure the data for the sunburst chart.
|
| 1413 |
+
leaf_labels = [format_category_name(cat_key) for cat_key, score in filtered_categories]
|
| 1414 |
+
leaf_values = [score for _, score in filtered_categories]
|
| 1415 |
+
|
| 1416 |
+
leaf_parent_keys = [category_to_func_type[cat_key] for cat_key, _ in filtered_categories]
|
| 1417 |
+
function_type_order = {key: idx for idx, key in enumerate(function_type_mapping.keys())}
|
| 1418 |
+
parent_keys = sorted(
|
| 1419 |
+
set(leaf_parent_keys),
|
| 1420 |
+
key=lambda key: function_type_order.get(key, len(function_type_order))
|
| 1421 |
+
)
|
| 1422 |
+
parent_labels_map = {key: tr(key) for key in parent_keys}
|
| 1423 |
+
|
| 1424 |
+
parent_values = [
|
| 1425 |
+
sum(leaf_values[i] for i, parent_key in enumerate(leaf_parent_keys) if parent_key == key)
|
| 1426 |
+
for key in parent_keys
|
| 1427 |
+
]
|
| 1428 |
+
|
| 1429 |
+
sunburst_labels = [parent_labels_map[key] for key in parent_keys] + leaf_labels
|
| 1430 |
+
sunburst_parents = [""] * len(parent_keys) + [parent_labels_map[key] for key in leaf_parent_keys]
|
| 1431 |
+
sunburst_values = parent_values + leaf_values
|
| 1432 |
+
|
| 1433 |
+
# Create a color map for the labels.
|
| 1434 |
+
label_to_color_map = {
|
| 1435 |
+
parent_labels_map[key]: FUNCTION_TYPE_COLORS.get(key, '#CCCCCC')
|
| 1436 |
+
for key in parent_keys
|
| 1437 |
+
}
|
| 1438 |
+
|
| 1439 |
+
# --- Generate gradient colors for leaves based on score ---
|
| 1440 |
+
def hex_to_rgb_float(h):
|
| 1441 |
+
h = h.lstrip('#')
|
| 1442 |
+
return [int(h[i:i+2], 16) / 255.0 for i in (0, 2, 4)]
|
| 1443 |
+
|
| 1444 |
+
def rgb_float_to_hex(rgb):
|
| 1445 |
+
return '#%02x%02x%02x' % tuple(int(c * 255) for c in rgb)
|
| 1446 |
+
|
| 1447 |
+
leaf_scores = leaf_values
|
| 1448 |
+
min_score = min(leaf_scores) if leaf_scores else 0
|
| 1449 |
+
max_score = max(leaf_scores) if leaf_scores else 1
|
| 1450 |
+
score_range = max_score - min_score
|
| 1451 |
+
|
| 1452 |
+
sunburst_marker_colors = []
|
| 1453 |
+
# Add solid colors for the parent categories.
|
| 1454 |
+
for key in parent_keys:
|
| 1455 |
+
parent_label = parent_labels_map[key]
|
| 1456 |
+
sunburst_marker_colors.append(label_to_color_map[parent_label])
|
| 1457 |
+
|
| 1458 |
+
# Add gradient colors for the leaf categories.
|
| 1459 |
+
for i, parent_key in enumerate(leaf_parent_keys):
|
| 1460 |
+
base_color_hex = FUNCTION_TYPE_COLORS.get(parent_key, '#CCCCCC')
|
| 1461 |
+
|
| 1462 |
+
# Normalize the score for this leaf.
|
| 1463 |
+
normalized_score = (leaf_scores[i] - min_score) / score_range if score_range > 0 else 0.5
|
| 1464 |
+
|
| 1465 |
+
# Convert to HLS to get the original lightness.
|
| 1466 |
+
r, g, b = hex_to_rgb_float(base_color_hex)
|
| 1467 |
+
h, base_l, s = colorsys.rgb_to_hls(r, g, b)
|
| 1468 |
+
|
| 1469 |
+
# Define a lightness range.
|
| 1470 |
+
lightest_shade = 0.9
|
| 1471 |
+
lightness_range = lightest_shade - base_l
|
| 1472 |
+
|
| 1473 |
+
# Interpolate the lightness.
|
| 1474 |
+
new_l = lightest_shade - (normalized_score * lightness_range)
|
| 1475 |
+
|
| 1476 |
+
# Convert back to RGB and then to Hex.
|
| 1477 |
+
new_r, new_g, new_b = colorsys.hls_to_rgb(h, new_l, s)
|
| 1478 |
+
new_hex = rgb_float_to_hex((new_r, new_g, new_b))
|
| 1479 |
+
sunburst_marker_colors.append(new_hex)
|
| 1480 |
+
|
| 1481 |
+
# --- Highlight the top match with a stronger visual cue ---
|
| 1482 |
+
top_category_name, _ = filtered_categories[0]
|
| 1483 |
+
formatted_top_category_name = format_category_name(top_category_name)
|
| 1484 |
+
top_parent_key = category_to_func_type.get(top_category_name)
|
| 1485 |
+
top_category_parent_str = parent_labels_map.get(top_parent_key, tr('unmapped_function_type'))
|
| 1486 |
+
|
| 1487 |
+
sunburst_line_widths = [1] * len(sunburst_labels)
|
| 1488 |
+
sunburst_line_colors = ['#333'] * len(sunburst_labels)
|
| 1489 |
+
|
| 1490 |
+
try:
|
| 1491 |
+
top_leaf_index = sunburst_labels.index(formatted_top_category_name)
|
| 1492 |
+
sunburst_line_widths[top_leaf_index] = 5
|
| 1493 |
+
sunburst_line_colors[top_leaf_index] = '#FFFFFF'
|
| 1494 |
+
except ValueError:
|
| 1495 |
+
pass
|
| 1496 |
+
|
| 1497 |
+
try:
|
| 1498 |
+
top_parent_index = sunburst_labels.index(top_category_parent_str)
|
| 1499 |
+
sunburst_line_widths[top_parent_index] = 5
|
| 1500 |
+
sunburst_line_colors[top_parent_index] = '#FFFFFF'
|
| 1501 |
+
except ValueError:
|
| 1502 |
+
pass
|
| 1503 |
+
|
| 1504 |
+
fig = go.Figure(go.Sunburst(
|
| 1505 |
+
labels=sunburst_labels,
|
| 1506 |
+
parents=sunburst_parents,
|
| 1507 |
+
values=sunburst_values,
|
| 1508 |
+
branchvalues="total",
|
| 1509 |
+
hovertemplate='<b>%{label}</b><br>Score: %{value:.3f}<extra></extra>',
|
| 1510 |
+
marker=dict(
|
| 1511 |
+
colors=sunburst_marker_colors,
|
| 1512 |
+
line=dict(color=sunburst_line_colors, width=sunburst_line_widths)
|
| 1513 |
+
),
|
| 1514 |
+
maxdepth=2,
|
| 1515 |
+
textfont=dict(color='black'),
|
| 1516 |
+
leaf=dict(opacity=1)
|
| 1517 |
+
))
|
| 1518 |
+
|
| 1519 |
+
fig.update_layout(
|
| 1520 |
+
title=dict(
|
| 1521 |
+
text=tr('sunburst_chart_title'),
|
| 1522 |
+
font=dict(size=18, family="Arial", color="#EAEAEA"),
|
| 1523 |
+
x=0.5
|
| 1524 |
+
),
|
| 1525 |
+
height=600,
|
| 1526 |
+
font=dict(family='Arial', size=12)
|
| 1527 |
+
)
|
| 1528 |
+
|
| 1529 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 1530 |
+
|
| 1531 |
+
# --- AI Explanation for Category Plot ---
|
| 1532 |
+
if st.session_state.get('enable_ai_explanation') and 'explanation_part_3' in st.session_state:
|
| 1533 |
+
if st.session_state.explanation_part_3:
|
| 1534 |
+
explanation_html = markdown.markdown(st.session_state.explanation_part_3)
|
| 1535 |
+
st.markdown(
|
| 1536 |
+
f"<div style='background-color: #2b2b2b; color: #ffffff; padding: 1.2rem; border-radius: 10px; margin: 1rem 0; border-left: 5px solid #FBBF24; font-size: 0.9rem;'>{explanation_html}</div>",
|
| 1537 |
+
unsafe_allow_html=True
|
| 1538 |
+
)
|
| 1539 |
+
|
| 1540 |
+
# Faithfulness Check for Category Plot
|
| 1541 |
+
with st.expander(tr('faithfulness_check_expander')):
|
| 1542 |
+
st.markdown(tr('fv_faithfulness_explanation_pca_html'), unsafe_allow_html=True)
|
| 1543 |
+
|
| 1544 |
+
if 'pca_faithfulness' in st.session_state.analysis_results:
|
| 1545 |
+
verification_results = st.session_state.analysis_results['pca_faithfulness']
|
| 1546 |
+
else:
|
| 1547 |
+
api_config = init_qwen_api()
|
| 1548 |
+
if api_config:
|
| 1549 |
+
with st.spinner(tr('running_faithfulness_check_spinner')):
|
| 1550 |
+
claims = _cached_extract_fv_claims(api_config, st.session_state.explanation_part_3, "pca")
|
| 1551 |
+
verification_results = verify_fv_claims(claims, results, "pca")
|
| 1552 |
+
else:
|
| 1553 |
+
verification_results = []
|
| 1554 |
+
st.warning(tr('api_key_not_configured_warning'))
|
| 1555 |
+
|
| 1556 |
+
if verification_results:
|
| 1557 |
+
for result in verification_results:
|
| 1558 |
+
status_text = tr('verified_status') if result['verified'] else tr('contradicted_status')
|
| 1559 |
+
st.markdown(f"""
|
| 1560 |
+
<div style="margin-bottom: 1rem; padding: 0.8rem; border-radius: 8px; border-left: 5px solid {'#28a745' if result['verified'] else '#dc3545'}; background-color: #1a1a1a;">
|
| 1561 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('claim_label')}:</strong> <em>"{result['claim_text']}"</em></p>
|
| 1562 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('status_label')}:</strong> {status_text}</p>
|
| 1563 |
+
<p style="margin-bottom: 0;"><strong>{tr('evidence_label')}:</strong> {result['evidence']}</p>
|
| 1564 |
+
</div>
|
| 1565 |
+
""", unsafe_allow_html=True)
|
| 1566 |
+
else:
|
| 1567 |
+
st.info(tr('no_verifiable_claims_info'))
|
| 1568 |
+
else:
|
| 1569 |
+
st.warning("No category attribution data available to display.")
|
| 1570 |
+
|
| 1571 |
+
st.markdown("---")
|
| 1572 |
+
|
| 1573 |
+
# --- Section 3: Layer Evolution ---
|
| 1574 |
+
st.markdown(f"<h2>{tr('layer_evolution_tab')}</h2>", unsafe_allow_html=True)
|
| 1575 |
+
st.markdown(tr('layer_evolution_header'))
|
| 1576 |
+
if 'evolution' in results and results['evolution']:
|
| 1577 |
+
display_evolution_results(results['evolution'])
|
| 1578 |
+
else:
|
| 1579 |
+
st.info(tr('evolution_not_available_info'))
|
| 1580 |
+
|
| 1581 |
+
|
| 1582 |
+
def display_evolution_results(evolution_results):
|
| 1583 |
+
# Displays the layer evolution analysis results.
|
| 1584 |
+
|
| 1585 |
+
import plotly.graph_objects as go
|
| 1586 |
+
import numpy as np
|
| 1587 |
+
|
| 1588 |
+
# Extract key metrics from the results.
|
| 1589 |
+
layer_vectors = evolution_results['layer_vectors']
|
| 1590 |
+
similarity_matrix = evolution_results['similarity_matrix']
|
| 1591 |
+
layer_changes = evolution_results['layer_changes']
|
| 1592 |
+
|
| 1593 |
+
# Calculate activation strengths.
|
| 1594 |
+
activation_strengths = [float(np.sqrt(np.sum(vec ** 2))) for vec in layer_vectors.values()]
|
| 1595 |
+
|
| 1596 |
+
# Display the key insights.
|
| 1597 |
+
col1, col2, col3 = st.columns(3)
|
| 1598 |
+
|
| 1599 |
+
with col1:
|
| 1600 |
+
max_change_layer = np.argmax(layer_changes) + 1
|
| 1601 |
+
st.metric(
|
| 1602 |
+
"Biggest Change",
|
| 1603 |
+
f"Layer {max_change_layer}→{max_change_layer+1}",
|
| 1604 |
+
f"{layer_changes[max_change_layer-1]:.3f}",
|
| 1605 |
+
help="Layer transition with the largest representational change"
|
| 1606 |
+
)
|
| 1607 |
+
|
| 1608 |
+
with col2:
|
| 1609 |
+
max_activation_layer = np.argmax(activation_strengths)
|
| 1610 |
+
st.metric(
|
| 1611 |
+
"Peak Activation",
|
| 1612 |
+
f"Layer {max_activation_layer}",
|
| 1613 |
+
f"{activation_strengths[max_activation_layer]:.3f}",
|
| 1614 |
+
help="Layer with strongest overall activation"
|
| 1615 |
+
)
|
| 1616 |
+
|
| 1617 |
+
with col3:
|
| 1618 |
+
avg_change = np.mean(layer_changes)
|
| 1619 |
+
st.metric(
|
| 1620 |
+
"Avg Change",
|
| 1621 |
+
f"{avg_change:.3f}",
|
| 1622 |
+
help="Average change magnitude across all layer transitions"
|
| 1623 |
+
)
|
| 1624 |
+
|
| 1625 |
+
# Plot the activation strength.
|
| 1626 |
+
st.markdown("<h3><i class='bi bi-lightning-charge-fill'></i> Activation Strength Across Layers</h3>", unsafe_allow_html=True)
|
| 1627 |
+
|
| 1628 |
+
# Create the line plot.
|
| 1629 |
+
peak_idx = np.argmax(activation_strengths)
|
| 1630 |
+
|
| 1631 |
+
fig = go.Figure()
|
| 1632 |
+
|
| 1633 |
+
# Add the main line with gradient colors.
|
| 1634 |
+
fig.add_trace(go.Scatter(
|
| 1635 |
+
x=list(range(len(activation_strengths))),
|
| 1636 |
+
y=activation_strengths,
|
| 1637 |
+
mode='lines+markers',
|
| 1638 |
+
line=dict(color='#4ECDC4', width=4),
|
| 1639 |
+
marker=dict(size=10, color='#45B7D1', line=dict(color='white', width=2)),
|
| 1640 |
+
name='Activation Strength',
|
| 1641 |
+
hovertemplate='<b>Layer %{x}</b><br>Strength: %{y:.3f}<extra></extra>'
|
| 1642 |
+
))
|
| 1643 |
+
|
| 1644 |
+
# Highlight the peak activation.
|
| 1645 |
+
fig.add_vline(
|
| 1646 |
+
x=peak_idx,
|
| 1647 |
+
line_dash="dash",
|
| 1648 |
+
line_color="#FF6B6B",
|
| 1649 |
+
line_width=3,
|
| 1650 |
+
annotation_text=f"Peak at Layer {peak_idx}",
|
| 1651 |
+
annotation_position="top"
|
| 1652 |
+
)
|
| 1653 |
+
|
| 1654 |
+
# Add a marker for the peak.
|
| 1655 |
+
fig.add_trace(go.Scatter(
|
| 1656 |
+
x=[peak_idx],
|
| 1657 |
+
y=[activation_strengths[peak_idx]],
|
| 1658 |
+
mode='markers',
|
| 1659 |
+
marker=dict(size=15, color='#FF6B6B', symbol='star', line=dict(color='white', width=2)),
|
| 1660 |
+
name=f'Peak Layer {peak_idx}',
|
| 1661 |
+
hovertemplate=f'<b>Peak Layer {peak_idx}</b><br>Strength: {activation_strengths[peak_idx]:.3f}<extra></extra>'
|
| 1662 |
+
))
|
| 1663 |
+
|
| 1664 |
+
fig.update_layout(
|
| 1665 |
+
xaxis=dict(
|
| 1666 |
+
title=dict(text="Layer Index", font=dict(size=16, color='#EAEAEA'), standoff=50),
|
| 1667 |
+
tickfont=dict(size=14, color='#EAEAEA'),
|
| 1668 |
+
gridcolor='rgba(200,200,200,0.3)',
|
| 1669 |
+
showgrid=True,
|
| 1670 |
+
zeroline=False
|
| 1671 |
+
),
|
| 1672 |
+
yaxis=dict(
|
| 1673 |
+
title=dict(text="Activation Strength (L2 norm)", font=dict(size=16, color='#EAEAEA')),
|
| 1674 |
+
tickfont=dict(size=14, color='#EAEAEA'),
|
| 1675 |
+
gridcolor='rgba(200,200,200,0.3)',
|
| 1676 |
+
showgrid=True,
|
| 1677 |
+
zeroline=False
|
| 1678 |
+
),
|
| 1679 |
+
height=500,
|
| 1680 |
+
margin=dict(l=80, r=80, t=100, b=80),
|
| 1681 |
+
legend=dict(
|
| 1682 |
+
orientation="h",
|
| 1683 |
+
yanchor="bottom",
|
| 1684 |
+
y=-0.2,
|
| 1685 |
+
xanchor="center",
|
| 1686 |
+
x=0.5,
|
| 1687 |
+
font=dict(size=12, color='#EAEAEA')
|
| 1688 |
+
),
|
| 1689 |
+
font=dict(family='Arial'),
|
| 1690 |
+
hovermode='x'
|
| 1691 |
+
)
|
| 1692 |
+
|
| 1693 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 1694 |
+
|
| 1695 |
+
# --- AI Explanation for Activation Strength ---
|
| 1696 |
+
if st.session_state.get('enable_ai_explanation') and 'evolution_explanation_part_1' in st.session_state:
|
| 1697 |
+
if st.session_state.evolution_explanation_part_1:
|
| 1698 |
+
explanation_html = markdown.markdown(st.session_state.evolution_explanation_part_1)
|
| 1699 |
+
st.markdown(
|
| 1700 |
+
f"<div style='background-color: #2b2b2b; color: #ffffff; padding: 1.2rem; border-radius: 10px; margin: 1rem 0; border-left: 5px solid #A78BFA; font-size: 0.9rem;'>{explanation_html}</div>",
|
| 1701 |
+
unsafe_allow_html=True
|
| 1702 |
+
)
|
| 1703 |
+
|
| 1704 |
+
# Faithfulness Check for Activation Strength plot
|
| 1705 |
+
with st.expander(tr('faithfulness_check_expander')):
|
| 1706 |
+
st.markdown(tr('fv_faithfulness_explanation_evolution_html'), unsafe_allow_html=True)
|
| 1707 |
+
|
| 1708 |
+
if 'evolution_faithfulness' in st.session_state.analysis_results:
|
| 1709 |
+
verification_results = st.session_state.analysis_results['evolution_faithfulness']
|
| 1710 |
+
else:
|
| 1711 |
+
api_config = init_qwen_api()
|
| 1712 |
+
if api_config:
|
| 1713 |
+
with st.spinner(tr('running_faithfulness_check_spinner')):
|
| 1714 |
+
claims = _cached_extract_fv_claims(api_config, st.session_state.evolution_explanation_part_1, "evolution")
|
| 1715 |
+
verification_results = verify_fv_claims(claims, st.session_state.analysis_results, "evolution")
|
| 1716 |
+
else:
|
| 1717 |
+
verification_results = []
|
| 1718 |
+
st.warning(tr('api_key_not_configured_warning'))
|
| 1719 |
+
|
| 1720 |
+
if verification_results:
|
| 1721 |
+
for result in verification_results:
|
| 1722 |
+
status_text = tr('verified_status') if result['verified'] else tr('contradicted_status')
|
| 1723 |
+
st.markdown(f"""
|
| 1724 |
+
<div style="margin-bottom: 1rem; padding: 0.8rem; border-radius: 8px; border-left: 5px solid {'#28a745' if result['verified'] else '#dc3545'}; background-color: #1a1a1a;">
|
| 1725 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('claim_label')}:</strong> <em>"{result['claim_text']}"</em></p>
|
| 1726 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('status_label')}:</strong> {status_text}</p>
|
| 1727 |
+
<p style="margin-bottom: 0;"><strong>{tr('evidence_label')}:</strong> {result['evidence']}</p>
|
| 1728 |
+
</div>
|
| 1729 |
+
""", unsafe_allow_html=True)
|
| 1730 |
+
else:
|
| 1731 |
+
st.info(tr('no_verifiable_claims_info'))
|
| 1732 |
+
|
| 1733 |
+
# Plot the layer changes.
|
| 1734 |
+
st.markdown("<h3><i class='bi bi-arrow-repeat'></i> Layer-to-Layer Changes</h3>", unsafe_allow_html=True)
|
| 1735 |
+
|
| 1736 |
+
max_change_idx = np.argmax(layer_changes)
|
| 1737 |
+
|
| 1738 |
+
fig2 = go.Figure()
|
| 1739 |
+
|
| 1740 |
+
# Add the main line with gradient colors.
|
| 1741 |
+
fig2.add_trace(go.Scatter(
|
| 1742 |
+
x=list(range(1, len(layer_changes) + 1)),
|
| 1743 |
+
y=layer_changes,
|
| 1744 |
+
mode='lines+markers',
|
| 1745 |
+
line=dict(color='#FECA57', width=4),
|
| 1746 |
+
marker=dict(size=10, color='#FF9FF3', line=dict(color='white', width=2)),
|
| 1747 |
+
name='Layer Changes',
|
| 1748 |
+
hovertemplate='<b>Layer %{x}→%{customdata}</b><br>Change: %{y:.3f}<extra></extra>',
|
| 1749 |
+
customdata=[i+2 for i in range(len(layer_changes))]
|
| 1750 |
+
))
|
| 1751 |
+
|
| 1752 |
+
# Highlight the biggest change.
|
| 1753 |
+
fig2.add_vline(
|
| 1754 |
+
x=max_change_idx + 1,
|
| 1755 |
+
line_dash="dash",
|
| 1756 |
+
line_color="#FF6B6B",
|
| 1757 |
+
line_width=3,
|
| 1758 |
+
annotation_text=f"Biggest Change: {max_change_idx+1}→{max_change_idx+2}",
|
| 1759 |
+
annotation_position="top"
|
| 1760 |
+
)
|
| 1761 |
+
|
| 1762 |
+
# Add a marker for the peak.
|
| 1763 |
+
fig2.add_trace(go.Scatter(
|
| 1764 |
+
x=[max_change_idx + 1],
|
| 1765 |
+
y=[layer_changes[max_change_idx]],
|
| 1766 |
+
mode='markers',
|
| 1767 |
+
marker=dict(size=15, color='#FF6B6B', symbol='diamond', line=dict(color='white', width=2)),
|
| 1768 |
+
name=f'Max Change: L{max_change_idx+1}→L{max_change_idx+2}',
|
| 1769 |
+
hovertemplate=f'<b>Max Change: Layer {max_change_idx+1}→{max_change_idx+2}</b><br>Change: {layer_changes[max_change_idx]:.3f}<extra></extra>'
|
| 1770 |
+
))
|
| 1771 |
+
|
| 1772 |
+
fig2.update_layout(
|
| 1773 |
+
xaxis=dict(
|
| 1774 |
+
title=dict(text="Layer Transition", font=dict(size=16, color='#EAEAEA'), standoff=50),
|
| 1775 |
+
tickfont=dict(size=14, color='#EAEAEA'),
|
| 1776 |
+
gridcolor='rgba(200,200,200,0.3)',
|
| 1777 |
+
showgrid=True,
|
| 1778 |
+
zeroline=False
|
| 1779 |
+
),
|
| 1780 |
+
yaxis=dict(
|
| 1781 |
+
title=dict(text="Change Magnitude (Cosine Distance)", font=dict(size=16, color='#EAEAEA')),
|
| 1782 |
+
tickfont=dict(size=14, color='#EAEAEA'),
|
| 1783 |
+
gridcolor='rgba(200,200,200,0.3)',
|
| 1784 |
+
showgrid=True,
|
| 1785 |
+
zeroline=False
|
| 1786 |
+
),
|
| 1787 |
+
height=500,
|
| 1788 |
+
margin=dict(l=80, r=80, t=100, b=80),
|
| 1789 |
+
legend=dict(
|
| 1790 |
+
orientation="h",
|
| 1791 |
+
yanchor="bottom",
|
| 1792 |
+
y=-0.2,
|
| 1793 |
+
xanchor="center",
|
| 1794 |
+
x=0.5,
|
| 1795 |
+
font=dict(size=12, color='#EAEAEA')
|
| 1796 |
+
),
|
| 1797 |
+
font=dict(family='Arial'),
|
| 1798 |
+
hovermode='x'
|
| 1799 |
+
)
|
| 1800 |
+
|
| 1801 |
+
st.plotly_chart(fig2, use_container_width=True)
|
| 1802 |
+
|
| 1803 |
+
# --- AI Explanation for Layer Changes ---
|
| 1804 |
+
if st.session_state.get('enable_ai_explanation') and 'evolution_explanation_part_2' in st.session_state:
|
| 1805 |
+
if st.session_state.evolution_explanation_part_2:
|
| 1806 |
+
explanation_html = markdown.markdown(st.session_state.evolution_explanation_part_2)
|
| 1807 |
+
st.markdown(
|
| 1808 |
+
f"<div style='background-color: #2b2b2b; color: #ffffff; padding: 1.2rem; border-radius: 10px; margin: 1rem 0; border-left: 5px solid #6EE7B7; font-size: 0.9rem;'>{explanation_html}</div>",
|
| 1809 |
+
unsafe_allow_html=True
|
| 1810 |
+
)
|
| 1811 |
+
|
| 1812 |
+
# Faithfulness Check for Layer Changes plot
|
| 1813 |
+
with st.expander(tr('faithfulness_check_expander')):
|
| 1814 |
+
st.markdown(tr('fv_faithfulness_explanation_evolution_html'), unsafe_allow_html=True)
|
| 1815 |
+
|
| 1816 |
+
if 'evolution_faithfulness' in st.session_state.analysis_results:
|
| 1817 |
+
verification_results = st.session_state.analysis_results['evolution_faithfulness']
|
| 1818 |
+
else:
|
| 1819 |
+
api_config = init_qwen_api()
|
| 1820 |
+
if api_config:
|
| 1821 |
+
with st.spinner(tr('running_faithfulness_check_spinner')):
|
| 1822 |
+
claims = _cached_extract_fv_claims(api_config, st.session_state.evolution_explanation_part_2, "evolution")
|
| 1823 |
+
verification_results = verify_fv_claims(claims, st.session_state.analysis_results, "evolution")
|
| 1824 |
+
else:
|
| 1825 |
+
verification_results = []
|
| 1826 |
+
st.warning(tr('api_key_not_configured_warning'))
|
| 1827 |
+
|
| 1828 |
+
if verification_results:
|
| 1829 |
+
for result in verification_results:
|
| 1830 |
+
status_text = tr('verified_status') if result['verified'] else tr('contradicted_status')
|
| 1831 |
+
st.markdown(f"""
|
| 1832 |
+
<div style="margin-bottom: 1rem; padding: 0.8rem; border-radius: 8px; border-left: 5px solid {'#28a745' if result['verified'] else '#dc3545'}; background-color: #1a1a1a;">
|
| 1833 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('claim_label')}:</strong> <em>"{result['claim_text']}"</em></p>
|
| 1834 |
+
<p style="margin-bottom: 0.3rem;"><strong>{tr('status_label')}:</strong> {status_text}</p>
|
| 1835 |
+
<p style="margin-bottom: 0;"><strong>{tr('evidence_label')}:</strong> {result['evidence']}</p>
|
| 1836 |
+
</div>
|
| 1837 |
+
""", unsafe_allow_html=True)
|
| 1838 |
+
else:
|
| 1839 |
+
st.info(tr('no_verifiable_claims_info'))
|
| 1840 |
+
|
| 1841 |
+
|
| 1842 |
+
if __name__ == "__main__":
|
| 1843 |
+
from utilities.localization import initialize_localization, tr
|
| 1844 |
+
initialize_localization()
|
| 1845 |
+
show_function_vectors_page()
|
function_vectors/generate_function_vectors.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
# Adjust path to import from the new 'data' directory
|
| 10 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 11 |
+
from function_vectors.data.multilingual_function_categories import FUNCTION_CATEGORIES
|
| 12 |
+
|
| 13 |
+
def generate_all_vectors():
|
| 14 |
+
# Generates and saves function vectors for all English and German prompts.
|
| 15 |
+
print("🚀 Starting function vector generation for both English and German...")
|
| 16 |
+
|
| 17 |
+
# Load the model and tokenizer.
|
| 18 |
+
print("🔧 Loading OLMo-2-7B model and tokenizer...")
|
| 19 |
+
try:
|
| 20 |
+
model_path = "./models/OLMo-2-1124-7B"
|
| 21 |
+
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
|
| 22 |
+
|
| 23 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 24 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 25 |
+
tokenizer.padding_side = "left"
|
| 26 |
+
|
| 27 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 28 |
+
model_path,
|
| 29 |
+
torch_dtype=torch.float16,
|
| 30 |
+
low_cpu_mem_usage=True,
|
| 31 |
+
device_map="auto",
|
| 32 |
+
output_hidden_states=True
|
| 33 |
+
)
|
| 34 |
+
print(f"✅ Model loaded successfully on device: {device}")
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"❌ Error loading model: {e}")
|
| 37 |
+
return
|
| 38 |
+
|
| 39 |
+
# Function to get activation vectors.
|
| 40 |
+
def get_activation_for_prompt(prompt):
|
| 41 |
+
# Calculates the model's activation for a given prompt.
|
| 42 |
+
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True, max_length=512)
|
| 43 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 44 |
+
|
| 45 |
+
with torch.no_grad():
|
| 46 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 47 |
+
|
| 48 |
+
last_token_pos = inputs['attention_mask'].sum(dim=1) - 1
|
| 49 |
+
last_hidden_state = outputs.hidden_states[-1]
|
| 50 |
+
activation = last_hidden_state[0, last_token_pos[0], :].cpu().numpy()
|
| 51 |
+
return activation.astype(np.float64)
|
| 52 |
+
|
| 53 |
+
# Generate and save vectors for both languages.
|
| 54 |
+
output_dir = Path(__file__).parent / "data" / "vectors"
|
| 55 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 56 |
+
|
| 57 |
+
for lang in ["en", "de"]:
|
| 58 |
+
print(f"\n🌍 Generating vectors for {lang.upper()} prompts...")
|
| 59 |
+
category_vectors = {}
|
| 60 |
+
|
| 61 |
+
for category_key, data in tqdm(FUNCTION_CATEGORIES.items(), desc=f"Processing {lang.upper()} Categories"):
|
| 62 |
+
prompts = data.get(lang, [])
|
| 63 |
+
|
| 64 |
+
if not prompts:
|
| 65 |
+
print(f"⚠️ Warning: No {lang.upper()} prompts for '{category_key}'. Skipping.")
|
| 66 |
+
continue
|
| 67 |
+
|
| 68 |
+
activations = [get_activation_for_prompt(p) for p in prompts]
|
| 69 |
+
|
| 70 |
+
if activations:
|
| 71 |
+
category_vectors[category_key] = np.mean(activations, axis=0)
|
| 72 |
+
|
| 73 |
+
if not category_vectors:
|
| 74 |
+
print(f"❌ No vectors were generated for {lang.upper()}. Aborting save.")
|
| 75 |
+
continue
|
| 76 |
+
|
| 77 |
+
output_path = output_dir / f"{lang}_category_vectors.npz"
|
| 78 |
+
try:
|
| 79 |
+
np.savez_compressed(output_path, **category_vectors)
|
| 80 |
+
print(f"✅ Successfully saved {lang.upper()} vectors to: {output_path}")
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"❌ Error saving {lang.upper()} vectors: {e}")
|
| 83 |
+
|
| 84 |
+
if __name__ == "__main__":
|
| 85 |
+
generate_all_vectors()
|
function_vectors/generate_german_vectors.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
# Add root project dir to path
|
| 10 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 11 |
+
from function_vectors.data.multilingual_function_categories import FUNCTION_CATEGORIES
|
| 12 |
+
|
| 13 |
+
def generate_german_vectors():
|
| 14 |
+
# Generates and saves function vectors for all German prompts.
|
| 15 |
+
print("🚀 Starting German function vector generation...")
|
| 16 |
+
|
| 17 |
+
# Load the model and tokenizer.
|
| 18 |
+
print("🔧 Loading OLMo-2-7B model and tokenizer... (this may take a moment)")
|
| 19 |
+
try:
|
| 20 |
+
model_path = "./models/OLMo-2-1124-7B"
|
| 21 |
+
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
|
| 22 |
+
|
| 23 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 24 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 25 |
+
tokenizer.padding_side = "left"
|
| 26 |
+
|
| 27 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 28 |
+
model_path,
|
| 29 |
+
torch_dtype=torch.float16,
|
| 30 |
+
low_cpu_mem_usage=True,
|
| 31 |
+
device_map="auto",
|
| 32 |
+
output_hidden_states=True
|
| 33 |
+
)
|
| 34 |
+
print(f"✅ Model loaded successfully on device: {device}")
|
| 35 |
+
except Exception as e:
|
| 36 |
+
print(f"❌ Error loading model: {e}")
|
| 37 |
+
print("Please ensure the model exists at './Models/OLMo-2-1124-7B'")
|
| 38 |
+
return
|
| 39 |
+
|
| 40 |
+
# Function to get activation vectors.
|
| 41 |
+
def get_activation_for_prompt(prompt):
|
| 42 |
+
# Calculates the model's activation for a given prompt.
|
| 43 |
+
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True, max_length=512)
|
| 44 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 45 |
+
|
| 46 |
+
with torch.no_grad():
|
| 47 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 48 |
+
|
| 49 |
+
last_token_pos = inputs['attention_mask'].sum(dim=1) - 1
|
| 50 |
+
last_hidden_state = outputs.hidden_states[-1]
|
| 51 |
+
activation = last_hidden_state[0, last_token_pos[0], :].cpu().numpy()
|
| 52 |
+
return activation.astype(np.float64)
|
| 53 |
+
|
| 54 |
+
# Generate vectors for German prompts.
|
| 55 |
+
print("\n🇩🇪 Generating vectors for German prompts...")
|
| 56 |
+
german_category_vectors = {}
|
| 57 |
+
|
| 58 |
+
# Loop over all categories and generate vectors.
|
| 59 |
+
for category_key, data in tqdm(FUNCTION_CATEGORIES.items(), desc="Processing Categories"):
|
| 60 |
+
german_prompts = data.get('de', [])
|
| 61 |
+
|
| 62 |
+
if not german_prompts:
|
| 63 |
+
print(f"⚠️ Warning: No German prompts found for category '{category_key}'. Skipping.")
|
| 64 |
+
continue
|
| 65 |
+
|
| 66 |
+
# Get activations for all German prompts in the category
|
| 67 |
+
activations = [get_activation_for_prompt(p) for p in german_prompts]
|
| 68 |
+
|
| 69 |
+
if activations:
|
| 70 |
+
# Average the activations to get one vector per category.
|
| 71 |
+
german_category_vectors[category_key] = np.mean(activations, axis=0)
|
| 72 |
+
|
| 73 |
+
# Save the generated vectors.
|
| 74 |
+
if not german_category_vectors:
|
| 75 |
+
print("❌ No vectors were generated. Aborting save.")
|
| 76 |
+
return
|
| 77 |
+
|
| 78 |
+
output_dir = Path(__file__).parent / "data" / "vectors"
|
| 79 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 80 |
+
output_path = output_dir / "de_category_vectors.npz"
|
| 81 |
+
|
| 82 |
+
try:
|
| 83 |
+
np.savez_compressed(output_path, **german_category_vectors)
|
| 84 |
+
print(f"\n✅ Successfully generated and saved German function vectors to:")
|
| 85 |
+
print(f" {output_path}")
|
| 86 |
+
except Exception as e:
|
| 87 |
+
print(f"❌ Error saving vectors: {e}")
|
| 88 |
+
|
| 89 |
+
if __name__ == "__main__":
|
| 90 |
+
generate_german_vectors()
|
function_vectors/generate_page_assets.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
import json
|
| 9 |
+
import plotly.graph_objects as go
|
| 10 |
+
from sklearn.decomposition import PCA
|
| 11 |
+
|
| 12 |
+
# Adjust path to import from the new 'data' directory
|
| 13 |
+
sys.path.append(str(Path(__file__).resolve().parent.parent))
|
| 14 |
+
from function_vectors.data.multilingual_function_categories import FUNCTION_CATEGORIES, FUNCTION_TYPES
|
| 15 |
+
|
| 16 |
+
def generate_all_assets():
|
| 17 |
+
# Generates all pre-computed assets for the Function Vectors page.
|
| 18 |
+
print("🚀 Starting generation of all page assets...")
|
| 19 |
+
|
| 20 |
+
# Load the model and tokenizer.
|
| 21 |
+
print("🔧 Loading OLMo-2-7B model...")
|
| 22 |
+
try:
|
| 23 |
+
model_path = "./models/OLMo-2-1124-7B"
|
| 24 |
+
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
|
| 25 |
+
|
| 26 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 27 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 28 |
+
tokenizer.padding_side = "left"
|
| 29 |
+
|
| 30 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 31 |
+
model_path,
|
| 32 |
+
torch_dtype=torch.float16,
|
| 33 |
+
low_cpu_mem_usage=True,
|
| 34 |
+
device_map="auto",
|
| 35 |
+
output_hidden_states=True
|
| 36 |
+
)
|
| 37 |
+
print(f"✅ Model loaded successfully on device: {device}")
|
| 38 |
+
except Exception as e:
|
| 39 |
+
print(f"❌ Error loading model: {e}")
|
| 40 |
+
return
|
| 41 |
+
|
| 42 |
+
# Function to get activation vectors.
|
| 43 |
+
def get_activation_for_prompt(prompt):
|
| 44 |
+
inputs = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True, max_length=512)
|
| 45 |
+
inputs = {k: v.to(device) for k, v in inputs.items()}
|
| 46 |
+
with torch.no_grad():
|
| 47 |
+
outputs = model(**inputs, output_hidden_states=True)
|
| 48 |
+
last_token_pos = inputs['attention_mask'].sum(dim=1) - 1
|
| 49 |
+
last_hidden_state = outputs.hidden_states[-1]
|
| 50 |
+
activation = last_hidden_state[0, last_token_pos[0], :].cpu().numpy()
|
| 51 |
+
return activation.astype(np.float64)
|
| 52 |
+
|
| 53 |
+
# Generate and save function vectors.
|
| 54 |
+
output_dir = Path(__file__).parent / "data" / "vectors"
|
| 55 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 56 |
+
all_vectors_by_lang = {}
|
| 57 |
+
|
| 58 |
+
for lang in ["en", "de"]:
|
| 59 |
+
print(f"\n🌍 Generating vectors for {lang.upper()} prompts...")
|
| 60 |
+
category_vectors = {}
|
| 61 |
+
for category_key, data in tqdm(FUNCTION_CATEGORIES.items(), desc=f"Processing {lang.upper()}"):
|
| 62 |
+
prompts = data.get(lang, [])
|
| 63 |
+
if not prompts: continue
|
| 64 |
+
activations = [get_activation_for_prompt(p) for p in prompts]
|
| 65 |
+
if activations:
|
| 66 |
+
category_vectors[category_key] = np.mean(activations, axis=0)
|
| 67 |
+
|
| 68 |
+
all_vectors_by_lang[lang] = category_vectors.copy()
|
| 69 |
+
|
| 70 |
+
output_path = output_dir / f"{lang}_category_vectors.npz"
|
| 71 |
+
np.savez_compressed(output_path, **category_vectors)
|
| 72 |
+
print(f"✅ Saved {lang.upper()} vectors to: {output_path}")
|
| 73 |
+
|
| 74 |
+
# Generate and save 3D PCA visualizations.
|
| 75 |
+
viz_dir = Path(__file__).parent / "data" / "visualizations"
|
| 76 |
+
viz_dir.mkdir(parents=True, exist_ok=True)
|
| 77 |
+
|
| 78 |
+
for lang, vectors_to_plot in all_vectors_by_lang.items():
|
| 79 |
+
print(f"\n🎨 Generating 3D PCA visualization for {lang.upper()}...")
|
| 80 |
+
if not vectors_to_plot:
|
| 81 |
+
print(f"⚠️ Skipping PCA for {lang.upper()} as vectors are missing.")
|
| 82 |
+
continue
|
| 83 |
+
|
| 84 |
+
try:
|
| 85 |
+
categories = list(vectors_to_plot.keys())
|
| 86 |
+
vectors = np.vstack([vectors_to_plot[cat] for cat in categories])
|
| 87 |
+
|
| 88 |
+
pca = PCA(n_components=3)
|
| 89 |
+
reduced_vectors = pca.fit_transform(vectors)
|
| 90 |
+
|
| 91 |
+
# Define colors and symbols for the plot.
|
| 92 |
+
func_type_keys = list(FUNCTION_TYPES.keys())
|
| 93 |
+
colors = ["skyblue", "lightgreen", "salmon", "orchid", "gold", "lightcoral"]
|
| 94 |
+
symbols = ["circle", "diamond", "square", "cross", "diamond-open", "square-open"]
|
| 95 |
+
function_type_colors = {key: colors[i % len(colors)] for i, key in enumerate(func_type_keys)}
|
| 96 |
+
plotly_symbols = {key: symbols[i % len(symbols)] for i, key in enumerate(func_type_keys)}
|
| 97 |
+
|
| 98 |
+
fig = go.Figure()
|
| 99 |
+
for func_type_key, cats in FUNCTION_TYPES.items():
|
| 100 |
+
func_categories = [cat for cat in cats if cat in categories]
|
| 101 |
+
if func_categories:
|
| 102 |
+
indices = [categories.index(cat) for cat in func_categories]
|
| 103 |
+
fig.add_trace(go.Scatter3d(
|
| 104 |
+
x=reduced_vectors[indices, 0], y=reduced_vectors[indices, 1], z=reduced_vectors[indices, 2],
|
| 105 |
+
mode='markers',
|
| 106 |
+
marker=dict(size=8, color=function_type_colors.get(func_type_key, 'gray'), symbol=plotly_symbols.get(func_type_key, 'circle'), line=dict(width=1, color='black'), opacity=0.8),
|
| 107 |
+
name=func_type_key.replace("_", " ").title(),
|
| 108 |
+
text=[cat.replace("_", " ").title() for cat in func_categories],
|
| 109 |
+
hovertemplate="<b>%{text}</b><br>PC1: %{x:.3f}<br>PC2: %{y:.3f}<br>PC3: %{z:.3f}<extra></extra>"
|
| 110 |
+
))
|
| 111 |
+
|
| 112 |
+
fig.update_layout(
|
| 113 |
+
title=f"3D PCA of {lang.upper()} Function Vector Categories",
|
| 114 |
+
width=1400, height=900,
|
| 115 |
+
scene=dict(xaxis_title='PC1', yaxis_title='PC2', zaxis_title='PC3'),
|
| 116 |
+
legend_title_text='Function Types'
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
# Save the plot to an HTML file.
|
| 120 |
+
file_suffix = "pca_3d_categories_layer_-1.html"
|
| 121 |
+
viz_path = viz_dir / f"{lang}_{file_suffix}"
|
| 122 |
+
fig.write_html(viz_path)
|
| 123 |
+
print(f"✅ Saved {lang.upper()} 3D PCA visualization to: {viz_path}")
|
| 124 |
+
except Exception as e:
|
| 125 |
+
print(f"❌ Failed to generate PCA plot for {lang.upper()}: {e}")
|
| 126 |
+
|
| 127 |
+
# Layer evolution data is handled dynamically in the app.
|
| 128 |
+
print("\n✅ Layer Evolution analysis is handled dynamically in the app. No pre-computation needed.")
|
| 129 |
+
print("\n🎉 All assets generated successfully!")
|
| 130 |
+
|
| 131 |
+
if __name__ == "__main__":
|
| 132 |
+
generate_all_assets()
|
function_vectors/translate_prompts.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
import requests
|
| 5 |
+
import json
|
| 6 |
+
import time
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
|
| 9 |
+
# Add root project dir to path
|
| 10 |
+
sys.path.append(str(Path(__file__).parent.parent))
|
| 11 |
+
from function_vectors.data.multilingual_function_categories import FUNCTION_CATEGORIES, FUNCTION_TYPES
|
| 12 |
+
|
| 13 |
+
# API configuration for Qwen.
|
| 14 |
+
QWEN_API_CONFIG = {
|
| 15 |
+
"api_key": "6e3def45d61b0b20547a1fcbab6464d8",
|
| 16 |
+
"api_endpoint": "https://chat-ai.academiccloud.de/v1",
|
| 17 |
+
"model": "qwen2.5-vl-72b-instruct",
|
| 18 |
+
"rate_limit_per_minute": 2,
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
# --- Translation Logic ---
|
| 22 |
+
|
| 23 |
+
def translate_text(text, target_language="German"):
|
| 24 |
+
# Translates a single string using the Qwen API.
|
| 25 |
+
headers = {
|
| 26 |
+
"Authorization": f"Bearer {QWEN_API_CONFIG['api_key']}",
|
| 27 |
+
"Content-Type": "application/json"
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
prompt = f"Translate the following English text to {target_language}. Respond with ONLY the translated text, without any introductory phrases, explanations, or quotation marks. The original text is:\n\n'{text}'"
|
| 31 |
+
|
| 32 |
+
data = {
|
| 33 |
+
"model": QWEN_API_CONFIG["model"],
|
| 34 |
+
"messages": [{"role": "user", "content": prompt}],
|
| 35 |
+
"max_tokens": 150,
|
| 36 |
+
"temperature": 0.1,
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
try:
|
| 40 |
+
response = requests.post(
|
| 41 |
+
f"{QWEN_API_CONFIG['api_endpoint']}/chat/completions",
|
| 42 |
+
headers=headers,
|
| 43 |
+
json=data,
|
| 44 |
+
timeout=60
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
if response.status_code == 200:
|
| 48 |
+
result = response.json()
|
| 49 |
+
translated_text = result["choices"][0]["message"]["content"].strip()
|
| 50 |
+
# Clean up quotes from the model's response.
|
| 51 |
+
if translated_text.startswith('"') and translated_text.endswith('"'):
|
| 52 |
+
translated_text = translated_text[1:-1]
|
| 53 |
+
return translated_text
|
| 54 |
+
elif response.status_code == 429:
|
| 55 |
+
# Handle rate limiting.
|
| 56 |
+
reset_time = response.headers.get('RateLimit-Reset', '0')
|
| 57 |
+
try:
|
| 58 |
+
wait_seconds = int(reset_time)
|
| 59 |
+
print(f"Hourly rate limit reached. Waiting {wait_seconds} seconds for reset...")
|
| 60 |
+
return f"RATE_LIMIT_HOURLY:{wait_seconds}"
|
| 61 |
+
except ValueError:
|
| 62 |
+
print("Rate limit exceeded. Waiting 60 seconds...")
|
| 63 |
+
return "RATE_LIMIT_EXCEEDED"
|
| 64 |
+
else:
|
| 65 |
+
print(f"API Error: Status {response.status_code}, Response: {response.text}")
|
| 66 |
+
return None
|
| 67 |
+
|
| 68 |
+
except requests.RequestException as e:
|
| 69 |
+
print(f"Request failed: {e}")
|
| 70 |
+
return None
|
| 71 |
+
|
| 72 |
+
def translate_batch_texts(texts, target_language="German"):
|
| 73 |
+
# Translates a batch of strings in one API call.
|
| 74 |
+
headers = {
|
| 75 |
+
"Authorization": f"Bearer {QWEN_API_CONFIG['api_key']}",
|
| 76 |
+
"Content-Type": "application/json"
|
| 77 |
+
}
|
| 78 |
+
# A stronger prompt to ensure full translation.
|
| 79 |
+
batch_prompt = (
|
| 80 |
+
f"Translate the following English texts to {target_language}. "
|
| 81 |
+
"For each text, translate ALL words and phrases, including any words in quotation marks, into natural German. "
|
| 82 |
+
"Do NOT leave any English words in the translation. Respond with ONLY the German translations, one per line, in the same order.\n\n"
|
| 83 |
+
)
|
| 84 |
+
for i, text in enumerate(texts, 1):
|
| 85 |
+
batch_prompt += f"{i}. {text}\n"
|
| 86 |
+
batch_prompt += "\nProvide the German translations in the same order, one per line:"
|
| 87 |
+
data = {
|
| 88 |
+
"model": QWEN_API_CONFIG["model"],
|
| 89 |
+
"messages": [{"role": "user", "content": batch_prompt}],
|
| 90 |
+
"max_tokens": 300, # Increased for batch processing
|
| 91 |
+
"temperature": 0.1,
|
| 92 |
+
}
|
| 93 |
+
try:
|
| 94 |
+
response = requests.post(
|
| 95 |
+
f"{QWEN_API_CONFIG['api_endpoint']}/chat/completions",
|
| 96 |
+
headers=headers,
|
| 97 |
+
json=data,
|
| 98 |
+
timeout=60
|
| 99 |
+
)
|
| 100 |
+
if response.status_code == 200:
|
| 101 |
+
result = response.json()
|
| 102 |
+
translated_text = result["choices"][0]["message"]["content"].strip()
|
| 103 |
+
# Split the response into individual lines.
|
| 104 |
+
lines = [line.strip() for line in translated_text.split('\n') if line.strip()]
|
| 105 |
+
cleaned_translations = []
|
| 106 |
+
for line in lines:
|
| 107 |
+
# Remove numbering if the model adds it.
|
| 108 |
+
if line and line[0].isdigit() and '.' in line:
|
| 109 |
+
line = line.split('.', 1)[1].strip()
|
| 110 |
+
# Clean up quotes.
|
| 111 |
+
if line.startswith('"') and line.endswith('"'):
|
| 112 |
+
line = line[1:-1]
|
| 113 |
+
if line:
|
| 114 |
+
cleaned_translations.append(line)
|
| 115 |
+
# Make sure we have the right number of translations.
|
| 116 |
+
if len(cleaned_translations) >= len(texts):
|
| 117 |
+
return cleaned_translations[:len(texts)]
|
| 118 |
+
else:
|
| 119 |
+
print(f"Warning: Expected {len(texts)} translations, got {len(cleaned_translations)}")
|
| 120 |
+
# Pad with error messages if some translations failed.
|
| 121 |
+
while len(cleaned_translations) < len(texts):
|
| 122 |
+
cleaned_translations.append(f"TRANSLATION_ERROR: {texts[len(cleaned_translations)]}")
|
| 123 |
+
return cleaned_translations
|
| 124 |
+
elif response.status_code == 429:
|
| 125 |
+
# Handle rate limiting.
|
| 126 |
+
reset_time = response.headers.get('RateLimit-Reset', '0')
|
| 127 |
+
try:
|
| 128 |
+
wait_seconds = int(reset_time)
|
| 129 |
+
print(f"Hourly rate limit reached. Waiting {wait_seconds} seconds for reset...")
|
| 130 |
+
return f"RATE_LIMIT_HOURLY:{wait_seconds}"
|
| 131 |
+
except ValueError:
|
| 132 |
+
print("Rate limit exceeded. Waiting 60 seconds...")
|
| 133 |
+
return "RATE_LIMIT_EXCEEDED"
|
| 134 |
+
else:
|
| 135 |
+
print(f"API Error: Status {response.status_code}, Response: {response.text}")
|
| 136 |
+
return None
|
| 137 |
+
except requests.RequestException as e:
|
| 138 |
+
print(f"Request failed: {e}")
|
| 139 |
+
return None
|
| 140 |
+
|
| 141 |
+
def update_multilingual_categories_file(new_categories):
|
| 142 |
+
# Updates the multilingual_function_categories.py file.
|
| 143 |
+
file_path = Path(__file__).parent / "data" / "multilingual_function_categories.py"
|
| 144 |
+
|
| 145 |
+
# Create the new file content.
|
| 146 |
+
file_content = "# -*- coding: utf-8 -*-\n"
|
| 147 |
+
file_content += '"""\nThis file contains the multilingual prompts for function vector analysis.\n'
|
| 148 |
+
file_content += 'It is automatically updated by the translate_prompts.py script.\n"""\n\n'
|
| 149 |
+
|
| 150 |
+
# Format the FUNCTION_TYPES dictionary.
|
| 151 |
+
ft_content = "FUNCTION_TYPES = {\n"
|
| 152 |
+
for ft, cats in FUNCTION_TYPES.items():
|
| 153 |
+
ft_content += f' "{ft}": [\n'
|
| 154 |
+
for cat in cats:
|
| 155 |
+
ft_content += f' "{cat}",\n'
|
| 156 |
+
ft_content += " ],\n"
|
| 157 |
+
ft_content += "}\n\n"
|
| 158 |
+
|
| 159 |
+
file_content += ft_content
|
| 160 |
+
|
| 161 |
+
# Add the function categories.
|
| 162 |
+
file_content += f"FUNCTION_CATEGORIES = {json.dumps(new_categories, indent=4, ensure_ascii=False)}\n"
|
| 163 |
+
|
| 164 |
+
with open(file_path, "w", encoding="utf-8") as f:
|
| 165 |
+
f.write(file_content)
|
| 166 |
+
print(f"\n✅ Progress saved to '{file_path}'")
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def main():
|
| 170 |
+
# Translates all prompts and updates the file.
|
| 171 |
+
print("🚀 Starting batch translation of prompts to German...")
|
| 172 |
+
|
| 173 |
+
# Load existing categories to resume from where we left off.
|
| 174 |
+
translated_categories = FUNCTION_CATEGORIES.copy()
|
| 175 |
+
|
| 176 |
+
# Count how many prompts need to be translated.
|
| 177 |
+
total_prompts = sum(len(prompts.get('en', [])) for prompts in FUNCTION_CATEGORIES.values())
|
| 178 |
+
|
| 179 |
+
# Set up a progress bar.
|
| 180 |
+
with tqdm(total=total_prompts, desc="Translating Prompts") as pbar:
|
| 181 |
+
# Check how many are already translated.
|
| 182 |
+
already_translated_count = 0
|
| 183 |
+
for category_key, data in FUNCTION_CATEGORIES.items():
|
| 184 |
+
if 'de' not in translated_categories.get(category_key, {}):
|
| 185 |
+
if category_key not in translated_categories:
|
| 186 |
+
translated_categories[category_key] = {}
|
| 187 |
+
translated_categories[category_key]['de'] = []
|
| 188 |
+
|
| 189 |
+
if 'de' in translated_categories[category_key]:
|
| 190 |
+
already_translated_count += len(translated_categories[category_key]['de'])
|
| 191 |
+
pbar.update(already_translated_count)
|
| 192 |
+
|
| 193 |
+
# Get a list of all prompts that still need to be translated.
|
| 194 |
+
all_prompts_to_translate = []
|
| 195 |
+
prompt_mapping = []
|
| 196 |
+
|
| 197 |
+
for category_key, data in FUNCTION_CATEGORIES.items():
|
| 198 |
+
english_prompts = data.get('en', [])
|
| 199 |
+
|
| 200 |
+
# Make sure the 'de' key exists.
|
| 201 |
+
if 'de' not in translated_categories[category_key]:
|
| 202 |
+
translated_categories[category_key]['de'] = []
|
| 203 |
+
|
| 204 |
+
german_prompts = translated_categories[category_key]['de']
|
| 205 |
+
|
| 206 |
+
# Skip if this category is already done.
|
| 207 |
+
if len(german_prompts) == len(english_prompts):
|
| 208 |
+
continue
|
| 209 |
+
|
| 210 |
+
# Add prompts that are missing a translation.
|
| 211 |
+
for i in range(len(german_prompts), len(english_prompts)):
|
| 212 |
+
all_prompts_to_translate.append(english_prompts[i])
|
| 213 |
+
prompt_mapping.append((category_key, i))
|
| 214 |
+
|
| 215 |
+
# Process the prompts in batches.
|
| 216 |
+
batch_size = 6
|
| 217 |
+
for i in range(0, len(all_prompts_to_translate), batch_size):
|
| 218 |
+
batch_prompts = all_prompts_to_translate[i:i + batch_size]
|
| 219 |
+
batch_mapping = prompt_mapping[i:i + batch_size]
|
| 220 |
+
|
| 221 |
+
# Wait between batches to avoid hitting the rate limit.
|
| 222 |
+
time.sleep(30)
|
| 223 |
+
|
| 224 |
+
translated_batch = translate_batch_texts(batch_prompts)
|
| 225 |
+
|
| 226 |
+
# Handle rate limit responses.
|
| 227 |
+
if translated_batch and isinstance(translated_batch, str) and translated_batch.startswith("RATE_LIMIT_HOURLY:"):
|
| 228 |
+
wait_seconds = int(translated_batch.split(":")[1])
|
| 229 |
+
print(f"Waiting {wait_seconds} seconds for hourly rate limit reset...")
|
| 230 |
+
time.sleep(wait_seconds)
|
| 231 |
+
# Retry the batch.
|
| 232 |
+
translated_batch = translate_batch_texts(batch_prompts)
|
| 233 |
+
|
| 234 |
+
retry_wait = 60
|
| 235 |
+
while translated_batch == "RATE_LIMIT_EXCEEDED":
|
| 236 |
+
# Wait and retry if we hit the rate limit.
|
| 237 |
+
print(f"Waiting for {retry_wait} seconds due to rate limit...")
|
| 238 |
+
time.sleep(retry_wait)
|
| 239 |
+
translated_batch = translate_batch_texts(batch_prompts)
|
| 240 |
+
retry_wait *= 1.5
|
| 241 |
+
|
| 242 |
+
if translated_batch and isinstance(translated_batch, list):
|
| 243 |
+
# Add the new translations to our data.
|
| 244 |
+
for j, (category_key, prompt_idx) in enumerate(batch_mapping):
|
| 245 |
+
if j < len(translated_batch):
|
| 246 |
+
translated_categories[category_key]['de'].append(translated_batch[j])
|
| 247 |
+
|
| 248 |
+
# Save progress every so often.
|
| 249 |
+
if (pbar.n + len(batch_prompts)) % 30 == 0:
|
| 250 |
+
update_multilingual_categories_file(translated_categories)
|
| 251 |
+
|
| 252 |
+
pbar.update(len(batch_prompts))
|
| 253 |
+
else:
|
| 254 |
+
print(f"❌ Failed to translate batch. Stopping.")
|
| 255 |
+
# Save any progress we made before stopping.
|
| 256 |
+
update_multilingual_categories_file(translated_categories)
|
| 257 |
+
return
|
| 258 |
+
|
| 259 |
+
# Final save at the end.
|
| 260 |
+
update_multilingual_categories_file(translated_categories)
|
| 261 |
+
print("\n✅ All prompts translated and file updated successfully.")
|
| 262 |
+
|
| 263 |
+
if __name__ == "__main__":
|
| 264 |
+
main()
|
influence_tracer/build_dolma_index.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import numpy as np
|
| 4 |
+
import faiss
|
| 5 |
+
from sentence_transformers import SentenceTransformer
|
| 6 |
+
from tqdm import tqdm as tqdm_iterator
|
| 7 |
+
import sys
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
# Configuration for the script.
|
| 11 |
+
DOLMA_DIR = os.path.join("influence_tracer", "dolma_dataset_sample_1.6v")
|
| 12 |
+
INDEX_DIR = os.path.join("influence_tracer", "influence_tracer_data")
|
| 13 |
+
INDEX_PATH = os.path.join(INDEX_DIR, "dolma_index_multi.faiss")
|
| 14 |
+
MAPPING_PATH = os.path.join(INDEX_DIR, "dolma_mapping_multi.json")
|
| 15 |
+
STATE_PATH = os.path.join(INDEX_DIR, "index_build_state_multi.json")
|
| 16 |
+
MODEL_NAME = 'paraphrase-multilingual-mpnet-base-v2'
|
| 17 |
+
|
| 18 |
+
# Performance tuning.
|
| 19 |
+
BATCH_SIZE = 131072
|
| 20 |
+
SAVE_INTERVAL = 10
|
| 21 |
+
|
| 22 |
+
def build_index():
|
| 23 |
+
# Scans the Dolma dataset, creates vector embeddings, and builds a FAISS index.
|
| 24 |
+
print("--- Starting Influence Tracer Index Build (Optimized for Speed) ---")
|
| 25 |
+
|
| 26 |
+
if not os.path.exists(DOLMA_DIR):
|
| 27 |
+
print(f"Error: Dolma directory not found at '{DOLMA_DIR}'")
|
| 28 |
+
print("Please ensure the dolma_dataset_sample_1.6v directory is in your project root.")
|
| 29 |
+
sys.exit(1)
|
| 30 |
+
|
| 31 |
+
os.makedirs(INDEX_DIR, exist_ok=True)
|
| 32 |
+
|
| 33 |
+
# Load or initialize the state to allow resuming.
|
| 34 |
+
processed_files = []
|
| 35 |
+
doc_id_counter = 0
|
| 36 |
+
total_docs_processed = 0
|
| 37 |
+
doc_mapping = {}
|
| 38 |
+
|
| 39 |
+
if os.path.exists(STATE_PATH):
|
| 40 |
+
print("Found existing state. Attempting to resume...")
|
| 41 |
+
try:
|
| 42 |
+
with open(STATE_PATH, 'r', encoding='utf-8') as f:
|
| 43 |
+
state = json.load(f)
|
| 44 |
+
processed_files = state.get('processed_files', [])
|
| 45 |
+
doc_id_counter = state.get('doc_id_counter', 0)
|
| 46 |
+
total_docs_processed = state.get('total_docs_processed', 0)
|
| 47 |
+
|
| 48 |
+
with open(MAPPING_PATH, 'r', encoding='utf-8') as f:
|
| 49 |
+
doc_mapping = json.load(f)
|
| 50 |
+
|
| 51 |
+
print(f"Reading existing index from {INDEX_PATH}...")
|
| 52 |
+
index = faiss.read_index(INDEX_PATH)
|
| 53 |
+
print(f"Resumed from state: {len(processed_files)} files processed, {total_docs_processed} documents indexed.")
|
| 54 |
+
except (IOError, json.JSONDecodeError, RuntimeError) as e:
|
| 55 |
+
print(f"Error resuming from state: {e}. Starting fresh.")
|
| 56 |
+
processed_files = []
|
| 57 |
+
doc_id_counter = 0
|
| 58 |
+
total_docs_processed = 0
|
| 59 |
+
doc_mapping = {}
|
| 60 |
+
index = None # Will be re-initialized
|
| 61 |
+
else:
|
| 62 |
+
print("No existing state found. Starting fresh.")
|
| 63 |
+
index = None
|
| 64 |
+
|
| 65 |
+
# Detect the best device to use (MPS, CUDA, or CPU).
|
| 66 |
+
device = "mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu"
|
| 67 |
+
print(f"Using device: {device.upper()}")
|
| 68 |
+
|
| 69 |
+
# Load the sentence transformer model.
|
| 70 |
+
print(f"Loading sentence transformer model: '{MODEL_NAME}'...")
|
| 71 |
+
try:
|
| 72 |
+
model = SentenceTransformer(MODEL_NAME, device=device)
|
| 73 |
+
except Exception as e:
|
| 74 |
+
print(f"Error loading model: {e}")
|
| 75 |
+
print("Please ensure you have an internet connection and the required libraries are installed.")
|
| 76 |
+
print("Try running: pip install sentence-transformers faiss-cpu numpy tqdm")
|
| 77 |
+
sys.exit(1)
|
| 78 |
+
print("Model loaded successfully.")
|
| 79 |
+
|
| 80 |
+
# Initialize the FAISS index if it wasn't loaded.
|
| 81 |
+
if index is None:
|
| 82 |
+
embedding_dim = model.get_sentence_embedding_dimension()
|
| 83 |
+
# Use Inner Product for cosine similarity.
|
| 84 |
+
index = faiss.IndexFlatIP(embedding_dim)
|
| 85 |
+
print(f"FAISS index initialized with dimension {embedding_dim} using Inner Product (IP) for similarity.")
|
| 86 |
+
|
| 87 |
+
# Get a list of all files to process.
|
| 88 |
+
print(f"Scanning for documents in '{DOLMA_DIR}'...")
|
| 89 |
+
all_files = sorted([os.path.join(DOLMA_DIR, f) for f in os.listdir(DOLMA_DIR) if f.endswith('.json')])
|
| 90 |
+
files_to_process = [f for f in all_files if os.path.basename(f) not in processed_files]
|
| 91 |
+
|
| 92 |
+
if not files_to_process:
|
| 93 |
+
if processed_files:
|
| 94 |
+
print("✅ All files have been processed. Index is up to date.")
|
| 95 |
+
print("--- Index Build Complete ---")
|
| 96 |
+
return
|
| 97 |
+
else:
|
| 98 |
+
print(f"Error: No JSON files found in '{DOLMA_DIR}'.")
|
| 99 |
+
sys.exit(1)
|
| 100 |
+
|
| 101 |
+
print(f"Found {len(all_files)} total files, {len(files_to_process)} remaining to process.")
|
| 102 |
+
|
| 103 |
+
# Process each file.
|
| 104 |
+
print(f"Processing remaining files with batch size {BATCH_SIZE}...")
|
| 105 |
+
|
| 106 |
+
files_processed_since_save = 0
|
| 107 |
+
for file_idx, path in enumerate(tqdm_iterator(files_to_process, desc="Processing files")):
|
| 108 |
+
texts_batch = []
|
| 109 |
+
batch_doc_info = []
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
with open(path, 'r', encoding='utf-8') as f:
|
| 113 |
+
for line in f:
|
| 114 |
+
try:
|
| 115 |
+
data = json.loads(line)
|
| 116 |
+
text = data.get('text', '')
|
| 117 |
+
if text:
|
| 118 |
+
texts_batch.append(text)
|
| 119 |
+
batch_doc_info.append({
|
| 120 |
+
'id': doc_id_counter,
|
| 121 |
+
'info': {
|
| 122 |
+
'source': data.get('source', 'Unknown'),
|
| 123 |
+
'file': os.path.basename(path),
|
| 124 |
+
'text_snippet': text[:200] + '...'
|
| 125 |
+
}
|
| 126 |
+
})
|
| 127 |
+
doc_id_counter += 1
|
| 128 |
+
|
| 129 |
+
# Process the batch when it's full.
|
| 130 |
+
if len(texts_batch) >= BATCH_SIZE:
|
| 131 |
+
embeddings = model.encode(texts_batch, show_progress_bar=False, convert_to_numpy=True, normalize_embeddings=True)
|
| 132 |
+
index.add(embeddings.astype('float32'))
|
| 133 |
+
|
| 134 |
+
# Update the document mapping.
|
| 135 |
+
for doc in batch_doc_info:
|
| 136 |
+
doc_mapping[str(doc['id'])] = doc['info']
|
| 137 |
+
|
| 138 |
+
total_docs_processed += len(texts_batch)
|
| 139 |
+
texts_batch = []
|
| 140 |
+
batch_doc_info = []
|
| 141 |
+
except json.JSONDecodeError:
|
| 142 |
+
continue
|
| 143 |
+
|
| 144 |
+
# Process any remaining documents in the last batch.
|
| 145 |
+
if texts_batch:
|
| 146 |
+
embeddings = model.encode(texts_batch, show_progress_bar=False, convert_to_numpy=True, normalize_embeddings=True)
|
| 147 |
+
index.add(embeddings.astype('float32'))
|
| 148 |
+
|
| 149 |
+
# Update the mapping for the final batch.
|
| 150 |
+
for doc in batch_doc_info:
|
| 151 |
+
doc_mapping[str(doc['id'])] = doc['info']
|
| 152 |
+
|
| 153 |
+
total_docs_processed += len(texts_batch)
|
| 154 |
+
|
| 155 |
+
# Save progress periodically.
|
| 156 |
+
processed_files.append(os.path.basename(path))
|
| 157 |
+
files_processed_since_save += 1
|
| 158 |
+
|
| 159 |
+
if files_processed_since_save >= SAVE_INTERVAL or file_idx == len(files_to_process) - 1:
|
| 160 |
+
print(f"\nSaving progress ({total_docs_processed} docs processed)...")
|
| 161 |
+
faiss.write_index(index, INDEX_PATH)
|
| 162 |
+
with open(MAPPING_PATH, 'w', encoding='utf-8') as f:
|
| 163 |
+
json.dump(doc_mapping, f)
|
| 164 |
+
|
| 165 |
+
current_state = {
|
| 166 |
+
'processed_files': processed_files,
|
| 167 |
+
'doc_id_counter': doc_id_counter,
|
| 168 |
+
'total_docs_processed': total_docs_processed
|
| 169 |
+
}
|
| 170 |
+
with open(STATE_PATH, 'w', encoding='utf-8') as f:
|
| 171 |
+
json.dump(current_state, f)
|
| 172 |
+
|
| 173 |
+
files_processed_since_save = 0
|
| 174 |
+
print("Progress saved.")
|
| 175 |
+
|
| 176 |
+
except (IOError) as e:
|
| 177 |
+
print(f"Warning: Could not read or parse {path}. Skipping. Error: {e}")
|
| 178 |
+
continue
|
| 179 |
+
|
| 180 |
+
if index.ntotal == 0:
|
| 181 |
+
print("Error: No text could be extracted from the documents. Cannot build index.")
|
| 182 |
+
sys.exit(1)
|
| 183 |
+
|
| 184 |
+
print(f"\n🎉 Total documents processed: {total_docs_processed}")
|
| 185 |
+
print(f"✅ --- Index Build Complete ---")
|
| 186 |
+
print(f"Created index for {index.ntotal} documents.")
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
if __name__ == "__main__":
|
| 190 |
+
# This allows the script to be run from the command line.
|
| 191 |
+
print("This script will build a searchable index from your Dolma dataset.")
|
| 192 |
+
print("It needs to download a model and process all documents, so it may take some time.")
|
| 193 |
+
|
| 194 |
+
# Check for required libraries.
|
| 195 |
+
try:
|
| 196 |
+
import sentence_transformers
|
| 197 |
+
import faiss
|
| 198 |
+
import numpy
|
| 199 |
+
import tqdm
|
| 200 |
+
except ImportError:
|
| 201 |
+
print("\n--- Missing Required Libraries ---")
|
| 202 |
+
print("To run this script, please install the necessary packages by running:")
|
| 203 |
+
print("pip install sentence-transformers faiss-cpu numpy tqdm")
|
| 204 |
+
print("---------------------------------\n")
|
| 205 |
+
sys.exit(1)
|
| 206 |
+
|
| 207 |
+
build_index()
|
locales/de/attribution_analysis_page.json
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"desc_integrated_gradients": "Dies bietet ein zuverlässigeres Maß für die Wichtigkeit, indem nicht nur die endgültige Eingabe, sondern der gesamte Pfad von einem neutralen 'leeren' Zustand zu Ihrer spezifischen Anfrage berücksichtigt wird. Es summiert sorgfältig den Beitrag jedes Wortes, verhindert irreführende Ergebnisse und gibt ein wahreres Bild vom Einfluss jedes Tokens.",
|
| 3 |
+
"desc_occlusion": "Diese Methode testet die Notwendigkeit jedes Wortes, indem sie fragt: 'Was passiert, wenn dieses Wort fehlt?' Sie verbirgt vorübergehend jedes Token der Eingabe und misst, wie stark sich die Ausgabe des Modells ändert. Eine hohe Punktzahl bedeutet, dass das Wort für das Ergebnis entscheidend war.",
|
| 4 |
+
"desc_saliency": "Diese Methode zeigt die anfängliche 'Bauchreaktion' des Modells auf jedes Eingabe-Token. Sie hebt hervor, welche Wörter das Modell am interessantesten oder überraschendsten fand, basierend auf einer direkten und schnellen Berechnung der Wichtigkeit. Betrachten Sie es als einen schnellen Blick auf den Fokus des Modells.",
|
| 5 |
+
"unsupported_method_desc": "Für diese Methode ist keine Beschreibung verfügbar.",
|
| 6 |
+
"ai_expert_intro": "Sie sind ein erstklassiger Experte für KI-Interpretierbarkeit. Ihre Aufgabe ist es, eine Attributions-Heatmap zu analysieren und eine umfassende, leicht verständliche Erklärung für ein nicht-technisches Publikum zu liefern.",
|
| 7 |
+
"analysis_details": "Analyse-Details",
|
| 8 |
+
"method_being_used": "Verwendete Methode:",
|
| 9 |
+
"prompt_analyzed": "Analysierte Eingabeaufforderung:",
|
| 10 |
+
"full_generated_text": "Vollständig generierter Text:",
|
| 11 |
+
"method_specific_context": "Methodenspezifischer Kontext",
|
| 12 |
+
"instructions_for_analysis": "Anweisungen für die Analyse",
|
| 13 |
+
"instruction_part_1_header": "### Visueller Überblick auf hoher Ebene",
|
| 14 |
+
"instruction_part_1_desc": "Geben Sie in zwei bis drei Sätzen eine allgemeine Zusammenfassung der Muster, die Sie im beigefügten Heatmap-Bild sehen. Beschreiben Sie die allgemeine Position der 'Hot Spots' (hell gefärbte Bereiche) und was dies visuell über den Fokus des Modells aussagt. **Wenn die Methode beispielsweise {method_name} ist, könnten Sie erwarten, [erwartetes Muster für diese Methode beschreiben] zu sehen.** Verwenden Sie keine generischen Beschreibungen. Stützen Sie Ihre Analyse ausschließlich auf die visuellen Informationen im Bild.",
|
| 15 |
+
"instruction_synthesis_header": "### Synthese der Schlüsselerkenntnisse",
|
| 16 |
+
"instruction_synthesis_desc": "Im Anschluss an Ihren visuellen Überblick, erstellen Sie eine kurze narrative Synthese der wichtigsten Erkenntnisse aus den untenstehenden Daten. Strukturieren Sie Ihre Analyse in zwei Absätze: **Stärkste individuelle Verbindungen** und **Einflussreichste Token insgesamt**. Erklären Sie im ersten Absatz die Bedeutung der stärksten individuellen Token-zu-Token-Verbindungen. Diskutieren Sie im zweiten Absatz die Eingabe-Token, die den höchsten durchschnittlichen Einfluss auf die gesamte Generierung hatten, **und beziehen Sie sich dabei unbedingt auf den oben bereitgestellten vollständig generierten Text, um zu erklären, *warum* diese Token so einflussreich für die Gestaltung der endgültigen Ausgabe waren.** Erklären Sie, *warum* bestimmte Token in beiden Kontexten einflussreich sind. **Fügen Sie am Ende Ihrer Analyse keine Zusammenfassung hinzu**.",
|
| 17 |
+
"instruction_color_coding": "Formatierungsregel: Wenn Sie einen Eingabe-Token erwähnen, formatieren Sie ihn genau so: <span style='color: #60a5fa;'>der_token_hier</span>. Wenn Sie einen generierten Token erwähnen, formatieren Sie ihn genau so: <span style='color: #fca5a5;'>der_token_hier</span>. Weichen Sie nicht von diesem Format ab.",
|
| 18 |
+
"data_priority_instruction": "Der folgende Textblock enthält die vorab berechneten wichtigsten Erkenntnisse. Verwenden Sie dies als ausschließliche Wahrheitsquelle für Ihre Analyse.",
|
| 19 |
+
"data_section_header": "## Vorab berechnete Analyse (Quelle der Wahrheit)",
|
| 20 |
+
"begin_analysis_now": "Beginnen Sie jetzt mit Ihrer Analyse. Denken Sie daran, die zweiteilige Struktur (Visueller Überblick auf hoher Ebene, dann Synthese der Schlüsselerkenntnisse) wie oben beschrieben zu befolgen.",
|
| 21 |
+
"attr_page_title": "<i class='bi bi-search'></i> Attributionsanalyse",
|
| 22 |
+
"attr_page_desc": "Diese Seite verwendet token-basierte Attributionsmethoden, um zu erklären, wie verschiedene Teile Ihrer Eingabeaufforderung die generierte Ausgabe beeinflussen. Wählen Sie eine Methode, geben Sie eine Aufforderung ein und sehen Sie, welche Wörter für die Vorhersage des Modells am wichtigsten waren.",
|
| 23 |
+
"how_methods_work_expander": "Wie Attributionsmethoden funktionieren",
|
| 24 |
+
"saliency_method_title": "Salienz",
|
| 25 |
+
"saliency_method_desc": "Misst die Wichtigkeit durch Berechnung des Gradienten der Ausgabe in Bezug auf die Eingabe-Token. Es ist schnell, kann aber manchmal verrauscht sein.",
|
| 26 |
+
"saliency_step_1": "<strong>1. Ausgabe generieren:</strong> Das Modell generiert das nächste Wort, z.B. 'springt', für die Eingabe 'Der schnelle braune Fuchs'.",
|
| 27 |
+
"saliency_step_2": "<strong>2. Gradienten berechnen:</strong> Es berechnet, wie stark sich die Wahrscheinlichkeit von 'springt' bei einer winzigen Änderung der Einbettung jedes Eingangswortes ändern würde.",
|
| 28 |
+
"saliency_step_3": "<strong>3. Werte zuweisen:</strong> Wörter, die die größte Änderung verursachen (z.B. 'Fuchs'), erhalten die höchsten Werte.",
|
| 29 |
+
"ig_method_title": "Integrierte Gradienten",
|
| 30 |
+
"ig_method_desc": "Eine robustere Methode, die die Vorhersage den Eingaben zuschreibt, indem sie Gradienten entlang eines Pfades von einer Basislinie (z. B. Null-Einbettung) zur Eingabe integriert.",
|
| 31 |
+
"ig_step_1": "<strong>1. Pfad erstellen:</strong> Es wird ein glatter Pfad von einer 'leeren' Eingabe zur vollständigen Eingabe 'Der schnelle braune Fuchs' erstellt.",
|
| 32 |
+
"ig_step_2": "<strong>2. Gradienten entlang des Pfades berechnen:</strong> Es berechnet Gradienten für die Ausgabe 'springt' in vielen kleinen Schritten entlang dieses Pfades.",
|
| 33 |
+
"ig_step_3": "<strong>3. Gradienten summieren:</strong> Es summiert all diese kleinen Gradientenwerte, um einen zuverlässigen Wert dafür zu erhalten, wie stark jedes Wort zur Ausgabe beigetragen hat.",
|
| 34 |
+
"occlusion_method_title": "Okklusion",
|
| 35 |
+
"occlusion_method_desc": "Eine einfache, intuitive Methode, die die Wichtigkeit misst, indem jedes Eingabe-Token ersetzt wird und beobachtet wird, wie stark sich die Ausgabewahrscheinlichkeit ändert.",
|
| 36 |
+
"occlusion_step_1": "<strong>1. Ursprüngliche Wahrscheinlichkeit ermitteln:</strong> Das Modell generiert 'springt' mit einer bestimmten Wahrscheinlichkeit.",
|
| 37 |
+
"occlusion_step_2": "<strong>2. Wörter ersetzen:</strong> Es ersetzt systematisch jedes Wort (z.B. 'Fuchs') durch ein neutrales Token und führt das Modell erneut aus.",
|
| 38 |
+
"occlusion_step_3": "<strong>3. Auswirkung messen:</strong> Wenn das Ersetzen von 'Fuchs' dazu führt, dass die Wahrscheinlichkeit von 'springt' erheblich sinkt, wird 'Fuchs' als sehr wichtig eingestuft.",
|
| 39 |
+
"input_header": "<i class='bi bi-pencil-square'></i> Eingabe & Einstellungen",
|
| 40 |
+
"enter_prompt": "Geben Sie Ihre Eingabeaufforderung ein:",
|
| 41 |
+
"enter_prompt_help": "Geben Sie den Text ein, den das Modell fortsetzen soll",
|
| 42 |
+
"enable_ai_explanations": "KI-Erklärungen aktivieren",
|
| 43 |
+
"enable_ai_explanations_help": "Erklärungen für Visualisierungen mit Qwen 2.5 VL 72B generieren (erfordert API-Zugang)",
|
| 44 |
+
"generate_and_analyze_button": "Alle Methoden generieren & analysieren",
|
| 45 |
+
"max_new_tokens_slider": "Anzahl der zu generierenden Token",
|
| 46 |
+
"max_new_tokens_slider_help": "Steuert die Länge des generierten Textes.",
|
| 47 |
+
"loading_models_spinner": "Lade OLMo-Modell mit allen Attributionsmethoden...",
|
| 48 |
+
"generating_attributions_spinner": "Generiere Text und Attributionen...",
|
| 49 |
+
"analysis_complete_success": "Alle Attributionsanalysen abgeschlossen!",
|
| 50 |
+
"failed_to_generate_analysis_error": "Analyse konnte nicht generiert werden",
|
| 51 |
+
"failed_to_load_models_error": "Modelle konnten nicht geladen werden",
|
| 52 |
+
"please_enter_prompt_warning": "Bitte geben Sie eine Eingabeaufforderung ein",
|
| 53 |
+
"output_header": "<i class='bi bi-display'></i> Ausgabe",
|
| 54 |
+
"generated_text_subheader": "Generierter Text",
|
| 55 |
+
"input_label": "Eingabe:",
|
| 56 |
+
"generated_label": "Generiert:",
|
| 57 |
+
"attribution_analysis_results_header": "Ergebnisse der Attributionsanalyse",
|
| 58 |
+
"attr_tab": "Integrierte Gradienten",
|
| 59 |
+
"occlusion_tab": "Okklusion",
|
| 60 |
+
"saliency_tab": "Salienz",
|
| 61 |
+
"attr_title": "Analyse der integrierten Gradienten",
|
| 62 |
+
"occlusion_title": "Okklusionsanalyse",
|
| 63 |
+
"saliency_title": "Salienzanalyse",
|
| 64 |
+
"attr_viz_desc": "**Wie man diese Heatmap der integrierten Gradienten liest:**\\n- **X-Achse**: Generierte Token (was das Modell erzeugt hat)\\n- **Y-Achse**: Eingabe-Token (Ihre ursprüngliche Eingabeaufforderung)\\n- **Farbintensität**: Mathematische gradientenbasierte Wichtigkeitswerte\\n- **Interpretation**: Wie stark jedes Eingabe-Token jedes generierte Token mathematisch beeinflusst",
|
| 65 |
+
"occlusion_viz_desc": "Die Okklusionsanalyse hebt wichtige Token hervor, indem sie vorübergehend maskiert (okkludiert) werden und die Auswirkung auf die Ausgabe gemessen wird. Ein höherer Attributionswert bedeutet, dass das Token kritischer war.",
|
| 66 |
+
"saliency_viz_desc": "Diese Visualisierung hebt die salientesten Token in der Eingabe hervor, die zur Generierung beigetragen haben.",
|
| 67 |
+
"how_to_read_heatmap": "Wie man diese Heatmap liest:",
|
| 68 |
+
"xaxis_label": "X-Achse",
|
| 69 |
+
"xaxis_desc": "Generierte Token (was das Modell erzeugt hat)",
|
| 70 |
+
"yaxis_label": "Y-Achse",
|
| 71 |
+
"yaxis_desc": "Eingabe-Token (Ihre ursprüngliche Eingabeaufforderung)",
|
| 72 |
+
"color_intensity_label": "Farbintensität",
|
| 73 |
+
"color_intensity_desc": "Mathematische Wichtigkeitswerte",
|
| 74 |
+
"interpretation_label": "Interpretation",
|
| 75 |
+
"interpretation_desc": "Wie stark jedes Eingabe-Token jedes generierte Token beeinflusst.",
|
| 76 |
+
"special_tokens_label": "Spezielle Token (z.B., `Ġ`, `Ċ`)",
|
| 77 |
+
"special_tokens_desc": "Dies sind Artefakte des Tokenizers. Häufige sind:<ul><li>`Ġ`: Ein Leerzeichen, das ein neues Wort markiert.</li><li>`Ċ`: Ein Zeilenumbruchzeichen.</li><li>`<|endoftext|>`: Ein spezielles Token, das das Ende einer Sequenz markiert.</li></ul>",
|
| 78 |
+
"creating_viz_spinner": "Erstelle {method_title} Visualisierung...",
|
| 79 |
+
"generating_ai_explanation_spinner": "Generiere KI-Erklärung für {method_title}...",
|
| 80 |
+
"what_this_method_shows": "Was diese Methode zeigt:",
|
| 81 |
+
"ai_generated_analysis": "KI-generierte Analyse",
|
| 82 |
+
"download_results_subheader": "Ergebnisse herunterladen",
|
| 83 |
+
"download_html_button": "{method_title} HTML herunterladen",
|
| 84 |
+
"download_csv_button": "Werte herunterladen (CSV)",
|
| 85 |
+
"download_png_button": "{method_title} PNG herunterladen",
|
| 86 |
+
"heatmap_title": "Attributions-Heatmap",
|
| 87 |
+
"heatmap_xaxis": "Generierte Token",
|
| 88 |
+
"heatmap_yaxis": "Eingabe-Token",
|
| 89 |
+
"feedback_survey_header": "Feedback & Verständnisumfrage",
|
| 90 |
+
"feedback_survey_desc": "Ihr Feedback ist wertvoll für die Verbesserung dieses Tools. Bitte nehmen Sie sich einen Moment Zeit, um diese Fragen zu beantworten.",
|
| 91 |
+
"ux_feedback_subheader": "User Experience Feedback",
|
| 92 |
+
"q_visual_clarity": "1. Wie bewerten Sie die Klarheit der Heatmap-Visualisierungen?",
|
| 93 |
+
"q_visual_clarity_help": "1 = Sehr verwirrend, 5 = Sehr klar",
|
| 94 |
+
"q_cognitive_load": "2. Wie anspruchsvoll fanden Sie es, die Ergebnisse zu interpretieren?",
|
| 95 |
+
"q_cognitive_load_help": "1 = Überhaupt nicht anspruchsvoll, 5 = Sehr anspruchsvoll",
|
| 96 |
+
"q_influential_docs_plausibility": "3. Wie plausibel sind die 3 einflussreichsten Dokumente, die vom Influence Tracer identifiziert wurden?",
|
| 97 |
+
"q_influential_docs_plausibility_help": "1 = Überhaupt nicht plausibel, 5 = Sehr plausibel",
|
| 98 |
+
"comprehension_qs_subheader": "Kurze Verständnisprüfung",
|
| 99 |
+
"comprehension_qs_desc": "Basierend auf den Visualisierungen, die Sie gerade gesehen haben, welche Methode beantwortet die folgenden Fragen am besten?",
|
| 100 |
+
"q_options_ig": "Integrierte Gradienten",
|
| 101 |
+
"q_options_occlusion": "Okklusion",
|
| 102 |
+
"q_options_saliency": "Salienz",
|
| 103 |
+
"q_s1": "Welche Methode zeigt die anfängliche 'Bauchreaktion' des Modells auf jedes Wort und dessen direkten und unmittelbaren Fokus?",
|
| 104 |
+
"q_s2": "Welche Methode würden Sie verwenden, um die Auswirkung des Entfernens eines bestimmten Wortes zu verstehen?",
|
| 105 |
+
"q_s3": "Welche Methode erstellt ein zuverlässigeres Bild der Wichtigkeit, indem der gesamte Pfad von einer leeren Eingabe bis zu Ihrer endgültigen Eingabeaufforderung analysiert wird?",
|
| 106 |
+
"submit_feedback_button": "Feedback absenden",
|
| 107 |
+
"feedback_success_message": "Vielen Dank für Ihr Feedback!",
|
| 108 |
+
"feedback_error_message": "Entschuldigung, beim Senden Ihres Feedbacks ist ein Fehler aufgetreten: {e}",
|
| 109 |
+
"feedback_please_answer_all_qs": "Bitte beantworten Sie alle Verständnisfragen, bevor Sie absenden.",
|
| 110 |
+
"error_creating_heatmap": "Fehler beim Erstellen der Heatmap aus HTML: {e}",
|
| 111 |
+
"error_inseq_no_html": "Inseq konnte keine HTML-Ausgabe für {method_name} generieren.",
|
| 112 |
+
"error_no_table_in_html": "Konnte keine Datentabelle in der HTML-Ausgabe von inseq für {method_name} finden.",
|
| 113 |
+
"error_table_no_rows": "Tabelle in der HTML-Ausgabe enthält keine Zeilen für {method_name}.",
|
| 114 |
+
"error_failed_to_parse_rows": "Fehler beim Parsen von Datenzeilen aus dem HTML für {method_name}.",
|
| 115 |
+
"running_influence_trace_spinner": "Einflüsse in den Trainingsdaten werden verfolgt...",
|
| 116 |
+
"influence_index_not_found_warning": "Influence Tracer-Index nicht gefunden. Dieser Schritt wird übersprungen. Bitte führen Sie `build_dolma_index.py` aus, um ihn zu aktivieren.",
|
| 117 |
+
"influence_tracer_title": "Einfluss-Tracer",
|
| 118 |
+
"influence_tracer_desc": "Dieses Tool identifiziert Trainingsdokumente aus einer Stichprobe des <b>Dolma v1.6-Datensatzes</b>, die den größten Einfluss auf die Ausgabe des Modells hatten. Dolma v1.6 ist ein offener Datensatz mit 3 Billionen Token, der aus einer vielfältigen Mischung von Webinhalten (Common Crawl), wissenschaftlichen Veröffentlichungen (C4, arXiv), Code (The Stack), Büchern (Project Gutenberg) und enzyklopädischen Daten (Wikipedia) besteht. Indem wir die Generierung des Modells auf seine Trainingsdaten zurückführen, können wir seine Argumentation und Wissensquellen besser verstehen.",
|
| 119 |
+
"top_influential_docs_header": "Top {num_docs} einflussreichste Trainingsdokumente",
|
| 120 |
+
"no_influential_docs_found": "Für diese Generierung wurden keine einflussreichen Dokumente gefunden.",
|
| 121 |
+
"file_label": "Datei",
|
| 122 |
+
"source_label": "Quelle",
|
| 123 |
+
"similarity_label": "Ähnlichkeit",
|
| 124 |
+
"run_analysis_for_influence_info": "Führen Sie eine Analyse durch, um hier einflussreiche Trainingsdokumente zu sehen.",
|
| 125 |
+
"prompt_placeholder_text": "z.B., 'Die Hauptstadt von Frankreich ist' oder 'Sein oder Nichtsein, das ist hier die'",
|
| 126 |
+
"running_attribution_analysis_spinner": "Generiere Attributions-Heatmaps...",
|
| 127 |
+
"generating_ai_explanations_spinner": "Generiere KI-Erklärungen...",
|
| 128 |
+
"how_influence_is_found_header": "Wie Einfluss gefunden wird: Ein Blick auf die Kosinus-Ähnlichkeit",
|
| 129 |
+
"how_influence_is_found_desc": "Der Influence Tracer sucht nicht nur nach Schlüsselwörtern, sondern nach Bedeutung. Dazu wandelt er sowohl Ihre Eingabeaufforderung als auch jeden Satz in den Trainingsdaten in hochdimensionale Vektoren um. Anschließend verwendet er eine Technik namens <strong>Kosinus-Ähnlichkeit</strong>, um die engsten Übereinstimmungen zu finden.",
|
| 130 |
+
"influence_step_1_title": "<strong>1. Vektorumwandlung</strong>",
|
| 131 |
+
"influence_step_1_desc": "Ihre Eingabeaufforderung und jeder Satz aus den Trainingsdaten werden in numerische Vektoren umgewandelt.",
|
| 132 |
+
"influence_step_2_title": "<strong>2. Winkelberechnung</strong>",
|
| 133 |
+
"influence_step_2_desc": "Das System berechnet den Winkel (θ) zwischen dem Vektor Ihrer Eingabeaufforderung und jedem anderen Satzvektor.",
|
| 134 |
+
"influence_step_3_title": "<strong>3. Ähnlichkeitswert</strong>",
|
| 135 |
+
"influence_step_3_desc": "Ein kleinerer Winkel bedeutet eine höhere Ähnlichkeit. Ein Wert von 1 bedeutet, dass die Sätze in ihrer Bedeutung identisch sind, während ein Wert von 0 bedeutet, dass sie völlig unabhängig voneinander sind.",
|
| 136 |
+
"influence_example_sentence_a": "Ihre Eingabe",
|
| 137 |
+
"influence_example_sentence_b": "Trainingssatz",
|
| 138 |
+
"generating_all_visualizations_spinner": "Generiere alle Visualisierungen und KI-Erklärungen...",
|
| 139 |
+
"searching_influential_docs_progress": "Suche nach einflussreichen Dokumenten...",
|
| 140 |
+
"processing_doc_progress": "Verarbeite Dokument {i} von {k}...",
|
| 141 |
+
"search_complete_progress": "Suche abgeschlossen!",
|
| 142 |
+
"faithfulness_check_expander": "Überprüfung der Faktentreue",
|
| 143 |
+
"running_faithfulness_check_spinner": "Führe Überprüfung der Faktentreue aus...",
|
| 144 |
+
"verified_status": "Verifiziert",
|
| 145 |
+
"contradicted_status": "Widersprochen",
|
| 146 |
+
"claim_label": "Aussage",
|
| 147 |
+
"status_label": "Status",
|
| 148 |
+
"evidence_label": "Beweis",
|
| 149 |
+
"no_verifiable_claims_info": "Aus der Erklärung konnten keine überprüfbaren Aussagen extrahiert werden.",
|
| 150 |
+
"faithfulness_check_error": "Bei der Überprüfung der Faktentreue ist ein Fehler aufgetreten: {e}",
|
| 151 |
+
"faithfulness_check_results_header": "Ergebnisse der Überprüfung der Faktentreue:",
|
| 152 |
+
"faithfulness_check_explanation_html": "<div style='font-size: 0.9rem; color: #DCDCDC; margin-bottom: 1rem;'><p style='margin-bottom: 0.5rem;'><strong>Wie das funktioniert:</strong> Der Faktentreue-Prüfer verifiziert zwei Arten von Behauptungen aus der Erklärung der KI:</p><ul style='margin-left: 1.5rem; padding-left: 0; list-style-type: disc;'><li style='margin-bottom: 0.3rem;'><strong>Numerische Behauptungen:</strong> Überprüft, ob der Attributionswert eines Tokens (entweder sein Spitzenwert 'Hotspot' oder sein Durchschnittswert) einen dynamischen Schwellenwert erreicht.<ul style='margin-left: 1.5rem; padding-left: 0; list-style-type: circle;'><li>Eine <strong>\\\"hohe\\\"</strong> Behauptung (z.B. \\\"höchste\\\", \\\"stärkste\\\") muss über <strong>70%</strong> des Maximalwerts in der Analyse liegen.</li><li>Eine <strong>\\\"signifikante\\\"</strong> Behauptung (z.B. \\\"bemerkenswert\\\") muss über <strong>50%</strong> des Maximalwerts liegen.</li></ul></li><li style='margin-bottom: 0.3rem;'><strong>Begründungsbehauptungen:</strong> Verwendet eine weitere KI, um semantisch zu analysieren, ob die <strong>Begründung</strong> für die Wichtigkeit eines Tokens plausibel und logisch konsistent ist.</li></ul></div>",
|
| 153 |
+
"claim_extraction_prompt_header": "Sie sind ein Experten-System zur Extraktion von Behauptungen. Ihre Aufgabe ist es, eine Erklärung einer Text-Attributionsanalyse zu lesen und alle überprüfbaren, sachlichen Behauptungen in eine strukturierte JSON-Liste zu extrahieren. Ein einzelner Satz kann mehrere unterschiedliche Behauptungen enthalten.",
|
| 154 |
+
"claim_extraction_prompt_instruction": "Jedes Objekt in der Liste MUSS die folgenden Schlüssel haben:\n1. `claim_text`: Der exakte Satz oder die exakte Phrase aus der Erklärung, die die Behauptung aufstellt.\n2. `claim_type`: Einer der verfügbaren Behauptungstypen.\n3. `details`: Ein Objekt, das die spezifischen Parameter für die Überprüfung enthält.",
|
| 155 |
+
"claim_extraction_prompt_context_header": "**Kontext der Analysemethode:** {analysis_method}",
|
| 156 |
+
"claim_extraction_prompt_types_header": "**Verfügbare Behauptungstypen:**",
|
| 157 |
+
"claim_extraction_prompt_types_details": "- `attribution_claim`: Ein Anspruch, der behauptet, dass ein oder mehrere Token hohe oder signifikante Attributionswerte haben, entweder basierend auf ihrem Spitzenwert (Hotspot) oder ihrem durchschnittlichen Einfluss.\n - `details`: {{ \"tokens\": [\"...\"], \"qualifier\": \"hoch\" | \"signifikant\", \"score_type\": \"spitze\" | \"durchschnitt\" }}\n- `token_begruendung_anspruch`: Ein Anspruch, der einen spezifischen Grund für die Wichtigkeit oder den Attributionswert eines oder mehrerer Tokens liefert.\n - `details`: {{ \"tokens\": [\"...\"], \"begruendung\": \"...\" }}",
|
| 158 |
+
"claim_extraction_prompt_example_header": "**Beispiel:**",
|
| 159 |
+
"claim_extraction_prompt_example_explanation": "- **Satz der Erklärung:** \"Insgesamt hat 'Frankreich' den höchsten durchschnittlichen Einfluss, während '.' einen signifikanten Spitzenwert hat.\"",
|
| 160 |
+
"claim_extraction_prompt_example_json": "- **Ergebnis-JSON-Objekt:**\n ```json\n [\n {{\n \"claim_text\": \"Insgesamt hat 'Frankreich' den höchsten durchschnittlichen Einfluss...\",\n \"claim_type\": \"attribution_claim\",\n \"details\": {{ \"tokens\": [\"Frankreich\"], \"qualifier\": \"hoch\", \"score_type\": \"durchschnitt\" }}\n }},\n {{\n \"claim_text\": \"...während '.' einen signifikanten Spitzenwert hat.\",\n \"claim_type\": \"attribution_claim\",\n \"details\": {{ \"tokens\": [\".\"], \"qualifier\": \"signifikant\", \"score_type\": \"spitze\" }}\n }}\n ]\n ```",
|
| 161 |
+
"claim_extraction_prompt_analyze_header": "**Zu analysierende Erklärung:**",
|
| 162 |
+
"claim_extraction_prompt_instruction_footer": "Antworten Sie NUR mit der JSON-Liste der Ansprüche.",
|
| 163 |
+
"justification_verification_prompt_collective_reasoning": "**Kollektive Begründung:** Die Begründung kann sich auf mehrere Token gleichzeitig beziehen (z.B. 'diese Token gemeinsam...'). Bei der Bewertung einer solchen Behauptung betrachten Sie die Gruppe von Token als eine einzige Einheit und beurteilen Sie, ob die Begründung für sie als Ganzes plausibel ist, auch wenn sie nicht auf jedes Token einzeln perfekt zutrifft.",
|
| 164 |
+
"justification_verification_prompt_header": "Sie sind ein KI-Faktenprüfer, der auf NLP und semantisches Denken spezialisiert ist. Ihre Aufgabe ist es zu bestimmen, ob eine Begründung für die Wichtigkeit eines Tokens plausibel und logisch konsistent ist, wenn der gesamte Kontext gegeben ist.",
|
| 165 |
+
"justification_verification_prompt_crucial_rule": "**Entscheidende Regel:** Eine Begründung ist plausibel, wenn sie eine vernünftige, kreative oder kontextuell relevante Verbindung darstellt. Widersprechen Sie nur, wenn die Argumentation völlig unlogisch, sachlich falsch oder inkonsistent mit dem gegebenen Eingabe- oder Ausgabetext ist.",
|
| 166 |
+
"justification_verification_prompt_token_location": "**Token-Standort:** Das „fragliche Token“ kann entweder aus der „Eingabeaufforderung“ oder dem „generierten Text“ stammen. Ein Token aus der Eingabe kann immer noch einen entscheidenden Einfluss auf die generierte Ausgabe haben. Widersprechen Sie einer Behauptung nicht einfach deshalb, weil das Token im generierten Text nicht vorhanden ist.",
|
| 167 |
+
"justification_verification_prompt_special_tokens": "**Spezielle Token:** Das 'fragliche Token' kann Sonderzeichen vom Tokenizer enthalten. `Ġ` steht für ein führendes Leerzeichen (z. B. ist `Ġof` ` of`), und Suffixe wie ` (1)` dienen der Eindeutigkeit (z. B. ist `. (1)` einfach `.`). Sie MÜSSEN dies berücksichtigen, wenn Sie prüfen, ob ein Token im Text vorhanden ist.",
|
| 168 |
+
"justification_verification_prompt_evaluating_justifications": "**Bewertung von Begründungen:** Eine Begründung sollte als plausibel angesehen werden, wenn sie eine vernünftige Verbindung aufzeigt, auch wenn es sich nicht um eine direkte oder einfache kausale Verknüpfung handelt. Dies schließt Beziehungen ein, die auf dem breiteren Kontext des Textes oder der grammatikalischen Struktur der Sprache basieren. Achten Sie besonders auf Token, die gebräuchliche Kollokationen, Entitäten oder Abkürzungen bilden; Verbindungen zwischen solchen Token sollten als plausibel angesehen werden, da sie vom Modell oft als eine einzige semantische Einheit verarbeitet werden.",
|
| 169 |
+
"justification_verification_prompt_linguistic_context": "**Linguistischer Kontext für autoregressive Modelle:** Es ist entscheidend, sich daran zu erinnern, dass in autoregressiven Modellen wie diesem JEDES Token die Wahrscheinlichkeit des nächsten Tokens direkt beeinflusst. Daher sind Begründungen, die auf grammatikalischer Struktur, Zeichensetzung oder syntaktischen Rollen basieren, nicht nur gültig, sondern stellen einen Kernbestandteil des Entscheidungsprozesses des Modells dar. Die strukturelle Rolle eines Tokens (wie eine Präposition oder ein Punkt) ist ein direkter und wichtiger Beitrag zur Inhaltsgenerierung. Weisen Sie diese Begründungen nicht als 'bloße Grammatik' zurück.",
|
| 170 |
+
"justification_verification_prompt_task_header": "**Ihre Aufgabe:**",
|
| 171 |
+
"justification_verification_prompt_task_instruction": "Ist die Begründung auf der Grundlage der obigen Regel plausibel?",
|
| 172 |
+
"justification_verification_prompt_json_instruction": "Antworten Sie mit einem JSON-Objekt mit zwei Schlüsseln:\n1. `is_verified`: boolean (wahr, wenn die Begründung plausibel ist, falsch, wenn sie unlogisch oder falsch ist).\n2. `reasoning`: Eine kurze, einzeilige Erklärung für Ihre Entscheidung.",
|
| 173 |
+
"justification_verification_prompt_footer": "Antworten Sie NUR mit dem JSON-Objekt."
|
| 174 |
+
}
|
locales/de/circuit_trace_page.json
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"circuit_trace_page_title": "Circuit-Trace-Analyse",
|
| 3 |
+
"circuit_trace_page_desc": "Erkunden Sie die internen Pfade des OLMo-Modells. Diese Seite visualisiert, wie Informationen von Eingabe-Token über verschiedene Schichten und Merkmale fließen, um die endgültige Ausgabe zu erzeugen, basierend auf einer neuartigen Cross-Layer-Transcoder-Methode.",
|
| 4 |
+
"how_circuit_tracing_works_header": "Wie das funktioniert: Ein dreistufiger Prozess",
|
| 5 |
+
"how_circuit_tracing_works_desc": "Anstatt das gesamte Modell auf einmal zu betrachten, vereinfacht diese Technik die Analyse, indem sie sich auf 'Merkmale' konzentriert – spezifische, gelernte Muster von Neuronenaktivierungen. Durch das Training kleiner 'Transcoder'-Modelle können wir identifizieren, welche Merkmale in einer Schicht Merkmale in der nächsten aktivieren, was es uns ermöglicht, einen Schaltkreis des Informationsflusses zu verfolgen.",
|
| 6 |
+
"circuit_tracing_step1_title": "1. Merkmalsextraktion",
|
| 7 |
+
"circuit_tracing_step1_desc": "Kleine <strong>Autoencoder</strong>-Modelle (stellen Sie sie sich als Komprimierungswerkzeuge vor, die wichtige Informationen zusammenfassen) werden auf jeder Schicht des Haupt-OLMo-Modells trainiert, um wiederkehrende Muster von Neuronenaktivierungen zu entdecken, die wir 'Merkmale' nennen.",
|
| 8 |
+
"circuit_tracing_step2_title": "2. Schichtübergreifendes Mapping",
|
| 9 |
+
"circuit_tracing_step2_desc": "Winzige <strong>Transcoder</strong>-Modelle (die wie Übersetzer zwischen den Schichten agieren) werden trainiert, um die Aktivierung eines Merkmals in einer späteren Schicht basierend auf den Aktivierungen von Merkmalen in einer früheren Schicht vorherzusagen.",
|
| 10 |
+
"circuit_tracing_step3_title": "3. Graph-Konstruktion",
|
| 11 |
+
"circuit_tracing_step3_desc": "Indem wir die vorhersagekräftigsten Merkmalspaare aus den Transcoder-Modellen verbinden, erstellen wir einen gerichteten Graphen, der die wichtigsten Pfade des Informationsflusses für eine gegebene Eingabe darstellt.",
|
| 12 |
+
"enable_ai_explanations_circuit": "KI-Erklärungen aktivieren",
|
| 13 |
+
"enable_ai_explanations_circuit_help": "Generieren Sie detaillierte Erklärungen für Schaltungsvisualisierungen mit Qwen 2.5 VL 72B.",
|
| 14 |
+
"no_results_warning": "Ergebnisse der Attributionsgraphen nicht gefunden.",
|
| 15 |
+
"run_analysis_info": "Bitte führen Sie zuerst das Analyse-Skript aus: `python3 circuit_analysis/attribution_graphs_olmo_de.py --prompt-index 0 --force-retrain-clt`",
|
| 16 |
+
"config_header": "Konfiguration",
|
| 17 |
+
"model_label": "Modell:",
|
| 18 |
+
"device_label": "Gerät:",
|
| 19 |
+
"features_per_layer_label": "Merkmale pro Schicht:",
|
| 20 |
+
"training_steps_label": "Trainingsschritte:",
|
| 21 |
+
"batch_size_label": "Batch-Größe:",
|
| 22 |
+
"learning_rate_label": "Lernrate:",
|
| 23 |
+
"interactive_analysis_header": "Interaktive Analyse",
|
| 24 |
+
"select_prompt_label": "Wählen Sie einen Prompt zur Analyse aus:",
|
| 25 |
+
"select_prompt_help": "Diese Auswahl steuert sowohl den interaktiven Schaltungsgraphen als auch den Merkmals-Explorer unten.",
|
| 26 |
+
"graph_stats_header": "Graph-Statistiken",
|
| 27 |
+
"full_graph_nodes_label": "Knoten (vollständiger Graph)",
|
| 28 |
+
"full_graph_edges_label": "Kanten (vollständiger Graph)",
|
| 29 |
+
"pruned_graph_nodes_label": "Knoten (reduzierter Graph)",
|
| 30 |
+
"pruned_graph_edges_label": "Kanten (reduzierter Graph)",
|
| 31 |
+
"feature_explorer_header": "Merkmals-Explorer",
|
| 32 |
+
"token_analysis_header": "Token-Analyse",
|
| 33 |
+
"input_tokens_label": "Eingabe-Token:",
|
| 34 |
+
"feature_explorer_title": "Merkmals-Explorer: {prompt}",
|
| 35 |
+
"select_layer_label": "Wählen Sie eine Schicht zum Erkunden aus:",
|
| 36 |
+
"layer_label_format": "Schicht {layer_num}",
|
| 37 |
+
"no_feature_viz_warning": "Für diesen Prompt sind keine Merkmalsvisualisierungen verfügbar.",
|
| 38 |
+
"no_features_in_layer_warning": "In {selected_layer} wurden keine Merkmale gefunden.",
|
| 39 |
+
"active_features_label": "**Aktive Merkmale:**",
|
| 40 |
+
"choose_feature_label": "Wählen Sie ein Merkmal aus:",
|
| 41 |
+
"max_activation_label": "Maximale Aktivierung",
|
| 42 |
+
"mean_activation_label": "Mittlere Aktivierung",
|
| 43 |
+
"sparsity_label": "Sparsamkeit",
|
| 44 |
+
"interpretation_label": "Interpretation",
|
| 45 |
+
"top_activating_tokens_title": "Top aktivierende Token für {selected_feature}",
|
| 46 |
+
"xaxis_token_label": "Token",
|
| 47 |
+
"yaxis_activation_label": "Aktivierungsstärke",
|
| 48 |
+
"generating_feature_explanation_spinner": "Generiere KI-Erklärung für Merkmalsaktivierung...",
|
| 49 |
+
"feature_explanation_error": "Konnte keine Merkmalserklärung generieren: {e}",
|
| 50 |
+
"ai_feature_analysis_header": "KI-Merkmalsanalyse",
|
| 51 |
+
"node_size_label": "Knotengröße",
|
| 52 |
+
"edge_threshold_label": "Kantenschwellenwert",
|
| 53 |
+
"tip_scroll_horizontally": "Tipp: Verwenden Sie das Mausrad + Shift, um horizontal zu scrollen und alle 32 Schichten zu sehen.",
|
| 54 |
+
"colorbar_title": "Aktivierung",
|
| 55 |
+
"path_highlight_label": "Schaltkreis-Pfad",
|
| 56 |
+
"connections_legend": "Verbindungen",
|
| 57 |
+
"embedding_legend": "Einbettung",
|
| 58 |
+
"feature_legend": "Merkmal",
|
| 59 |
+
"layer_nav_header": "Schichtnavigation",
|
| 60 |
+
"layer_nav_desc": "Dieser Graph zeigt <strong>{num_layers} Schichten</strong> mit Merkmalen. Verwenden Sie den Bereichsregler unter dem Graphen, um durch alle Schichten zu navigieren, oder verwenden Sie <strong>Shift + Mausrad</strong>, um horizontal zu scrollen.",
|
| 61 |
+
"generating_circuit_explanation_spinner": "Generiere KI-Erklärung für den Schaltungsgraphen...",
|
| 62 |
+
"circuit_explanation_error": "Konnte keine Schaltungserklärung generieren: {e}",
|
| 63 |
+
"ai_circuit_analysis_header": "KI-Schaltungsanalyse",
|
| 64 |
+
"layer_stats_header": "Schichtstatistiken",
|
| 65 |
+
"total_layers_label": "Gesamtzahl der Schichten mit Merkmalen",
|
| 66 |
+
"total_features_label": "Gesamtzahl der Merkmale",
|
| 67 |
+
"avg_features_per_layer_label": "Durchschnittliche Merkmale pro Schicht",
|
| 68 |
+
"features_by_layer_header": "Merkmale nach Schicht",
|
| 69 |
+
"feature_dist_title": "Merkmalsverteilung über die Schichten",
|
| 70 |
+
"feature_count_label": "Anzahl der Merkmale",
|
| 71 |
+
"subnetwork_explorer_title": "Subnetzwerk-Explorer",
|
| 72 |
+
"subnetwork_explorer_desc": "Wählen Sie ein zentrales Merkmal aus, um dessen lokale Nachbarschaft zu visualisieren, die sowohl seine vorgeschalteten Ursachen als auch seine nachgeschalteten Effekte innerhalb einer bestimmten Verbindungstiefe anzeigt.",
|
| 73 |
+
"subnetwork_graph_empty_info": "Der Hauptschaltungsgraph wurde noch nicht generiert. Bitte warten Sie, bis er geladen ist.",
|
| 74 |
+
"no_features_in_graph_warning": "In der aktuellen Graphansicht sind keine Merkmale verfügbar, um ein Subnetzwerk zu erstellen.",
|
| 75 |
+
"select_layer_label_subnetwork": "1. Wählen Sie eine Schicht",
|
| 76 |
+
"no_features_in_layer_subnetwork_warning": "Keine Merkmale in {selected_layer} zum Auswählen.",
|
| 77 |
+
"select_feature_label_subnetwork": "2. Wählen Sie ein zentrales Merkmal",
|
| 78 |
+
"traversal_depth_label": "3. Verbindungstiefe einstellen",
|
| 79 |
+
"subnetwork_graph_title": "Subnetzwerk zentriert auf Merkmal: {feature}",
|
| 80 |
+
"subnetwork_no_connections_info": "Dieses Merkmal hat keine Verbindungen innerhalb der ausgewählten Tiefe.",
|
| 81 |
+
"generating_subnetwork_explanation_spinner": "Analysiere Subnetzwerk mit KI...",
|
| 82 |
+
"ai_subnetwork_analysis_header": "KI-Subnetzwerkanalyse",
|
| 83 |
+
"subnetwork_analysis_title": "Token-Aktivierungsanalyse",
|
| 84 |
+
"subnetwork_no_features_info": "In diesem Subnetzwerk wurden keine Merkmale zur Analyse gefunden.",
|
| 85 |
+
"subnetwork_no_token_info": "Für die Merkmale in diesem Subnetzwerk sind keine Token-Aktivierungsdaten verfügbar.",
|
| 86 |
+
"subnetwork_top_tokens_desc": "Die folgenden Eingabe-Token haben die Merkmale in diesem Subnetzwerk am stärksten aktiviert:",
|
| 87 |
+
"subnetwork_token_interpretation_info": "Dies zeigt, auf welche Teile des Prompts das Subnetzwerk 'achtet'.",
|
| 88 |
+
"what_is_a_feature_header": "Schlüsselkonzept: Was ist ein 'Merkmal'?",
|
| 89 |
+
"what_is_a_feature_title": "Ein Merkmal ist ein erlerntes, interpretierbares Muster von Neuronenaktivität.",
|
| 90 |
+
"what_is_a_feature_desc": "Stellen Sie es sich wie einen Konzept-Detektor vor. Ein Merkmal könnte zum Beispiel stark auf Wörter reagieren, die sich auf 'Programmierung' beziehen, während ein anderes 'Fragen zur Geschichte' erkennt. Diese Merkmale sind die Bausteine, die das Modell verwendet, um Eingaben zu verstehen und eine Antwort zu erstellen. Indem wir sie verfolgen, können wir den Denkprozess des Modells nachzeichnen.",
|
| 91 |
+
"faithfulness_check_expander": "Überprüfung der Faktentreue",
|
| 92 |
+
"running_faithfulness_check_spinner": "Führe Überprüfung der Faktentreue aus...",
|
| 93 |
+
"verified_status": "Verifiziert",
|
| 94 |
+
"contradicted_status": "Widersprochen",
|
| 95 |
+
"claim_label": "Aussage",
|
| 96 |
+
"status_label": "Status",
|
| 97 |
+
"evidence_label": "Beweis",
|
| 98 |
+
"no_verifiable_claims_info": "Aus der Erklärung konnten keine überprüfbaren Aussagen extrahiert werden.",
|
| 99 |
+
"faithfulness_explanation_circuit_graph_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>Wie das funktioniert:</strong> Der Faithfulness Checker überprüft zwei Arten von Behauptungen aus der KI-Erklärung:<ul><li><strong>Behauptungen zur Merkmalsinterpretation:</strong> Überprüft mittels Fuzzy-String-Matching, ob eine behauptete Interpretation für ein Merkmal in einer bestimmten Schicht (z.B. 'Erkennung des grammatikalischen Modus') eng mit der tatsächlichen Interpretation eines Merkmals in dieser Schicht übereinstimmt.</li><li><strong>Behauptungen zur Schichtrolle:</strong> Überprüft semantisch, ob die Zusammenfassung der Rolle eines Schichtabschnitts durch die KI (z.B. 'frühe Schichten behandeln die Syntax') eine plausible Verallgemeinerung der tatsächlichen Top-Merkmalsinterpretationen in diesem Abschnitt ist.</li></ul></div>",
|
| 100 |
+
"faithfulness_explanation_feature_explorer_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>Wie das funktioniert:</strong> Der Faithfulness Checker überprüft zwei Arten von Behauptungen aus der KI-Erklärung:<ul><li><strong>Behauptungen zu Top-Token:</strong> Überprüft, ob ein Token, das als Top-Aktivator für ein Merkmal beansprucht wird, tatsächlich in der Liste der Top-aktivierenden Token aus den Analysedaten vorhanden ist.</li><li><strong>Behauptungen zur Merkmalsrolle:</strong> Überprüft mittels Fuzzy-String-Matching, ob die zusammengefasste Interpretation der Rolle eines Merkmals durch die KI eng mit der detaillierten Interpretation aus den Analysedaten übereinstimmt.</li></ul></div>",
|
| 101 |
+
"faithfulness_explanation_subnetwork_graph_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>Wie das funktioniert:</strong> Der Faithfulness Checker überprüft drei Arten von Behauptungen aus der KI-Erklärung:<ul><li><strong>Kausale Behauptungen:</strong> Überprüft, ob eine behauptete kausale Verbindung (vorgelagert oder nachgelagert) gültig ist, indem mittels Fuzzy-String-Matching bestätigt wird, dass die Interpretation des behaupteten Merkmals in der tatsächlichen Liste der vor- oder nachgelagerten Nachbarn existiert.</li><li><strong>Behauptungen zum Token-Einfluss:</strong> Überprüft, ob als vorgelagerte Einflüsse beanspruchte Token in der tatsächlichen Liste der direkten vorgelagerten Token für das zentrale Merkmal vorhanden sind.</li><li><strong>Behauptungen zur Rolle des zentralen Merkmals:</strong> Überprüft mittels Fuzzy-String-Matching, ob die Interpretation der Rolle des zentralen Merkmals durch die KI eng mit der Interpretation aus den Analysedaten übereinstimmt.</li></ul></div>",
|
| 102 |
+
"claim_extraction_prompt_header": "Sie sind ein Experten-System zur Extraktion von Behauptungen. Ihre Aufgabe ist es, eine Erklärung einer Circuit-Trace-Visualisierung zu lesen und überprüfbare Behauptungen in eine strukturierte JSON-Liste zu extrahieren.",
|
| 103 |
+
"claim_extraction_prompt_instruction": "Jedes Objekt in der Liste MUSS enthalten: `claim_text`, `claim_type`, und `details`. Der `claim_text` sollte der vollständige, ursprüngliche Satz aus der Erklärung sein.",
|
| 104 |
+
"claim_extraction_prompt_rule": "**Extraktionsregeln:**\n1. **Behalten Sie die ursprüngliche Reihenfolge bei.** Die Behauptungen in der endgültigen JSON-Liste müssen in derselben Reihenfolge erscheinen wie im Quelltext.\n2. **Ignorieren Sie legendenartige Beschreibungen.** Extrahieren Sie keine Behauptungen aus Sätzen, die nur erklären, was die visuellen Elemente des Graphen darstellen (z. B. 'Jeder Knoten ist ein Merkmal', 'Farbe zeigt Aktivierung an'). Extrahieren Sie nur Behauptungen, die eine spezifische Aussage darüber machen, *was das Modell tut* für den aktuellen Prompt (z. B. 'Schicht 10 zeigt hohe Aktivierung für 'Syntax'-Merkmale').\n3. **Halten Sie Behauptungen kurz.** Eine einzelne Behauptung sollte keinen ganzen Absatz umfassen. Gliedern Sie lange Absätze in mehrere, kleinere Behauptungen, in der Regel eine für jeden Hauptpunkt oder eine kleine Gruppe verwandter Punkte.\n4. Extrahieren Sie für jede `interpretation_summary` oder `role_summary` nur das Kernkonzept, das sich normalerweise in einfachen Anführungszeichen befindet (z. B. aus „bemerkenswerte Aktivität für 'Satzstruktur'“ extrahieren Sie nur „Satzstruktur“).\n5. **Entscheidend: Wenn ein einzelner Satz mehrere Behauptungen aufstellt, MÜSSEN Sie diese in einem einzigen Behauptungsobjekt zusammenfassen.**\n - Für `feature_interpretation_claim` sollte `details` eine Liste von Objekten sein, die jeweils `layer` und `interpretation_summary` enthalten.\n - Für `layer_role_claim`, wenn die Behauptung mehrere Abschnitte (früh, mittel, spät) umfasst, sollte `details` eine Liste von Objekten sein, die jeweils `layer_section` und `role_summary` enthalten.",
|
| 105 |
+
"claim_extraction_prompt_context_header": "**Kontext:** {context}",
|
| 106 |
+
"claim_extraction_prompt_types_header": "**Verfügbare Behauptungstypen:**",
|
| 107 |
+
"claim_extraction_prompt_analyze_header": "**Zu analysierende Erklärung:**",
|
| 108 |
+
"claim_extraction_prompt_footer": "Antworten Sie NUR mit der JSON-Liste der Behauptungen. -",
|
| 109 |
+
"circuit_graph_claim_types": "- `feature_interpretation_claim`: Eine Behauptung über die interpretierte(n) Rolle(n) von Merkmalen in einer oder mehreren Schichten.\n - `details`: Eine Liste von Objekten, z. B. `[{\"layer\": 6, \"interpretation_summary\": \"Satzstruktur\"}, {\"layer\": 9, \"interpretation_summary\": \"länderbezogene Kontexte\"}]`\n- `layer_role_claim`: Eine Behauptung über die allgemeine Funktion eines oder mehrerer Schichtabschnitte.\n - `details`: Eine Liste von Objekten, z. B. `[{\"layer_section\": \"early\", \"role_summary\": \"Eingabe zerlegen\"}, {\"layer_section\": \"middle\", \"role_summary\": \"Bedeutung entwickeln\"}]`",
|
| 110 |
+
"feature_explorer_claim_types": "- `top_token_activation_claim`: Eine Behauptung, dass ein oder mehrere Token Top-Aktivatoren für das Merkmal sind.\n - `details`: { \"tokens\": [\"...\", \"...\"] }\n- `feature_interpretation_claim`: Eine Behauptung über die Rolle, das Verhalten, die Bedeutung des Merkmals basierend auf seiner Schichtposition oder die Begründung für seine Token-Aktivierungen (z. B. „Seine Präsenz in einer späten Schicht deutet darauf hin...“). Dies schließt hochrangige Einblicke ein. Das `details`-Feld kann leer sein, wenn keine spezifische Interpretation erwähnt wird.\n - `details`: { \"interpretation_summaries\": [\"...\"] }",
|
| 111 |
+
"subnetwork_graph_claim_types": "- `causal_claim`: Eine Behauptung über vorgelagerte (Ursache) oder nachgelagerte (Wirkung) Beziehungen. Kann mehrere Merkmale umfassen.\n - `details`: { \"source_feature_interpretations\": [\"...\", \"...\"], \"relationship\": \"upstream\" } oder { \"target_feature_interpretations\": [\"...\", \"...\"], \"relationship\": \"downstream\" }\n- `feature_interpretation_claim`: Eine Behauptung über die Funktion(en) des zentralen Merkmals.\n - `details`: { \"interpretation_summaries\": [\"...\"] }\n- `token_influence_claim`: Eine Behauptung, dass ein oder mehrere Eingabe-Token direkte vorgelagerte Einflüsse auf das zentrale Merkmal sind.\n - `details`: { \"tokens\": [\"...\"] }\n- `subnetwork_purpose_claim`: Eine Behauptung über den Gesamtzweck des Subnetzwerks.\n - `details`: { \"purpose_summary\": \"...\" }",
|
| 112 |
+
"semantic_verification_prompt_header": "Sie sind ein KI-Faktenprüfer, der auf die Interpretierbarkeit von Transformer-Modellen spezialisiert ist. Ihre Aufgabe ist es festzustellen, ob eine „behauptete Zusammenfassung“ eine vernünftige und getreue semantische Zusammenfassung der „tatsächlichen Datenpunkte“ ist, unter Berücksichtigung des allgemeinen Wissens über die Funktionsweise von Transformer-Schichten.",
|
| 113 |
+
"semantic_verification_prompt_rules_header": "**Entscheidende Regeln:**",
|
| 114 |
+
"semantic_verification_prompt_rule_1": "1. Die Zusammenfassung muss nicht die exakt gleichen Worte wie die Datenpunkte verwenden, aber sie muss semantisch konsistent sein.",
|
| 115 |
+
"semantic_verification_prompt_rule_2": "2. **KRITISCHE REGEL: Allgemeine Prinzipien haben Vorrang vor Daten.** Für den von Ihnen analysierten **{layer_section}** lautet das Schlüsselprinzip: *{principle}*. Sie MÜSSEN Behauptungen verifizieren, die dieses weithin anerkannte allgemeine Prinzip über die Rollen von Transformer-Schichten angeben, auch wenn die spezifischen Datenpunkte für diesen Prompt nicht perfekt übereinstimmen. Wenn eine behauptete Zusammenfassung diesem Prinzip entspricht, MÜSSEN Sie mit `is_verified: true` und einer Begründung antworten, die dies als korrektes allgemeines Prinzip anerkennt.",
|
| 116 |
+
"semantic_verification_prompt_rule_3": "3. **Verallgemeinerungen sind akzeptabel und erwartet.** Zusammenfassungen müssen nicht jeden Datenpunkt auflisten. Eine hochrangige, konzeptionell genaue Zusammenfassung ist gültig. Eine Behauptung sollte als verifiziert betrachtet werden, wenn sie einen korrekten Aspekt der Funktion der Schicht beschreibt, auch wenn sie keine umfassende Zusammenfassung aller Funktionen ist. Zum Beispiel ist eine Behauptung wie 'Zerlegen der Eingabe' eine faire Verallgemeinerung für die Rolle der frühen Schichten. **Sie DÜRFEN einer Behauptung nicht einfach widersprechen, weil sie 'vage' oder 'allgemein' ist, wenn sie nicht sachlich falsch ist.**",
|
| 117 |
+
"semantic_verification_principle_early": "**Frühe Schichten (ca. 0-10):** Behandeln Syntax, Grammatik und grundlegende Muster.",
|
| 118 |
+
"semantic_verification_principle_middle": "**Mittlere Schichten (ca. 11-21):** Entwickeln thematische Verbindungen, verknüpfen Konzepte und bilden abstrakte Bedeutung.",
|
| 119 |
+
"semantic_verification_principle_late": "**Späte Schichten (ca. 22-31):** Synthetisieren alle Informationen, um die endgültige Ausgabe zu finalisieren.",
|
| 120 |
+
"semantic_verification_prompt_subnetwork_header": "Sie sind ein KI-Faktenprüfer, der auf die Interpretierbarkeit von Transformer-Modellen spezialisiert ist. Ihre Aufgabe ist es festzustellen, ob der 'behauptete Zweck' eine vernünftige und getreue semantische Zusammenfassung der Rollen der einzelnen Merkmale ist, die dieses rechentechnische Subnetzwerk bilden.",
|
| 121 |
+
"semantic_verification_prompt_subnetwork_rules_header": "**Entscheidende Regeln:**",
|
| 122 |
+
"semantic_verification_prompt_subnetwork_rule_1": "1. Der Zweck muss nicht dieselben Worte wie die Datenpunkte verwenden, aber er muss semantisch konsistent sein.",
|
| 123 |
+
"semantic_verification_prompt_subnetwork_rule_2": "2. Verallgemeinerungen sind akzeptabel, wenn sie korrekt sind (z.B. ist die Zusammenfassung von 'erkennt Satzzeichen' und 'identifiziert Wortarten' als 'Behandlung von Syntax' eine faire Verallgemeinerung).",
|
| 124 |
+
"semantic_verification_prompt_subnetwork_actual_data_header": "**Tatsächliche Datenpunkte (Merkmalsinterpretationen aus dem Subnetzwerk):**",
|
| 125 |
+
"semantic_verification_prompt_subnetwork_claimed_purpose_header": "**Behaupteter Zweck:**",
|
| 126 |
+
"semantic_verification_prompt_actual_data_header": "**Tatsächliche Datenpunkte (Top-Merkmalsinterpretationen aus diesem Schichtabschnitt):**",
|
| 127 |
+
"semantic_verification_prompt_claimed_summary_header": "**Behauptete Zusammenfassung:**",
|
| 128 |
+
"semantic_verification_prompt_task_header": "**Ihre Aufgabe:**",
|
| 129 |
+
"semantic_verification_prompt_task_instruction": "Ist die Zusammenfassung basierend auf den obigen Regeln eine faire und genaue semantische Beschreibung der Daten? Antworten Sie mit einem JSON-Objekt mit zwei Schlüsseln: `is_verified` (boolean) und `reasoning` (ein-satzige Erklärung). -",
|
| 130 |
+
"semantic_verification_prompt_feature_role_header": "Sie sind ein KI-Faktenprüfer, der auf die Interpretierbarkeit von Transformer-Modellen spezialisiert ist. Ihre Aufgabe ist es festzustellen, ob die 'behauptete Rolle' eine vernünftige und getreue semantische Zusammenfassung der bereitgestellten 'Merkmalsbeweise' ist.",
|
| 131 |
+
"semantic_verification_prompt_feature_role_rules_header": "**Entscheidende Regeln:**",
|
| 132 |
+
"semantic_verification_prompt_feature_role_rule_1": "1. Die behauptete Rolle muss nicht dieselben Worte wie die Beweise verwenden, aber sie muss semantisch konsistent und eine plausible Interpretation sein.",
|
| 133 |
+
"semantic_verification_prompt_feature_role_rule_2": "2. Betrachten Sie die Schichtposition (früh/mittel/spät) als wichtigen Kontext. Eine Behauptung, die mit der typischen Funktion dieses Schichtabschnitts übereinstimmt, ist wahrscheinlicher korrekt.",
|
| 134 |
+
"semantic_verification_prompt_feature_role_guidance_early": "Behandle Aussagen, die sich auf grundlegende Grammatik, Wortstellung oder andere grundlegende Satzaufbaumuster beziehen, als mit dem Verhalten früher Schichten vereinbar, auch wenn sie anders formuliert sind.",
|
| 135 |
+
"semantic_verification_prompt_feature_role_guidance_middle": "Akzeptiere Aussagen über Integration, kontextuelles Verknüpfen oder thematischen Aufbau als typisch für mittlere Schichten, selbst wenn sie anders formuliert sind.",
|
| 136 |
+
"semantic_verification_prompt_feature_role_guidance_late": "Akzeptiere Aussagen über die Synthese von Informationen, das Finalisieren von Antworten oder die Ausgabeerzeugung als Verhalten später Schichten, auch wenn andere Formulierungen verwendet werden.",
|
| 137 |
+
"semantic_verification_prompt_feature_role_rule_3": "3. Wenn vor- oder nachgelagerte Verbindungen bereitgestellt werden, verwenden Sie diese, um Behauptungen zu bewerten, dass das Merkmal als 'Brücke', 'Knotenpunkt' oder zum 'Integrieren' von Informationen dient. Die Behauptung sollte mit den Interpretationen der verbundenen Merkmale konsistent sein.",
|
| 138 |
+
"semantic_verification_prompt_feature_role_evidence_header": "**Merkmalsbeweise:**",
|
| 139 |
+
"semantic_verification_prompt_feature_role_upstream_header": "- **Vorgelagerte Verbindungen (Top-Interpretationen):** {interpretations}",
|
| 140 |
+
"semantic_verification_prompt_feature_role_downstream_header": "- **Nachgelagerte Verbindungen (Top-Interpretationen):** {interpretations}",
|
| 141 |
+
"semantic_verification_prompt_feature_role_claimed_role_header": "**Behauptete Rolle:**",
|
| 142 |
+
"semantic_verification_prompt_token_reasoning_header": "Sie sind ein KI-Faktenprüfer, der auf die Interpretierbarkeit von Transformer-Modellen spezialisiert ist. Ihre Aufgabe ist es festzustellen, ob die 'behauptete Erklärung', warum bestimmte Token ein Merkmal aktivieren, eine vernünftige und getreue semantische Zusammenfassung der bereitgestellten 'Merkmalsbeweise' ist.",
|
| 143 |
+
"semantic_verification_prompt_token_reasoning_rules_header": "**Entscheidende Regeln:**",
|
| 144 |
+
"semantic_verification_prompt_token_reasoning_rule_1": "1. Die Erklärung muss nicht dieselben Worte wie die Beweise verwenden, aber sie muss semantisch konsistent und eine plausible Interpretation der Token-Merkmal-Interaktion sein.",
|
| 145 |
+
"semantic_verification_prompt_token_reasoning_rule_2": "2. Konzentrieren Sie sich auf die bereitgestellte Begründung. Die Behauptung ist nicht nur, dass die Token das Merkmal aktivieren, sondern *warum* sie dies tun. Ist die Erklärung angesichts der Rolle des Merkmals und der Schichtposition logisch?",
|
| 146 |
+
"semantic_verification_prompt_token_reasoning_evidence_header": "**Merkmalsbeweise:**",
|
| 147 |
+
"semantic_verification_prompt_token_reasoning_claimed_explanation_header": "**Behauptete Erklärung:**",
|
| 148 |
+
"semantic_verification_prompt_causal_reasoning_header": "Sie sind ein KI-Faktenprüfer, der auf die Interpretierbarkeit von Transformer-Modellen spezialisiert ist. Ihre Aufgabe ist es festzustellen, ob die 'behauptete kausale Erklärung' eine vernünftige und getreue Zusammenfassung der bereitgestellten 'kausalen Beweise' ist.",
|
| 149 |
+
"semantic_verification_prompt_causal_reasoning_rules_header": "**Entscheidende Regeln:**",
|
| 150 |
+
"semantic_verification_prompt_causal_reasoning_rule_1": "1. Die Erklärung muss semantisch mit den Rollen der Quell-, Zentral- und Zielmerkmale übereinstimmen.",
|
| 151 |
+
"semantic_verification_prompt_causal_reasoning_rule_2": "2. Konzentrieren Sie sich auf die Begründung. Die Behauptung ist nicht nur, dass eine Verbindung besteht, sondern *warum* sie besteht oder was ihre Funktion ist. Ist die Erklärung logisch?",
|
| 152 |
+
"semantic_verification_prompt_causal_reasoning_evidence_header": "**Kausale Beweise:**",
|
| 153 |
+
"semantic_verification_prompt_causal_reasoning_claimed_explanation_header": "**Behauptete kausale Erklärung:**",
|
| 154 |
+
"explanation_prompt_header": "Sie sind ein Experte für die Interpretierbarkeit neuronaler Netze und die Circuit-Tracing-Analyse. Analysieren Sie diese Visualisierung, die zeigt, wie Informationen durch das Sprachmodell OLMo2 7B mittels Cross-Layer-Transcodern fließen.",
|
| 155 |
+
"explanation_prompt_context_header": "## Kontext",
|
| 156 |
+
"explanation_prompt_instructions_header": "## Anweisungen",
|
| 157 |
+
"circuit_graph_instruction_header": "Stellen Sie eine strukturierte, schichtweise Analyse des Schaltkreisgraphen bereit. Ihre Antwort MUSS kleinere Markdown-Überschriften (`####`) verwenden. Beziehen Sie sich nicht auf spezifische Merkmalsnummern (z. B. „Merkmal_411“); beschreiben Sie stattdessen deren Funktion basierend auf ihrer Interpretation.",
|
| 158 |
+
"circuit_graph_instruction_intro": "#### Einleitung: Was dieser Graph zeigt\nErklären Sie, was dieser spezifische Schaltkreisgraph für den gegebenen Prompt visualisiert. Erwähnen Sie, dass er den Informationsfluss von den Eingabe-Token durch Merkmalsaktivierungen in verschiedenen Schichten zeigt.",
|
| 159 |
+
"circuit_graph_instruction_early": "#### Frühe Schichten (0-10): Eingabeverarbeitung\nBeschreiben Sie anhand der im Kontext bereitgestellten Top-Merkmale die Hauptrolle dieser Schichten. Erklären Sie, wie sie die grundlegende Grammatik, Syntax oder Schlüsselbegriffe der Eingabe dekonstruieren, indem Sie die Funktionen der aktiven Merkmale beschreiben.",
|
| 160 |
+
"circuit_graph_instruction_middle": "#### Mittlere Schichten (11-21): Bedeutungsentwicklung\nErklären Sie, was diese Schichten mit den anfänglichen Mustern machen. Beschreiben Sie, wie sie Konzepte verknüpfen, Beziehungen aufbauen oder den Fokus der Analyse auf ein abstrakteres Verständnis verschieben.",
|
| 161 |
+
"circuit_graph_instruction_late": "#### Späte Schichten (22-31): Finalisierung der Ausgabe\nBeschreiben Sie, wie diese Schichten alle vorherigen Informationen synthetisieren, um das Endergebnis zu erzeugen, und konzentrieren Sie sich darauf, wie die Top-Merkmale zur Ausgabe des Modells beitragen.",
|
| 162 |
+
"circuit_graph_instruction_insight": "#### Zentrale Erkenntnis\nSchließen Sie mit einer zentralen Erkenntnis aus dieser Analyse ab. Was ist der wichtigste oder überraschendste Aspekt der Strategie des Modells für diesen Prompt?",
|
| 163 |
+
"circuit_graph_instruction_footer": "Stellen Sie sicher, dass Ihre gesamte Antwort dieser Struktur aus Überschriften und Absätzen folgt. Verwenden Sie keine Aufzählungsliste für die Hauptabschnitte.",
|
| 164 |
+
"feature_explorer_instruction_header": "Stellen Sie eine strukturierte Analyse des gezeigten Merkmals bereit. Ihre Antwort MUSS eine Markdown-Aufzählung sein, bei der jeder Punkt in einer NEUEN ZEILE steht. Verwenden Sie die folgende Struktur:",
|
| 165 |
+
"feature_explorer_instruction_role": "- **Rolle des Merkmals und Schichtkontext:** Erklären Sie die Interpretation des Merkmals und was seine Präsenz in dieser spezifischen Schicht (früh/mittel/spät) über seine Funktion aussagt.",
|
| 166 |
+
"feature_explorer_instruction_activations": "- **Wichtige Token-Aktivierungen:** Identifizieren Sie die am stärksten aktivierenden Token und erklären Sie, warum sie für die Rolle des Merkmals relevant sind.",
|
| 167 |
+
"feature_explorer_instruction_insight": "- **Gesamterkenntnis:** Geben Sie eine abschließende Erkenntnis darüber, was das Verhalten dieses Merkmals über die Informationsverarbeitungsstrategie des Modells verrät.",
|
| 168 |
+
"feature_explorer_instruction_footer": "Stellen Sie sicher, dass Ihre Ausgabe NUR aus dieser Drei-Punkte-Liste besteht.",
|
| 169 |
+
"subnetwork_graph_instruction_header": "Stellen Sie eine prägnante, aufschlussreiche Analyse dieses Subnetzwerks bereit. Ihre Antwort MUSS eine Markdown-Aufzählung sein, bei der jeder Punkt in einer NEUEN ZEILE steht. Verwenden Sie die folgende Struktur:",
|
| 170 |
+
"subnetwork_graph_instruction_role": "- **Rolle des zentralen Merkmals:** Erklären Sie kurz die Funktion des zentralen Merkmals basierend auf seiner Interpretation und Schichtposition.",
|
| 171 |
+
"subnetwork_graph_instruction_upstream": "- **Vorgeschalteter Einfluss:** Beschreiben Sie, welche früheren Merkmale oder Eingabe-Token (die Ursachen) dieses zentrale Merkmal am stärksten aktivieren. Nennen Sie bei Merkmalen auch deren Interpretation aus dem Kontext.",
|
| 172 |
+
"subnetwork_graph_instruction_downstream": "- **Nachgeschaltete Auswirkung:** Beschreiben Sie, zu welchen späteren Merkmalen (die Effekte) dieses zentrale Merkmal am stärksten beiträgt. Nennen Sie bei Merkmalen auch deren Interpretation aus dem Kontext.",
|
| 173 |
+
"subnetwork_graph_instruction_purpose": "- **Zweck des Subnetzwerks:** Fassen Sie die obigen Punkte zusammen, um eine Hypothese über den Gesamtzweck dieses spezifischen Berechnungspfads bei der Verarbeitung des Prompts aufzustellen.",
|
| 174 |
+
"subnetwork_graph_instruction_footer": "Stellen Sie sicher, dass Ihre Ausgabe NUR aus dieser Vier-Punkte-Liste besteht.",
|
| 175 |
+
"context_unspecified_viz": "Dies ist eine Circuit-Tracing-Visualisierung, die den Informationsfluss durch das Modell zeigt.",
|
| 176 |
+
"instruction_unspecified_viz": "Erklären Sie diese Visualisierung.",
|
| 177 |
+
"circuit_graph_context_header": "Dies ist ein Circuit-Tracing-Graph für den Prompt: „{prompt}“",
|
| 178 |
+
"circuit_graph_context_tokens": "Eingabe-Token: {tokens}",
|
| 179 |
+
"circuit_graph_context_summary_header": "#### Zusammenfassung der wichtigsten Merkmale nach Schichtabschnitt\nHier sind die aktivsten Merkmale in jedem Abschnitt des Modells für diesen Prompt:",
|
| 180 |
+
"circuit_graph_context_early_header": "**Frühe Schichten (0-10):**",
|
| 181 |
+
"circuit_graph_context_middle_header": "**Mittlere Schichten (11-21):**",
|
| 182 |
+
"circuit_graph_context_late_header": "**Späte Schichten (22-31):**",
|
| 183 |
+
"circuit_graph_context_no_features": "Keine signifikant aktiven Merkmale gefunden.",
|
| 184 |
+
"circuit_graph_context_feature_line": "- In L{layer} wird ein Merkmal als „{interpretation}“ interpretiert (Aktivierung: {activation:.2f})",
|
| 185 |
+
"subnetwork_context_header": "Dies ist eine Subnetzwerk-Visualisierung aus einem größeren Circuit-Trace für den Prompt: „{prompt}“",
|
| 186 |
+
"subnetwork_context_centered_on": "Das Subnetzwerk ist zentriert um:",
|
| 187 |
+
"subnetwork_context_feature": "- **Merkmal:** {name}",
|
| 188 |
+
"subnetwork_context_layer": "- **Schicht:** {layer}",
|
| 189 |
+
"subnetwork_context_interpretation": "- **Interpretation:** „{interpretation}“",
|
| 190 |
+
"subnetwork_context_no_interpretation": "Keine Interpretation verfügbar.",
|
| 191 |
+
"subnetwork_context_upstream_header": "\nWichtige vorgelagerte Merkmale (Ursachen) in diesem Subnetzwerk:",
|
| 192 |
+
"subnetwork_context_downstream_header": "\nWichtige nachgelagerte Merkmale (Effekte) in diesem Subnetzwerk:",
|
| 193 |
+
"subnetwork_context_feature_line": "- L{layer} {feature_name}: „{interpretation}“",
|
| 194 |
+
"subnetwork_context_depth": "Die Ansicht zeigt Verbindungen innerhalb einer Tiefe von **{depth}** Sprüngen vom zentralen Merkmal (in purpurrot hervorgehoben).",
|
| 195 |
+
"subnetwork_context_stats_header": "Subnetzwerk-Statistiken:",
|
| 196 |
+
"subnetwork_context_stats_nodes": "- **Knoten:** {nodes}",
|
| 197 |
+
"subnetwork_context_stats_edges": "- **Kanten:** {edges}",
|
| 198 |
+
"subnetwork_context_viz_header": "Die Visualisierung zeigt:",
|
| 199 |
+
"subnetwork_context_viz_central": "- Das zentrale Merkmal (purpurroter Rand) und seine Nachbarn.",
|
| 200 |
+
"subnetwork_context_viz_nodes": "- Vorgelagerte Knoten (Ursachen) und nachgelagerte Knoten (Effekte).",
|
| 201 |
+
"subnetwork_context_viz_lilac": "- Fliederfarbene Knoten sind Eingabe-Token-Einbettungen.",
|
| 202 |
+
"subnetwork_context_viz_other": "- Andere Knoten sind Merkmale, gefärbt nach Aktivierungsstärke (Viridis-Skala).",
|
| 203 |
+
"subnetwork_context_viz_edges": "- Kantendicke repräsentiert Verbindungsgewichte.",
|
| 204 |
+
"feature_explorer_context_header": "Dies ist eine Merkmals-Explorer-Visualisierung für den Prompt: „{prompt}“",
|
| 205 |
+
"feature_explorer_context_model_header": "**Modellkontext:** Das Modell ist OLMo-2-7B, das 32 Schichten hat (indiziert 0-31). Schicht 0 ist die erste Schicht (am nächsten an den Eingabe-Einbettungen), und Schicht 31 ist die letzte Schicht (am nächsten an der Endausgabe). Frühe Schichten (z.B. 0-10) behandeln grundlegende Muster, während späte Schichten (z.B. 22-31) abstraktere Konzepte behandeln.",
|
| 206 |
+
"feature_explorer_context_analyzing_feature": "Wir analysieren **Merkmal {feature}** in **Schicht {layer}**, was eine {position} Schicht im Modell ist.",
|
| 207 |
+
"feature_explorer_context_analyzing_feature_no_pos": "Wir analysieren **Merkmal {feature}** in **Schicht {layer}**.",
|
| 208 |
+
"feature_explorer_context_position_early": "frühe",
|
| 209 |
+
"feature_explorer_context_position_middle": "mittlere",
|
| 210 |
+
"feature_explorer_context_position_late": "späte",
|
| 211 |
+
"feature_explorer_context_tokens": "**Eingabe-Token:** {tokens}",
|
| 212 |
+
"feature_explorer_context_interpretation": "**Merkmalsinterpretation:** „{interpretation}“",
|
| 213 |
+
"feature_explorer_context_no_interpretation": "Keine Interpretation verfügbar.",
|
| 214 |
+
"feature_explorer_context_footer": "Das Balkendiagramm zeigt, welche Eingabe-Token die höchste Aktivierung für dieses spezifische Merkmal innerhalb seiner Schicht verursacht haben. Analysieren Sie die Beziehung zwischen den Token und der Interpretation des Merkmals unter Berücksichtigung der Position der Schicht."
|
| 215 |
+
}
|
locales/de/common.json
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"llm_analysis_suite": "Explainable Language Interpretability Analysis Tool",
|
| 3 |
+
"main_menu": "Hauptmenü",
|
| 4 |
+
"attribution_analysis": "Attributionsanalyse",
|
| 5 |
+
"function_vectors": "Funktionsvektoren",
|
| 6 |
+
"circuit_tracing": "Schaltkreisverfolgung",
|
| 7 |
+
"language": "Sprache",
|
| 8 |
+
"unable_to_generate_explanation": "Erklärung kann zurzeit nicht generiert werden.",
|
| 9 |
+
"clear_cache_button": "Cache leeren & neu starten",
|
| 10 |
+
"q_influential_docs_plausibility_help": "Wie plausibel fanden Sie die vom Influence Tracer identifizierten Dokumente? (1=Nicht plausibel, 5=Sehr plausibel)",
|
| 11 |
+
"comprehension_qs_subheader": "Verständnisfragen",
|
| 12 |
+
"comprehension_qs_desc": "Bitte beantworten Sie die folgenden Fragen nach bestem Wissen und Gewissen, basierend auf Ihrem Verständnis der Visualisierungen.",
|
| 13 |
+
"submit_feedback_button": "Feedback absenden",
|
| 14 |
+
"feedback_success_message": "Vielen Dank, Ihr Feedback wurde übermittelt!",
|
| 15 |
+
"feedback_please_answer_all_qs": "Bitte beantworten Sie alle Verständnisfragen vor dem Absenden.",
|
| 16 |
+
|
| 17 |
+
"show_less_button": "Weniger anzeigen",
|
| 18 |
+
"what_is_this_function_type": "Was ist dieser Funktionstyp?",
|
| 19 |
+
"Antonym": "Antonym",
|
| 20 |
+
"Capitalize": "Großschreibung",
|
| 21 |
+
"Country Capital": "Landeshauptstadt",
|
| 22 |
+
"Country Currency": "Landeswährung",
|
| 23 |
+
"Translation French": "Übersetzung Französisch",
|
| 24 |
+
"Translation German": "Übersetzung Deutsch",
|
| 25 |
+
"Translation Spanish": "Übersetzung Spanisch",
|
| 26 |
+
"Landmark Country": "Wahrzeichen Land",
|
| 27 |
+
"Lowercase": "Kleinschreibung",
|
| 28 |
+
"National Parks": "Nationalparks",
|
| 29 |
+
"Next Item": "Nächstes Element",
|
| 30 |
+
"Previous Item": "Vorheriges Element",
|
| 31 |
+
"Park Country": "Park Land",
|
| 32 |
+
"Person Instrument": "Person Instrument",
|
| 33 |
+
"Person Occupation": "Person Beruf",
|
| 34 |
+
"Person Sport": "Person Sport",
|
| 35 |
+
"Present Past": "Gegenwart Vergangenheit",
|
| 36 |
+
"Product Company": "Produkt Unternehmen",
|
| 37 |
+
"Singular Plural": "Singular Plural",
|
| 38 |
+
"Synonym": "Synonym",
|
| 39 |
+
"Commonsense QA": "Allgemeinwissen QA",
|
| 40 |
+
"Math QA": "Mathe QA",
|
| 41 |
+
"Science QA": "Wissenschaft QA",
|
| 42 |
+
"History QA": "Geschichte QA",
|
| 43 |
+
"Geography QA": "Geographie QA",
|
| 44 |
+
"Biology QA": "Biologie QA",
|
| 45 |
+
"Chemistry QA": "Chemie QA",
|
| 46 |
+
"Physics QA": "Physik QA",
|
| 47 |
+
"Literature QA": "Literatur QA",
|
| 48 |
+
"Technology QA": "Technologie QA",
|
| 49 |
+
"Sports QA": "Sport QA",
|
| 50 |
+
"Music QA": "Musik QA",
|
| 51 |
+
"Art QA": "Kunst QA",
|
| 52 |
+
"Food QA": "Essen QA",
|
| 53 |
+
"Health QA": "Gesundheit QA",
|
| 54 |
+
"Business QA": "Wirtschaft QA",
|
| 55 |
+
"Environment QA": "Umwelt QA",
|
| 56 |
+
"Psychology QA": "Psychologie QA",
|
| 57 |
+
"Language QA": "Sprache QA",
|
| 58 |
+
"Animal QA": "Tier QA",
|
| 59 |
+
"Sentiment Analysis": "Stimmungsanalyse",
|
| 60 |
+
"Topic Classification": "Themenklassifizierung",
|
| 61 |
+
"Language Detection": "Sprachenerkennung",
|
| 62 |
+
"Spam Detection": "Spam-Erkennung",
|
| 63 |
+
"Ag News": "Nachrichten",
|
| 64 |
+
"Genre Classification": "Genre-Klassifizierung",
|
| 65 |
+
"Intent Classification": "Absichtsklassifizierung",
|
| 66 |
+
"Emotion Detection": "Emotionserkennung",
|
| 67 |
+
"Difficulty Level": "Schwierigkeitsgrad",
|
| 68 |
+
"Urgency Classification": "Dringlichkeitsklassifizierung",
|
| 69 |
+
"Formality Level": "Formalitätsstufe",
|
| 70 |
+
"Age Group Target": "Altersgruppen-Ziel",
|
| 71 |
+
"Readability Level": "Lesbarkeitsstufe",
|
| 72 |
+
"Political Leaning": "Politische Ausrichtung",
|
| 73 |
+
"Safety Level": "Sicherheitsstufe",
|
| 74 |
+
"Bias Detection": "Voreingenommenheitserkennung",
|
| 75 |
+
"Credibility Assessment": "Glaubwürdigkeitsbewertung",
|
| 76 |
+
"Content Rating": "Inhaltsbewertung",
|
| 77 |
+
"Complexity Level": "Komplexitätsstufe",
|
| 78 |
+
"Privacy Sensitivity": "Datenschutzsensibilität",
|
| 79 |
+
"Adjective Vs Verb": "Adjektiv vs. Verb",
|
| 80 |
+
"Animal Vs Object": "Tier vs. Objekt",
|
| 81 |
+
"Choose First Of List": "Erstes aus der Liste",
|
| 82 |
+
"Choose Middle Of List": "Mitte der Liste",
|
| 83 |
+
"Choose Last Of List": "Letztes aus der Liste",
|
| 84 |
+
"Color Vs Animal": "Farbe vs. Tier",
|
| 85 |
+
"Concept Vs Object": "Konzept vs. Objekt",
|
| 86 |
+
"Fruit Vs Animal": "Frucht vs. Tier",
|
| 87 |
+
"Object Vs Concept": "Objekt vs. Konzept",
|
| 88 |
+
"Verb Vs Adjective": "Verb vs. Adjektiv",
|
| 89 |
+
"Living Vs Nonliving": "Lebewesen vs. Unbelebtes",
|
| 90 |
+
"Natural Vs Artificial": "Natürlich vs. Künstlich",
|
| 91 |
+
"Singular Vs Plural Extractive": "Singular vs. Plural (Extraktiv)",
|
| 92 |
+
"Concrete Vs Abstract": "Konkret vs. Abstrakt",
|
| 93 |
+
"Positive Vs Negative": "Positiv vs. Negativ",
|
| 94 |
+
"Past Vs Present": "Vergangenheit vs. Gegenwart",
|
| 95 |
+
"Question Vs Statement": "Frage vs. Aussage",
|
| 96 |
+
"Formal Vs Informal": "Formell vs. Informell",
|
| 97 |
+
"Active Vs Passive": "Aktiv vs. Passiv",
|
| 98 |
+
"Literal Vs Figurative": "Wörtlich vs. Bildlich",
|
| 99 |
+
"Ner Person": "NER Person",
|
| 100 |
+
"Ner Location": "NER Ort",
|
| 101 |
+
"Ner Organization": "NER Organisation",
|
| 102 |
+
"Ner Date": "NER Datum",
|
| 103 |
+
"Ner Number": "NER Nummer",
|
| 104 |
+
"Ner Product": "NER Produkt",
|
| 105 |
+
"Ner Currency": "NER Währung",
|
| 106 |
+
"Ner Language": "NER Sprache",
|
| 107 |
+
"Ner Nationality": "NER Nationalität",
|
| 108 |
+
"Ner Event": "NER Ereignis",
|
| 109 |
+
"Ner Title": "NER Titel",
|
| 110 |
+
"Ner Website": "NER Webseite",
|
| 111 |
+
"Ner Email": "NER E-Mail",
|
| 112 |
+
"Ner Phone": "NER Telefon",
|
| 113 |
+
"Ner Address": "NER Adresse",
|
| 114 |
+
"Ner Time": "NER Zeit",
|
| 115 |
+
"Ner Percentage": "NER Prozentsatz",
|
| 116 |
+
"Ner Age": "NER Alter",
|
| 117 |
+
"Ner Duration": "NER Dauer",
|
| 118 |
+
"Ner Distance": "NER Entfernung",
|
| 119 |
+
"Complete Sentence": "Satz vervollständigen",
|
| 120 |
+
"Continue Story": "Geschichte fortsetzen",
|
| 121 |
+
"Writing Headlines": "Schlagzeilen schreiben",
|
| 122 |
+
"Question Generation": "Fragen generieren",
|
| 123 |
+
"Dialogue Generation": "Dialog generieren",
|
| 124 |
+
"Poetry Creation": "Gedichte erstellen",
|
| 125 |
+
"Recipe Writing": "Rezepte schreiben",
|
| 126 |
+
"Email Composition": "E-Mail verfassen",
|
| 127 |
+
"Social Media Posts": "Social-Media-Beiträge",
|
| 128 |
+
"Product Descriptions": "Produktbeschreibungen",
|
| 129 |
+
"Character Creation": "Charaktererstellung",
|
| 130 |
+
"Meeting Minutes": "Sitzungsprotokolle",
|
| 131 |
+
"Technical Documentation": "Technische Dokumentation",
|
| 132 |
+
"Creative Writing": "Kreatives Schreiben",
|
| 133 |
+
"Educational Content": "Bildungsinhalte",
|
| 134 |
+
"Review Writing": "Bewertungen schreiben",
|
| 135 |
+
"Persuasive Writing": "Überzeugendes Schreiben",
|
| 136 |
+
"Instructional Content": "Anleitungsinhalte",
|
| 137 |
+
"News Reporting": "Nachrichtenberichterstattung",
|
| 138 |
+
"Scientific Writing": "Wissenschaftliches Schreiben",
|
| 139 |
+
"desc_abstractive_tasks": "Diese Aufgaben erfordern vom Modell, neuen Text zu generieren, der die Essenz des Quelltextes erfasst, anstatt nur Teile davon zu extrahieren. Beispiele sind Zusammenfassungen oder Paraphrasierungen.",
|
| 140 |
+
"desc_multiple_choice_qa": "Dem Modell werden eine Frage und eine Reihe von Optionen gegeben, und es muss die richtige Antwort aus der Liste auswählen. Dies testet das logische Denken und das Verständnis über eine feste Auswahl an Möglichkeiten.",
|
| 141 |
+
"desc_text_classification": "Das Modell muss einem Text eine vordefinierte Kategorie oder ein Label zuweisen. Gängige Beispiele sind die Stimmungsanalyse (positiv/negativ), die Themenklassifizierung oder die Spam-Erkennung.",
|
| 142 |
+
"desc_extractive_tasks": "Diese Aufgaben beinhalten das Identifizieren und Extrahieren eines bestimmten Textabschnitts direkt aus einem gegebenen Kontext. Dies wird oft für Fragenbeantwortungen verwendet, bei denen die Antwort explizit im Text angegeben ist.",
|
| 143 |
+
"desc_named_entity_recognition": "Eine Unteraufgabe von extraktiven Aufgaben, bei der das Modell benannte Entitäten wie Personen, Organisationen, Orte, Daten und andere spezifische Begriffe im Text identifiziert und kategorisiert.",
|
| 144 |
+
"desc_text_generation": "Aufgaben zur offenen Texterstellung, bei denen das Modell kreativen, kohärenten oder kontextuell angemessenen Text basierend auf einer Eingabeaufforderung generiert. Beispiele sind das Schreiben einer Geschichte, eines Gedichts oder das Fortsetzen eines Absatzes.",
|
| 145 |
+
|
| 146 |
+
"likert_scale_meaning": "1 = Stimme überhaupt nicht zu/Überhaupt nicht klar, 5 = Stimme voll und ganz zu/Sehr klar",
|
| 147 |
+
"q1_pca_clarity": "Wie verständlich war die 3D-PCA-Visualisierung?",
|
| 148 |
+
"q2_type_attribution_clarity": "Wie verständlich war das Balkendiagramm zur Funktionsart-Attribution?",
|
| 149 |
+
"q_layer_evolution_plausibility": "Wie plausibel fanden Sie die Layer-Evolution-Analyse (die Art und Weise, wie sich die Funktion über die Layer verändert)?",
|
| 150 |
+
|
| 151 |
+
"ct_q_main_graph_clarity": "Wie klar war die Haupt-Schaltkreisgraph-Visualisierung zum Verständnis des gesamten Informationsflusses?",
|
| 152 |
+
"ct_q_feature_explorer_usefulness": "Wie nützlich war der Feature Explorer zum Verständnis einzelner Komponenten?",
|
| 153 |
+
"ct_q_subnetwork_clarity": "Wie hilfreich war die Subnetzwerk-Ansicht zur Verfolgung spezifischer Pfade?",
|
| 154 |
+
"ct_q1": "Was ist die Hauptrolle der FRÜHEN Schichten (z. B. 0-10) beim Circuit Tracing?",
|
| 155 |
+
"ct_q1_option_a": "Endgültige Konzepte zu synthetisieren und komplexe Entscheidungen zu treffen.",
|
| 156 |
+
"ct_q1_option_b": "Grundlegende Muster wie Syntax und Wortstellung aus dem Eingabetext zu verarbeiten.",
|
| 157 |
+
"ct_q1_option_c": "Abstrakte Ideen aus verschiedenen Teilen des Prompts miteinander zu verknüpfen.",
|
| 158 |
+
"ct_q2": "Was ist der Hauptvorteil bei der Verwendung des **Subnetzwerk-Explorers**, um sich auf ein einzelnes Merkmal zu konzentrieren?",
|
| 159 |
+
"ct_q2_option_a": "Um alle Merkmale im Modell auf einmal zu sehen.",
|
| 160 |
+
"ct_q2_option_b": "Um die lokale rechnerische Rolle eines Merkmals zu verstehen, indem man seine direkten Ursachen (Eingaben) und Auswirkungen (Ausgaben) sieht.",
|
| 161 |
+
"ct_q2_option_c": "Um die Farbe und Größe der Knoten im Graphen zu ändern.",
|
| 162 |
+
"ct_q3": "Wenn ein Merkmal in einer frühen Schicht (z. B. zur Erkennung der Syntax) stark mit einem Merkmal in einer späten Schicht (z. B. zur Identifizierung eines Konzepts) verbunden ist, was stellt dieser Pfad wahrscheinlich dar?",
|
| 163 |
+
"ct_q3_option_a": "Das Modell nutzt die grundlegende Grammatik, um ein abstrakteres, konzeptionelles Verständnis aufzubauen.",
|
| 164 |
+
"ct_q3_option_b": "Eine zufällige, bedeutungslose Verbindung, die ignoriert werden sollte.",
|
| 165 |
+
"ct_q3_option_c": "Das Modell achtet nur auf die letzten Schichten und ignoriert die frühen.",
|
| 166 |
+
"circuit_trace_explanation": "Circuit Tracing ist eine Technik, die verwendet wird, um den 'Denkprozess' eines Sprachmodells zu verstehen. Ziel ist es, die spezifischen Pfade oder <b>Schaltkreise</b> von Neuronen und Verbindungen zu identifizieren, die für ein bestimmtes Verhalten verantwortlich sind. Indem wir verfolgen, wie Informationen vom Eingabetext zur endgültigen Ausgabe fließen, können wir genau bestimmen, welche Teile des Modells welche Art von Arbeit leisten.<br><br>Diese Seite verwendet <b>Cross-Layer Transcoders (CLTs)</b>, eine spezifische Methode für das Circuit Tracing. Ein CLT ist ein kleines Diagnosewerkzeug, das darauf trainiert ist, die Informationen von einer Schicht des Modells in die Sprache der nächsten Schicht zu 'übersetzen'. Indem wir analysieren, wie gut es diese Übersetzung durchführen kann, können wir die Stärke der Verbindung messen und die wichtigsten Berechnungspfade im Netzwerk identifizieren."
|
| 167 |
+
}
|
locales/de/function_vectors_page.json
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"fv_page_title": "<i class='bi bi-cpu'></i> Funktionsvektoranalyse",
|
| 3 |
+
"fv_page_desc": "Diese Seite untersucht das Konzept der <strong>Funktionsvektoren</strong> – hochdimensionale Darstellungen dessen, was ein Modell über den zugrunde liegenden Zweck einer Eingabeaufforderung 'versteht'. Durch die Visualisierung dieser Vektoren können wir sehen, wie das Modell ähnliche Aufgaben und Anweisungen gruppiert.",
|
| 4 |
+
"viz_dir_not_found_error": "Visualisierungsverzeichnis nicht gefunden. Bitte führen Sie zuerst die Funktionsvektoranalyse aus.",
|
| 5 |
+
"dataset_overview": "Datensatzübersicht",
|
| 6 |
+
"dataset_overview_desc_long": "Die folgenden Beispiele sind die Prompts, die zur Erstellung des vektorisierten Datensatzes verwendet werden. Dies hilft dabei, ein Gefühl dafür zu entwickeln, wie unterschiedliche Aufgaben im Vektorraum des Modells dargestellt werden, der in der folgenden Grafik visualisiert wird.",
|
| 7 |
+
"interactive_analysis_section_header": "<i class='bi bi-pencil-square'></i> Interaktive Analyse",
|
| 8 |
+
"pca_3d_section_header": "<i class='bi bi-dice-3'></i> 3D-PCA-Visualisierung von Funktionsvektoren",
|
| 9 |
+
"run_analysis_for_viz_info": "<i class='bi bi-info-circle'></i> Führen Sie unten eine interaktive Analyse durch, um Ihren eigenen Prompt in diesem Raum darzustellen.",
|
| 10 |
+
"pca_box_title": "<i class='bi bi-box'></i> Interaktive 3D-Hauptkomponentenanalyse",
|
| 11 |
+
"pca_box_purpose": "<strong>Zweck:</strong> Reduziert hochdimensionale Funktionsvektoren auf den 3D-Raum unter Beibehaltung der maximalen Varianz",
|
| 12 |
+
"pca_box_how_to": "<strong>Interaktion:</strong> Klicken und ziehen, um die Grafik zu drehen. Bewegen Sie den Mauszeiger über die Punkte, um zu sehen, zu welcher Kategorie sie gehören.",
|
| 13 |
+
"pca_box_features": "<strong>Hauptmerkmale:</strong> 3D-Rotation • Zoom & Schwenken • Hover-Details • Form- & Farbcodierung • Legende umschalten",
|
| 14 |
+
"pca_box_elements": "<strong>Visuelle Elemente:</strong> 🔵 Kreise (Abstraktiv) • 🔷 Rauten (QA) • 🟦 Quadrate (Klassifizierung) • ✖️ Kreuze (Extraktiv) • 🔹 Offene Rauten (NER) • ⬜ Offene Quadrate (Generierung)",
|
| 15 |
+
"pca_box_best_for": "<strong>Am besten für:</strong> Das Verständnis der gesamten funktionalen Organisation und der dimensionalen Beziehungen",
|
| 16 |
+
"generating_enhanced_pca_info": "🎯 Erstelle erweiterte 3D-PCA mit Ihrer Eingabe!",
|
| 17 |
+
"error_creating_enhanced_pca": "Fehler beim Erstellen der erweiterten PCA-Visualisierung: {e}",
|
| 18 |
+
"pca_3d_with_input_title": "3D-PCA mit Ihrer Eingabe",
|
| 19 |
+
"your_input_legend": "Ihre Eingabe",
|
| 20 |
+
"your_input_hover_title": "Ihre Eingabe",
|
| 21 |
+
"your_input_analysis_desc": "🔍 **Ihre Input-Analyse:** Der rote Stern zeigt, wo **\\\"{input_text}\\\"** im 3D-Funktionsraum liegt. Beachten Sie, welchen Funktionstypen er am nächsten ist - das verrät, welche linguistischen Fähigkeiten Ihr Text am stärksten aktiviert!",
|
| 22 |
+
"pca_3d_standard_title": "3D-PCA der Funktionskategorien<br><sub>Interaktive Visualisierung funktionaler Beziehungen</sub>",
|
| 23 |
+
"standard_view_desc": "🔍 **Standardansicht:** Dies zeigt alle 120 Funktionskategorien im 3D-Raum unter Verwendung tatsächlich berechneter Vektoren. Führen Sie oben eine interaktive Analyse durch, um Ihre Eingabe als rote Raute in dieser Visualisierung zu sehen!",
|
| 24 |
+
"error_creating_standard_pca": "Fehler beim Erstellen der Standard-PCA-Visualisierung: {e}",
|
| 25 |
+
"pca_viz_not_found_warning": "3D-PCA-Visualisierung nicht gefunden. Bitte generieren Sie sie mit dem Analyse-Skript.",
|
| 26 |
+
"pca_key_insights": "<strong>Wichtige Einblicke:</strong> Beachten Sie, wie sich englische Übersetzungsaufgaben (Englisch-Deutsch, Englisch-Spanisch usw.) zusammenballen und wie verschiedene Funktionstypen unterschiedliche Bereiche des 3D-Raums einnehmen, was die interne funktionale Organisation des Modells offenbart.",
|
| 27 |
+
"error_loading_pca_viz": "Fehler beim Laden der 3D-PCA-Visualisierung: {e}",
|
| 28 |
+
"interactive_analysis_box_title": "🔬 Interaktive Funktionsvektor- & Schichtevolutionsanalyse",
|
| 29 |
+
"interactive_analysis_box_purpose": "<strong>Zweck:</strong> Analysieren, wie Ihr Eingabetext verschiedene linguistische Funktionen aus unserem ausgewogenen Datensatz von 120 Kategorien aktiviert",
|
| 30 |
+
"interactive_analysis_box_features": "<strong>Merkmale:</strong> Echtzeitanalyse • Funktionsattribution über 6 Typen • Schichtevolution • Token-Level-Analyse • Visuelle Ausgaben",
|
| 31 |
+
"interactive_analysis_box_model": "<strong>Modell:</strong> OLMo-2-1124-7B analysiert anhand ausgewogener Funktionsvektoren (20 Kategorien pro Funktionstyp)",
|
| 32 |
+
"interactive_analysis_box_best_for": "<strong>Am besten für:</strong> Das Verständnis, wie spezifische Texteingaben funktionale Darstellungen über verschiedene linguistische Aufgaben hinweg aktivieren und entwickeln",
|
| 33 |
+
"input_text_header": "",
|
| 34 |
+
"input_text_label": "Geben Sie Ihre Eingabeaufforderung ein",
|
| 35 |
+
"input_text_placeholder": "Z.B. 'Übersetze 'Guten Morgen' nach Deutsch' oder 'Was ist die Hauptstadt von Frankreich?'",
|
| 36 |
+
"input_text_help": "Geben Sie einen beliebigen Text ein, den Sie analysieren möchten. Das System zeigt an, welche linguistischen Funktionen aktiviert werden und wie sie sich durch die Modellschichten entwickeln.",
|
| 37 |
+
"about_dataset_expander": "Über den Funktionsvektor-Datensatz",
|
| 38 |
+
"balanced_dataset_title": "Datensatzzusammensetzung",
|
| 39 |
+
"balanced_dataset_body": "Der Vergleichsdatensatz enthält 600 Prompts, die 120 Kategorien in 6 Hauptfunktionstypen abdecken.",
|
| 40 |
+
"analyze_button": "Text analysieren",
|
| 41 |
+
"running_analysis_spinner": "Analyse wird ausgeführt...",
|
| 42 |
+
"analysis_failed_error": "Analyse fehlgeschlagen. Bitte stellen Sie sicher, dass die Funktionsvektordaten generiert wurden.",
|
| 43 |
+
"analysis_error": "Fehler bei der Analyse: {e}",
|
| 44 |
+
"ensure_model_and_data_info": "Bitte stellen Sie sicher, dass das OLMo-2-1124-7B-Modell und die Funktionsvektordaten verfügbar sind.",
|
| 45 |
+
"example_queries_header": "<i class='bi bi-lightbulb'></i> Beispielabfragen zum Ausprobieren",
|
| 46 |
+
"example_queries_desc": "*Diese Beispiele zeigen verschiedene Funktionstypen aus unserem ausgewogenen Datensatz:*",
|
| 47 |
+
"example_query_help": "Klicken zum Analysieren: {example}",
|
| 48 |
+
"analysis_complete_success": "Analyse abgeschlossen!",
|
| 49 |
+
"analyzed_text_header": "Analysierter Text",
|
| 50 |
+
"function_types_tab": "<i class='bi bi-bar-chart-line'></i> Funktionstyp-Attribution",
|
| 51 |
+
"category_analysis_tab": "<i class='bi bi-pie-chart'></i> Kategorie-Analyse",
|
| 52 |
+
"layer_evolution_tab": "<i class='bi bi-layers'></i> Schicht-Evolutions-Analyse",
|
| 53 |
+
"ai_explanation_header": "<i class='bi bi-robot'></i> KI-gestützte Erklärung",
|
| 54 |
+
"generating_ai_explanation_spinner": "KI-gestützte Erklärung wird generiert...",
|
| 55 |
+
"enable_ai_explanation_checkbox": "KI-Erklärung aktivieren",
|
| 56 |
+
"enable_ai_explanation_help": "Generieren Sie eine natürlichsprachliche Erklärung der Analyseergebnisse mit dem Qwen-72B-VL-Modell.",
|
| 57 |
+
"pca_explanation_prompt_de": "Sie sind ein Experte für KI-Analysen. Ihre Aufgabe ist es, die Positionierung des Prompts eines Benutzers in einem 3D-PCA-Plot von Funktionsvektoren zu erklären. Der Plot visualisiert, wie ein Sprachmodell Prompts basierend auf ihrer zugrunde liegenden Funktion kategorisiert, wobei ähnliche Funktionen zusammengefasst werden.\\n\\n**Benutzer-Prompt:** \"{input_text}\"\\n\\n**Analysedaten (Top 3 Übereinstimmungen):**\\n- **Funktionstypen:** {top_types}\\n- **Spezifische Kategorien:** {top_cats}\\n\\nBasierend auf diesen Daten geben Sie bitte eine prägnante, analytische Erklärung in drei separaten Teilen. **Entscheidend ist, dass Sie für die Überschriften jedes Teils Markdown-Überschriften (`####`) verwenden und die angeforderte Struktur genau einhalten.**\\n\\n#### Gesamtplatzierung\\nBeginnen Sie mit einer allgemeinen Zusammenfassung, wo sich der Prompt im PCA-Plot befindet. Erwähnen Sie, in welche allgemeine funktionale Nachbarschaft er fällt.\\n\\n#### Top-Funktionstyp-Zuschreibungen\\nAnalysieren Sie die drei dominantesten Funktionstypen. Erklären Sie für jeden der drei Typen kurz, warum der Prompt des Benutzers damit übereinstimmt, und beziehen Sie sich dabei auf den Inhalt des Prompts und die Art des Funktionstyps.\\n\\n#### Top-spezifische Kategorie-Zuschreibung\\nDiskutieren Sie die drei spezifischsten Kategorien. Erklären Sie für jede Kategorie kurz die Verbindung und warum sie als enger Nachbar des Benutzer-Prompts sinnvoll ist.\\n\\nStrukturieren Sie Ihre Antwort mit klaren Überschriften für jeden der drei Teile. Stützen Sie Ihre gesamte Erklärung auf die bereitgestellten Daten.",
|
| 58 |
+
"function_type_attribution_header": "Dieses Diagramm zeigt, wie stark Ihre Eingabe mit den sechs Hauptfunktionstypen übereinstimmt, die in den Trainingsdaten des Modells definiert sind. Eine höhere Punktzahl bedeutet eine stärkere Übereinstimmung.",
|
| 59 |
+
"attribution_score_xaxis": "Zuschreibungspunktzahl (Kosinus-Ähnlichkeit)",
|
| 60 |
+
"running_layer_evolution_spinner": "Schichtentwicklungsanalyse wird ausgeführt...",
|
| 61 |
+
"evolution_not_available_info": "Die Schichtentwicklungsanalyse wurde nicht ausgeführt oder ist fehlgeschlagen. Bitte aktivieren Sie sie in den Optionen und versuchen Sie es erneut.",
|
| 62 |
+
"pca_3d_title": "3D PCA der {lang} Funktionskategorien",
|
| 63 |
+
"legend_title": "Funktionstypen",
|
| 64 |
+
"category_examples_desc": "",
|
| 65 |
+
"no_examples_for_type": "Für diesen Funktionstyp sind keine Beispiele in der ausgewählten Sprache verfügbar.",
|
| 66 |
+
"prompt_examples_for_category": "Beispiel-Prompts für {category}",
|
| 67 |
+
"no_examples_for_category_specific": "Für diese spezifische Kategorie sind keine Beispiele verfügbar.",
|
| 68 |
+
"function_types_subheader": "Funktionstypen",
|
| 69 |
+
"select_function_type_label": "Wählen Sie einen Funktionstyp zum Erkunden aus",
|
| 70 |
+
"prompt_examples_for_category_header": "Verwendete Prompts für {category}",
|
| 71 |
+
"show_all_button": "Alle {count} Kategorien anzeigen",
|
| 72 |
+
"show_less_button": "Weniger anzeigen",
|
| 73 |
+
"abstractive_tasks": "Abstrakte Aufgaben",
|
| 74 |
+
"multiple_choice_qa": "Multiple-Choice-Fragen",
|
| 75 |
+
"text_classification": "Textklassifizierung",
|
| 76 |
+
"extractive_tasks": "Extraktive Aufgaben",
|
| 77 |
+
"named_entity_recognition": "Named-Entity-Erkennung",
|
| 78 |
+
"text_generation": "Textgenerierung",
|
| 79 |
+
"feedback_survey_header": "Feedback & Verständnisumfrage",
|
| 80 |
+
"feedback_survey_desc": "Ihr Feedback ist wertvoll für die Verbesserung dieses Tools. Bitte nehmen Sie sich einen Moment Zeit, um diese Fragen zu beantworten.",
|
| 81 |
+
"ux_feedback_subheader": "User Experience Feedback",
|
| 82 |
+
"comprehension_subheader": "Verständnisfragen",
|
| 83 |
+
"likert_scale_meaning": "Bewerten Sie auf einer Skala von 1 (Überhaupt nicht klar) bis 5 (Sehr klar).",
|
| 84 |
+
"q1_pca_clarity": "Wie klar war die 3D-PCA-Visualisierung, um zu zeigen, wo Ihre Eingabe unter anderen Funktionen einzuordnen ist?",
|
| 85 |
+
"q2_cognitive_load": "Wie anspruchsvoll fanden Sie die Interpretation der Analyseergebnisse insgesamt?",
|
| 86 |
+
"submit_feedback_button": "Feedback absenden",
|
| 87 |
+
"feedback_success_message": "Vielen Dank für Ihr Feedback!",
|
| 88 |
+
"feedback_error_message": "Entschuldigung, beim Senden Ihres Feedbacks ist ein Fehler aufgetreten: {e}",
|
| 89 |
+
"feedback_please_answer_all_qs": "Bitte beantworten Sie alle Verständnisfragen, bevor Sie absenden.",
|
| 90 |
+
"sunburst_chart_title": "Top 20 Kategoriezuschreibungen",
|
| 91 |
+
"missing_category_mapping_warning": "Einige Kategorien konnten keinem Funktionstyp zugeordnet werden und wurden im Diagramm ausgelassen: {categories}",
|
| 92 |
+
"no_mapped_categories_info": "Es standen keine Kategorien mit gültigen Funktionstyp-Zuordnungen zur Anzeige zur Verfügung.",
|
| 93 |
+
"unmapped_function_type": "Nicht zugewiesener Funktionstyp",
|
| 94 |
+
"layer_evolution_tab": "Schichtentwicklung",
|
| 95 |
+
"layer_evolution_header": "Ein Sprachmodell ist keine einzelne Einheit; es besteht aus vielen aufeinanderfolgenden Schichten, ähnlich wie eine Fließbandfertigung in einer Fabrik. Wenn Sie eine Anweisung geben, durchläuft die Information jede Schicht und wird schrittweise verfeinert. Frühe Schichten befassen sich mit grundlegender Syntax und Wortbedeutungen, mittlere Schichten bauen komplexere Beziehungen auf, und die letzten Schichten synthetisieren diese Informationen, um eine Ausgabe zu erzeugen. Diese Analyse visualisiert diese Reise und zeigt, wie sich das 'Verständnis' des Modells für Ihre Eingabe entwickelt. Die folgenden Diagramme zeigen, welche Teile dieses 'Fließbands' für Ihren spezifischen Text am aktivsten sind, und geben Aufschluss über den Denkprozess des Modells.",
|
| 96 |
+
"evolution_not_available_info": "Die Schichtentwicklungsanalyse wurde nicht ausgeführt oder ist fehlgeschlagen. Bitte aktivieren Sie sie in den Optionen und versuchen Sie es erneut.",
|
| 97 |
+
"evolution_explanation_prompt_de": "Sie sind ein Experte für KI-Analysen. Ihre Aufgabe ist es, zwei Diagramme zur Schichtentwicklung für den Prompt eines Benutzers zu erklären.\\n\\n**Benutzer-Prompt:** \"{input_text}\"\\n\\n**Analysedaten:**\\n- **Spitzenaktivierung:** Schicht {peak_activation_layer} (Stärke: {peak_activation_strength:.2f})\\n- **Größte Veränderung:** Zwischen Schicht {biggest_change_start_layer} und {biggest_change_end_layer} (Veränderungsgröße: {biggest_change_magnitude:.2f})\\n\\nBasierend auf diesen Daten geben Sie bitte eine detaillierte (2-3 Sätze pro Teil) Erklärung in zwei Teilen. **Sie MÜSSEN für jeden Teil Markdown-Überschriften (`####`) verwenden.**\\n\\n#### Analyse der Aktivierungsstärke\\nErklären Sie die Bedeutung der Spitzenaktivierung in Schicht {peak_activation_layer}. Was deutet dies auf die Verarbeitungsstufe des Modells hin (z. B. frühe Merkmalsextraktion, mittlere Abstraktion oder späte Entscheidungsfindung)?\\n\\n#### Analyse der Schicht-zu-Schicht-Veränderung\\nErklären Sie die Bedeutung der größten Veränderung zwischen den Schichten {biggest_change_start_layer} und {biggest_change_end_layer}. Was impliziert diese Verschiebung über die Verarbeitung des Modells?\\n\\nStützen Sie Ihre Erklärung auf die bereitgestellten Daten.",
|
| 98 |
+
"top_category_attribution_header": "Dieses Sunburst-Diagramm schlüsselt die Zuordnung in granularere Kategorien auf und zeigt die 20 ähnlichsten Funktionen zu Ihrer Eingabe.",
|
| 99 |
+
"activation_strength_plot_title": "Aktivierungsstärke über die Schichten",
|
| 100 |
+
"layer_changes_plot_title": "Repräsentative Veränderung zwischen den Schichten",
|
| 101 |
+
"fv_faithfulness_explanation_pca_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>Wie das funktioniert:</strong> Der Faithfulness Checker überprüft drei Arten von Behauptungen aus der KI-Erklärung:<ul><li><strong>Ranking-Behauptungen:</strong> Überprüft, ob ein behaupteter 'ähnlichster' Funktionstyp oder eine Kategorie tatsächlich unter den Top-3-Übereinstimmungen basierend auf den Kosinus-Ähnlichkeitswerten liegt.</li><li><strong>Positionsbezogene Behauptungen:</strong> Überprüft semantisch, ob die Beschreibung der Position der Eingabe durch die KI (z.B. 'in der Nähe von Textklassifikation') eine plausible Zusammenfassung der tatsächlich am besten bewerteten Funktionen ist.</li><li><strong>Begründungsbehauptungen:</strong> Analysiert semantisch, ob die Begründung für die Relevanz einer Kategorie plausibel und logisch mit der Eingabeaufforderung übereinstimmt.</li></ul></div>",
|
| 102 |
+
"fv_faithfulness_explanation_evolution_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>Wie das funktioniert:</strong> Der Faithfulness Checker überprüft drei Arten von Behauptungen aus der KI-Erklärung:<ul><li><strong>Spitzen-/Tiefpunkt-Behauptungen:</strong> Überprüft, ob eine Behauptung über ein Spitzenereignis die Schicht, in der das Ereignis aufgetreten ist, korrekt identifiziert.</li><li><strong>Numerische Behauptungen:</strong> Überprüft, ob ein genannter spezifischer Wert mit dem tatsächlich berechneten Wert übereinstimmt.</li><li><strong>Schicht-Behauptungen:</strong> Überprüft, ob eine Behauptung den Schichtindex für eine bestimmte Metrik korrekt angibt.</li></ul></div>",
|
| 103 |
+
|
| 104 |
+
"desc_named_entity_recognition": "Identifizierung von benannten Entitäten wie Personen, Orten und Organisationen im Text.",
|
| 105 |
+
"desc_text_generation": "Offene Textgenerierung, einschließlich kreativem Schreiben oder dem Fortsetzen einer Geschichte.",
|
| 106 |
+
|
| 107 |
+
"how_vectors_are_made_header": "Wie werden diese Vektoren erstellt?",
|
| 108 |
+
"how_vectors_are_made_desc": "Die Erstellung eines Funktionsvektors ist ein mehrstufiger Prozess, der Rohtext in eine aussagekräftige numerische Darstellung umwandelt. Das nachstehende Diagramm veranschaulicht diese Umwandlung und zeigt, wie ein einfacher Prompt vom Modell verarbeitet wird, um einen Vektor zu erzeugen, der seine Kernfunktion zusammenfasst.",
|
| 109 |
+
"how_vectors_are_made_step1_title": "SCHRITT 1: EINGABEAUFFORDERUNG",
|
| 110 |
+
"how_vectors_are_made_step2_title": "SCHRITT 2: TOKENIZER",
|
| 111 |
+
"how_vectors_are_made_step3_title": "SCHRITT 3: OLMo-2-7B MODELL",
|
| 112 |
+
"how_vectors_are_made_step3_desc": "Verborgene Zustände aus allen 32 Schichten",
|
| 113 |
+
"how_vectors_are_made_step4_title": "SCHRITT 4: EXTRAKTION DER LETZTEN SCHICHT",
|
| 114 |
+
"how_vectors_are_made_step4_desc": "Vektor mit 4096 Zahlen",
|
| 115 |
+
"how_vectors_are_made_step5_title": "SCHRITT 5: FUNKTIONSVEKTOR",
|
| 116 |
+
"how_vectors_are_made_step1_example": "Übersetze 'Guten Morgen' ins Deutsche",
|
| 117 |
+
"how_vectors_are_made_step2_example": "[\"Übersetze\", \"'\", \"Guten\", ..., \"Deutsche\"]",
|
| 118 |
+
|
| 119 |
+
"comprehension_qs_subheader": "Verständnisfragen",
|
| 120 |
+
"comprehension_qs_desc": "Bitte beantworten Sie die folgenden Fragen nach bestem Wissen und Gewissen. Ihre Antworten helfen uns, die Klarheit der Visualisierungen zu bewerten.",
|
| 121 |
+
|
| 122 |
+
"fv_q1": "Was stellt ein 'Funktionsvektor' in diesem Kontext dar?",
|
| 123 |
+
"fv_q1_option_a": "Ein einzelnes Wort aus der Eingabeaufforderung.",
|
| 124 |
+
"fv_q1_option_b": "Die grammatikalische Struktur der Eingabeaufforderung.",
|
| 125 |
+
"fv_q1_option_c": "Ein numerischer Fingerabdruck des Kernzwecks der Eingabeaufforderung.",
|
| 126 |
+
|
| 127 |
+
"fv_q2": "Was ist der Hauptzweck der Verwendung der Hauptkomponentenanalyse (PCA) für die 3D-Visualisierung?",
|
| 128 |
+
"fv_q2_option_a": "Um das Diagramm bunter aussehen zu lassen.",
|
| 129 |
+
"fv_q2_option_b": "Um hochdimensionale Vektordaten zur Visualisierung in einen 3D-Raum zu reduzieren.",
|
| 130 |
+
"fv_q2_option_c": "Um die Verarbeitungszeit des Modells zu beschleunigen.",
|
| 131 |
+
|
| 132 |
+
"fv_q3": "Was gibt der Abstand zwischen zwei Punkten im 3D-PCA-Diagramm an?",
|
| 133 |
+
"fv_q3_option_a": "Der Längenunterschied zwischen zwei Eingabeaufforderungen.",
|
| 134 |
+
"fv_q3_option_c": "Die funktionale Ähnlichkeit zwischen den Eingabeaufforderungen (nähere Punkte sind ähnlicher).",
|
| 135 |
+
"fv_q3_option_d": "Die Anzahl der von jeder Eingabeaufforderung aktivierten Schichten.",
|
| 136 |
+
"pca_explanation_prompt_de": "Sie sind ein Experte für KI-Analysen. Ihre Aufgabe ist es, die Positionierung des Prompts eines Benutzers in einem 3D-PCA-Plot von Funktionsvektoren zu erklären. Der Plot visualisiert, wie ein Sprachmodell Prompts basierend auf ihrer zugrunde liegenden Funktion kategorisiert, wobei ähnliche Funktionen zusammengefasst werden.\\n\\n**Benutzer-Prompt:** \"{input_text}\"\\n\\n**Analysedaten (Top 3 Übereinstimmungen):**\\n- **Funktionstypen:** {top_types}\\n- **Spezifische Kategorien:** {top_cats}\\n\\nBasierend auf diesen Daten geben Sie bitte eine prägnante, analytische Erklärung in drei separaten Teilen. **Entscheidend ist, dass Sie für die Überschriften jedes Teils Markdown-Überschriften (`####`) verwenden und die angeforderte Struktur genau einhalten.**\\n\\n#### Gesamtplatzierung\\nBeginnen Sie mit einer allgemeinen Zusammenfassung, wo sich der Prompt im PCA-Plot befindet. Erwähnen Sie, in welche allgemeine funktionale Nachbarschaft er fällt.\\n\\n#### Top-Funktionstyp-Zuschreibungen\\nAnalysieren Sie die drei dominantesten Funktionstypen. Erklären Sie für jeden der drei Typen kurz, warum der Prompt des Benutzers damit übereinstimmt, und beziehen Sie sich dabei auf den Inhalt des Prompts und die Art des Funktionstyps.\\n\\n#### Top-spezifische Kategorie-Zuschreibung\\nDiskutieren Sie die drei spezifischsten Kategorien. Erklären Sie für jede Kategorie kurz die Verbindung und warum sie als enger Nachbar des Benutzer-Prompts sinnvoll ist.\\n\\nStrukturieren Sie Ihre Antwort mit klaren Überschriften für jeden der drei Teile. Stützen Sie Ihre gesamte Erklärung auf die bereitgestellten Daten.",
|
| 137 |
+
"evolution_explanation_prompt_de": "Sie sind ein Experte für KI-Analysen. Ihre Aufgabe ist es, zwei Diagramme zur Schichtentwicklung für den Prompt eines Benutzers zu erklären.\\n\\n**Benutzer-Prompt:** \"{input_text}\"\\n\\n**Analysedaten:**\\n- **Spitzenaktivierung:** Schicht {peak_activation_layer} (Stärke: {peak_activation_strength:.2f})\\n- **Größte Veränderung:** Zwischen Schicht {biggest_change_start_layer} und {biggest_change_end_layer} (Veränderungsgröße: {biggest_change_magnitude:.2f})\\n\\nBasierend auf diesen Daten geben Sie bitte eine detaillierte (2-3 Sätze pro Teil) Erklärung in zwei Teilen. **Sie MÜSSEN für jeden Teil Markdown-Überschriften (`####`) verwenden.**\\n\\n#### Analyse der Aktivierungsstärke\\nErklären Sie die Bedeutung der Spitzenaktivierung in Schicht {peak_activation_layer}. Was deutet dies auf die Verarbeitungsstufe des Modells hin (z. B. frühe Merkmalsextraktion, mittlere Abstraktion oder späte Entscheidungsfindung)?\\n\\n#### Analyse der Schicht-zu-Schicht-Veränderung\\nErklären Sie die Bedeutung der größten Veränderung zwischen den Schichten {biggest_change_start_layer} und {biggest_change_end_layer}. Was impliziert diese Verschiebung über die Verarbeitung des Modells?\\n\\nStützen Sie Ihre Erklärung auf die bereitgestellten Daten.",
|
| 138 |
+
"fv_claim_extraction_prompt_header": "Sie sind ein Experten-System zur Extraktion von Behauptungen. Ihre Aufgabe ist es, eine Erklärung einer Datenvisualisierung zu lesen und alle überprüfbaren, faktischen Behauptungen in eine strukturierte JSON-Liste zu extrahieren. Ein einzelner Satz kann mehrere Behauptungen enthalten.",
|
| 139 |
+
"fv_claim_extraction_prompt_instruction": "Jedes Objekt in der Liste MUSS die folgenden Schlüssel haben:\n1. `claim_text`: Der exakte Satz oder die Phrase aus der Erklärung, die die Behauptung aufstellt.\n2. `claim_type`: Einer der verfügbaren Behauptungstypen für den gegebenen Kontext.\n3. `details`: Ein Objekt, das die spezifischen Parameter für die Überprüfung enthält.",
|
| 140 |
+
"fv_claim_extraction_prompt_context_header": "**Kontext dieser Erklärung:** {context}",
|
| 141 |
+
"fv_claim_extraction_prompt_types_header": "**Verfügbare Behauptungstypen:**",
|
| 142 |
+
"fv_claim_extraction_prompt_pca_types_details": "- `top_k_similarity`: Eine Behauptung, dass ein oder mehrere Funktionstypen/Kategorien dem Input am ähnlichsten sind.\n - `details`: {{ \"item_type\": \"function_type\" oder \"category\", \"items\": [\"...\"], \"rank_description\": \"most/least\" }}\n- `positional_claim`: Eine Behauptung über die Position des Inputs im Verhältnis zu einem oder mehreren Clustern im PCA-Plot.\n - `details`: {{ \"cluster_names\": [\"...\"], \"position\": \"near/far/between\" }}\n- `category_justification_claim`: Eine Behauptung, die einen bestimmten Grund für die Relevanz einer Kategorie für den Input-Prompt liefert.\n - `details`: {{ \"category_name\": \"...\", \"justification\": \"...\" }}",
|
| 143 |
+
"fv_claim_extraction_prompt_evolution_types_details": "- `peak_activation`: Eine Behauptung darüber, welche Schicht die höchste Aktivierungsstärke hatte.\n - `details`: {{ \"layer_index\": 12 }}\n- `biggest_change`: Eine Behauptung darüber, welcher Schichtübergang die größte Veränderung hatte.\n - `details`: {{ \"start_layer\": 10, \"end_layer\": 11 }}\n- `specific_value_claim`: Eine Behauptung über einen spezifischen numerischen Wert.\n - `details`: {{ \"metric\": \"activation_strength\" oder \"change_magnitude\", \"layer_index\": 12, \"value\": 65.91 }}\n - **Hinweis:** Bei \"change_magnitude\" bezieht sich `layer_index` auf die **Startschicht** des Übergangs (z. B. für Schicht 1->2 ist `layer_index` 1).",
|
| 144 |
+
"fv_claim_extraction_prompt_pca_example_header": "**Beispiel für einen 'pca'-Kontext:**",
|
| 145 |
+
"fv_claim_extraction_prompt_pca_example_explanation": "- **Erklärungssatz:** \"Insbesondere fällt es in eine Region, die durch abstrakte Aufgaben, Textklassifizierung und Textgenerierung gekennzeichnet ist.\"",
|
| 146 |
+
"fv_claim_extraction_prompt_pca_example_json": "- **Ergebnis-JSON-Objekt:**\n ```json\n [\n {{\n \"claim_text\": \"Insbesondere fällt es in eine Region, die durch abstrakte Aufgaben, Textklassifizierung und Textgenerierung gekennzeichnet ist.\",\n \"claim_type\": \"positional_claim\",\n \"details\": {{\n \"cluster_names\": [\"abstrakte Aufgaben\", \"Textklassifizierung\", \"Textgenerierung\"],\n \"position\": \"near\"\n }}\n }},\n {{\n \"claim_text\": \"Der Prompt ist eng mit Language QA verbunden, da er die Beantwortung einer Frage zu einem literarischen Werk beinhaltet.\",\n \"claim_type\": \"category_justification_claim\",\n \"details\": {{\n \"category_name\": \"Language QA\",\n \"justification\": \"er beinhaltet die Beantwortung einer Frage zu einem literarischen Werk\"\n }}\n }}\n ]\n ```",
|
| 147 |
+
"fv_claim_extraction_prompt_evolution_example_header": "**Beispiel für einen 'evolution'-Kontext:**",
|
| 148 |
+
"fv_claim_extraction_prompt_evolution_example_explanation": "- **Erklärungssatz:** \"Die größte Veränderung tritt zwischen Schicht 1 und 2 auf, mit einer Größenordnung von 0,40...\"",
|
| 149 |
+
"fv_claim_extraction_prompt_evolution_example_json": "- **Ergebnis-JSON-Objekt:**\n ```json\n [\n {{\n \"claim_text\": \"Die größte Veränderung tritt zwischen Schicht 1 und 2 auf, mit einer Größenordnung von 0,40...\",\n \"claim_type\": \"biggest_change\",\n \"details\": {{ \"start_layer\": 1, \"end_layer\": 2 }}\n }},\n {{\n \"claim_text\": \"Die größte Veränderung tritt zwischen Schicht 1 und 2 auf, mit einer Größenordnung von 0,40...\",\n \"claim_type\": \"specific_value_claim\",\n \"details\": {{ \"metric\": \"change_magnitude\", \"layer_index\": 1, \"value\": 0.40 }}\n }}\n ]\n ```",
|
| 150 |
+
"fv_claim_extraction_prompt_analyze_header": "**Zu analysierende Erklärung:**",
|
| 151 |
+
"fv_claim_extraction_prompt_footer": "Antworten Sie NUR mit der JSON-Liste der Behauptungen. Wenn keine überprüfbaren Behauptungen gefunden werden, geben Sie eine leere Liste `[]` zurück.",
|
| 152 |
+
"fv_semantic_verification_prompt_header": "Sie sind ein KI-Faktenchecker, der auf semantische Analyse spezialisiert ist. Ihre Aufgabe ist es, festzustellen, ob eine behauptete „funktionale Nachbarschaft“ plausibel mit den tatsächlich am höchsten eingestuften Funktionen für einen bestimmten Prompt zusammenhängt.",
|
| 153 |
+
"fv_semantic_verification_prompt_rule": "**Entscheidende Regel:** Die behauptete Nachbarschaft muss keine direkte Zusammenfassung der Top-Funktionen sein. Sie sollte als „verifiziert“ betrachtet werden, wenn sie ein plausibles, kontextuell relevantes oder semantisch benachbartes Konzept darstellt. Kennzeichnen Sie sie als „nicht verifiziert“, wenn die behauptete Nachbarschaft nicht zusammenhängt oder logisch inkonsistent mit den Top-Funktionen ist.",
|
| 154 |
+
"fv_semantic_verification_prompt_actual_header": "**Tatsächliche Top-bewertete Funktionen:**",
|
| 155 |
+
"fv_semantic_verification_prompt_claimed_header": "**Behauptete funktionale Nachbarschaft:**",
|
| 156 |
+
"fv_semantic_verification_prompt_task_header": "**Ihre Aufgabe:**",
|
| 157 |
+
"fv_semantic_verification_prompt_task_instruction": "Ist die „behauptete funktionale Nachbarschaft“ basierend auf der obigen Regel plausibel mit den „tatsächlichen Top-bewerteten Funktionen“ verknüpft? Geben Sie eine eindeutige Entscheidung und stützen Sie diese auf konkrete Hinweise.",
|
| 158 |
+
"fv_semantic_verification_prompt_json_instruction": "Antworten Sie mit einem JSON-Objekt mit zwei Schlüsseln:\n1. `is_verified`: boolean (true, wenn plausibel verknüpft, sonst false).\n2. `reasoning`: Eine ausführliche Begründung in 2-3 Sätzen, die mindestens einen Eintrag aus der tatsächlichen Liste erwähnt, erläutert, warum die Behauptung passt oder widerspricht, und den ursprünglichen Wortlaut nicht einfach wiederholt.",
|
| 159 |
+
"fv_semantic_verification_prompt_footer": "Antworten Sie NUR mit dem JSON-Objekt und nichts anderem.",
|
| 160 |
+
"fv_justification_verification_prompt_header": "Sie sind ein KI-Faktenchecker, der auf semantisches Denken spezialisiert ist. Ihre Aufgabe ist es zu bestimmen, ob eine Begründung für die Relevanz einer funktionalen Kategorie für eine Eingabeaufforderung plausibel und logisch konsistent ist.",
|
| 161 |
+
"fv_justification_verification_prompt_rule": "**Entscheidende Regel:** Die Begründung muss nicht das stärkste mögliche Argument sein. Sie sollte als „verifiziert“ betrachtet werden, wenn sie eine plausible, kreative oder kontextuell relevante Verbindung darstellt, auch wenn sie weit hergeholt erscheint. Kennzeichnen Sie sie nur dann als „nicht verifiziert“, wenn die Argumentation völlig unlogisch, sachlich falsch ist oder der Eingabeaufforderung direkt widerspricht.",
|
| 162 |
+
"fv_justification_verification_prompt_input_header": "**Eingabeaufforderung:**",
|
| 163 |
+
"fv_justification_verification_prompt_category_header": "**Funktionale Kategorie:**",
|
| 164 |
+
"fv_justification_verification_prompt_justification_header": "**Gegebene Begründung:**",
|
| 165 |
+
"fv_justification_verification_prompt_task_header": "**Ihre Aufgabe:**",
|
| 166 |
+
"fv_justification_verification_prompt_task_instruction": "Ist die Begründung basierend auf der obigen Regel plausibel? Beziehen Sie Ihre Entscheidung ausdrücklich auf den Eingabeprompt und die Kategorie.",
|
| 167 |
+
"fv_justification_verification_prompt_json_instruction": "Antworten Sie mit einem JSON-Objekt mit zwei Schlüsseln:\n1. `is_verified`: boolean (true, wenn die Begründung plausibel ist, false, wenn sie unlogisch oder falsch ist).\n2. `reasoning`: Eine Begründung in 2-3 Sätzen, die klar auf den Eingabeprompt und die Kategorie eingeht und erläutert, weshalb die Begründung stimmig oder unstimmig ist, ohne den ursprünglichen Wortlaut einfach zu wiederholen.",
|
| 168 |
+
"fv_justification_verification_prompt_footer": "Antworten Sie NUR mit dem JSON-Objekt und nichts anderem."
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
|
locales/de/welcome_page.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"welcome_page_title": "Willkommen & Einrichtung",
|
| 3 |
+
"welcome_page_header": "Bevor Sie beginnen...",
|
| 4 |
+
"welcome_page_intro": "Um unsere Forschung zur Benutzerfreundlichkeit dieses Tools zu unterstützen, geben Sie bitte einige anonyme Informationen an. Diese werden sicher gespeichert und nur für akademische Zwecke verwendet.",
|
| 5 |
+
"research_tool_intro": "Ein fortschrittliches Forschungswerkzeug zur Erkundung der inneren Funktionsweise von großen Sprachmodellen.",
|
| 6 |
+
"about_this_tool": "Über dieses Tool",
|
| 7 |
+
"research_study_info": "Diese Anwendung ist Teil einer Forschungsstudie, die darauf abzielt zu verstehen, wie Benutzer mit komplexen KI-Modellen interagieren und diese interpretieren. Durch die Nutzung dieses Tools nehmen Sie an dieser Studie teil.",
|
| 8 |
+
"your_role": "Ihre Rolle als Teilnehmer:",
|
| 9 |
+
"role_1": "Sie werden die verschiedenen Analysewerkzeuge verwenden, um das Verhalten eines Sprachmodells zu untersuchen.",
|
| 10 |
+
"role_2": "Sie werden gebeten, Feedback zur Benutzerfreundlichkeit und Klarheit der Visualisierungen zu geben.",
|
| 11 |
+
"role_3": "Ihre Interaktionen und Ihr Feedback helfen uns, bessere und transparentere KI-Werkzeuge zu entwickeln.",
|
| 12 |
+
"data_privacy": "Datenschutz & Einwilligung:",
|
| 13 |
+
"privacy_1": "Ihre Antworten und Interaktionen sind anonym. Wir speichern nur Ihr Alter, Ihr Fachwissen und Ihr Feedback.",
|
| 14 |
+
"privacy_2": "Alle gesammelten Daten werden ausschließlich für akademische Forschungszwecke verwendet.",
|
| 15 |
+
"privacy_3": "Indem Sie fortfahren, stimmen Sie der Erhebung und Nutzung dieser anonymen Daten zu.",
|
| 16 |
+
"tell_us_about_yourself": "Erzählen Sie uns von sich",
|
| 17 |
+
"what_is_your_age_group": "Was ist Ihre Altersgruppe?",
|
| 18 |
+
"under_18": "Unter 18",
|
| 19 |
+
"18_24": "18-24",
|
| 20 |
+
"25_34": "25-34",
|
| 21 |
+
"35_44": "35-44",
|
| 22 |
+
"45_54": "45-54",
|
| 23 |
+
"55_64": "55-64",
|
| 24 |
+
"65_or_over": "65 oder älter",
|
| 25 |
+
"prefer_not_to_say": "Keine Angabe",
|
| 26 |
+
"rate_your_expertise": "Wie würden Sie Ihre Erfahrung mit KI und Sprachmodellen bewerten?",
|
| 27 |
+
"novice": "Anfänger (Wenig bis keine Erfahrung mit KI-Tools)",
|
| 28 |
+
"intermediate": "Fortgeschritten (Sicherer Umgang mit KI für alltägliche Aufgaben)",
|
| 29 |
+
"expert": "Experte (Tiefes technisches Wissen oder Forschung im Bereich KI)",
|
| 30 |
+
"start_analysis_button": "Analyse starten",
|
| 31 |
+
"form_submitted": "formular_abgesendet",
|
| 32 |
+
"thank_you_proceed": "Vielen Dank! Sie können nun mit der Analyse fortfahren.",
|
| 33 |
+
"thank_you_main_suite": "Vielen Dank! Die Hauptanalyse-Suite wird geladen...",
|
| 34 |
+
"welcome_to_llm_analysis_suite": "Willkommen beim Explainable Language Interpretability Analysis Tool!",
|
| 35 |
+
"toolkit_description": "Dieses Toolkit bietet eine Sammlung fortschrittlicher Methoden zur Interpretation und zum Verständnis der inneren Funktionsweise von Sprachmodellen. Wählen Sie eine Analyse aus der Seitenleiste, um zu beginnen.",
|
| 36 |
+
"attribution_analysis_description": "<strong>Attributionsanalyse:</strong> Verstehen Sie, welche Teile des Eingabetextes die Ausgabe des Modells beeinflussen, mithilfe von Methoden wie Integrierte Gradienten, Okklusion und Salienz.",
|
| 37 |
+
"function_vectors_description": "<strong>Funktionsvektoren:</strong> Analysieren Sie, wie Text verschiedene funktionale Fähigkeiten innerhalb des Modells aktiviert.",
|
| 38 |
+
"circuit_tracing_description": "<strong>Schaltkreisverfolgung:</strong> Erforschen Sie die Rechenpfade innerhalb des Modells, um zu sehen, wie Informationen fließen."
|
| 39 |
+
}
|
locales/en/attribution_analysis_page.json
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"desc_integrated_gradients": "This provides a more reliable measure of importance by considering not just the final input, but the entire path from a neutral 'blank' state to your specific prompt. It carefully adds up the contribution of each word, preventing misleading results and giving a truer picture of each token's influence.",
|
| 3 |
+
"desc_occlusion": "This method tests each word's necessity by asking, 'What happens if this word is missing?' It temporarily hides (or 'occludes') each token from the input and measures how much the model's output changes. A high score means the word was critical for the result.",
|
| 4 |
+
"desc_saliency": "This method reveals the model's initial 'gut reaction' to each input token. It highlights which words the model found most interesting or surprising, based on a direct and fast calculation of importance. Think of it as a quick look at the model's focus.",
|
| 5 |
+
"unsupported_method_desc": "Description not available for this method.",
|
| 6 |
+
"ai_expert_intro": "You are a world-class AI interpretability expert. Your task is to analyze an attribution heatmap and provide a comprehensive, easy-to-understand explanation for a non-technical audience.",
|
| 7 |
+
"analysis_details": "Analysis Details",
|
| 8 |
+
"method_being_used": "Method Used:",
|
| 9 |
+
"prompt_analyzed": "Prompt Analyzed:",
|
| 10 |
+
"full_generated_text": "Full Generated Text:",
|
| 11 |
+
"method_specific_context": "Method-Specific Context",
|
| 12 |
+
"instructions_for_analysis": "Instructions for Analysis",
|
| 13 |
+
"instruction_part_1_header": "### High-Level Visual Overview",
|
| 14 |
+
"instruction_part_1_desc": "In two to three sentences, provide a general summary of the patterns you see in the attached heatmap image. Describe the general location of the 'hot spots' (brightly colored areas) and what this visually implies about the model's focus. **For example, if the method is {method_name}, you might expect to see [describe expected pattern for this method].** Do not use generic descriptions. Base your analysis exclusively on the visual information in the image.",
|
| 15 |
+
"instruction_synthesis_header": "### Synthesis of Key Findings",
|
| 16 |
+
"instruction_synthesis_desc": "Following your high-level visual overview, create a brief narrative synthesis of the key findings from the data provided below. Structure your analysis into two paragraphs: **Strongest Individual Connections** and **Most Influential Tokens Overall**. In the first paragraph, explain the significance of the strongest individual token-to-token connections. In the second, discuss the input tokens that had the highest average influence on the entire generation, **making sure to refer to the full generated text provided above to explain *why* these tokens were so influential in shaping the final output.** Explain *why* certain tokens are influential in both contexts. **Do not add a summary at the end of your analysis**.",
|
| 17 |
+
"instruction_color_coding": "Formatting Rule: When you mention an input token, format it exactly like this: <span style='color: #60a5fa;'>the_token_here</span>. When you mention a generated token, format it exactly like this: <span style='color: #fca5a5;'>the_token_here</span>. Do not deviate from this format.",
|
| 18 |
+
"data_priority_instruction": "The following text block contains the pre-calculated key findings. Use this as the exclusive source of truth for your analysis.",
|
| 19 |
+
"data_section_header": "## Pre-Calculated Analysis (Source of Truth)",
|
| 20 |
+
"begin_analysis_now": "Begin your analysis now. Remember to follow the two-part structure (High-Level Visual Overview, then Synthesis of Key Findings) as described above.",
|
| 21 |
+
"attr_page_title": "<i class='bi bi-search'></i> Attribution Analysis",
|
| 22 |
+
"attr_page_desc": "This page uses token-level attribution methods to explain how different parts of your input prompt influence the generated output. Select a method, enter a prompt, and see which words were most important for the model's prediction.",
|
| 23 |
+
"how_methods_work_expander": "How Attribution Methods Work",
|
| 24 |
+
"saliency_method_title": "Saliency",
|
| 25 |
+
"saliency_method_desc": "Measures importance by calculating the gradient of the output with respect to the input tokens. It's fast but can sometimes be noisy.",
|
| 26 |
+
"saliency_step_1": "<strong>1. Generate Output:</strong> The model generates the next word, e.g., 'over', for the prompt 'The quick brown fox jumps'.",
|
| 27 |
+
"saliency_step_2": "<strong>2. Calculate Gradients:</strong> It computes how much the probability of 'over' would change with a tiny nudge to each input word's embedding.",
|
| 28 |
+
"saliency_step_3": "<strong>3. Assign Scores:</strong> Words that cause the biggest change (e.g., 'jumps') get the highest scores.",
|
| 29 |
+
"ig_method_title": "Integrated Gradients",
|
| 30 |
+
"ig_method_desc": "A more robust method that attributes the prediction to the inputs by integrating gradients along a path from a baseline (e.g., zero embedding) to the input.",
|
| 31 |
+
"ig_step_1": "<strong>1. Create Path:</strong> It creates a smooth path from a 'blank' input to the full prompt, 'The quick brown fox jumps'.",
|
| 32 |
+
"ig_step_2": "<strong>2. Compute Gradients Along Path:</strong> It calculates gradients for the output 'over' at many small steps along this path.",
|
| 33 |
+
"ig_step_3": "<strong>3. Sum Gradients:</strong> It sums up all these small gradient values to get a reliable score for how much each word contributed to the output.",
|
| 34 |
+
"occlusion_method_title": "Occlusion",
|
| 35 |
+
"occlusion_method_desc": "A simple, intuitive method that measures importance by replacing each input token and seeing how much the output probability changes.",
|
| 36 |
+
"occlusion_step_1": "<strong>1. Get Original Probability:</strong> The model generates 'over' with a certain probability.",
|
| 37 |
+
"occlusion_step_2": "<strong>2. Replace Words:</strong> It systematically replaces each word (e.g., 'jumps') with a neutral token and re-runs the model.",
|
| 38 |
+
"occlusion_step_3": "<strong>3. Measure Impact:</strong> If replacing 'jumps' causes the probability of 'over' to drop significantly, 'jumps' is considered very important.",
|
| 39 |
+
"input_header": "<i class='bi bi-pencil-square'></i> Input & Settings",
|
| 40 |
+
"enter_prompt": "Enter your prompt:",
|
| 41 |
+
"enter_prompt_help": "Enter the text for the model to continue",
|
| 42 |
+
"enable_ai_explanations": "Enable AI Explanations",
|
| 43 |
+
"enable_ai_explanations_help": "Generate explanations for visualizations using Qwen 2.5 VL 72B (requires API access)",
|
| 44 |
+
"generate_and_analyze_button": "Generate & Analyze All Methods",
|
| 45 |
+
"max_new_tokens_slider": "Number of Tokens to Generate",
|
| 46 |
+
"max_new_tokens_slider_help": "Controls the length of the generated text.",
|
| 47 |
+
"loading_models_spinner": "Loading OLMo model with all attribution methods...",
|
| 48 |
+
"generating_attributions_spinner": "Generating text and attributions...",
|
| 49 |
+
"analysis_complete_success": "All attribution analyses complete!",
|
| 50 |
+
"failed_to_generate_analysis_error": "Failed to generate analysis",
|
| 51 |
+
"failed_to_load_models_error": "Failed to load models",
|
| 52 |
+
"please_enter_prompt_warning": "Please enter a prompt",
|
| 53 |
+
"output_header": "<i class='bi bi-display'></i> Output",
|
| 54 |
+
"generated_text_subheader": "Generated Text",
|
| 55 |
+
"input_label": "Input:",
|
| 56 |
+
"generated_label": "Generated:",
|
| 57 |
+
"attribution_analysis_results_header": "Attribution Analysis Results",
|
| 58 |
+
"attr_tab": "Integrated Gradients",
|
| 59 |
+
"occlusion_tab": "Occlusion",
|
| 60 |
+
"saliency_tab": "Saliency",
|
| 61 |
+
"attr_title": "Integrated Gradients Analysis",
|
| 62 |
+
"occlusion_title": "Occlusion Analysis",
|
| 63 |
+
"saliency_title": "Saliency Analysis",
|
| 64 |
+
"attr_viz_desc": "**How to read this Integrated Gradients heatmap:**\\n- **X-axis**: Generated tokens (what the model produced)\\n- **Y-axis**: Input tokens (your original prompt)\\n- **Color intensity**: Mathematical gradient-based importance scores\\n- **Interpretation**: How much each input token mathematically influences each generated token",
|
| 65 |
+
"occlusion_viz_desc": "Occlusion analysis highlights important tokens by temporarily masking (occluding) them and measuring the impact on the output. A larger attribution score means the token was more critical.",
|
| 66 |
+
"saliency_viz_desc": "This visualization highlights the most salient tokens in the input that contributed to the generation.",
|
| 67 |
+
"how_to_read_heatmap": "How to read this heatmap:",
|
| 68 |
+
"xaxis_label": "X-axis",
|
| 69 |
+
"xaxis_desc": "Generated tokens (what the model produced)",
|
| 70 |
+
"yaxis_label": "Y-axis",
|
| 71 |
+
"yaxis_desc": "Input tokens (your original prompt)",
|
| 72 |
+
"color_intensity_label": "Color Intensity",
|
| 73 |
+
"color_intensity_desc": "Mathematical importance scores",
|
| 74 |
+
"interpretation_label": "Interpretation",
|
| 75 |
+
"interpretation_desc": "How much each input token influences each generated token.",
|
| 76 |
+
"special_tokens_label": "Special Tokens (e.g., `Ġ`, `Ċ`)",
|
| 77 |
+
"special_tokens_desc": "These are artifacts from the tokenizer. Common ones include:<ul><li>`Ġ`: A space, marking a new word.</li><li>`Ċ`: A newline character.</li><li>`<|endoftext|>`: A special token marking the end of a sequence.</li></ul>",
|
| 78 |
+
"creating_viz_spinner": "Creating {method_title} visualization...",
|
| 79 |
+
"generating_ai_explanation_spinner": "Generating AI explanation for {method_title}...",
|
| 80 |
+
"what_this_method_shows": "What this method shows:",
|
| 81 |
+
"ai_generated_analysis": "AI Generated Analysis",
|
| 82 |
+
"download_results_subheader": "Download Results",
|
| 83 |
+
"download_html_button": "Download {method_title} HTML",
|
| 84 |
+
"download_csv_button": "Download Scores (CSV)",
|
| 85 |
+
"download_png_button": "Download {method_title} PNG",
|
| 86 |
+
"heatmap_title": "Attribution Heatmap",
|
| 87 |
+
"heatmap_xaxis": "Generated Tokens",
|
| 88 |
+
"heatmap_yaxis": "Input Tokens",
|
| 89 |
+
"feedback_survey_header": "Feedback & Comprehension Survey",
|
| 90 |
+
"feedback_survey_desc": "Your feedback is valuable for improving this tool. Please take a moment to answer these questions.",
|
| 91 |
+
"ux_feedback_subheader": "User Experience Feedback",
|
| 92 |
+
"q_visual_clarity": "1. How would you rate the clarity of the heatmap visualizations?",
|
| 93 |
+
"q_visual_clarity_help": "1 = Very Confusing, 5 = Very Clear",
|
| 94 |
+
"q_cognitive_load": "2. How mentally demanding did you find it to interpret the results?",
|
| 95 |
+
"q_cognitive_load_help": "1 = Not Demanding at all, 5 = Very Demanding",
|
| 96 |
+
"q_influential_docs_plausibility": "3. How plausible are the 3 most influential documents identified by the Influence Tracer?",
|
| 97 |
+
"q_influential_docs_plausibility_help": "1 = Not Plausible at all, 5 = Very Plausible",
|
| 98 |
+
"comprehension_qs_subheader": "Quick Comprehension Check",
|
| 99 |
+
"comprehension_qs_desc": "Based on the visualizations you just saw, which method best answers the following questions?",
|
| 100 |
+
"q_options_ig": "Integrated Gradients",
|
| 101 |
+
"q_options_occlusion": "Occlusion",
|
| 102 |
+
"q_options_saliency": "Saliency",
|
| 103 |
+
"q_s1": "Which method reveals the model's initial 'gut reaction' to each word, showing its most direct and immediate focus?",
|
| 104 |
+
"q_s2": "Which method would you use to understand the impact of removing a specific word?",
|
| 105 |
+
"q_s3": "Which method builds a more reliable picture of importance by analyzing the entire path from a blank input to your final prompt?",
|
| 106 |
+
"submit_feedback_button": "Submit Feedback",
|
| 107 |
+
"feedback_success_message": "Thank you for your feedback!",
|
| 108 |
+
"feedback_error_message": "Sorry, there was an error submitting your feedback: {e}",
|
| 109 |
+
"feedback_please_answer_all_qs": "Please answer all comprehension questions before submitting.",
|
| 110 |
+
"error_creating_heatmap": "Error creating heatmap from HTML: {e}",
|
| 111 |
+
"error_inseq_no_html": "Inseq failed to generate HTML output for {method_name}.",
|
| 112 |
+
"error_no_table_in_html": "Could not find data table in inseq's HTML output for {method_name}.",
|
| 113 |
+
"error_table_no_rows": "Table in HTML output contains no rows for {method_name}.",
|
| 114 |
+
"error_failed_to_parse_rows": "Failed to parse any data rows from the HTML for {method_name}.",
|
| 115 |
+
"running_influence_trace_spinner": "Tracing influences in the training data...",
|
| 116 |
+
"influence_index_not_found_warning": "Influence tracer index not found. Skipping this step. Please run `build_dolma_index.py` to enable it.",
|
| 117 |
+
"influence_tracer_title": "Influence Tracer",
|
| 118 |
+
"influence_tracer_desc": "This tool identifies training documents from a sample of the <b>Dolma v1.6 dataset</b> that were most influential on the model's output. Dolma v1.6 is a 3-trillion-token open dataset composed of a diverse mix of web content (Common Crawl), academic publications (C4, arXiv), code (The Stack), books (Project Gutenberg), and encyclopedic data (Wikipedia). By tracing the model's generation back to its training data, we can better understand its reasoning and knowledge sources.",
|
| 119 |
+
"top_influential_docs_header": "Top {num_docs} Most Influential Training Documents",
|
| 120 |
+
"no_influential_docs_found": "No influential documents were found for this generation.",
|
| 121 |
+
"file_label": "File",
|
| 122 |
+
"source_label": "Source",
|
| 123 |
+
"similarity_label": "Similarity",
|
| 124 |
+
"run_analysis_for_influence_info": "Run an analysis to see influential training documents here.",
|
| 125 |
+
"prompt_placeholder_text": "e.g., 'The capital of France is' or 'To be or not to be, that is the'",
|
| 126 |
+
"running_attribution_analysis_spinner": "Generating attribution heatmaps...",
|
| 127 |
+
"generating_ai_explanations_spinner": "Generating AI explanations...",
|
| 128 |
+
"how_influence_is_found_header": "How Influence is Found: A Look at Cosine Similarity",
|
| 129 |
+
"how_influence_is_found_desc": "The Influence Tracer doesn't just search for keywords; it searches for meaning. It does this by converting both your prompt and every sentence in the training data into high-dimensional vectors. It then uses a technique called <strong>Cosine Similarity</strong> to find the closest matches.",
|
| 130 |
+
"influence_step_1_title": "<strong>1. Vector Conversion</strong>",
|
| 131 |
+
"influence_step_1_desc": "Your prompt and each sentence from the training data are transformed into numerical vectors.",
|
| 132 |
+
"influence_step_2_title": "<strong>2. Angle Calculation</strong>",
|
| 133 |
+
"influence_step_2_desc": "The system calculates the angle (θ) between your prompt's vector and every other sentence vector.",
|
| 134 |
+
"influence_step_3_title": "<strong>3. Similarity Score</strong>",
|
| 135 |
+
"influence_step_3_desc": "A smaller angle means a higher similarity. A score of 1 means the sentences are identical in meaning, while a score of 0 means they are completely unrelated.",
|
| 136 |
+
"influence_example_sentence_a": "Your Prompt",
|
| 137 |
+
"influence_example_sentence_b": "Training Sentence",
|
| 138 |
+
"generating_all_visualizations_spinner": "Generating all visualizations and AI explanations...",
|
| 139 |
+
"searching_influential_docs_progress": "Searching for influential documents...",
|
| 140 |
+
"processing_doc_progress": "Processing document {i} of {k}...",
|
| 141 |
+
"search_complete_progress": "Search complete!",
|
| 142 |
+
"faithfulness_check_expander": "Faithfulness Check",
|
| 143 |
+
"running_faithfulness_check_spinner": "Running faithfulness check...",
|
| 144 |
+
"verified_status": "Verified",
|
| 145 |
+
"contradicted_status": "Contradicted",
|
| 146 |
+
"claim_label": "Claim",
|
| 147 |
+
"status_label": "Status",
|
| 148 |
+
"evidence_label": "Evidence",
|
| 149 |
+
"no_verifiable_claims_info": "No verifiable claims were extracted from the explanation.",
|
| 150 |
+
"faithfulness_check_error": "An error occurred during the faithfulness check: {e}",
|
| 151 |
+
"faithfulness_check_results_header": "Faithfulness Check Results:",
|
| 152 |
+
"faithfulness_check_explanation_html": "<div style='font-size: 0.9rem; color: #DCDCDC; margin-bottom: 1rem;'><p style='margin-bottom: 0.5rem;'><strong>How This Works:</strong> The faithfulness checker verifies two types of claims from the AI's explanation:</p><ul style='margin-left: 1.5rem; padding-left: 0; list-style-type: disc;'><li style='margin-bottom: 0.3rem;'><strong>Numerical Claims:</strong> Checks if a token's attribution score (either its peak 'hotspot' or its average score) meets a dynamic threshold.<ul style='margin-left: 1.5rem; padding-left: 0; list-style-type: circle;'><li>A <strong>\"high\"</strong> claim (e.g., \"highest,\" \"strongest\") must be above <strong>70%</strong> of the maximum score in the analysis.</li><li>A <strong>\"significant\"</strong> claim (e.g., \"notable\") must be above <strong>50%</strong> of the maximum score.</li></ul></li><li style='margin-bottom: 0.3rem;'><strong>Justification Claims:</strong> Uses another AI to semantically analyze whether the <strong>reasoning</strong> provided for a token's importance is plausible and logically consistent.</li></ul></div>",
|
| 153 |
+
"claim_extraction_prompt_header": "You are an expert claim extraction system. Your task is to read an explanation of a text attribution analysis and extract all verifiable, factual claims into a structured JSON list. A single sentence may contain multiple distinct claims.",
|
| 154 |
+
"claim_extraction_prompt_instruction": "Each object in the list MUST have the following keys:\n1. `claim_text`: The exact sentence or phrase from the explanation that makes the claim.\n2. `claim_type`: One of the available claim types.\n3. `details`: An object containing the specific parameters for verification.",
|
| 155 |
+
"claim_extraction_prompt_context_header": "**Analysis Method Context:** {analysis_method}",
|
| 156 |
+
"claim_extraction_prompt_types_header": "**Available Claim Types:**",
|
| 157 |
+
"claim_extraction_prompt_types_details": "- `attribution_claim`: A claim that one or more tokens have high or significant attribution scores, either based on their peak (hotspot) or average influence.\n - `details`: {{ \"tokens\": [\"...\"], \"qualifier\": \"high\" | \"significant\", \"score_type\": \"peak\" | \"average\" }}\n - **Note:** Use \"peak\" for claims about hotspots or specific connections. Use \"average\" for claims about overall or average influence.\n- `token_justification_claim`: A claim that provides a specific reason for one or more tokens' importance or attribution score.\n - `details`: {{ \"tokens\": [\"...\"], \"justification\": \"...\" }}",
|
| 158 |
+
"claim_extraction_prompt_example_header": "**Example:**",
|
| 159 |
+
"claim_extraction_prompt_example_explanation": "- **Explanation sentence:** \"Overall, 'France' has the highest average influence, while '.' has a significant peak score.\"",
|
| 160 |
+
"claim_extraction_prompt_example_json": "- **Resulting JSON object:**\n ```json\n [\n {{\n \"claim_text\": \"Overall, 'France' has the highest average influence...\",\n \"claim_type\": \"attribution_claim\",\n \"details\": {{ \"tokens\": [\"France\"], \"qualifier\": \"high\", \"score_type\": \"average\" }}\n }},\n {{\n \"claim_text\": \"...while '.' has a significant peak score.\",\n \"claim_type\": \"attribution_claim\",\n \"details\": {{ \"tokens\": [\".\"], \"qualifier\": \"significant\", \"score_type\": \"peak\" }}\n }}\n ]\n ```",
|
| 161 |
+
"claim_extraction_prompt_analyze_header": "**Explanation to Analyze:**",
|
| 162 |
+
"claim_extraction_prompt_instruction_footer": "Respond with ONLY the JSON list of claims.",
|
| 163 |
+
"justification_verification_prompt_collective_reasoning": "**Collective Reasoning:** The justification may refer to multiple tokens at once (e.g., 'these tokens collectively...'). When evaluating such a claim, consider the group of tokens as a single unit and assess if the justification is plausible for them as a whole, even if it doesn't apply perfectly to each token individually.",
|
| 164 |
+
"justification_verification_prompt_header": "You are an AI fact-checker specializing in NLP and semantic reasoning. Your task is to determine if a justification for a token's importance is plausible and logically consistent, given the full context.",
|
| 165 |
+
"justification_verification_prompt_crucial_rule": "**Crucial Rule:** A justification is plausible if it presents a reasonable, creative, or contextually relevant connection. Only contradict if the reasoning is completely illogical, factually incorrect, or inconsistent with the given input or output text.",
|
| 166 |
+
"justification_verification_prompt_token_location": "**Token Location:** The \"Token in Question\" can be from either the \"Input Prompt\" or the \"Generated Text\". A token from the input can still have a crucial influence on the generated output. Do not contradict a claim simply because the token is not present in the generated text.",
|
| 167 |
+
"justification_verification_prompt_special_tokens": "**Special Tokens:** The 'Token in Question' may contain special characters from the tokenizer. `Ġ` represents a leading space (e.g., `Ġof` is ` of`), and suffixes like ` (1)` are for uniqueness (e.g., `. (1)` is just `.`). You MUST account for these when checking if a token exists in the text.",
|
| 168 |
+
"justification_verification_prompt_evaluating_justifications": "**Evaluating Justifications:** A justification should be considered plausible if it identifies a reasonable connection, even if it is not a direct or simple causal link. This includes relationships based on the broader context of the text or the grammatical structure of the language. Pay special attention to tokens that form common collocations, entities, or abbreviations; connections between such tokens should be considered plausible as they are often processed as a single semantic unit by the model.",
|
| 169 |
+
"justification_verification_prompt_linguistic_context": "**Linguistic Context for Autoregressive Models:** It is crucial to remember that in autoregressive models like this one, EVERY token directly influences the probability of the next token. Therefore, justifications based on grammatical structure, punctuation, or syntactic roles are not just valid, but represent a core part of the model's decision-making process. A token's structural role (like a preposition or a period) is a direct and important contributor to content generation. Do not dismiss these justifications as 'mere grammar'.",
|
| 170 |
+
"justification_verification_prompt_task_header": "**Your Task:**",
|
| 171 |
+
"justification_verification_prompt_task_instruction": "Based on the rule above, is the justification plausible?",
|
| 172 |
+
"justification_verification_prompt_json_instruction": "Respond with a JSON object with two keys:\n1. `is_verified`: boolean (true if the justification is plausible, false if it is illogical or incorrect).\n2. `reasoning`: A brief, one-sentence explanation for your decision.",
|
| 173 |
+
"justification_verification_prompt_footer": "Respond with ONLY the JSON object."
|
| 174 |
+
}
|
locales/en/circuit_trace_page.json
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"circuit_trace_page_title": "Circuit Trace Analysis",
|
| 3 |
+
"circuit_trace_page_desc": "Explore the internal pathways of the OLMo model. This page visualizes how information flows from input tokens through different layers and features to produce the final output, based on a novel Cross-Layer Transcoder method.",
|
| 4 |
+
"how_circuit_tracing_works_header": "How This Works: A Three-Step Process",
|
| 5 |
+
"how_circuit_tracing_works_desc": "Instead of looking at the entire model at once, this technique simplifies the analysis by focusing on 'features'—specific, learned patterns of neuron activations. By training small 'transcoder' models, we can identify which features in one layer activate features in the next, allowing us to trace a circuit of information flow.",
|
| 6 |
+
"circuit_tracing_step1_title": "1. Feature Extraction",
|
| 7 |
+
"circuit_tracing_step1_desc": "Small <strong>autoencoder</strong> models (think of them as compression tools that summarize important information) are trained on each layer of the main OLMo model to discover recurring patterns of neuron activations, which we call 'features'.",
|
| 8 |
+
"circuit_tracing_step2_title": "2. Cross-Layer Mapping",
|
| 9 |
+
"circuit_tracing_step2_desc": "Tiny <strong>transcoder</strong> models (which act like translators between layers) are trained to predict the activation of a feature in a later layer based on the activations of features in an earlier layer.",
|
| 10 |
+
"circuit_tracing_step3_title": "3. Graph Construction",
|
| 11 |
+
"circuit_tracing_step3_desc": "By connecting the most predictive feature pairs from the transcoder models, we construct a directed graph that represents the most important pathways of information flow for a given prompt.",
|
| 12 |
+
"enable_ai_explanations_circuit": "Enable AI Explanations",
|
| 13 |
+
"enable_ai_explanations_circuit_help": "Generate detailed explanations for circuit visualizations using Qwen 2.5 VL 72B",
|
| 14 |
+
"about_circuit_tracing_header": "About Circuit Tracing",
|
| 15 |
+
"about_circuit_tracing_body": "Circuit tracing is a technique to understand a model's decision-making by mapping the information flow through its internal components, much like following wires on a circuit board. This is achieved by identifying key 'features' in each layer and then building a graph showing how they influence each other from input to output. This page provides tools like the Interactive Circuit Graph, Feature Explorer, and Subnetwork Explorer to visualize and analyze these computational pathways.",
|
| 16 |
+
"no_results_warning": "Attribution graph results not found.",
|
| 17 |
+
"run_analysis_info": "Please run the analysis script first: `python3 circuit_analysis/attribution_graphs_olmo.py --prompt-index 0 --force-retrain-clt`",
|
| 18 |
+
"config_header": "Configuration",
|
| 19 |
+
"model_label": "Model:",
|
| 20 |
+
"device_label": "Device:",
|
| 21 |
+
"features_per_layer_label": "Features per layer:",
|
| 22 |
+
"training_steps_label": "Training steps:",
|
| 23 |
+
"batch_size_label": "Batch size:",
|
| 24 |
+
"learning_rate_label": "Learning rate:",
|
| 25 |
+
"interactive_analysis_header": "Interactive Analysis",
|
| 26 |
+
"select_prompt_label": "Select a Prompt to Analyze:",
|
| 27 |
+
"select_prompt_help": "This selection controls both the Interactive Circuit Graph and the Feature Explorer below.",
|
| 28 |
+
"graph_stats_header": "Graph Statistics",
|
| 29 |
+
"full_graph_nodes_label": "Full Graph Nodes",
|
| 30 |
+
"full_graph_edges_label": "Full Graph Edges",
|
| 31 |
+
"pruned_graph_nodes_label": "Pruned Graph Nodes",
|
| 32 |
+
"pruned_graph_edges_label": "Pruned Graph Edges",
|
| 33 |
+
"feature_explorer_header": "Feature Explorer",
|
| 34 |
+
"token_analysis_header": "Token Analysis",
|
| 35 |
+
"input_tokens_label": "Input tokens:",
|
| 36 |
+
"feature_explorer_title": "Feature Explorer: {prompt}",
|
| 37 |
+
"select_layer_label": "Select Layer to Explore:",
|
| 38 |
+
"layer_label_format": "Layer {layer_num}",
|
| 39 |
+
"no_feature_viz_warning": "No feature visualizations available for this prompt.",
|
| 40 |
+
"no_features_in_layer_warning": "No features found in {selected_layer}",
|
| 41 |
+
"active_features_label": "**Active Features:**",
|
| 42 |
+
"choose_feature_label": "Choose a feature:",
|
| 43 |
+
"max_activation_label": "Max Activation",
|
| 44 |
+
"mean_activation_label": "Mean Activation",
|
| 45 |
+
"sparsity_label": "Sparsity",
|
| 46 |
+
"interpretation_label": "Interpretation",
|
| 47 |
+
"top_activating_tokens_title": "Top Activating Tokens for {selected_feature}",
|
| 48 |
+
"xaxis_token_label": "Token",
|
| 49 |
+
"yaxis_activation_label": "Activation Strength",
|
| 50 |
+
"generating_feature_explanation_spinner": "Generating AI explanation for feature activation...",
|
| 51 |
+
"feature_explanation_error": "Could not generate feature explanation: {e}",
|
| 52 |
+
"ai_feature_analysis_header": "AI Feature Analysis",
|
| 53 |
+
"node_size_label": "Node Size",
|
| 54 |
+
"edge_threshold_label": "Edge Threshold",
|
| 55 |
+
"tip_scroll_horizontally": "Tip: Use mouse wheel + Shift to scroll horizontally and see all 32 layers",
|
| 56 |
+
"colorbar_title": "Activation",
|
| 57 |
+
"path_highlight_label": "Circuit path",
|
| 58 |
+
"connections_legend": "Connections",
|
| 59 |
+
"embedding_legend": "Embedding",
|
| 60 |
+
"feature_legend": "Feature",
|
| 61 |
+
"layer_nav_header": "Layer Navigation",
|
| 62 |
+
"layer_nav_desc": "This graph shows <strong>{num_layers} layers</strong> with features. Use the range slider below the graph to navigate through all layers, or use <strong>Shift + Mouse Wheel</strong> to scroll horizontally.",
|
| 63 |
+
"generating_circuit_explanation_spinner": "Generating AI explanation for circuit graph...",
|
| 64 |
+
"circuit_explanation_error": "Could not generate circuit explanation: {e}",
|
| 65 |
+
"ai_circuit_analysis_header": "AI Circuit Analysis",
|
| 66 |
+
"layer_stats_header": "Layer Statistics",
|
| 67 |
+
"total_layers_label": "Total Layers with Features",
|
| 68 |
+
"total_features_label": "Total Features",
|
| 69 |
+
"avg_features_per_layer_label": "Avg Features per Layer",
|
| 70 |
+
"features_by_layer_header": "Features by Layer",
|
| 71 |
+
"feature_dist_title": "Feature Distribution Across Layers",
|
| 72 |
+
"feature_count_label": "Feature Count",
|
| 73 |
+
"subnetwork_explorer_title": "Subnetwork Explorer",
|
| 74 |
+
"subnetwork_explorer_desc": "Select a central feature to visualize its local neighborhood, showing both its upstream causes and downstream effects within a specific connection depth.",
|
| 75 |
+
"subnetwork_graph_empty_info": "The main circuit graph has not been generated yet. Please wait for it to load.",
|
| 76 |
+
"no_features_in_graph_warning": "No features are available in the current graph view to build a subnetwork from.",
|
| 77 |
+
"select_layer_label_subnetwork": "1. Select a Layer",
|
| 78 |
+
"no_features_in_layer_subnetwork_warning": "No features to select in {selected_layer}.",
|
| 79 |
+
"select_feature_label_subnetwork": "2. Select a Central Feature",
|
| 80 |
+
"traversal_depth_label": "3. Set Connection Depth",
|
| 81 |
+
"subnetwork_graph_title": "Subnetwork Centered on Feature: {feature}",
|
| 82 |
+
"subnetwork_no_connections_info": "This feature has no connections within the selected depth.",
|
| 83 |
+
"generating_subnetwork_explanation_spinner": "Analyzing subnetwork with AI...",
|
| 84 |
+
"ai_subnetwork_analysis_header": "AI Subnetwork Analysis",
|
| 85 |
+
"subnetwork_analysis_title": "Token Activation Analysis",
|
| 86 |
+
"subnetwork_no_features_info": "No features were found in this subnetwork to analyze.",
|
| 87 |
+
"subnetwork_no_token_info": "No token activation data is available for the features in this subnetwork.",
|
| 88 |
+
"subnetwork_top_tokens_desc": "The following input tokens most strongly activated the features in this subnetwork:",
|
| 89 |
+
"subnetwork_token_interpretation_info": "This shows what parts of the prompt the subnetwork is 'paying attention to.'",
|
| 90 |
+
"what_is_a_feature_header": "Key Concept: What is a 'Feature'?",
|
| 91 |
+
"what_is_a_feature_title": "A feature is a learned, interpretable pattern of neuron activity.",
|
| 92 |
+
"what_is_a_feature_desc": "Think of it as a concept detector. For example, one feature might activate strongly for words related to 'programming,' while another might detect 'questions about history.' These features are the building blocks the model uses to understand input and construct a response. By tracing them, we can map out the model's reasoning process.",
|
| 93 |
+
"faithfulness_check_expander": "Faithfulness Check",
|
| 94 |
+
"running_faithfulness_check_spinner": "Running faithfulness check...",
|
| 95 |
+
"verified_status": "Verified",
|
| 96 |
+
"contradicted_status": "Contradicted",
|
| 97 |
+
"claim_label": "Claim",
|
| 98 |
+
"status_label": "Status",
|
| 99 |
+
"evidence_label": "Evidence",
|
| 100 |
+
"no_verifiable_claims_info": "No verifiable claims were extracted from the explanation.",
|
| 101 |
+
"faithfulness_explanation_circuit_graph_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>How This Works:</strong> The faithfulness checker verifies two types of claims from the AI's explanation:<ul><li><strong>Feature Interpretation Claims:</strong> Checks if a claimed interpretation for a feature in a specific layer (e.g., 'detecting grammatical mood') closely matches an actual feature's interpretation in that layer using fuzzy string matching.</li><li><strong>Layer Role Claims:</strong> Semantically verifies if the AI's summary of a layer section's role (e.g., 'early layers handle syntax') is a plausible generalization of the actual top feature interpretations within that section.</li></ul></div>",
|
| 102 |
+
"faithfulness_explanation_feature_explorer_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>How This Works:</strong> The faithfulness checker verifies two types of claims from the AI's explanation:<ul><li><strong>Top Token Claims:</strong> Checks if a token claimed to be a top activator for a feature is actually present in the list of top activating tokens from the analysis data.</li><li><strong>Feature Role Claims:</strong> Checks if the AI's summarized interpretation of a feature's role closely matches the detailed interpretation from the analysis data using fuzzy string matching.</li></ul></div>",
|
| 103 |
+
"faithfulness_explanation_subnetwork_graph_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>How This Works:</strong> The faithfulness checker verifies three types of claims from the AI's explanation:<ul><li><strong>Causal Claims:</strong> Checks if a claimed causal link (upstream or downstream) is valid by using fuzzy string matching to confirm that the claimed feature's interpretation exists in the actual list of upstream or downstream neighbors.</li><li><strong>Token Influence Claims:</strong> Checks if tokens claimed to be upstream influences are present in the actual list of direct upstream tokens for the central feature.</li><li><strong>Central Feature Role Claims:</strong> Checks if the AI's interpretation of the central feature's role closely matches the interpretation from the analysis data using fuzzy string matching.</li></ul></div>",
|
| 104 |
+
"claim_extraction_prompt_header": "You are an expert claim extraction system. Your task is to read an explanation of a circuit trace visualization and extract verifiable claims into a structured JSON list.",
|
| 105 |
+
"claim_extraction_prompt_instruction": "Each object in the list MUST have: `claim_text`, `claim_type`, and `details`. The `claim_text` should be the full, original sentence from the explanation.",
|
| 106 |
+
"claim_extraction_prompt_rule": "**Extraction Rules:**\n1. **Maintain Original Order.** The claims in the final JSON list must appear in the same order as they do in the source text.\n2. **Ignore legend-like descriptions.** Do not extract claims from sentences that only explain what the visual elements of the graph represent (e.g., 'Each node is a feature', 'Color indicates activation'). Only extract claims that make a specific point about *what the model is doing* for the current prompt (e.g., 'Layer 10 shows high activation for 'syntax' features').\n3. **Keep claims concise.** A single claim should not span an entire paragraph. Break down long paragraphs into multiple, smaller claims, generally one for each main point or a small group of related points.\n4. For each `interpretation_summary` or `role_summary`, extract only the core concept, usually found within single quotes (e.g., from \"notable activity for 'sentence structure'\", extract just \"sentence structure\").\n5. **Crucially, if a single sentence makes multiple claims, you MUST group them into a single claim object.**\n - For `feature_interpretation_claim`, `details` should be a list of objects, each containing `layer` and `interpretation_summary`.\n - For `layer_role_claim`, if the claim spans multiple sections (early, middle, late), `details` should be a list of objects, each with `layer_section` and `role_summary`.",
|
| 107 |
+
"claim_extraction_prompt_context_header": "**Context:** {context}",
|
| 108 |
+
"claim_extraction_prompt_types_header": "**Available Claim Types:**",
|
| 109 |
+
"claim_extraction_prompt_analyze_header": "**Explanation to Analyze:**",
|
| 110 |
+
"claim_extraction_prompt_footer": "Respond with ONLY the JSON list of claims. -",
|
| 111 |
+
"circuit_graph_claim_types": "- `feature_interpretation_claim`: A claim about the interpreted role(s) of features in one or more layers. \n - `details`: A list of objects, e.g., `[{\"layer\": 6, \"interpretation_summary\": \"sentence structure\"}, {\"layer\": 9, \"interpretation_summary\": \"country-related contexts\"}]`\n- `layer_role_claim`: A claim about the general function of one or more layer sections.\n - `details`: A list of objects, e.g., `[{\"layer_section\": \"early\", \"role_summary\": \"dissect the input\"}, {\"layer_section\": \"middle\", \"role_summary\": \"develop meaning\"}]`",
|
| 112 |
+
"feature_explorer_claim_types": "- `top_token_activation_claim`: A claim that one or more tokens are top activators for the feature.\n - `details`: { \"tokens\": [\"...\", \"...\"] }\n- `feature_interpretation_claim`: A claim about the feature's role, behavior, significance based on its layer position, or the reasoning for its token activations (e.g., \"Its presence in a late layer indicates...\"). This includes high-level insights. The `details` can be empty if no specific interpretation is mentioned.\n - `details`: { \"interpretation_summaries\": [\"...\"] }",
|
| 113 |
+
"subnetwork_graph_claim_types": "- `causal_claim`: A claim about upstream (cause) or downstream (effect) relationships. Can involve multiple features.\n - `details`: { \"source_feature_interpretations\": [\"...\", \"...\"], \"relationship\": \"upstream\" } or { \"target_feature_interpretations\": [\"...\", \"...\"], \"relationship\": \"downstream\" }\n- `feature_interpretation_claim`: A claim about the central feature's function(s).\n - `details`: { \"interpretation_summaries\": [\"...\"] }\n- `token_influence_claim`: A claim that one or more input tokens are direct upstream influences.\n - `details`: { \"tokens\": [\"...\"] }\n- `subnetwork_purpose_claim`: A claim about the overall purpose of the subnetwork.\n - `details`: { \"purpose_summary\": \"...\" }",
|
| 114 |
+
"semantic_verification_prompt_header": "You are an AI fact-checker specializing in transformer model interpretability. Your task is to determine if a \"Claimed Summary\" is a reasonable and faithful semantic summary of the \"Actual Data Points,\" taking into account general knowledge of how transformer layers function.",
|
| 115 |
+
"semantic_verification_prompt_rules_header": "**Crucial Rules:**",
|
| 116 |
+
"semantic_verification_prompt_rule_1": "1. The summary does not need to use the exact same words as the data points, but it must be semantically consistent.",
|
| 117 |
+
"semantic_verification_prompt_rule_2": "2. **CRITICAL RULE: General Principles Override Data.** For the **{layer_section}** you are analyzing, the key principle is: *{principle}*. You MUST verify claims that state this widely accepted general principle, even if the specific data points for this prompt do not perfectly align. If a claimed summary matches this principle, you MUST respond with `is_verified: true` and a reasoning that acknowledges it as a correct general principle.",
|
| 118 |
+
"semantic_verification_prompt_rule_3": "3. **Generalizations are acceptable and expected.** Summaries do not need to list every data point. A high-level, conceptually accurate summary is valid. A claim should be considered verified if it describes a correct aspect of the layer's function, even if it is not a comprehensive summary of all functions. For instance, a claim like 'dissecting the input' is a fair generalization for the early layers' role. **You MUST NOT contradict a claim simply for being 'vague' or 'general' if it is not factually incorrect.**",
|
| 119 |
+
"semantic_verification_principle_early": "**Early layers (approx. 0-10):** Handle syntax, grammar, and basic patterns.",
|
| 120 |
+
"semantic_verification_principle_middle": "**Middle layers (approx. 11-21):** Develop thematic connections, link concepts, and build abstract meaning.",
|
| 121 |
+
"semantic_verification_principle_late": "**Late layers (approx. 22-31):** Synthesize all information to finalize the output.",
|
| 122 |
+
"semantic_verification_prompt_subnetwork_header": "You are an AI fact-checker specializing in transformer model interpretability. Your task is to determine if the 'Claimed Purpose' is a reasonable and faithful semantic summary of the roles of the individual features that make up this computational subnetwork.",
|
| 123 |
+
"semantic_verification_prompt_subnetwork_rules_header": "**Crucial Rules:**",
|
| 124 |
+
"semantic_verification_prompt_subnetwork_rule_1": "1. The purpose does not need to use the exact same words as the data points, but it must be semantically consistent.",
|
| 125 |
+
"semantic_verification_prompt_subnetwork_rule_2": "2. Generalizations are acceptable if they are accurate (e.g., summarizing 'detects punctuation' and 'identifies parts of speech' as 'handling syntax' is a fair generalization).",
|
| 126 |
+
"semantic_verification_prompt_subnetwork_actual_data_header": "**Actual Data Points (Feature interpretations from the subnetwork):**",
|
| 127 |
+
"semantic_verification_prompt_subnetwork_claimed_purpose_header": "**Claimed Purpose:**",
|
| 128 |
+
"semantic_verification_prompt_actual_data_header": "**Actual Data Points (Top feature interpretations from this layer section):**",
|
| 129 |
+
"semantic_verification_prompt_claimed_summary_header": "**Claimed Summary:**",
|
| 130 |
+
"semantic_verification_prompt_task_header": "**Your Task:**",
|
| 131 |
+
"semantic_verification_prompt_task_instruction": "Based on the rules above, is the summary a fair and accurate semantic description of the data? Respond with a JSON object with two keys: `is_verified` (boolean) and `reasoning` (one-sentence explanation). -",
|
| 132 |
+
"semantic_verification_prompt_feature_role_header": "You are an AI fact-checker specializing in transformer model interpretability. Your task is to determine if the 'Claimed Role' is a reasonable and faithful semantic summary of the provided 'Feature Evidence.'",
|
| 133 |
+
"semantic_verification_prompt_feature_role_rules_header": "**Crucial Rules:**",
|
| 134 |
+
"semantic_verification_prompt_feature_role_rule_1": "1. The Claimed Role does not need to use the exact same words as the evidence, but it must be semantically consistent and a plausible interpretation.",
|
| 135 |
+
"semantic_verification_prompt_feature_role_rule_2": "2. Consider the layer position (early/middle/late) as important context. A claim that aligns with the typical function of that layer section is more likely to be correct.",
|
| 136 |
+
"semantic_verification_prompt_feature_role_guidance_early": "Treat claims mentioning foundational grammar, basic sentence structure, or token order as consistent with early-layer behavior even if the exact wording differs from the evidence.",
|
| 137 |
+
"semantic_verification_prompt_feature_role_guidance_middle": "Treat claims about integrating context, linking concepts, or building thematic meaning as consistent with middle-layer behavior even when phrased differently.",
|
| 138 |
+
"semantic_verification_prompt_feature_role_guidance_late": "Treat claims about synthesizing information, finalizing answers, or generating outputs as consistent with late-layer behavior even if the exact words differ from the evidence.",
|
| 139 |
+
"semantic_verification_prompt_feature_role_rule_3": "3. If Upstream or Downstream connections are provided, use them to evaluate claims about the feature acting as a 'bridge', 'hub', or 'integrating' information. The claim should be consistent with the interpretations of the connected features.",
|
| 140 |
+
"semantic_verification_prompt_feature_role_evidence_header": "**Feature Evidence:**",
|
| 141 |
+
"semantic_verification_prompt_feature_role_upstream_header": "- **Upstream Connections (Top Interpretations):** {interpretations}",
|
| 142 |
+
"semantic_verification_prompt_feature_role_downstream_header": "- **Downstream Connections (Top Interpretations):** {interpretations}",
|
| 143 |
+
"semantic_verification_prompt_feature_role_claimed_role_header": "**Claimed Role:**",
|
| 144 |
+
"semantic_verification_prompt_token_reasoning_header": "You are an AI fact-checker specializing in transformer model interpretability. Your task is to determine if the 'Claimed Explanation' for why certain tokens activate a feature is a reasonable and faithful semantic summary of the provided 'Feature Evidence.'",
|
| 145 |
+
"semantic_verification_prompt_token_reasoning_rules_header": "**Crucial Rules:**",
|
| 146 |
+
"semantic_verification_prompt_token_reasoning_rule_1": "1. The explanation does not need to use the exact same words as the evidence, but it must be semantically consistent and a plausible interpretation of the token-feature interaction.",
|
| 147 |
+
"semantic_verification_prompt_token_reasoning_rule_2": "2. Focus on the reasoning provided. The claim is not just that the tokens activate the feature, but *why* they do. Is the explanation logical given the feature's role and layer position?",
|
| 148 |
+
"semantic_verification_prompt_token_reasoning_evidence_header": "**Feature Evidence:**",
|
| 149 |
+
"semantic_verification_prompt_token_reasoning_claimed_explanation_header": "**Claimed Explanation:**",
|
| 150 |
+
"semantic_verification_prompt_causal_reasoning_header": "You are an AI fact-checker specializing in transformer model interpretability. Your task is to determine if the 'Claimed Causal Explanation' is a reasonable and faithful summary of the provided 'Causal Evidence.'",
|
| 151 |
+
"semantic_verification_prompt_causal_reasoning_rules_header": "**Crucial Rules:**",
|
| 152 |
+
"semantic_verification_prompt_causal_reasoning_rule_1": "1. The explanation must be semantically consistent with the roles of the source, central, and target features.",
|
| 153 |
+
"semantic_verification_prompt_causal_reasoning_rule_2": "2. Focus on the reasoning. The claim is not just that a connection exists, but *why* it exists or what its function is. Is the explanation logical?",
|
| 154 |
+
"semantic_verification_prompt_causal_reasoning_evidence_header": "**Causal Evidence:**",
|
| 155 |
+
"semantic_verification_prompt_causal_reasoning_claimed_explanation_header": "**Claimed Causal Explanation:**",
|
| 156 |
+
"explanation_prompt_header": "You are an expert in neural network interpretability and circuit tracing analysis. Analyze this visualization that shows how information flows through the OLMo2 7B language model using Cross-Layer Transcoders.",
|
| 157 |
+
"explanation_prompt_context_header": "## Context",
|
| 158 |
+
"explanation_prompt_instructions_header": "## Instructions",
|
| 159 |
+
"circuit_graph_instruction_header": "Provide a structured, layer-by-layer analysis of the circuit graph. Your response MUST use smaller Markdown headings (`####`). Do not refer to specific feature numbers (e.g., \"feature_411\"); instead, describe their function based on their interpretation.",
|
| 160 |
+
"circuit_graph_instruction_intro": "#### Introduction: What This Graph Shows\nExplain what this specific circuit graph visualizes for the given prompt. Mention that it shows information flow from input tokens through feature activations in different layers.",
|
| 161 |
+
"circuit_graph_instruction_early": "#### Early Layers (0-10): Input Processing\nBased on the top features provided in the context, describe the primary role of these layers. Explain how they deconstruct the input's basic grammar, syntax, or key terms by describing the functions of the active features.",
|
| 162 |
+
"circuit_graph_instruction_middle": "#### Middle Layers (11-21): Developing Meaning\nExplain what these layers do with the initial patterns. Describe how they link concepts, build relationships, or shift the focus of the analysis toward a more abstract understanding.",
|
| 163 |
+
"circuit_graph_instruction_late": "#### Late Layers (22-31): Finalizing the Output\nDescribe how these layers synthesize all the previous information to produce the final result, focusing on how the top features contribute to the model's output.",
|
| 164 |
+
"circuit_graph_instruction_insight": "#### Primary Insight\nConclude with a key takeaway from this analysis. What is the most important or surprising aspect of the model's strategy for this prompt?",
|
| 165 |
+
"circuit_graph_instruction_footer": "Ensure your entire response follows this structure of headings and paragraphs. Do not use bullet points for the main sections.",
|
| 166 |
+
"feature_explorer_instruction_header": "Provide a structured analysis of the feature shown. Your response MUST be a Markdown bulleted list, with each bullet point on a NEW LINE. Use the following structure:",
|
| 167 |
+
"feature_explorer_instruction_role": "- **Feature Role and Layer Context:** Explain the feature's interpretation and what its presence in this specific layer (early/middle/late) implies about its function.",
|
| 168 |
+
"feature_explorer_instruction_activations": "- **Key Token Activations:** Identify the top activating tokens and explain why they are relevant to the feature's role.",
|
| 169 |
+
"feature_explorer_instruction_insight": "- **Overall Insight:** Provide a concluding insight about what this feature's behavior reveals about the model's information processing strategy.",
|
| 170 |
+
"feature_explorer_instruction_footer": "Ensure your output is ONLY this three-bullet list.",
|
| 171 |
+
"subnetwork_graph_instruction_header": "Provide a concise, insightful analysis of this subnetwork. Your response MUST be a Markdown bulleted list, with each bullet point on a NEW LINE. Use the following structure:",
|
| 172 |
+
"subnetwork_graph_instruction_role": "- **Central Feature's Role:** Briefly explain the function of the central feature based on its interpretation and layer position.",
|
| 173 |
+
"subnetwork_graph_instruction_upstream": "- **Upstream Influence:** Describe which earlier features or input tokens (the causes) are most strongly activating this central feature. When mentioning features, refer to their interpretation provided in the context.",
|
| 174 |
+
"subnetwork_graph_instruction_downstream": "- **Downstream Impact:** Describe what later features (the effects) this central feature contributes to most strongly. When mentioning features, refer to their interpretation provided in the context.",
|
| 175 |
+
"subnetwork_graph_instruction_purpose": "- **Subnetwork's Purpose:** Synthesize the above points to hypothesize the overall purpose of this specific computational pathway in processing the prompt.",
|
| 176 |
+
"subnetwork_graph_instruction_footer": "Ensure your output is ONLY this four-bullet list.",
|
| 177 |
+
"context_unspecified_viz": "This is a circuit tracing visualization showing information flow through the model.",
|
| 178 |
+
"instruction_unspecified_viz": "Explain this visualization.",
|
| 179 |
+
"circuit_graph_context_header": "This is a circuit tracing graph for the prompt: \"{prompt}\"",
|
| 180 |
+
"circuit_graph_context_tokens": "Input tokens: {tokens}",
|
| 181 |
+
"circuit_graph_context_summary_header": "#### Key Feature Summary by Layer Section\nHere are the most active features in each section of the model for this prompt:",
|
| 182 |
+
"circuit_graph_context_early_header": "**Early Layers (0-10):**",
|
| 183 |
+
"circuit_graph_context_middle_header": "**Middle Layers (11-21):**",
|
| 184 |
+
"circuit_graph_context_late_header": "**Late Layers (22-31):**",
|
| 185 |
+
"circuit_graph_context_no_features": "No significantly active features found.",
|
| 186 |
+
"circuit_graph_context_feature_line": "- In L{layer}, a feature interpreted as \"{interpretation}\" (Activation: {activation:.2f})",
|
| 187 |
+
"subnetwork_context_header": "This is a subnetwork visualization from a larger circuit trace for the prompt: \"{prompt}\"",
|
| 188 |
+
"subnetwork_context_centered_on": "The subnetwork is centered around:",
|
| 189 |
+
"subnetwork_context_feature": "- **Feature:** {name}",
|
| 190 |
+
"subnetwork_context_layer": "- **Layer:** {layer}",
|
| 191 |
+
"subnetwork_context_interpretation": "- **Interpretation:** \"{interpretation}\"",
|
| 192 |
+
"subnetwork_context_no_interpretation": "No interpretation available.",
|
| 193 |
+
"subnetwork_context_upstream_header": "\nKey Upstream Features (Causes) in this Subgraph:",
|
| 194 |
+
"subnetwork_context_downstream_header": "\nKey Downstream Features (Effects) in this Subgraph:",
|
| 195 |
+
"subnetwork_context_feature_line": "- L{layer} {feature_name}: \"{interpretation}\"",
|
| 196 |
+
"subnetwork_context_depth": "The view shows connections within a depth of **{depth}** hops from the central feature (highlighted in crimson).",
|
| 197 |
+
"subnetwork_context_stats_header": "Subnetwork Statistics:",
|
| 198 |
+
"subnetwork_context_stats_nodes": "- **Nodes:** {nodes}",
|
| 199 |
+
"subnetwork_context_stats_edges": "- **Edges:** {edges}",
|
| 200 |
+
"subnetwork_context_viz_header": "The visualization shows:",
|
| 201 |
+
"subnetwork_context_viz_central": "- The central feature (crimson border) and its neighbors.",
|
| 202 |
+
"subnetwork_context_viz_nodes": "- Upstream nodes (causes) and downstream nodes (effects).",
|
| 203 |
+
"subnetwork_context_viz_lilac": "- Lilac nodes are input token embeddings.",
|
| 204 |
+
"subnetwork_context_viz_other": "- Other nodes are features, colored by activation strength (viridis scale).",
|
| 205 |
+
"subnetwork_context_viz_edges": "- Edge thickness represents connection weights.",
|
| 206 |
+
"feature_explorer_context_header": "This is a feature explorer visualization for the prompt: \"{prompt}\"",
|
| 207 |
+
"feature_explorer_context_model_header": "**Model Context:** The model is OLMo-2-7B, which has 32 layers (indexed 0-31). Layer 0 is the first layer (closest to input embeddings), and Layer 31 is the last layer (closest to the final output). Early layers (e.g., 0-10) handle basic patterns, while late layers (e.g., 22-31) handle more abstract concepts.",
|
| 208 |
+
"feature_explorer_context_analyzing_feature": "We are analyzing **Feature {feature}** in **Layer {layer}**, which is {position} layer in the model.",
|
| 209 |
+
"feature_explorer_context_analyzing_feature_no_pos": "We are analyzing **Feature {feature}** in **Layer {layer}**.",
|
| 210 |
+
"feature_explorer_context_position_early": "an early",
|
| 211 |
+
"feature_explorer_context_position_middle": "a middle",
|
| 212 |
+
"feature_explorer_context_position_late": "a late",
|
| 213 |
+
"feature_explorer_context_tokens": "**Input tokens:** {tokens}",
|
| 214 |
+
"feature_explorer_context_interpretation": "**Feature Interpretation:** \"{interpretation}\"",
|
| 215 |
+
"feature_explorer_context_no_interpretation": "No interpretation available.",
|
| 216 |
+
"feature_explorer_context_footer": "The bar chart shows which input tokens caused the highest activation for this specific feature within its layer. Analyze the relationship between the tokens and the feature's interpretation, keeping the layer's position in mind."
|
| 217 |
+
}
|
locales/en/common.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"llm_analysis_suite": "Explainable Language Interpretability Analysis Tool",
|
| 3 |
+
"main_menu": "Main Menu",
|
| 4 |
+
"attribution_analysis": "Attribution Analysis",
|
| 5 |
+
"function_vectors": "Function Vectors",
|
| 6 |
+
"circuit_tracing": "Circuit Tracing",
|
| 7 |
+
"language": "Language",
|
| 8 |
+
"unable_to_generate_explanation": "Unable to generate explanation at this time.",
|
| 9 |
+
"clear_cache_button": "Clear Cache & Rerun",
|
| 10 |
+
"q_influential_docs_plausibility_help": "How plausible did you find the documents identified by the Influence Tracer? (1=Not plausible, 5=Very plausible)",
|
| 11 |
+
"comprehension_qs_subheader": "Comprehension Questions",
|
| 12 |
+
"comprehension_qs_desc": "Please answer the following questions to the best of your ability based on your understanding of the visualizations.",
|
| 13 |
+
"submit_feedback_button": "Submit Feedback",
|
| 14 |
+
"feedback_success_message": "Thank you, your feedback has been submitted!",
|
| 15 |
+
"feedback_please_answer_all_qs": "Please answer all comprehension questions before submitting.",
|
| 16 |
+
|
| 17 |
+
"what_is_this_function_type": "What is this function type?",
|
| 18 |
+
"desc_abstractive_tasks": "These tasks require the model to generate new text that captures the essence of the source text, rather than just extracting parts of it. Examples include summarization or paraphrasing.",
|
| 19 |
+
"desc_multiple_choice_qa": "The model is given a question and a set of options, and it must choose the correct answer from the list. This tests reasoning and comprehension over a fixed set of choices.",
|
| 20 |
+
"desc_text_classification": "The model must assign a predefined category or label to a piece of text. Common examples include sentiment analysis (positive/negative), topic classification, or spam detection.",
|
| 21 |
+
"desc_extractive_tasks": "These tasks involve identifying and extracting a specific span of text directly from a given context. This is often used for question answering where the answer is explicitly stated in the text.",
|
| 22 |
+
"desc_named_entity_recognition": "A sub-task of extractive tasks where the model identifies and categorizes named entities such as people, organizations, locations, dates, and other specific terms in text.",
|
| 23 |
+
"desc_text_generation": "Open-ended text creation tasks where the model generates creative, coherent, or contextually appropriate text based on a prompt. Examples include writing a story, a poem, or continuing a paragraph.",
|
| 24 |
+
|
| 25 |
+
"likert_scale_meaning": "1 = Strongly Disagree/Not at all Clear, 5 = Strongly Agree/Very Clear",
|
| 26 |
+
"q1_pca_clarity": "How clear was the 3D PCA visualization?",
|
| 27 |
+
"q2_type_attribution_clarity": "How clear was the Function Type Attribution bar chart?",
|
| 28 |
+
"q_layer_evolution_plausibility": "How plausible did you find the Layer Evolution analysis (the way function changes across layers)?",
|
| 29 |
+
|
| 30 |
+
"ct_q_main_graph_clarity": "How clear was the main circuit graph visualization for understanding the overall information flow?",
|
| 31 |
+
"ct_q_feature_explorer_usefulness": "How useful was the Feature Explorer for understanding individual components?",
|
| 32 |
+
"ct_q_subnetwork_clarity": "How helpful was the Subnetwork view for tracing specific pathways?",
|
| 33 |
+
"ct_q1": "What is the primary role of the EARLY layers (e.g., 0-10) in circuit tracing?",
|
| 34 |
+
"ct_q1_option_a": "To synthesize final concepts and make complex decisions.",
|
| 35 |
+
"ct_q1_option_b": "To process basic patterns like syntax and word order from the input text.",
|
| 36 |
+
"ct_q1_option_c": "To link abstract ideas from different parts of the prompt together.",
|
| 37 |
+
"ct_q2": "What is the primary benefit of using the **Subnetwork Explorer** to focus on a single feature?",
|
| 38 |
+
"ct_q2_option_a": "To see all features in the model at once.",
|
| 39 |
+
"ct_q2_option_b": "To understand the local computational role of a feature by seeing its direct causes (inputs) and effects (outputs).",
|
| 40 |
+
"ct_q2_option_c": "To change the color and size of the nodes in the graph.",
|
| 41 |
+
"ct_q3": "If an early-layer feature (e.g., detecting syntax) strongly connects to a late-layer feature (e.g., identifying a concept), what does this pathway likely represent?",
|
| 42 |
+
"ct_q3_option_a": "The model using foundational grammar to build a more abstract, conceptual understanding.",
|
| 43 |
+
"ct_q3_option_b": "A random, meaningless connection that should be ignored.",
|
| 44 |
+
"ct_q3_option_c": "The model only paying attention to the final layers and ignoring early ones."
|
| 45 |
+
}
|
locales/en/function_vectors_page.json
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"fv_page_title": "<i class='bi bi-cpu'></i> Function Vector Analysis",
|
| 3 |
+
"fv_page_desc": "This page explores the concept of <strong>function vectors</strong>—high-dimensional representations of what a model 'understands' about a prompt's underlying purpose. By visualizing these vectors, we can see how the model groups similar tasks and instructions.",
|
| 4 |
+
"viz_dir_not_found_error": "Visualizations directory not found. Please run the function vector analysis first.",
|
| 5 |
+
"dataset_overview": "Dataset Overview",
|
| 6 |
+
"interactive_analysis_section_header": "<i class='bi bi-pencil-square'></i> Interactive Analysis",
|
| 7 |
+
"pca_3d_section_header": "<i class='bi bi-dice-3'></i> 3D PCA Visualization of Function Vectors",
|
| 8 |
+
"run_analysis_for_viz_info": "<i class='bi bi-info-circle'></i> Run an interactive analysis below to see your own prompt plotted in this space.",
|
| 9 |
+
"dataset_overview_desc_long": "The following examples are the prompts used to generate the vectorized dataset. This helps build intuition for how different tasks are represented in the model's vector space, which is visualized in the plot below.",
|
| 10 |
+
"try_your_own_subheader": "Or try one of these examples:",
|
| 11 |
+
"pca_box_title": "<i class='bi bi-box'></i> Interactive 3D Principal Component Analysis",
|
| 12 |
+
"pca_box_purpose": "<strong>Purpose:</strong> Reduces high-dimensional function vectors to 3D space while preserving maximum variance",
|
| 13 |
+
"pca_box_how_to": "<strong>How to interact:</strong> Click and drag to rotate the plot. Hover over points to see which category they belong to.",
|
| 14 |
+
"pca_box_features": "<strong>Key Features:</strong> 3D rotation • Zoom & pan • Hover details • Shape & color coding • Legend toggle",
|
| 15 |
+
"pca_box_elements": "<strong>Visual Elements:</strong> 🔵 Circles (Abstractive) • 🔷 Diamonds (QA) • 🟦 Squares (Classification) • ✖️ Crosses (Extractive) • 🔹 Open Diamonds (NER) • ⬜ Open Squares (Generation)",
|
| 16 |
+
"pca_box_best_for": "<strong>Best For:</strong> Understanding overall functional organization and dimensional relationships",
|
| 17 |
+
"generating_enhanced_pca_info": "🎯 Generating enhanced 3D PCA with your input!",
|
| 18 |
+
"error_creating_enhanced_pca": "Error creating enhanced PCA visualization: {e}",
|
| 19 |
+
"pca_3d_with_input_title": "3D PCA with Your Input<br><sub>Red star shows where your text sits in function space</sub>",
|
| 20 |
+
"your_input_legend": "Your Input",
|
| 21 |
+
"your_input_hover_title": "Your Input Text",
|
| 22 |
+
"your_input_analysis_desc": "🔍 **Your Input Analysis:** The red star shows where **\\\"{input_text}\\\"** sits in the 3D function space. Notice which function types it's closest to - this reveals what linguistic capabilities your text most strongly activates!",
|
| 23 |
+
"pca_3d_standard_title": "3D PCA of Function Categories<br><sub>Interactive visualization of functional relationships</sub>",
|
| 24 |
+
"standard_view_desc": "🔍 **Standard View:** This shows all 120 function categories in 3D space using actual computed vectors. Run an interactive analysis above to see your input as a red diamond in this visualization!",
|
| 25 |
+
"error_creating_standard_pca": "Error creating standard PCA visualization: {e}",
|
| 26 |
+
"pca_viz_not_found_warning": "3D PCA visualization not found. Please generate it using the analysis script.",
|
| 27 |
+
"pca_key_insights": "<strong>Key Insights:</strong> Notice how English translation tasks (English-German, English-Spanish, etc.) cluster together, and how different function types occupy distinct regions of the 3D space, revealing the model's internal functional organization.",
|
| 28 |
+
"error_loading_pca_viz": "Error loading 3D PCA visualization: {e}",
|
| 29 |
+
"interactive_analysis_box_title": "🔬 Interactive Function Vector & Layer Evolution Analysis",
|
| 30 |
+
"interactive_analysis_box_purpose": "<strong>Purpose:</strong> Analyze how your input text activates different linguistic functions from our balanced dataset of 120 categories",
|
| 31 |
+
"interactive_analysis_box_features": "<strong>Features:</strong> Real-time analysis • Function attribution across 6 types • Layer evolution • Token-level analysis • Visual outputs",
|
| 32 |
+
"interactive_analysis_box_model": "<strong>Model:</strong> OLMo-2-1124-7B analyzing against balanced function vectors (20 categories per function type)",
|
| 33 |
+
"interactive_analysis_box_best_for": "<strong>Best For:</strong> Understanding how specific text inputs activate and evolve functional representations across diverse linguistic tasks",
|
| 34 |
+
"input_text_header": "",
|
| 35 |
+
"input_text_label": "Enter your prompt",
|
| 36 |
+
"input_text_placeholder": "E.g., 'Translate 'Good morning' to German' or 'What is the capital of France?'",
|
| 37 |
+
"input_text_help": "Enter any text you want to analyze. The system will show which linguistic functions are activated and how they evolve through the model layers.",
|
| 38 |
+
"about_dataset_expander": "About the function vector dataset",
|
| 39 |
+
"balanced_dataset_title": "Dataset Composition",
|
| 40 |
+
"balanced_dataset_body": "The comparison dataset contains 600 prompts, covering 120 categories across 6 main function types.",
|
| 41 |
+
"analyze_button": "Analyze Text",
|
| 42 |
+
"running_analysis_spinner": "Running analysis...",
|
| 43 |
+
"analysis_failed_error": "Analysis failed. Please ensure the function vector data has been generated.",
|
| 44 |
+
"analysis_error": "Error during analysis: {e}",
|
| 45 |
+
"ensure_model_and_data_info": "Please ensure the OLMo-2-1124-7B model and function vector data are available.",
|
| 46 |
+
"example_queries_header": "<i class='bi bi-lightbulb'></i> Example Queries to Try",
|
| 47 |
+
"example_queries_desc": "*These examples showcase different function types from our balanced dataset:*",
|
| 48 |
+
"example_query_help": "Click to analyze: {example}",
|
| 49 |
+
"analysis_complete_success": "Analysis completed!",
|
| 50 |
+
"analyzed_text_header": "Analyzed Text",
|
| 51 |
+
"function_types_tab": "<i class='bi bi-bar-chart-line'></i> Function Type Attribution",
|
| 52 |
+
"category_analysis_tab": "<i class='bi bi-pie-chart'></i> Category Analysis",
|
| 53 |
+
"layer_evolution_tab": "<i class='bi bi-layers'></i> Layer Evolution Analysis",
|
| 54 |
+
"ai_explanation_header": "<i class='bi bi-robot'></i> AI-Powered Explanation",
|
| 55 |
+
"generating_ai_explanation_spinner": "Generating AI-powered explanation...",
|
| 56 |
+
"enable_ai_explanation_checkbox": "Enable AI Explanation",
|
| 57 |
+
"enable_ai_explanation_help": "Generate a natural language explanation of the analysis results using the Qwen-72B-VL model.",
|
| 58 |
+
"pca_explanation_prompt": "You are an expert AI analyst. Your task is to explain the positioning of a user's prompt on a 3D PCA plot of function vectors. The plot visualizes how a language model categorizes prompts based on their underlying function, with similar functions clustering together.\\n\\n**User's Prompt:** \"{input_text}\"\\n\\n**Analysis Data (Top 3 Closest Matches):**\\n- **Function Types:** {top_types}\\n- **Specific Categories:** {top_cats}\\n\\nBased on this data, please provide a concise, analytical explanation in three distinct parts. **Crucially, you MUST use markdown headings (`####`) for each part and follow the requested structure exactly.**\\n\\n#### Overall Placement\\nStart with a high-level summary of where the prompt is located in the PCA plot. Mention which general functional neighborhood it falls into.\\n\\n#### Top Function Type Attributions\\nAnalyze the top 3 most dominant function types. For each of the top 3 types, briefly explain why the user's prompt aligns with it, referencing the prompt's content and the nature of that function type.\\n\\n#### Top Specific Category Attribution\\nDiscuss the top 3 specific categories. For each category, briefly explain the connection and why it makes sense as a close neighbor to the user's prompt.\\n\\nStructure your answer with clear headings for each of the three parts. Ground your entire explanation in the provided data.",
|
| 59 |
+
"function_type_attribution_header": "This chart shows how strongly your input aligns with the six major function types defined in the model's training data. A higher score indicates a stronger match.",
|
| 60 |
+
"top_category_attribution_header": "This sunburst chart breaks down the attribution into more granular categories, showing the top 20 most similar functions to your input.",
|
| 61 |
+
"sunburst_chart_title": "Top 20 Category Attributions",
|
| 62 |
+
"missing_category_mapping_warning": "Some categories could not be assigned to a function type. They were skipped in the chart: {categories}",
|
| 63 |
+
"no_mapped_categories_info": "No categories with valid function-type mappings were available to display.",
|
| 64 |
+
"unmapped_function_type": "Unmapped Function Type",
|
| 65 |
+
"layer_evolution_header": "A language model isn't a single entity; it's composed of many sequential layers, much like a factory assembly line. When you provide a prompt, the information passes through each layer, getting progressively refined. Early layers handle basic syntax and word meanings, middle layers build more complex relationships, and final layers synthesize this information to produce an output. This analysis visualizes that journey, showing how the model's 'understanding' of your prompt evolves. The charts below reveal which parts of this 'assembly line' are most active for your specific text, offering clues into the model's reasoning process.",
|
| 66 |
+
"evolution_explanation_prompt": "You are an expert AI analyst. Your task is to explain two charts about layer evolution for a user's prompt.\\n\\n**User's Prompt:** \"{input_text}\"\\n\\n**Analysis Data:**\\n- **Peak Activation:** Layer {peak_activation_layer} (Strength: {peak_activation_strength:.2f})\\n- **Biggest Change:** Between Layer {biggest_change_start_layer} and {biggest_change_end_layer} (Change Magnitude: {biggest_change_magnitude:.2f})\\n\\nBased on this data, provide a detailed (2-3 sentences per part) explanation in two parts. **You MUST use markdown headings (`####`) for each part.**\\n\\n#### Activation Strength Analysis\\nExplain the significance of the peak activation occurring at layer {peak_activation_layer}. What does this suggest about the model's processing stage (e.g., early feature extraction, mid-level abstraction, or late-stage decision making)?\\n\\n#### Layer-to-Layer Change Analysis\\nExplain the significance of the largest change occurring between layers {biggest_change_start_layer} and {biggest_change_end_layer}. What does this shift imply about the model's processing?\\n\\nGround your explanation in the provided data.",
|
| 67 |
+
"attribution_score_xaxis": "Attribution Score (Cosine Similarity)",
|
| 68 |
+
"running_layer_evolution_spinner": "Running layer evolution analysis...",
|
| 69 |
+
"evolution_not_available_info": "Layer evolution analysis was not run or failed. Please enable it in the options and try again.",
|
| 70 |
+
"pca_3d_title": "3D PCA of {lang} Function Categories",
|
| 71 |
+
"legend_title": "Function Types",
|
| 72 |
+
"category_examples_desc": "",
|
| 73 |
+
"no_examples_for_type": "No examples available for this function type in the selected language.",
|
| 74 |
+
"prompt_examples_for_category": "Prompt Examples for {category}",
|
| 75 |
+
"no_examples_for_category_specific": "No examples available for this specific category.",
|
| 76 |
+
"function_types_subheader": "Function Types",
|
| 77 |
+
"select_function_type_label": "Select a function type to explore",
|
| 78 |
+
"prompt_examples_for_category_header": "Prompts Used for {category}",
|
| 79 |
+
"show_all_button": "Show all {count} categories",
|
| 80 |
+
"show_less_button": "Show less",
|
| 81 |
+
"abstractive_tasks": "Abstractive Tasks",
|
| 82 |
+
"multiple_choice_qa": "Multiple Choice QA",
|
| 83 |
+
"text_classification": "Text Classification",
|
| 84 |
+
"extractive_tasks": "Extractive Tasks",
|
| 85 |
+
"named_entity_recognition": "Named Entity Recognition",
|
| 86 |
+
"text_generation": "Text Generation",
|
| 87 |
+
"feedback_survey_header": "Feedback & Comprehension Survey",
|
| 88 |
+
"feedback_survey_desc": "Your feedback is valuable for improving this tool. Please take a moment to answer these questions.",
|
| 89 |
+
"ux_feedback_subheader": "User Experience Feedback",
|
| 90 |
+
"comprehension_subheader": "Comprehension Questions",
|
| 91 |
+
"likert_scale_meaning": "Rate on a scale of 1 (Not clear at all) to 5 (Very clear).",
|
| 92 |
+
"q1_pca_clarity": "How clear was the 3D PCA visualization for showing where your input fits among other functions?",
|
| 93 |
+
"q2_cognitive_load": "How mentally demanding did you find it to interpret the analysis results as a whole?",
|
| 94 |
+
"submit_feedback_button": "Submit Feedback",
|
| 95 |
+
"feedback_success_message": "Thank you for your feedback!",
|
| 96 |
+
"feedback_error_message": "Sorry, there was an error submitting your feedback: {e}",
|
| 97 |
+
"feedback_please_answer_all_qs": "Please answer all comprehension questions before submitting.",
|
| 98 |
+
"comprehension_qs_subheader": "Comprehension Questions",
|
| 99 |
+
"comprehension_qs_desc": "Please answer the following questions to the best of your ability. Your answers help us evaluate the clarity of the visualizations.",
|
| 100 |
+
|
| 101 |
+
"desc_text_generation": "Open-ended text generation, including creative writing or continuing a story.",
|
| 102 |
+
|
| 103 |
+
"how_vectors_are_made_header": "How Are These Vectors Created?",
|
| 104 |
+
"how_vectors_are_made_desc": "The process of creating a function vector is a multi-step pipeline that transforms raw text into a meaningful numerical representation. The diagram below illustrates this transformation, showing how a simple prompt is processed by the model to produce a vector that encapsulates its core function.",
|
| 105 |
+
"how_vectors_are_made_step1_title": "STEP 1: INPUT PROMPT",
|
| 106 |
+
"how_vectors_are_made_step2_title": "STEP 2: TOKENIZER",
|
| 107 |
+
"how_vectors_are_made_step3_title": "STEP 3: OLMo-2-7B MODEL",
|
| 108 |
+
"how_vectors_are_made_step3_desc": "Hidden States from all 32 Layers",
|
| 109 |
+
"how_vectors_are_made_step4_title": "STEP 4: FINAL LAYER EXTRACTION",
|
| 110 |
+
"how_vectors_are_made_step4_desc": "Vector of 4096 numbers",
|
| 111 |
+
"how_vectors_are_made_step5_title": "STEP 5: FUNCTION VECTOR",
|
| 112 |
+
"how_vectors_are_made_step1_example": "Translate 'Good morning' to German",
|
| 113 |
+
"how_vectors_are_made_step2_example": "[\"Translate\", \"'\", \"Good\", ..., \"German\"]",
|
| 114 |
+
|
| 115 |
+
"fv_q1": "What does a 'function vector' represent in this context?",
|
| 116 |
+
"fv_q1_option_a": "A single word from the input prompt.",
|
| 117 |
+
"fv_q1_option_b": "The grammatical structure of the prompt.",
|
| 118 |
+
"fv_q1_option_c": "A numerical fingerprint of the prompt's core purpose.",
|
| 119 |
+
|
| 120 |
+
"fv_q2": "What is the primary purpose of using Principal Component Analysis (PCA) for the 3D visualization?",
|
| 121 |
+
"fv_q2_option_a": "To make the plot look more colorful.",
|
| 122 |
+
"fv_q2_option_b": "To reduce high-dimensional vector data into a 3D space for visualization.",
|
| 123 |
+
"fv_q2_option_c": "To speed up the model's processing time.",
|
| 124 |
+
|
| 125 |
+
"fv_q3": "In the 3D PCA plot, what does the distance between two points indicate?",
|
| 126 |
+
"fv_q3_option_a": "The difference in length between two prompts.",
|
| 127 |
+
"fv_q3_option_c": "The functional similarity between the prompts (closer points are more similar).",
|
| 128 |
+
"fv_q3_option_d": "The number of layers activated by each prompt.",
|
| 129 |
+
"activation_strength_plot_title": "Activation Strength Across Layers",
|
| 130 |
+
"layer_changes_plot_title": "Representational Change Between Layers",
|
| 131 |
+
"fv_faithfulness_explanation_pca_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>How This Works:</strong> The faithfulness checker verifies three types of claims from the AI's explanation:<ul><li><strong>Ranking Claims:</strong> Checks if a claimed 'most similar' function type or category is actually within the top 3 matches based on cosine similarity scores.</li><li><strong>Positional Claims:</strong> Semantically verifies if the AI's description of the input's position (e.g., 'near text classification') is a plausible summary of the actual top-ranked functions.</li><li><strong>Justification Claims:</strong> Semantically analyzes whether the reasoning provided for a category's relevance is plausible and logically consistent with the input prompt.</li></ul></div>",
|
| 132 |
+
"fv_faithfulness_explanation_evolution_html": "<div style='font-size: 0.9rem; margin-bottom: 1rem;'><strong>How This Works:</strong> The faithfulness checker verifies three types of claims from the AI's explanation:<ul><li><strong>Peak/Trough Claims:</strong> Checks if a claim about a peak event correctly identifies the layer where the event occurred.</li><li><strong>Numerical Claims:</strong> Checks if a specific numerical value mentioned in the explanation correctly matches the calculated value.</li><li><strong>Layer Claims:</strong> Checks if a claim correctly identifies the layer index for a specific metric.</li></ul></div>",
|
| 133 |
+
"fv_claim_extraction_prompt_header": "You are an expert claim extraction system. Your task is to read an explanation of a data visualization and extract all verifiable, factual claims into a structured JSON list. A single sentence may contain multiple claims.",
|
| 134 |
+
"fv_claim_extraction_prompt_instruction": "Each object in the list MUST have the following keys:\n1. `claim_text`: The exact sentence or phrase from the explanation that makes the claim.\n2. `claim_type`: One of the available claim types for the given context.\n3. `details`: An object containing the specific parameters for verification.",
|
| 135 |
+
"fv_claim_extraction_prompt_context_header": "**Context of this explanation:** {context}",
|
| 136 |
+
"fv_claim_extraction_prompt_types_header": "**Available Claim Types:**",
|
| 137 |
+
"fv_claim_extraction_prompt_pca_types_details": "- `top_k_similarity`: A claim that one or more function types/categories are the most similar to the input.\n - `details`: {{ \"item_type\": \"function_type\" or \"category\", \"items\": [\"...\"], \"rank_description\": \"most/least\" }}\n- `positional_claim`: A claim about the input's position relative to one or more clusters in the PCA plot.\n - `details`: {{ \"cluster_names\": [\"...\"], \"position\": \"near/far/between\" }}\n- `category_justification_claim`: A claim that provides a specific reason for a category's relevance to the input prompt.\n - `details`: {{ \"category_name\": \"...\", \"justification\": \"...\" }}",
|
| 138 |
+
"fv_claim_extraction_prompt_evolution_types_details": "- `peak_activation`: A claim about which layer had the highest activation strength.\n - `details`: {{ \"layer_index\": 12 }}\n- `biggest_change`: A claim about which layer transition had the biggest change.\n - `details`: {{ \"start_layer\": 10, \"end_layer\": 11 }}\n- `specific_value_claim`: A claim about a specific numerical value.\n - `details`: {{ \"metric\": \"activation_strength\" or \"change_magnitude\", \"layer_index\": 12, \"value\": 65.91 }}\n - **Note:** For \"change_magnitude\", `layer_index` refers to the **starting layer** of the transition (e.g., for layer 1->2, `layer_index` is 1).",
|
| 139 |
+
"fv_claim_extraction_prompt_pca_example_header": "**Example for a 'pca' context:**",
|
| 140 |
+
"fv_claim_extraction_prompt_pca_example_explanation": "- **Explanation sentence:** \"Specifically, it falls into a region characterized by abstractive tasks, text classification, and text generation.\"",
|
| 141 |
+
"fv_claim_extraction_prompt_pca_example_json": "- **Resulting JSON object:**\n ```json\n [\n {{\n \"claim_text\": \"Specifically, it falls into a region characterized by abstractive tasks, text classification, and text generation.\",\n \"claim_type\": \"positional_claim\",\n \"details\": {{\n \"cluster_names\": [\"abstractive tasks\", \"text classification\", \"text generation\"],\n \"position\": \"near\"\n }}\n }},\n {{\n \"claim_text\": \"The prompt is closely linked to Language QA because it involves answering a question about a literary work.\",\n \"claim_type\": \"category_justification_claim\",\n \"details\": {{\n \"category_name\": \"Language QA\",\n \"justification\": \"it involves answering a question about a literary work\"\n }}\n }}\n ]\n ```",
|
| 142 |
+
"fv_claim_extraction_prompt_evolution_example_header": "**Example for an 'evolution' context:**",
|
| 143 |
+
"fv_claim_extraction_prompt_evolution_example_explanation": "- **Explanation sentence:** \"The biggest change occurring between Layer 1 and 2, with a magnitude of 0.40...\"",
|
| 144 |
+
"fv_claim_extraction_prompt_evolution_example_json": "- **Resulting JSON object:**\n ```json\n [\n {{\n \"claim_text\": \"The biggest change occurring between Layer 1 and 2, with a magnitude of 0.40...\",\n \"claim_type\": \"biggest_change\",\n \"details\": {{ \"start_layer\": 1, \"end_layer\": 2 }}\n }},\n {{\n \"claim_text\": \"The biggest change occurring between Layer 1 and 2, with a magnitude of 0.40...\",\n \"claim_type\": \"specific_value_claim\",\n \"details\": {{ \"metric\": \"change_magnitude\", \"layer_index\": 1, \"value\": 0.40 }}\n }}\n ]\n ```",
|
| 145 |
+
"fv_claim_extraction_prompt_analyze_header": "**Explanation to Analyze:**",
|
| 146 |
+
"fv_claim_extraction_prompt_footer": "Respond with ONLY the JSON list of claims. If no verifiable claims are found, return an empty list `[]`.",
|
| 147 |
+
"fv_semantic_verification_prompt_header": "You are an AI fact-checker specializing in semantic analysis. Your task is to determine if a claimed \"functional neighborhood\" is plausibly related to the actual top-ranked functions for a given prompt.",
|
| 148 |
+
"fv_semantic_verification_prompt_rule": "**Crucial Rule:** The claimed neighborhood does not need to be a direct summary of the top functions. It should be considered \"verified\" if it represents a plausible, contextually relevant, or semantically adjacent concept. Flag as \"not verified\" if the claimed neighborhood is unrelated or logically inconsistent with the top functions.",
|
| 149 |
+
"fv_semantic_verification_prompt_actual_header": "**Actual Top-Ranked Functions:**",
|
| 150 |
+
"fv_semantic_verification_prompt_claimed_header": "**Claimed Functional Neighborhood:**",
|
| 151 |
+
"fv_semantic_verification_prompt_task_header": "**Your Task:**",
|
| 152 |
+
"fv_semantic_verification_prompt_task_instruction": "Based on the rule above, is the \"Claimed Functional Neighborhood\" plausibly related to the \"Actual Top-Ranked Functions\"? Give a clear verdict and cite concrete evidence.",
|
| 153 |
+
"fv_semantic_verification_prompt_json_instruction": "Respond with a JSON object with two keys:\n1. `is_verified`: boolean (true if plausibly related, false otherwise).\n2. `reasoning`: A detailed 2-3 sentence explanation that references at least one item from the actual list, describes why the claim aligns or conflicts, and avoids simply repeating the claim verbatim.",
|
| 154 |
+
"fv_semantic_verification_prompt_footer": "Respond with ONLY the JSON object and nothing else.",
|
| 155 |
+
"fv_justification_verification_prompt_header": "You are an AI fact-checker specializing in semantic reasoning. Your task is to determine if a justification for a functional category's relevance to an input prompt is plausible and logically consistent.",
|
| 156 |
+
"fv_justification_verification_prompt_rule": "**Crucial Rule:** The justification does not need to be the strongest possible argument. It should be considered \"verified\" if it presents a plausible, creative, or contextually relevant connection, even if it seems like a stretch. Only flag it as \"not verified\" if the reasoning is completely illogical, factually incorrect, or directly contradicts the prompt.",
|
| 157 |
+
"fv_justification_verification_prompt_input_header": "**Input Prompt:**",
|
| 158 |
+
"fv_justification_verification_prompt_category_header": "**Functional Category:**",
|
| 159 |
+
"fv_justification_verification_prompt_justification_header": "**Provided Justification:**",
|
| 160 |
+
"fv_justification_verification_prompt_task_header": "**Your Task:**",
|
| 161 |
+
"fv_justification_verification_prompt_task_instruction": "Based on the rule above, is the justification plausible? Refer directly to the prompt and category when explaining your decision.",
|
| 162 |
+
"fv_justification_verification_prompt_json_instruction": "Respond with a JSON object with two keys:\n1. `is_verified`: boolean (true if the justification is plausible, false if it is illogical or incorrect).\n2. `reasoning`: A 2-3 sentence explanation that explicitly references the input prompt and category, and explains why the justification holds or fails without merely echoing the original wording.",
|
| 163 |
+
"fv_justification_verification_prompt_footer": "Respond with ONLY the JSON object and nothing else."
|
| 164 |
+
}
|
locales/en/welcome_page.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"welcome_page_title": "Welcome & Setup",
|
| 3 |
+
"welcome_page_header": "Before you begin...",
|
| 4 |
+
"welcome_page_intro": "To help with our research on the usability of this tool, please provide some anonymous information. This will be stored securely and used only for academic purposes.",
|
| 5 |
+
"research_tool_intro": "An advanced research tool for exploring the inner workings of Large Language Models.",
|
| 6 |
+
"about_this_tool": "About This Tool",
|
| 7 |
+
"research_study_info": "This application is part of a research study aiming to understand how users interact with and interpret complex AI models. By using this tool, you are participating in this study.",
|
| 8 |
+
"your_role": "Your Role as a Participant:",
|
| 9 |
+
"role_1": "You will use the different analysis tools to explore the behavior of a language model.",
|
| 10 |
+
"role_2": "You will be asked to provide feedback on the usability and clarity of the visualizations.",
|
| 11 |
+
"role_3": "Your interactions and feedback will help us build better, more transparent AI tools.",
|
| 12 |
+
"data_privacy": "Data Privacy & Consent:",
|
| 13 |
+
"privacy_1": "Your responses and interactions are anonymous. We will only store your age, expertise level, and feedback.",
|
| 14 |
+
"privacy_2": "All collected data will be used exclusively for academic research purposes.",
|
| 15 |
+
"privacy_3": "By proceeding, you consent to the collection and use of this anonymous data.",
|
| 16 |
+
"tell_us_about_yourself": "Tell Us About Yourself",
|
| 17 |
+
"what_is_your_age_group": "What is your age group?",
|
| 18 |
+
"under_18": "Under 18",
|
| 19 |
+
"18_24": "18-24",
|
| 20 |
+
"25_34": "25-34",
|
| 21 |
+
"35_44": "35-44",
|
| 22 |
+
"45_54": "45-54",
|
| 23 |
+
"55_64": "55-64",
|
| 24 |
+
"65_or_over": "65 or over",
|
| 25 |
+
"prefer_not_to_say": "Prefer not to say",
|
| 26 |
+
"rate_your_expertise": "How would you rate your expertise with AI and language models?",
|
| 27 |
+
"novice": "Novice (Limited to no experience with AI tools)",
|
| 28 |
+
"intermediate": "Intermediate (Comfortable using AI for everyday tasks)",
|
| 29 |
+
"expert": "Expert (Deep technical knowledge or research in AI)",
|
| 30 |
+
"start_analysis_button": "Start Analysis",
|
| 31 |
+
"form_submitted": "form_submitted",
|
| 32 |
+
"thank_you_proceed": "Thank you! You can now proceed to the analysis.",
|
| 33 |
+
"thank_you_main_suite": "Thank you! Loading the main analysis suite...",
|
| 34 |
+
"welcome_to_llm_analysis_suite": "Welcome to the Explainable Language Interpretability Analysis Tool!",
|
| 35 |
+
"toolkit_description": "This toolkit offers a collection of advanced methods to interpret and understand the inner workings of language models. Select an analysis from the sidebar to begin.",
|
| 36 |
+
"attribution_analysis_description": "<strong>Attribution Analysis:</strong> Understand which parts of the input text influence the model's output using methods like Integrated Gradients, Occlusion, and Saliency.",
|
| 37 |
+
"function_vectors_description": "<strong>Function Vectors:</strong> Analyze how text activates different functional capabilities within the model.",
|
| 38 |
+
"circuit_tracing_description": "<strong>Circuit Tracing:</strong> Explore the computational pathways inside the model to see how information flows."
|
| 39 |
+
}
|
packages.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
libgomp1
|
| 2 |
+
|
requirements.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.28.0
|
| 2 |
+
streamlit-option-menu>=0.3.0
|
| 3 |
+
torch>=2.0.0
|
| 4 |
+
transformers>=4.30.0
|
| 5 |
+
inseq>=0.5.0
|
| 6 |
+
pandas>=1.5.0
|
| 7 |
+
numpy>=1.24.0
|
| 8 |
+
scikit-learn>=1.3.0
|
| 9 |
+
plotly>=5.15.0
|
| 10 |
+
requests>=2.25.0
|
| 11 |
+
beautifulsoup4>=4.11.0
|
| 12 |
+
Pillow>=9.0.0
|
| 13 |
+
markdown>=3.0.0
|
| 14 |
+
faiss-cpu>=1.7.0
|
| 15 |
+
sentence-transformers>=2.2.0
|
| 16 |
+
sentence-splitter>=1.0.0
|
| 17 |
+
thefuzz>=0.19.0
|
| 18 |
+
python-Levenshtein>=0.20.0
|
| 19 |
+
networkx>=3.0
|
| 20 |
+
matplotlib>=3.6.0
|
run_webapp.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# A simple launcher script for the web app.
|
| 2 |
+
|
| 3 |
+
import subprocess
|
| 4 |
+
import sys
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
def main():
|
| 8 |
+
print("LLM Attribution Analysis Web App")
|
| 9 |
+
print("=" * 50)
|
| 10 |
+
|
| 11 |
+
# Check if the script is being run from the correct directory.
|
| 12 |
+
if not os.path.exists("web_app.py"):
|
| 13 |
+
print("Error: web_app.py not found!")
|
| 14 |
+
print("Please run this script from the Bachelor Arbeit directory.")
|
| 15 |
+
return
|
| 16 |
+
|
| 17 |
+
# Check if streamlit is installed.
|
| 18 |
+
try:
|
| 19 |
+
import streamlit
|
| 20 |
+
print("Streamlit found")
|
| 21 |
+
except ImportError:
|
| 22 |
+
print("Streamlit not found. Installing dependencies...")
|
| 23 |
+
subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
|
| 24 |
+
|
| 25 |
+
print("Starting the web application...")
|
| 26 |
+
print("The app will open in your browser at http://localhost:8501")
|
| 27 |
+
print("To stop the app, press Ctrl+C isn this terminal")
|
| 28 |
+
print("=" * 50)
|
| 29 |
+
|
| 30 |
+
# Run the streamlit app.
|
| 31 |
+
try:
|
| 32 |
+
subprocess.run(["streamlit", "run", "web_app.py"])
|
| 33 |
+
except KeyboardInterrupt:
|
| 34 |
+
print("\nWeb app stopped. Goodbye!")
|
| 35 |
+
except FileNotFoundError:
|
| 36 |
+
print("Error: streamlit command not found.")
|
| 37 |
+
print("Please install streamlit: pip install streamlit")
|
| 38 |
+
|
| 39 |
+
if __name__ == "__main__":
|
| 40 |
+
main()
|