Alicesong's picture
Deploy Detection + Segmentation Studio
1efe34a verified
Raw
History Blame Contribute Delete
8.77 kB
"""Optimizer-race and loss-breakdown demos."""
from __future__ import annotations
from functools import lru_cache
import numpy as np
from matplotlib import pyplot as plt
from .utils import figure_to_image
LOSS_LABELS = ["airplane", "car", "cat", "ship"]
def _objective(point: np.ndarray) -> float:
x_value, y_value = float(point[0]), float(point[1])
return (
0.18 * (x_value**2)
+ 0.75 * (y_value**2)
+ 0.35 * np.sin(1.35 * x_value) * np.cos(0.85 * y_value)
+ 0.08 * x_value * y_value
)
def _gradient(point: np.ndarray) -> np.ndarray:
x_value, y_value = float(point[0]), float(point[1])
grad_x = 0.36 * x_value + 0.4725 * np.cos(1.35 * x_value) * np.cos(0.85 * y_value) + 0.08 * y_value
grad_y = 1.5 * y_value - 0.2975 * np.sin(1.35 * x_value) * np.sin(0.85 * y_value) + 0.08 * x_value
return np.array([grad_x, grad_y], dtype=np.float32)
def _run_optimizer(
method: str,
learning_rate: float,
beta: float,
steps: int,
start: np.ndarray,
noise_bank: np.ndarray,
) -> tuple[np.ndarray, np.ndarray]:
path = [start.astype(np.float32)]
losses = [_objective(start)]
velocity = np.zeros(2, dtype=np.float32)
point = start.astype(np.float32).copy()
for step_id in range(steps):
grad = _gradient(point) + noise_bank[step_id]
if method == "momentum":
velocity = beta * velocity - learning_rate * grad
point = point + velocity
else:
point = point - learning_rate * grad
path.append(point.copy())
losses.append(_objective(point))
return np.stack(path, axis=0), np.asarray(losses, dtype=np.float32)
def _plot_optimizer_paths(sgd_path: np.ndarray, momentum_path: np.ndarray) -> np.ndarray:
xs = np.linspace(-3.2, 3.2, 240)
ys = np.linspace(-3.2, 3.2, 240)
grid_x, grid_y = np.meshgrid(xs, ys)
values = (
0.18 * np.square(grid_x)
+ 0.75 * np.square(grid_y)
+ 0.35 * np.sin(1.35 * grid_x) * np.cos(0.85 * grid_y)
+ 0.08 * grid_x * grid_y
)
fig, ax = plt.subplots(figsize=(6.0, 5.4))
contour = ax.contourf(grid_x, grid_y, values, levels=35, cmap="YlOrRd")
ax.contour(grid_x, grid_y, values, levels=18, colors="white", linewidths=0.45, alpha=0.55)
ax.plot(sgd_path[:, 0], sgd_path[:, 1], marker="o", markersize=3.2, linewidth=2.0, color="#355070", label="SGD")
ax.plot(momentum_path[:, 0], momentum_path[:, 1], marker="o", markersize=3.2, linewidth=2.0, color="#2a9d8f", label="Momentum")
ax.scatter([sgd_path[0, 0]], [sgd_path[0, 1]], color="#ff7f7f", s=55, zorder=5, label="Start")
ax.set_title("Optimizer paths on the same loss surface", fontsize=14, fontweight="bold")
ax.set_xlabel("w1")
ax.set_ylabel("w2")
ax.legend(frameon=False, loc="upper right")
fig.colorbar(contour, ax=ax, shrink=0.82, label="Loss value")
fig.tight_layout()
return figure_to_image(fig)
def _plot_loss_curves(sgd_losses: np.ndarray, momentum_losses: np.ndarray) -> np.ndarray:
fig, ax = plt.subplots(figsize=(6.0, 4.0))
steps = np.arange(len(sgd_losses))
ax.plot(steps, sgd_losses, color="#355070", linewidth=2.4, label="SGD")
ax.plot(steps, momentum_losses, color="#2a9d8f", linewidth=2.4, label="Momentum")
ax.set_title("Loss vs step", fontsize=14, fontweight="bold")
ax.set_xlabel("Step")
ax.set_ylabel("Loss")
ax.grid(alpha=0.24)
ax.legend(frameon=False)
fig.tight_layout()
return figure_to_image(fig)
@lru_cache(maxsize=40)
def run_optimizer_demo(
learning_rate: float,
beta: float,
steps: int,
noise_scale: float,
start_x: float,
start_y: float,
) -> dict[str, object]:
"""Compare SGD and momentum on the same toy objective."""
rng = np.random.default_rng(23)
start = np.array([start_x, start_y], dtype=np.float32)
noise_bank = rng.normal(0.0, noise_scale, size=(steps, 2)).astype(np.float32)
sgd_path, sgd_losses = _run_optimizer("sgd", learning_rate, beta, steps, start, noise_bank)
momentum_path, momentum_losses = _run_optimizer("momentum", learning_rate, beta, steps, start, noise_bank)
sgd_improvement = float(sgd_losses[0] - sgd_losses[-1])
momentum_improvement = float(momentum_losses[0] - momentum_losses[-1])
summary = (
"### Optimizer Race Snapshot\n"
f"- Start point: `({start_x:.2f}, {start_y:.2f})`\n"
f"- Learning rate `{learning_rate:.3f}`, momentum beta `{beta:.2f}`, gradient noise `{noise_scale:.2f}`\n"
f"- SGD final loss: **{sgd_losses[-1]:.4f}** (improvement `{sgd_improvement:.4f}`)\n"
f"- Momentum final loss: **{momentum_losses[-1]:.4f}** (improvement `{momentum_improvement:.4f}`)"
)
return {
"summary": summary,
"path_plot": _plot_optimizer_paths(sgd_path, momentum_path),
"curve_plot": _plot_loss_curves(sgd_losses, momentum_losses),
}
def _plot_loss_breakdown(scores: np.ndarray, probabilities: np.ndarray, target_index: int) -> np.ndarray:
fig, axes = plt.subplots(1, 2, figsize=(8.2, 3.8))
score_colors = ["#ff7f7f" if index == target_index else "#9bf6ff" for index in range(len(scores))]
prob_colors = ["#2a9d8f" if index == target_index else "#ffd166" for index in range(len(scores))]
axes[0].bar(LOSS_LABELS, scores, color=score_colors, edgecolor="#355070")
axes[0].set_title("Raw class scores", fontsize=13, fontweight="bold")
axes[0].grid(axis="y", alpha=0.22)
axes[1].bar(LOSS_LABELS, probabilities, color=prob_colors, edgecolor="#355070")
axes[1].set_title("Softmax probabilities", fontsize=13, fontweight="bold")
axes[1].set_ylim(0.0, 1.0)
axes[1].grid(axis="y", alpha=0.22)
fig.tight_layout()
return figure_to_image(fig)
def run_loss_demo(
score_airplane: float,
score_car: float,
score_cat: float,
score_ship: float,
target_name: str,
) -> dict[str, object]:
"""Show a step-by-step comparison of three different losses."""
scores = np.array([score_airplane, score_car, score_cat, score_ship], dtype=np.float32)
target_index = LOSS_LABELS.index(target_name)
target = np.zeros(len(LOSS_LABELS), dtype=np.float32)
target[target_index] = 1.0
correct_score = float(scores[target_index])
margins = np.maximum(0.0, scores - correct_score + 1.0)
margins[target_index] = 0.0
svm_loss = float(np.sum(margins))
shifted = scores - scores.max()
exp_values = np.exp(shifted)
probabilities = exp_values / exp_values.sum()
cross_entropy = float(-np.log(probabilities[target_index] + 1e-8))
squared_errors = np.square(probabilities - target)
mse_loss = float(np.mean(squared_errors))
prediction = LOSS_LABELS[int(np.argmax(scores))]
summary = (
"### Loss Breakdown Snapshot\n"
f"- Predicted class from raw scores: **{prediction}**\n"
f"- Target class: **{target_name}**\n"
f"- Multiclass SVM loss: `{svm_loss:.4f}` from `sum(max(0, s_j - s_y + 1))`\n"
f"- Softmax cross-entropy: `{cross_entropy:.4f}` from `-log(p_target)`\n"
f"- Probability MSE baseline: `{mse_loss:.4f}` from `mean((p - one_hot)^2)`"
)
table = []
for index, class_name in enumerate(LOSS_LABELS):
table.append(
[
class_name,
round(float(scores[index]), 4),
round(float(margins[index]), 4),
round(float(probabilities[index]), 4),
round(float(squared_errors[index]), 4),
]
)
return {
"summary": summary,
"plot": _plot_loss_breakdown(scores, probabilities, target_index),
"table": table,
}
def run_optimizer_demo_ui(
learning_rate: float,
beta: float,
steps: int,
noise_scale: float,
start_x: float,
start_y: float,
) -> tuple[str, np.ndarray, np.ndarray]:
"""UI adapter for the optimizer demo."""
result = run_optimizer_demo(
learning_rate=float(learning_rate),
beta=float(beta),
steps=int(steps),
noise_scale=float(noise_scale),
start_x=float(start_x),
start_y=float(start_y),
)
return result["summary"], result["path_plot"], result["curve_plot"]
def run_loss_demo_ui(
score_airplane: float,
score_car: float,
score_cat: float,
score_ship: float,
target_name: str,
) -> tuple[str, np.ndarray, list[list[object]]]:
"""UI adapter for the loss demo."""
result = run_loss_demo(
score_airplane=float(score_airplane),
score_car=float(score_car),
score_cat=float(score_cat),
score_ship=float(score_ship),
target_name=str(target_name),
)
return result["summary"], result["plot"], result["table"]