{"spec_id":"line-loss-training","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nline-loss-training: Training Loss Curve\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 95/100 | Created: 2026-05-14\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Prevent script name from shadowing bokeh module\nscript_dir = str(Path(__file__).parent)\nif script_dir in sys.path:\n    sys.path.remove(script_dir)\n\nimport numpy as np\nimport pandas as pd\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, Quad\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme tokens (see prompts/default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Okabe-Ito palette\nTRAIN_COLOR = \"#009E73\"  # bluish green - first series (brand)\nVAL_COLOR = \"#C475FD\"  # vermillion - second series\n\n# Generate realistic neural network training data\nnp.random.seed(42)\nn_epochs = 150\n\n# Training loss: smooth exponential decay with noise\nepochs = np.arange(1, n_epochs + 1)\ntrain_loss_base = 2.5 * np.exp(-0.015 * (epochs - 1)) + 0.15\ntrain_loss = train_loss_base + np.random.normal(0, 0.02, n_epochs)\ntrain_loss = np.maximum(train_loss, 0.15)  # ensure positive\n\n# Validation loss: slightly noisier, higher baseline, potential overfitting\nval_loss_base = 2.5 * np.exp(-0.012 * (epochs - 1)) + 0.2\nval_loss = val_loss_base + np.random.normal(0, 0.035, n_epochs)\n# Add slight overfitting effect in later epochs\nval_loss[80:] += np.linspace(0, 0.08, n_epochs - 80)\nval_loss = np.maximum(val_loss, 0.18)\n\n# Find minimum validation loss epoch (for annotation)\nmin_val_idx = np.argmin(val_loss)\nmin_val_epoch = epochs[min_val_idx]\nmin_val_loss = val_loss[min_val_idx]\n\n# Create DataFrame\ndf = pd.DataFrame({\"epoch\": epochs, \"train_loss\": train_loss, \"val_loss\": val_loss})\n\n# Create Bokeh figure\ntitle_text = \"line-loss-training · bokeh · anyplot.ai\"\np = figure(\n    width=4800,\n    height=2700,\n    title=title_text,\n    x_axis_label=\"Epoch\",\n    y_axis_label=\"Loss (Cross-Entropy)\",\n    toolbar_location=\"right\",\n)\n\n# Set up data sources\ntrain_source = ColumnDataSource(df[[\"epoch\", \"train_loss\"]])\nval_source = ColumnDataSource(df[[\"epoch\", \"val_loss\"]])\n\n# Create a shaded region to highlight potential overfitting area (after epoch 80)\noverfitting_start = 80\nmax_loss = max(df[\"val_loss\"].max(), df[\"train_loss\"].max())\noverfitting_quad = p.quad(\n    left=[overfitting_start],\n    right=[n_epochs],\n    bottom=[0],\n    top=[max_loss],\n    fill_alpha=0.08,\n    fill_color=VAL_COLOR,\n    line_color=None,\n    level=\"underlay\",\n)\n\n# Plot lines\ntrain_line = p.line(\n    x=\"epoch\",\n    y=\"train_loss\",\n    source=train_source,\n    line_width=4,\n    color=TRAIN_COLOR,\n    legend_label=\"Training Loss\",\n    muted_color=TRAIN_COLOR,\n    muted_alpha=0.15,\n)\n\nval_line = p.line(\n    x=\"epoch\",\n    y=\"val_loss\",\n    source=val_source,\n    line_width=4,\n    color=VAL_COLOR,\n    legend_label=\"Validation Loss\",\n    muted_color=VAL_COLOR,\n    muted_alpha=0.15,\n)\n\n# Add circle markers at data points\np.scatter(\n    x=\"epoch\",\n    y=\"train_loss\",\n    source=train_source,\n    size=5,\n    color=TRAIN_COLOR,\n    alpha=0.6,\n    hover_color=TRAIN_COLOR,\n    hover_alpha=1.0,\n)\n\np.scatter(\n    x=\"epoch\",\n    y=\"val_loss\",\n    source=val_source,\n    size=5,\n    color=VAL_COLOR,\n    alpha=0.6,\n    hover_color=VAL_COLOR,\n    hover_alpha=1.0,\n)\n\n# Mark the epoch with minimum validation loss - larger marker for emphasis\noptimal_marker = p.scatter(\n    x=[min_val_epoch],\n    y=[min_val_loss],\n    size=20,\n    color=VAL_COLOR,\n    line_color=INK,\n    line_width=3,\n    alpha=1.0,\n    legend_label=f\"Optimal epoch: {min_val_epoch}\",\n)\n\n# Add annotation label at the optimal epoch\nlabel = Label(\n    x=min_val_epoch,\n    y=min_val_loss,\n    text=f\"  Epoch {min_val_epoch}\\n  Loss {min_val_loss:.4f}\",\n    text_color=INK,\n    text_font_size=\"14pt\",\n    text_baseline=\"middle\",\n    text_align=\"left\",\n)\np.add_layout(label)\n\n# Add detailed hover tool\nhover = HoverTool(tooltips=[(\"Epoch\", \"@epoch{0}\"), (\"Loss\", \"@y{0.0000}\")], mode=\"vline\")\np.add_tools(hover)\n\n# Apply text sizing\np.title.text_font_size = \"28pt\"\np.xaxis.axis_label_text_font_size = \"22pt\"\np.yaxis.axis_label_text_font_size = \"22pt\"\np.xaxis.major_label_text_font_size = \"18pt\"\np.yaxis.major_label_text_font_size = \"18pt\"\n\n# Apply theme-adaptive chrome colors\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\np.title.text_color = INK\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\n# Y-axis grid (for line charts per style guide)\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.10\np.xgrid.grid_line_color = INK\np.xgrid.grid_line_alpha = 0.05\n\n# Configure legend\np.legend.background_fill_color = ELEVATED_BG\np.legend.border_line_color = INK_SOFT\np.legend.label_text_color = INK_SOFT\np.legend.location = \"top_right\"\np.legend.click_policy = \"mute\"\np.legend.label_text_font_size = \"16pt\"\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome using Selenium\nW, H = 4800, 2700\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\n\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}