{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "welcome",
   "metadata": {},
   "source": [
    "# Class 1：課中手動對齊，結尾自動學習\n",
    "\n",
    "對應 **116 頁 v11 簡報**。使用同一份副本完成整堂課，做到指定區塊就先停下。\n",
    "\n",
    "| 區塊 | 對應投影片 | 操作 |\n",
    "| --- | --- | --- |\n",
    "| 0 環境 | 11–13 | 保存副本，執行繪圖工具 |\n",
    "| A 直線 | 20–21 | 手動改 linear_w、linear_b |\n",
    "| B 單一片段 | 48、50、52 | 一次只改 w、b 或 c |\n",
    "| C 手動組合 | 57–61 | 三片段對齊，基準加三次修改 |\n",
    "| D 模型解釋 | 62–91 | 對應神經元與隱藏層 |\n",
    "| E1／E2 自動接手 | 105–111 | 最後才凍結起點並開啟訓練 |\n",
    "| F 反思與分享 | 112–113 | 保存結果，繳交唯讀連結 |\n",
    "\n",
    "## 開啟與操作\n",
    "\n",
    "1. 在 Colab 上傳本檔，保存到自己的 Google 雲端硬碟。若開啟教師的共用版本，先另存副本。\n",
    "2. 使用一般 Python CPU。這份練習不需 GPU，不需掛載 Drive，也沒有套件安裝步驟。\n",
    "3. 點程式格左側執行鈕，等待結束再讀下方輸出。**改數字後必須重新執行該格**。\n",
    "4. 跟著老師停在指定區塊，先不要「全部執行」。E1、E2 各有一個預設 False 的開關。\n",
    "\n",
    "圖表使用英文標籤避免缺字，操作說明與輸出文字保留繁體中文。\n",
    "本課數據都是教學合成資料。A 和 C 是不同任務，分數不能互相比高低。\n",
    "\n",
    "## 0 環境準備\n",
    "\n",
    "先執行下格一次，看到「環境準備完成」再前進。這格定義工具，暫時不用逐行讀。\n",
    "**重新執行 0 會清空本次記憶體的實驗紀錄與交接起點**。重新連線可能也需要重建狀態，請先保存已完成的輸出。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "setup",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from IPython.display import display, Markdown\n",
    "\n",
    "plt.rcParams.update({\"font.size\": 16, \"axes.labelsize\": 17,\n",
    "                     \"legend.fontsize\": 15, \"lines.linewidth\": 3.2,\n",
    "                     \"figure.dpi\": 130, \"axes.spines.top\": False,\n",
    "                     \"axes.spines.right\": False})\n",
    "NAVY, ORANGE, GRAY = \"#073F81\", \"#ED802C\", \"#A1ADBA\"\n",
    "\n",
    "def copy_params(p):\n",
    "    return {k: np.array(v, dtype=float, copy=True) for k, v in p.items()}\n",
    "\n",
    "def check_params(p):\n",
    "    if any(p[k].shape != (3,) for k in (\"w\", \"b\", \"c\")) or p[\"d\"].shape != ():\n",
    "        raise ValueError(\"w、b、c 各保留三個值，d 保留單一數值。請檢查 C 格。\")\n",
    "    if not all(np.all(np.isfinite(v)) for v in p.values()):\n",
    "        raise ValueError(\"參數必須是有限數值，請勿輸入 NaN 或 inf。\")\n",
    "\n",
    "def predict(p, xx):\n",
    "    z = np.asarray(xx)[:, None] * p[\"w\"] + p[\"b\"]\n",
    "    return p[\"d\"] + np.maximum(0, z) @ p[\"c\"]\n",
    "\n",
    "def mse(p, xx, yy):\n",
    "    with np.errstate(over=\"ignore\", invalid=\"ignore\"):\n",
    "        return float(np.mean((predict(p, xx) - yy) ** 2))\n",
    "\n",
    "def format_params(p):\n",
    "    return \"; \".join(f\"{k}={np.array2string(v, precision=4, separator=',')}\" for k,v in p.items())\n",
    "\n",
    "def show_records():\n",
    "    rows = [\"| 回合 | 說明／原先預期 | 修改值數 | MSE | 參數快照 |\",\n",
    "            \"| --- | --- | --- | --- | --- |\"]\n",
    "    for i,r in enumerate(experiments):\n",
    "        note=r[\"note\"].replace(\"|\", \"/\").replace(\"\\n\", \" \")\n",
    "        count=\"基準\" if r[\"changed_count\"] is None else str(r[\"changed_count\"])\n",
    "        rows.append(f\"| {i} | {note} | {count} | {r['mse']:.6f} | {format_params(r['params'])} |\")\n",
    "    display(Markdown(\"\\n\".join(rows)))\n",
    "\n",
    "# C 的目標是不同於投影片山形算例的合成資料。\n",
    "# 最佳化器只使用下列取樣得到的 x、y，不使用目標參數。\n",
    "x = np.linspace(0, 3, 121)\n",
    "y = .25 + 1.2*np.maximum(0,x-.35) - 2.2*np.maximum(0,x-1.45) + np.maximum(0,x-2.65)\n",
    "experiments = []\n",
    "auto_runs = []\n",
    "globals().pop(\"start_params\", None)\n",
    "globals().pop(\"manual_params\", None)\n",
    "print(\"環境準備完成。現在先到 A 手動調直線，E 區留到課程結尾。\")\n",
    "\n",
    "\n",
    "# C 區的顯示工具：移到準備格，學員只需要修改 C 的參數。\n",
    "def update_manual(w, b, c, d, change_note):\n",
    "    import base64\n",
    "    import io\n",
    "    from IPython.display import HTML, clear_output\n",
    "    global manual_params, manual_loss\n",
    "\n",
    "    candidate = {k: np.array(v, dtype=float, copy=True)\n",
    "                 for k, v in dict(w=w, b=b, c=c, d=d).items()}\n",
    "    check_params(candidate)\n",
    "    loss = mse(candidate, x, y)\n",
    "    if not np.isfinite(loss):\n",
    "        raise ValueError(\"計算結果超出範圍，請縮小參數再試。這次不列入紀錄。\")\n",
    "    changed_count = None\n",
    "    status = f\"基準｜本次 MSE：{loss:.6f}\"\n",
    "    warning = \"\"\n",
    "    if experiments:\n",
    "        previous = experiments[-1]\n",
    "        changed_count = sum(int(np.count_nonzero(candidate[k] != previous['params'][k]))\n",
    "                            for k in candidate)\n",
    "        status = (f\"本次 MSE：{loss:.6f}　｜前次：{previous['mse']:.6f}\"\n",
    "                  f\"　｜差值：{loss - previous['mse']:+.6f}　｜修改 {changed_count} 個值\")\n",
    "        if changed_count != 1:\n",
    "            warning = '<div style=\"color:#944600\">提醒：一次只改一個數值，方便解釋原因。</div>'\n",
    "\n",
    "    def png(fig):\n",
    "        buffer = io.BytesIO()\n",
    "        fig.savefig(buffer, format='png', dpi=130, facecolor='white')\n",
    "        plt.close(fig)\n",
    "        return base64.b64encode(buffer.getvalue()).decode('ascii')\n",
    "\n",
    "    # 圖中文字使用英文，避免不同作業系統的中文字型缺字。\n",
    "    with plt.rc_context({'font.size': 12, 'axes.labelsize': 12,\n",
    "                         'legend.fontsize': 11}):\n",
    "        fig, ax = plt.subplots(figsize=(9, 3.1), layout='constrained')\n",
    "        ax.plot(x, y, color=GRAY, linewidth=5, label='Target', zorder=2)\n",
    "        ax.plot(x, predict(candidate, x), color=NAVY, linewidth=3, label='Manual', zorder=3)\n",
    "        ax.set(xlabel='Input x', ylabel='Prediction')\n",
    "        ax.grid(alpha=.22)\n",
    "        ax.legend(loc='upper right')\n",
    "        main_image = png(fig)\n",
    "\n",
    "        fig, axes = plt.subplots(1, 3, figsize=(9, 2.6), sharex=True,\n",
    "                                 layout='constrained')\n",
    "        for i, ax in enumerate(axes):\n",
    "            component = candidate['c'][i] * np.maximum(0, candidate['w'][i] * x + candidate['b'][i])\n",
    "            ax.plot(x, component, color=[NAVY, ORANGE, '#188067'][i], linewidth=3)\n",
    "            ax.axhline(0, color=GRAY, linewidth=1.5)\n",
    "            ax.set(title=f'Component {i + 1}', xlabel='Input x')\n",
    "            ax.grid(alpha=.22)\n",
    "        component_image = png(fig)\n",
    "\n",
    "    manual_params = copy_params(candidate)\n",
    "    manual_loss = loss\n",
    "    experiments.append({'note': str(change_note), 'mse': loss,\n",
    "                        'params': copy_params(candidate), 'changed_count': changed_count})\n",
    "    image_style = 'display:block;width:100%;height:auto;max-width:900px'\n",
    "    html = f'''\n",
    "    <section style=\"max-width:900px;font:15px/1.5 system-ui,sans-serif;color:#163450;background:white;padding:6px;box-sizing:border-box\">\n",
    "      <div style=\"font-weight:600\">{status}</div>{warning}\n",
    "      <img style=\"{image_style}\" src=\"data:image/png;base64,{main_image}\" alt=\"目標灰線與手動模型藍線比較\">\n",
    "      <details>\n",
    "        <summary style=\"cursor:pointer;padding:5px 0\">展開三個片段的貢獻圖（完整實驗紀錄請到 C2）</summary>\n",
    "        <img style=\"{image_style}\" src=\"data:image/png;base64,{component_image}\" alt=\"三個片段各自的輸出貢獻\">\n",
    "        <div>三個片段相加，再加上 d，得到主圖的藍線。各片段的縱軸刻度可能不同，請一起觀察數值。</div>\n",
    "      </details>\n",
    "    </section>\n",
    "    '''\n",
    "    clear_output(wait=True)\n",
    "    display(HTML(html))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a-guide",
   "metadata": {},
   "source": [
    "## A 直線：斜率與高度\n",
    "\n",
    "投影片 20–21。先執行原始設定，再固定 w，將 b 從 8 改到 10。重跑後保留 b=10，再將 w 從 1 改到 2。\n",
    "只改下格最上方的兩個數字，不需改繪圖程式。\n",
    "\n",
    "觀察：灰線 Target 是目標，藍線 Manual 是預測。哪個參數改起點？哪個改每公里增加的時間？\n",
    "完成後先停在 A，等老師介紹 Loss。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a-linear",
   "metadata": {},
   "outputs": [],
   "source": [
    "linear_w = 1.0  # 先固定，再改成 2.0\n",
    "linear_b = 8.0  # 先改成 10.0\n",
    "\n",
    "ax_values = np.linspace(0, 6, 61)\n",
    "linear_target = 10 + 2*ax_values\n",
    "linear_prediction = linear_b + linear_w*ax_values\n",
    "linear_mse = float(np.mean((linear_prediction-linear_target)**2))\n",
    "print(f\"A 的 MSE = {linear_mse:.4f}\")\n",
    "print(f\"0 公里預測：{linear_b:.2f} 分鐘；目標：10 分鐘\")\n",
    "print(f\"3 公里預測：{linear_b+3*linear_w:.2f} 分鐘；目標：16 分鐘\")\n",
    "fig, ax = plt.subplots(figsize=(11,5), layout=\"constrained\")\n",
    "ax.plot(ax_values,linear_target,color=GRAY,linewidth=5,label=\"Target\")\n",
    "ax.plot(ax_values,linear_prediction,color=NAVY,label=\"Manual\")\n",
    "ax.set(xlabel=\"Distance (km)\",ylabel=\"Time (minutes)\",title=\"A: manually align a straight line\")\n",
    "ax.grid(alpha=.22); ax.legend(); plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b-guide",
   "metadata": {},
   "source": [
    "## B 單一片段：每次只改一個參數\n",
    "\n",
    "投影片 48、50、52 分三次回到同一格。B 的參數與 C 的參數互相獨立。\n",
    "\n",
    "| 回合 | 固定值 | 要改的值 | 先預測什麼 |\n",
    "| --- | --- | --- | --- |\n",
    "| B1 | b=-1、c=1 | w：1 改 2 | 斜率、轉折位置 |\n",
    "| B2 | w=1、c=1 | b：-0.5 改 -1.5 | 轉折方向 |\n",
    "| B3 | w=1、b=-1 | c：1、2、-1 | 貢獻大小與正負 |\n",
    "\n",
    "w 不為 0 時，轉折位置為 -b/w，啟動區斜率為 c*w。每次修改後重新執行。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b-piece",
   "metadata": {},
   "outputs": [],
   "source": [
    "w = 1.0   # 建議 -5 到 5\n",
    "b = -1.0  # 建議 -3 到 3\n",
    "c = 1.0   # 建議 -5 到 5\n",
    "\n",
    "xx = np.linspace(-3,3,301)\n",
    "piece = c*np.maximum(0,w*xx+b)\n",
    "fig,ax = plt.subplots(figsize=(11,5),layout=\"constrained\")\n",
    "ax.plot(xx,piece,color=NAVY,label=\"c * ReLU(w*x + b)\")\n",
    "ax.axhline(0,color=GRAY,linewidth=1.2)\n",
    "if abs(w)>1e-12:\n",
    "    knot=-b/w\n",
    "    if -3 <= knot <= 3:\n",
    "        ax.axvline(knot,color=ORANGE,linestyle=\"--\",label=f\"knot = {knot:.2f}\")\n",
    "    print(f\"轉折位置 {knot:.3f}，啟動區斜率 {c*w:.3f}。圖中 x 範圍是 -3 到 3。\")\n",
    "else:\n",
    "    print(\"w=0，片段是常數，沒有 -b/w 的轉折公式。\")\n",
    "ax.set(xlim=(-3,3),xlabel=\"Input x\",ylabel=\"Contribution\",title=\"B: one component\")\n",
    "ax.grid(alpha=.22);ax.legend();plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c-guide",
   "metadata": {},
   "source": [
    "## C 三片段手動對齊\n",
    "\n",
    "投影片 57–61。先執行基準，再至少做三次**單項修改**，其中一次讓 MSE 比前次降低。\n",
    "本關目標與投影片上的山形算例不同，請從灰線觀察需要的轉折位置與斜率。\n",
    "\n",
    "1. C 格的 `w、b、c` 各有三個數字。索引 `[1]` 指第二個，也就是中間的數字。\n",
    "2. 先保留 w=[1,1,1]，試著調 d。接著看轉折位置調 b、看各片段貢獻調 c。\n",
    "3. 例如將 b 中間的 -0.8 改成 -1.0，其他值保持上一次結果，change_note 寫下改動與預期。\n",
    "4. 修改下方短程式格後，按左側 ▶ 或 Shift + Enter 執行。先讀 MSE，再看緊接在下方的灰線（目標）與藍線（你的模型）。\n",
    "5. 要分析各片段時，點主圖下方「展開三個片段的貢獻圖」。每次執行仍累積紀錄，完整表格在 C2 顯示。\n",
    "\n",
    "只修改參數與 `change_note`，最後一行 `update_manual(...)` 保留。繪圖工具已放在 0 準備格；學習內容與 MSE 計算不變。\n",
    "\n",
    "建議範圍 w、c 在 -5 到 5，b 在 -3 到 3。d 改變整體高度。\n",
    "先不要執行 E。不要重跑 0，避免清空實驗紀錄。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c-manual",
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "w = [1.0, 1.0, 1.0]\n",
    "b = [-0.15, -0.8, -2.2]\n",
    "c = [0.7, -1.4, 0.8]\n",
    "d = 0.0\n",
    "change_note = \"基準：先觀察\"  # 修改時寫：改了哪個值，預期什麼效果？\n",
    "\n",
    "update_manual(w, b, c, d, change_note)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c2-guide",
   "metadata": {},
   "source": [
    "### C2 手動檢查點\n",
    "\n",
    "填寫三個文字欄位，執行後保留輸出。表格包含每次參數快照，方便回到先前設定。\n",
    "如果重新啟動執行環境，記憶體紀錄會清除。保存輸出後仍可閱讀，但要重新訓練需恢復程式狀態。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c2-reflection",
   "metadata": {},
   "outputs": [],
   "source": [
    "best_change = \"請填寫：最有效的一次修改與 MSE 變化\"\n",
    "reason = \"請填寫：它為什麼改變這一段曲線？\"\n",
    "next_try = \"請填寫：下一步想調哪個參數？\"\n",
    "show_records()\n",
    "single_changes=sum(r[\"changed_count\"]==1 for r in experiments)\n",
    "improved=any(experiments[i][\"mse\"]<experiments[i-1][\"mse\"] for i in range(1,len(experiments)))\n",
    "print(f\"單項修改紀錄 {single_changes} 次，曾比前次改善：{improved}\")\n",
    "print(best_change);print(reason);print(next_try)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d-reveal",
   "metadata": {},
   "source": [
    "## D 揭露：你剛才調整的就是神經網路\n",
    "\n",
    "投影片 62–91。C 的三個片段對應三個隱藏神經元：\n",
    "\n",
    "`hᵢ = ReLU(wᵢ*x + bᵢ)`，輸出層為 `ŷ = d + Σ cᵢ*hᵢ`。\n",
    "\n",
    "1 個輸入、3 個隱藏神經元、1 個線性輸出，共 **10 個模型參數**。\n",
    "你剛才已經替這個模型手動選參數。課程結尾讓梯度下降更新同一批參數，模型與資料不換。\n",
    "NumPy 寫出的計算也是神經網路，並不需要換成特定套件才算。\n",
    "\n",
    "本活動展示函數擬合與最佳化。你看得到整段目標曲線，因此它不是嚴格的未知資料測試。\n",
    "先回到課程，認識訓練與驗證的差別，**到課程結尾再執行 E**。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1-guide",
   "metadata": {},
   "source": [
    "## E1 課程結尾：凍結手動起點\n",
    "\n",
    "投影片 105。先確認 C 目前是哪一組參數，再將下格 CONFIRM_HANDOFF 改為 True 並執行。\n",
    "若想用以前某組設定，先從 C2 紀錄抄回 C，執行 C，再凍結。\n",
    "\n",
    "E1 複製目前參數，不會自動挑歷史最低分，也不會訓練。\n",
    "**比較學習率時只重跑 E2，不要重跑 E1**。若重新凍結起點，程式會清空自動試驗比較表。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e1-freeze",
   "metadata": {},
   "outputs": [],
   "source": [
    "CONFIRM_HANDOFF = False  # 課程最後，確認手動成果後改 True\n",
    "if CONFIRM_HANDOFF:\n",
    "    if \"manual_params\" not in globals():\n",
    "        raise ValueError(\"尚未執行 C，請先完成手動實驗。\")\n",
    "    check_params(manual_params)\n",
    "    start_params=copy_params(manual_params)\n",
    "    auto_runs=[]\n",
    "    print(f\"已凍結手動起點，MSE={mse(start_params,x,y):.6f}\")\n",
    "    print(format_params(start_params))\n",
    "else:\n",
    "    print(\"尚未交接。先完成手動實驗，課程結尾才把 CONFIRM_HANDOFF 改成 True。\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2-guide",
   "metadata": {},
   "source": [
    "## E2 自動訓練：同一模型、同一起點\n",
    "\n",
    "投影片 106–111。E1 完成後，把 RUN_AUTOMATIC 改成 True，learning_rate=0.1，steps=2000，再執行。\n",
    "\n",
    "程式顯示指定步數的曲線快照、最終對照、MSE 歷程與試驗表。每次 E2 都從 start_params 重新開始。\n",
    "接著只改學習率，比較 0.001、0.03。0.3 留作延伸觀察，它不保證每次都發散。\n",
    "如果數值不穩定，程式保留前一步並印出原因，不會暗中更換學習率。\n",
    "\n",
    "灰色 Target 是目標，藍色 Frozen manual 是凍結的手動結果，橘色 Automatic 是自動結果。\n",
    "MSE 曲線使用對數縱軸，方便看到接近零的變化。所有設定與實際完成步數保留在試驗表。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e2-train",
   "metadata": {},
   "outputs": [],
   "source": [
    "RUN_AUTOMATIC = False  # 課程結尾才改 True\n",
    "learning_rate = 0.01\n",
    "steps = 2000\n",
    "\n",
    "def gradients(p,xx,yy):\n",
    "    z=xx[:,None]*p[\"w\"]+p[\"b\"]\n",
    "    h=np.maximum(0,z)\n",
    "    e=p[\"d\"]+h@p[\"c\"]-yy\n",
    "    g_out=2*e/len(xx)\n",
    "    g_z=g_out[:,None]*p[\"c\"]*(z>0)\n",
    "    return {\"w\":np.sum(g_z*xx[:,None],axis=0),\"b\":np.sum(g_z,axis=0),\n",
    "            \"c\":h.T@g_out,\"d\":np.array(np.sum(g_out))}\n",
    "\n",
    "def update(p,g,lr):\n",
    "    return {k:p[k]-lr*g[k] for k in p}\n",
    "\n",
    "def train(start,xx,yy,lr=.01,steps=2000):\n",
    "    if not 0<lr<=.3:\n",
    "        raise ValueError(\"學習率請設在大於 0 到 0.3。\")\n",
    "    if isinstance(steps,bool) or not isinstance(steps,(int,np.integer)) or not 1<=steps<=10000:\n",
    "        raise ValueError(\"steps 請輸入 1 到 10000 的整數。\")\n",
    "    check_params(start)\n",
    "    p=copy_params(start)\n",
    "    history=[mse(p,xx,yy)]\n",
    "    if not np.isfinite(history[0]):\n",
    "        raise ValueError(\"起點 MSE 不是有限值，請檢查 C 與 E1。\")\n",
    "    snapshots={0:copy_params(p)}\n",
    "    status=\"完成指定更新次數\"\n",
    "    limit=max(1e4,history[0]*1000)\n",
    "    for step in range(1,steps+1):\n",
    "        with np.errstate(over=\"ignore\",invalid=\"ignore\"):\n",
    "            candidate=update(p,gradients(p,xx,yy),lr)\n",
    "            loss=mse(candidate,xx,yy)\n",
    "        if not np.isfinite(loss) or loss>limit:\n",
    "            status=f\"第 {step} 步不穩定，保留前一步。請縮小學習率或檢查起點。\"\n",
    "            break\n",
    "        p=candidate;history.append(loss)\n",
    "        if step in {10,100,500,steps}: snapshots[step]=copy_params(p)\n",
    "    snapshots[len(history)-1]=copy_params(p)\n",
    "    return p,np.asarray(history),snapshots,status\n",
    "\n",
    "if RUN_AUTOMATIC:\n",
    "    if \"start_params\" not in globals():\n",
    "        raise ValueError(\"還沒有凍結起點。請先到 E1 確認交接並執行。\")\n",
    "    automatic_params,history,snapshots,status=train(start_params,x,y,learning_rate,steps)\n",
    "    print(status)\n",
    "    print(f\"完成 {len(history)-1} 步，手動起點 MSE={history[0]:.6f}，自動 MSE={history[-1]:.6f}\")\n",
    "    fig,ax=plt.subplots(figsize=(11,6),layout=\"constrained\")\n",
    "    ax.plot(x,y,color=GRAY,linewidth=5,label=\"Target\")\n",
    "    for j,(step,p) in enumerate(snapshots.items()):\n",
    "        ax.plot(x,predict(p,x),color=[NAVY,\"#5393C4\",\"#188067\",\"#AD739E\",ORANGE][j%5],\n",
    "                linestyle=[\"-\",\"--\",\"-.\",\":\",\"-\"][j%5],label=f\"Step {step}\")\n",
    "    ax.set(xlabel=\"Input x\",ylabel=\"Prediction\",title=\"E2: snapshots during training\")\n",
    "    ax.legend(ncol=3);ax.grid(alpha=.22);plt.show()\n",
    "    print(\"各快照 MSE：\",[(step,round(mse(p,x,y),6)) for step,p in snapshots.items()])\n",
    "    fig,ax=plt.subplots(figsize=(11,5),layout=\"constrained\")\n",
    "    ax.plot(x,y,color=GRAY,linewidth=5,label=\"Target\")\n",
    "    ax.plot(x,predict(start_params,x),color=NAVY,label=\"Frozen manual\")\n",
    "    ax.plot(x,predict(automatic_params,x),color=ORANGE,linestyle=\"--\",label=\"Automatic\")\n",
    "    ax.set(xlabel=\"Input x\",ylabel=\"Prediction\",title=\"E2: same model and starting parameters\")\n",
    "    ax.grid(alpha=.22);ax.legend();plt.show()\n",
    "    fig,ax=plt.subplots(figsize=(11,5),layout=\"constrained\")\n",
    "    ax.semilogy(np.maximum(history,1e-12),color=ORANGE)\n",
    "    ax.set(xlabel=\"Update step\",ylabel=\"MSE (log scale)\",title=\"E2: training history\")\n",
    "    ax.grid(alpha=.22);plt.show()\n",
    "    auto_runs.append({\"lr\":learning_rate,\"requested\":steps,\"completed\":len(history)-1,\n",
    "                      \"start_mse\":history[0],\"end_mse\":history[-1],\"status\":status})\n",
    "    rows=[\"| 學習率 | 指定步數 | 完成步數 | 起點 MSE | 最後 MSE | 狀態 |\",\n",
    "          \"| --- | --- | --- | --- | --- | --- |\"]\n",
    "    for r in auto_runs:\n",
    "        rows.append(f\"| {r['lr']} | {r['requested']} | {r['completed']} | {r['start_mse']:.6f} | {r['end_mse']:.6f} | {r['status']} |\")\n",
    "    display(Markdown(\"\\n\".join(rows)))\n",
    "else:\n",
    "    print(\"尚未啟動自動訓練。課程結尾先完成 E1，再把 RUN_AUTOMATIC 改為 True。\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f-guide",
   "metadata": {},
   "source": [
    "## F 反思與完成分享\n",
    "\n",
    "投影片 112–113。填寫下方文字欄位，執行後保留輸出。\n",
    "1. 保留 C 的基準與三次單項修改、E2 的比較圖與試驗表、F 反思。\n",
    "2. 等待 Notebook 儲存完成。不要開啟「儲存時省略程式格輸出」的設定。\n",
    "3. 右上共用／Share，一般存取權選「知道連結的任何人」，角色選「檢視者」。\n",
    "4. 複製連結，用無痕視窗檢查可閱讀，再繳交這個唯讀 Colab 連結。\n",
    "5. 若校方限制公開分享，改與教師帳號共用並告知。不另上傳分數、密碼或截圖。\n",
    "\n",
    "分享前移除私密資訊。連結會分享文字、程式與已保存的輸出，執行中的記憶體不會跟著分享。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f-reflection",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你的學號:_____\n",
      "你的姓名:______\n",
      "請填寫：手動改參數進行圖形的fit，你的步驟是什麼？為什麼？ ANS:(你的作答)\n",
      "請填寫：全自動更新是否改善MSE？使用什麼起點、學習率與步數？ ANS:( 起點 MSE＝＿＿，學習率＝＿＿，實際更新＿＿步，結束 MSE＝＿＿，因此有／沒有改善)\n",
      "請填寫：最終你認為你手動調整模型與自動訓練作法的差異是什麼？ANS:(你的作答_)\n",
      "請填寫：自動訓練下即時 MSE 降低，是否是最終結果? 你的看法? ANS:(你的作答_)\n"
     ]
    }
   ],
   "source": [
    "studentID = \"你的學號:_____\"\n",
    "studentName = \"你的姓名:______\"\n",
    "manual_explanation = \"請填寫：手動改參數進行圖形的fit，你的步驟是什麼？為什麼？ ANS:(你的作答)\"\n",
    "automatic_explanation = \"請填寫：全自動更新是否改善MSE？使用什麼起點、學習率與步數？ ANS:( 起點 MSE＝＿＿，學習率＝＿＿，實際更新＿＿步，結束 MSE＝＿＿，因此有／沒有改善)\"\n",
    "learning_explanation = \"請填寫：最終你認為你手動調整模型與自動訓練作法的差異是什麼？ANS:(你的作答_)\"\n",
    "evaluation_explanation = \"請填寫：自動訓練下即時 MSE 降低，是否是最終結果? 你的看法? ANS:(你的作答_)\"\n",
    "for answer in [studentID,studentName,manual_explanation,automatic_explanation,learning_explanation,evaluation_explanation]:\n",
    "    print(answer)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "sources",
   "metadata": {},
   "source": [
    "## 參考\n",
    "\n",
    "- [Colab 官方 FAQ：儲存、分享與執行環境](https://research.google.com/colaboratory/faq.html)\n",
    "- [Colab 操作導覽](https://colab.research.google.com/notebooks/basic_features_overview.ipynb)\n",
    "- [Google ML Crash Course：梯度下降](https://developers.google.com/machine-learning/crash-course/linear-regression/gradient-descent)\n",
    "- [Toronto CSC311：多層感知器](https://www.cs.toronto.edu/~rahulgk/courses/csc311_f24/readings/mlps.pdf)\n"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "name": "Class1_課中手動與結尾自動_v12_主圖優先.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.14.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
