Skip to content

Commit a3225d6

Browse files
committed
update 1b 2b
1 parent 312e18d commit a3225d6

4 files changed

Lines changed: 671 additions & 555 deletions

File tree

‎notebooks/Notebook_1/Notebook_1b_Noise_and_Modeling_Data.ipynb‎

Lines changed: 53 additions & 95 deletions
Large diffs are not rendered by default.

‎notebooks/Notebook_1/Notebook_1b_Noise_and_Modeling_Data.py‎

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -270,23 +270,6 @@
270270
pdf = norm.pdf(x, loc=50, scale=8)
271271
cdf = norm.cdf(x, loc=50, scale=8)
272272

273-
# %%
274-
#| code-fold: true
275-
fig, ax = plt.subplots(1, 2, figsize=(12, 4))
276-
277-
ax[0].plot(x, pdf)
278-
ax[0].set_title("Probability Density Function (PDF)")
279-
ax[0].set_ylabel("Density")
280-
281-
ax[1].plot(x, cdf)
282-
ax[1].set_title("Cumulative Distribution Function (CDF)")
283-
ax[1].set_ylabel("Probability")
284-
for a in ax:
285-
a.set_xlabel("Value")
286-
287-
plt.suptitle("PDF vs CDF — Two Views of the Same Distribution")
288-
plt.show()
289-
290273
# %% [markdown]
291274
# The CDF is literally the *running total* of the area under the PDF. The
292275
# animation below sweeps a value $x_0$ from left to right: the shaded area under

‎notebooks/Notebook_2/Notebook_2b_Model_Selection.ipynb‎

Lines changed: 509 additions & 413 deletions
Large diffs are not rendered by default.

‎notebooks/Notebook_2/Notebook_2b_Model_Selection.py‎

Lines changed: 109 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -484,33 +484,102 @@ def grad(beta):
484484
X, Y = np.meshgrid(x, y)
485485
Z = X**2 + 0.5 * Y**2 + 1
486486

487-
fig = plt.figure(figsize=(13, 6))
488-
489-
ax_2d = fig.add_subplot(1, 2, 1)
490-
ax_2d.contour(X, Y, Z, levels=20)
491-
ax_2d.plot(trajectory[:, 0], trajectory[:, 1], marker="o", color="red")
492-
ax_2d.set_title("Gradient Descent Path on 2D Loss Landscape")
493-
ax_2d.set_xlabel("x")
494-
ax_2d.set_ylabel("y")
495-
ax_2d.set_aspect("equal")
496-
497-
ax_3d = fig.add_subplot(1, 2, 2, projection="3d")
498-
ax_3d.plot_surface(X, Y, Z, cmap="viridis", alpha=0.6, linewidth=0, antialiased=True)
499-
ax_3d.plot(
500-
trajectory[:, 0],
501-
trajectory[:, 1],
502-
trajectory_loss,
503-
marker="o",
504-
color="red",
505-
markersize=4,
506-
)
507-
ax_3d.set_title("Same Path on the 3D Loss Surface")
508-
ax_3d.set_xlabel("x")
509-
ax_3d.set_ylabel("y")
510-
ax_3d.set_zlabel("loss")
487+
# %%
488+
#| code-fold: true
489+
def plot_gradient_descent_2d3d_gif(
490+
trajectory, trajectory_loss, X, Y, Z, seconds=8.0, hold_seconds=1.2
491+
):
492+
"""
493+
Animate gradient descent on a 2D loss landscape: reveal one more step of
494+
the trajectory per frame, on both the contour view and the 3D surface.
495+
"""
496+
import base64
497+
import io
511498

512-
plt.tight_layout()
513-
plt.show()
499+
from IPython.display import HTML, display
500+
from PIL import Image
501+
502+
n_frames = len(trajectory)
503+
504+
frames = []
505+
for i in range(n_frames):
506+
fig = plt.figure(figsize=(13, 6))
507+
508+
ax_2d = fig.add_subplot(1, 2, 1)
509+
ax_2d.contour(X, Y, Z, levels=20)
510+
ax_2d.plot(
511+
trajectory[: i + 1, 0], trajectory[: i + 1, 1], marker="o", color="red"
512+
)
513+
ax_2d.scatter(
514+
trajectory[i, 0], trajectory[i, 1], color="darkorange", s=90, zorder=5
515+
)
516+
ax_2d.set_title("Gradient Descent Path on 2D Loss Landscape")
517+
ax_2d.set_xlabel("x")
518+
ax_2d.set_ylabel("y")
519+
ax_2d.set_aspect("equal")
520+
521+
ax_3d = fig.add_subplot(1, 2, 2, projection="3d")
522+
ax_3d.plot_surface(
523+
X, Y, Z, cmap="viridis", alpha=0.6, linewidth=0, antialiased=True
524+
)
525+
ax_3d.plot(
526+
trajectory[: i + 1, 0],
527+
trajectory[: i + 1, 1],
528+
trajectory_loss[: i + 1],
529+
marker="o",
530+
color="red",
531+
markersize=4,
532+
)
533+
ax_3d.scatter(
534+
trajectory[i, 0],
535+
trajectory[i, 1],
536+
trajectory_loss[i],
537+
color="darkorange",
538+
s=60,
539+
)
540+
ax_3d.set_title("Same Path on the 3D Loss Surface")
541+
ax_3d.set_xlabel("x")
542+
ax_3d.set_ylabel("y")
543+
ax_3d.set_zlabel("loss")
544+
545+
fig.suptitle(
546+
f"Step {i}/{n_frames - 1}: (x, y)=({trajectory[i, 0]:.2f}, "
547+
f"{trajectory[i, 1]:.2f}), loss={trajectory_loss[i]:.2f}"
548+
)
549+
plt.tight_layout()
550+
551+
buf = io.BytesIO()
552+
fig.savefig(buf, format="png", dpi=80)
553+
plt.close(fig)
554+
buf.seek(0)
555+
frames.append(Image.open(buf).convert("RGB"))
556+
557+
frame_duration = int(1000 * seconds / n_frames)
558+
hold_duration = int(1000 * hold_seconds)
559+
durations = [frame_duration] * n_frames
560+
durations[0] = hold_duration
561+
durations[-1] = hold_duration
562+
563+
gif_buf = io.BytesIO()
564+
frames[0].save(
565+
gif_buf,
566+
format="GIF",
567+
save_all=True,
568+
append_images=frames[1:],
569+
duration=durations,
570+
loop=0,
571+
)
572+
gif_buf.seek(0)
573+
574+
# Quarto's HTML renderer does not pick up embedded image/gif outputs, so we
575+
# emit an <img> tag with the GIF inlined as a base64 data URI instead.
576+
gif_b64 = base64.b64encode(gif_buf.getvalue()).decode("ascii")
577+
display(
578+
HTML(f'<img src="data:image/gif;base64,{gif_b64}" alt="Gradient descent 2D/3D">')
579+
)
580+
581+
582+
plot_gradient_descent_2d3d_gif(trajectory, trajectory_loss, X, Y, Z)
514583

515584

516585
# %% [markdown] id="VpGIH0Oy5H92"
@@ -576,10 +645,20 @@ def mse(y_pred, y):
576645
b -= eta * db
577646
losses.append(mse(y_pred, y))
578647

579-
plt.plot(losses)
580-
plt.title("Training Loss Over Iterations")
581-
plt.xlabel("Iteration")
582-
plt.ylabel("MSE")
648+
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
649+
650+
axes[0].plot(losses)
651+
axes[0].set_title("Training Loss Over Iterations")
652+
axes[0].set_xlabel("Iteration")
653+
axes[0].set_ylabel("MSE")
654+
655+
axes[1].plot(losses)
656+
axes[1].set_yscale("log")
657+
axes[1].set_title("Same Loss, Log Scale on Y")
658+
axes[1].set_xlabel("Iteration")
659+
axes[1].set_ylabel("MSE (log scale)")
660+
661+
plt.tight_layout()
583662
plt.show()
584663

585664
print("Learned w:", w)

0 commit comments

Comments
 (0)