跳到主要內容

Day 40 模型部署與系列回顧

Day 40 模型部署與系列回顧

引言

今天是這個系列的最後一天。我們會把前面 39 天學到的東西收尾:先用 torch.onnx.export 把模型匯出成 ONNX,再用 Streamlit 做一個簡單的展示介面,最後為整個系列做一個完整的回顧,並談談接下來可以怎麼繼續往前走。

這一篇的重點不只是「把程式碼跑起來」,而是理解「模型從訓練到被使用」這中間還需要哪些環節。當你能把模型變成一個能被同事、客戶或自己 demo 的成果,整個深度學習的學習歷程才真正完整。

匯出成 ONNX:跨框架的橋樑

ONNX(Open Neural Network Exchange)是一種開放的神經網路交換格式,把模型從 PyTorch 帶到其他框架或硬體時特別有用。常見情境:用 PyTorch 訓練、用 onnxruntime 在不同平台推論、或把模型送進 ONNX Runtime + TensorRT 做加速。

import torch
from src.model import build_model

# 載入最佳權重
model = build_model("resnet18", num_classes=10)
state = torch.load("checkpoints/best.pt", map_location="cpu")
model.load_state_dict(state)
model.eval()

# 準備一個虛擬輸入(batch=1, 3 通道, 32x32)
dummy = torch.randn(1, 3, 32, 32)

# 匯出成 ONNX(opset_version=17 是當時穩定的寫法)
torch.onnx.export(
    model,
    dummy,
    "checkpoints/svhn_resnet18.onnx",
    input_names=["input"],
    output_names=["logits"],
    opset_version=17,
    dynamic_axes={
        "input": {0: "batch"},
        "logits": {0: "batch"},
    },
)
print("已匯出到 checkpoints/svhn_resnet18.onnx")

幾個關鍵參數值得說明:第一,input_names 與 output_names 會寫進 ONNX 模型,之後用其他框架載入時就能用對應的名字取得輸入與輸出;第二,dynamic_axes 讓 batch 維度變成動態,這樣模型可以接受任意大小的 batch,不用每次重新匯出;第三,opset_version=17 是當時穩定的版本,舊版本可能不支援某些新算子,新版本則可能有相容性問題,選 17 通常是最安全的選擇。

用 onnxruntime 驗證匯出結果

匯出後,務必用 onnxruntime 載入跑一次,確認輸出和 PyTorch 原版一致。這個步驟很常被忽略,但它能抓出很多「PyTorch 跑得好好的,匯出後數值卻不對」的問題。

import numpy as np
import onnxruntime as ort

# 用 PyTorch 推論
torch_out = model(dummy).detach().cpu().numpy()

# 用 ONNX Runtime 推論
session = ort.InferenceSession("checkpoints/svhn_resnet18.onnx")
onnx_out = session.run(None, {"input": dummy.numpy()})[0]

# 比對兩個輸出
print("PyTorch 預測類別:", int(torch_out.argmax()))
print("ONNX 預測類別 :", int(onnx_out.argmax()))
print("最大差異    :", float(np.abs(torch_out - onnx_out).max()))

正常情況下,最大差異應該在 1e-5 以下,這代表 ONNX 模型與原始 PyTorch 模型在數值上幾乎完全一致。如果差異偏大,可能是匯出時某個算子被轉換成低精度的近似,這時就要檢查模型中是否有不常見的自訂層。

用 Streamlit 做展示介面

Streamlit 是 Python 生態裡最受歡迎的快速展示工具之一,只要幾十行程式碼就能做出一個可以讓使用者上傳圖片、看到預測結果的網頁。下面的範例會把剛剛匯出的 ONNX 模型接上來,做一個簡單的 SVHN 數字辨識 demo。

import streamlit as st
import numpy as np
import onnxruntime as ort
from PIL import Image

# 載入 ONNX 模型(全域變數,整個 app 共用一份)
@st.cache_resource
def load_model(path: str = "checkpoints/svhn_resnet18.onnx"):
    return ort.InferenceSession(path)

session = load_model()

st.title("SVHN 門牌數字辨識 Demo")
st.write("上傳一張 32x32 的門牌號碼圖片,看模型預測的數字。")

uploaded = st.file_uploader("選擇圖片...", type=["png", "jpg", "jpeg"])
if uploaded is not None:
    img = Image.open(uploaded).convert("RGB").resize((32, 32))
    arr = np.asarray(img).astype(np.float32) / 255.0
    arr = (arr - np.array([0.4377, 0.4438, 0.4728])) / np.array([0.1980, 0.2010, 0.1970])
    arr = arr.transpose(2, 0, 1)[None, :]  # (1, 3, 32, 32)

    logits = session.run(None, {"input": arr})[0][0]
    probs = np.exp(logits) / np.exp(logits).sum()
    pred = int(probs.argmax())

    st.image(img, caption=f"預測數字:{pred}(信心 {probs[pred]:.2%})", use_column_width=True)

啟動方式很簡單:在終端機執行 streamlit run app.py,瀏覽器就會自動打開 http://localhost:8501 看到介面。整支程式不到 30 行就有上傳、預測、信心分數顯示等基本功能,這正是 Streamlit 的魅力。@st.cache_resource 確保 ONNX 模型只載入一次,重新整理頁面不會重複讀檔。實務上也可以用 st.progress、st.metric、st.columns 等元件把結果展示得更精緻,加上 st.sidebar 放說明文字,整個介面就會顯得相當完整。對沒有前端經驗的 Python 開發者來說,Streamlit 幾乎是最低成本的展示解法。

部署選項與後續考量

把 Streamlit app 跑起來只是第一步,要讓別人也能用,還需要考慮部署。常見選項包括:

  • Streamlit Community Cloud:免費託管,把程式碼 push 到 GitHub 就能自動部署,適合 demo 與小型專案。
  • Docker + 雲端服務:把 Streamlit 包進 Docker image,部署到 AWS、Azure、GCP 或自家機房,彈性高但維運成本也較高。
  • 整合到既有後端:把 ONNX 模型用 FastAPI 包成 REST API,讓前端或行動裝置呼叫。這種方式最適合正式產品。

不論選哪種方式,有幾件事值得提前準備:把 requirements 寫成 requirements.txt、把模型權重放進同一個 repo 或物件儲存、加上基本的輸入檢查與錯誤處理、為關鍵流程加上 log。這些工作看起來瑣碎,卻是模型從「能跑」變成「能用」的關鍵。

系列回顧:40 天學到了什麼?

從 Day 1 的 Python 環境設定,到 Day 40 的模型部署,我們走完了一條完整的「Python → 資料 → 數學 → PyTorch → 深度學習專案」學習路徑。回顧整個系列,可以分成七個階段:

  • 第一階段:Python 基礎(Day 3–14):運算子、條件判斷、迴圈、函式、字串、容器、檔案、模組與物件導向。
  • 第二階段:資料處理(Day 15–19):NumPy 運算、Pandas 清理與合併。
  • 第三階段:資料視覺化(Day 20–22):Matplotlib、Seaborn、完整的 EDA 流程。
  • 第四階段:數學基礎(Day 23–25):線性代數、微積分與梯度、機率與統計。
  • 第五階段:PyTorch 入門(Day 26–31):張量、自動微分、資料載入、第一個神經網路、訓練迴圈、影像分類實作。
  • 第六階段:經典模型(Day 32–36):CNN、遷移學習、RNN 與 LSTM,並各做了一個實作專案。
  • 第七階段:專案實戰(Day 37–40):從資料、模型、評估一路到部署,把整個深度學習專案走完一遍。

這條路徑的設計精神是:先把 Python 與資料處理的基礎打穩,再進入深度學習的理論與實作,最後用一個端到端的專案把所有環節串起來。完成這個系列後,你應該已經具備獨立完成一個中小型深度學習專案的能力——從資料探索、模型訓練,到評估與部署。

值得再次強調的是,深度學習專案的成功並不全靠模型。資料清理、任務定義、評估設計、部署細節這些「看不見的工作」,往往才是真正決定成果能不能上線的關鍵。整個系列裡,每一篇都在提醒這件事:沒有 Day 11 的檔案讀寫,就沒有 Day 28 的 DataLoader;沒有 Day 24 的梯度觀念,就沒有 Day 30 的訓練迴圈;沒有 Day 39 的評估分析,就沒有 Day 40 的部署信心。把這些觀念內化,之後不論遇到什麼樣的任務,都能快速判斷該把心力放在哪裡。

結語

這一篇同時也是整個 40 天系列的收尾。回頭看,從 Python 的基本語法一路走到模型部署,我們其實只用了最少的數學與框架 API,就把深度學習的核心流程(資料 → 模型 → 評估 → 部署)走了一遍。接下來的路很長,但地基已經打好了。

下一步可以怎麼走?

40 天只是一個起點。深度學習的世界還有很多值得探索的方向,依個人興趣可以選擇不同的延伸路線:

  • 自然語言處理進階:認識 Transformer 與注意力機制,學會使用 Hugging Face 的預訓練模型(BERT、GPT 等),進到生成式 AI 的世界。
  • 電腦視覺進階:認識物件偵測(YOLO、Faster R-CNN)、語意分割(U-Net、Mask R-CNN)等任務,並動手做影像生成(VAE、GAN、Diffusion Model)。
  • 模型最佳化與 MLOps:學習模型量化、剪枝、知識蒸餾等加速技巧,並把模型部署到行動裝置或邊緣裝置;同時培養 MLflow、DVC、Airflow 等 MLOps 工具的使用習慣。
  • 參加社群與實作:Kaggle 競賽、Hugging Face Spaces、GitHub 開源貢獻,都是把所學變成實戰的好地方。

不論選擇哪條路線,今天所建立的 Python 與 PyTorch 基礎都會持續派上用場。最重要的是保持動手做的習慣:閱讀官方資源、讀程式、跑模型、修正錯誤,每一輪迭代都會讓自己更接近能獨立解決真實問題的能力。這個系列到這裡告一段落,但你的深度學習旅程才剛開始。

感謝你陪著這個系列走完 40 天。當你下次打開 Jupyter notebook 開始寫模型時,希望這系列累積的觀念與程式碼能成為你最好的參考書。祝你在深度學習的路上,做出自己真正想做的東西。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...