Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions plots/line-loss-training/implementations/altair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
""" pyplots.ai
line-loss-training: Training Loss Curve
Library: altair 6.0.0 | Python 3.13.11
Quality: 91/100 | Created: 2025-12-31
"""

import altair as alt
import numpy as np
import pandas as pd


# Data - Simulating neural network training loss curves
np.random.seed(42)
epochs = np.arange(1, 51)

# Training loss: exponential decay with noise (continues decreasing)
train_loss = 2.5 * np.exp(-0.08 * epochs) + 0.15 + np.random.normal(0, 0.02, len(epochs))

# Validation loss: decay then overfitting (U-shape after minimum)
val_base = 2.5 * np.exp(-0.07 * epochs) + 0.35
val_loss = val_base + np.random.normal(0, 0.025, len(epochs))
# Add overfitting: loss increases after epoch 25
val_loss[25:] = val_loss[25:] + np.linspace(0, 0.35, 25)

# Find minimum validation loss epoch for annotation
min_val_epoch = epochs[np.argmin(val_loss)]
min_val_loss = np.min(val_loss)

# Create DataFrame in long format for Altair
df = pd.DataFrame(
{
"Epoch": np.tile(epochs, 2),
"Loss": np.concatenate([train_loss, val_loss]),
"Type": ["Training Loss"] * len(epochs) + ["Validation Loss"] * len(epochs),
}
)

# Point for minimum validation loss annotation
min_point_df = pd.DataFrame({"Epoch": [min_val_epoch], "Loss": [min_val_loss], "Type": ["Optimal Stopping Point"]})

# Base line chart
lines = (
alt.Chart(df)
.mark_line(strokeWidth=3)
.encode(
x=alt.X("Epoch:Q", title="Epoch", axis=alt.Axis(labelFontSize=18, titleFontSize=22)),
y=alt.Y("Loss:Q", title="Cross-Entropy Loss", axis=alt.Axis(labelFontSize=18, titleFontSize=22)),
color=alt.Color(
"Type:N",
scale=alt.Scale(domain=["Training Loss", "Validation Loss"], range=["#306998", "#FFD43B"]),
legend=alt.Legend(title="Curve Type", labelFontSize=16, titleFontSize=18),
),
)
)

# Add points on lines for visibility
points = (
alt.Chart(df)
.mark_point(size=60, filled=True)
.encode(
x="Epoch:Q",
y="Loss:Q",
color=alt.Color(
"Type:N",
scale=alt.Scale(domain=["Training Loss", "Validation Loss"], range=["#306998", "#FFD43B"]),
legend=None,
),
)
)

# Annotation for minimum validation loss
min_marker = (
alt.Chart(min_point_df)
.mark_point(size=300, shape="diamond", filled=True, color="#E63946")
.encode(x="Epoch:Q", y="Loss:Q")
)

# Text annotation for optimal stopping point
min_text = (
alt.Chart(min_point_df)
.mark_text(align="left", dx=12, dy=-10, fontSize=16, fontWeight="bold", color="#E63946")
.encode(x="Epoch:Q", y="Loss:Q", text=alt.value(f"Min Val Loss (Epoch {min_val_epoch})"))
)

# Combine all layers
chart = (
(lines + points + min_marker + min_text)
.properties(
width=1600,
height=900,
title=alt.Title("line-loss-training · altair · pyplots.ai", fontSize=28, anchor="middle"),
)
.configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3)
.configure_legend(labelFontSize=16, titleFontSize=18)
.configure_view(strokeWidth=0)
)

# Save as PNG (4800 x 2700 with scale_factor=3)
chart.save("plot.png", scale_factor=3.0)

# Save as HTML for interactivity
chart.save("plot.html")
28 changes: 28 additions & 0 deletions plots/line-loss-training/metadata/altair.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
library: altair
specification_id: line-loss-training
created: '2025-12-31T00:11:50Z'
updated: '2025-12-31T00:16:02Z'
generated_by: claude-opus-4-5-20251101
workflow_run: 20608675031
issue: 2860
python_version: 3.13.11
library_version: 6.0.0
preview_url: https://storage.googleapis.com/pyplots-images/plots/line-loss-training/altair/plot.png
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/line-loss-training/altair/plot_thumb.png
preview_html: https://storage.googleapis.com/pyplots-images/plots/line-loss-training/altair/plot.html
quality_score: 91
review:
strengths:
- Excellent implementation of overfitting visualization with validation loss diverging
from training loss after epoch 25
- Clean use of Altair's layered composition with separate chart objects for lines,
points, and annotations
- Red diamond marker with annotation clearly indicates the optimal stopping point
at minimum validation loss
- Colorblind-safe color scheme with strong contrast between blue training and yellow
validation curves
- Proper title format and axis labeling including loss function type as specified
weaknesses:
- Legend title "Curve Type" is generic; could be more descriptive
- Points on every epoch (50 points per curve) add visual noise; could use opacity
or fewer markers