🎯學習目標
完成本教材後,你將能夠:
- 說明近紅外光譜 (NIR) 為何能非破壞性地反映雞蛋的老化。
- 對光譜資料做 SNV、Savitzky–Golay 微分等前處理,並理解每種方法的目的。
- 用 PLS 迴歸處理「特徵多又高度相關」的光譜資料,並用交叉驗證挑選潛在變數數量。
- 用 R²、RMSE、RMSECV 評估迴歸模型,並判讀「預測 vs 實際」圖。
- 辨識資料洩漏:理解「隨機分折」與「依樣本分組」驗證的差異。
- 分別用 Python(程式) 與 Orange Data Mining(視覺化拖拉) 完成同一個分析。
📄 dataset_egg_storage.csv(原始資料) 📓 PLS_egg_freshness_colab.ipynb(Python/Colab) 🍊 egg_storage_orange.tab(Orange 資料) 🍊 egg_pls_orange.ows(Orange 工作流程) 🐍 run_analysis.py(繪圖腳本)
1為什麼要測雞蛋的新鮮度?
雞蛋從產下那一刻就開始老化:蛋白變稀、氣室變大、蛋白 pH 因二氧化碳逸散而上升、蛋殼外層的角質層 (cuticle)逐漸乾燥變薄。 傳統評估新鮮度的哈夫單位 (Haugh Unit) 雖然準確,卻是破壞性的(必須打破蛋來量濃蛋白高度),無法用在產線上逐顆檢測。
論文指出:在室溫 (23±1°C) 下,雞蛋大約可存放 14 天;之後品質明顯下降。
因此若能用一支接在智慧型手機上的低成本光譜儀,不打破蛋就估出「這顆蛋放了幾天」,對產業與消費者都極具價值。
這正是本研究與本教材要解決的問題:用光譜迴歸出儲存天數 storage_days。
storage_days 是連續數值(0、1、2 … 21 天),所以我們用迴歸(PLS Regression)而非分類。
輸入則是每顆蛋的一條近紅外光譜。2近紅外光譜原理 & 蛋的化學變化
近紅外光 (NIR, 約 700–2500 nm) 照到樣品時,會激發分子中 C–H、O–H、N–H 等化學鍵的倍頻 (overtone) 與 合頻 (combination) 振動。不同化學組成 → 不同的吸收/反射特徵,因此光譜等於樣品的「化學指紋」。
本研究使用的 SCiO 手持式光譜儀波長範圍 740–1070 nm,屬於「短波近紅外」。在這個波段:
- ~890 nm:C–H 第三倍頻
- ~960 nm:O–H 第二倍頻(與水/角質層含水量有關)
- ~1020 nm:N–H 第二倍頻(與蛋白質有關)
光在 700–900 nm 可穿透約 3–4 mm,足以穿過蛋殼抵達角質層與蛋白(但到不了蛋黃)。 隨著儲存時間增加,角質層乾燥變薄、蛋殼碳酸鈣礦物更外露、CH/OH/NH 化學鍵改變 —— 這些都會反映在光譜上, 讓模型「看得出」蛋的年紀。
3認識資料集
研究團隊監測 30 顆褐殼蛋(H&N 品系、49–52 週齡母雞、蛋重 55–65 g), 從產下當天 (第 0 天) 起連續 22 天、每天量一次,得到 30 × 22 = 660 筆光譜。
| 欄位 | 意義 | 在建模中的角色 |
|---|---|---|
storage_days | 已儲存天數 0–21 | 目標 y(要預測的值) |
sample | 蛋的編號 1–30(同一顆蛋出現 22 次) | 分組驗證用(見第 8 節) |
Spectra_740 … Spectra_1070 | 各波長的反射率(331 個) | 特徵 X |
4PLS 迴歸是什麼?為什麼適合光譜?
我們有 331 個波長(特徵),但它們有兩個棘手特性:
- 高度共線性:相鄰波長的反射率幾乎一模一樣(高度相關)。
- 特徵多:波長數量可能比樣本數還多。
普通最小平方法 (OLS) 在這種情況會因矩陣不可逆而崩潰。PLS(偏最小平方, Partial Least Squares)的巧思是:
與只看 X 自身變異的 PCA 不同,PLS 是監督式的 —— 它在降維時就「偷看」了 y。 最關鍵的超參數就是 要用幾個 LV:太少會欠擬合,太多會過擬合。我們用交叉驗證來決定(第 7 節)。
5光譜前處理 (Preprocessing)
原始光譜混有與化學無關的干擾 —— 蛋殼粗糙造成的散射、儀器或擺放造成的基線漂移。 前處理的目的就是去除這些干擾、突顯化學訊號。常見方法:
| 方法 | 做什麼 | 解決什麼 |
|---|---|---|
| SNV Standard Normal Variate | 每條光譜各自減平均、除標準差 | 散射造成的乘性/加性偏移 |
| Savitzky–Golay 平滑 | 在小窗內用多項式擬合再取值 | 隨機雜訊(降噪) |
| 一階微分 (1st derivative) | 取相鄰點的斜率 | 固定基線偏移 |
| 二階微分 (2nd derivative) | 取斜率的變化率 | 線性基線;突顯吸收峰(但放大雜訊) |
6Python 建模流程
以下是用 scikit-learn 的核心程式碼(完整、可一鍵執行的版本在第 12 節的 Colab 筆記本)。
① 載入資料、切出 X / y
import numpy as np, pandas as pd from scipy.signal import savgol_filter from sklearn.cross_decomposition import PLSRegression from sklearn.model_selection import cross_val_predict, KFold, GroupKFold from sklearn.metrics import r2_score, mean_squared_error df = pd.read_csv("dataset_egg_storage.csv") Xcols = [c for c in df.columns if c.startswith("Spectra_")] X = df[Xcols].values # (660, 331) 光譜 y = df["storage_days"].values # 目標:天數 groups = df["sample"].values # 蛋編號(分組驗證用)
② 定義前處理
def snv(s): return (s - s.mean(axis=1, keepdims=True)) / s.std(axis=1, keepdims=True) def sg(s, deriv=2, window=15, poly=2): # Savitzky-Golay 微分 return savgol_filter(s, window_length=window, polyorder=poly, deriv=deriv, axis=1) Xp = sg(X, deriv=2) # 本例最佳:二階微分
③ 訓練 PLS 並用交叉驗證評估
kf = KFold(n_splits=10, shuffle=True, random_state=42) yhat = cross_val_predict(PLSRegression(n_components=12, scale=True), Xp, y, cv=kf) rmse = np.sqrt(mean_squared_error(y, yhat)) print(f"R² = {r2_score(y, yhat):.3f} RMSECV = {rmse:.2f} 天") # -> R² = 0.820 RMSECV = 2.69 天
scale=True?scale=True 會在建模前把每個波長標準化(StandardScaler),
避免數值大的波長主導模型。這等同論文流程中的「mean-center / autoscale」步驟。7選潛在變數數量:一倍標準誤法則
「要用幾個 LV」是 PLS 最重要的決定。作法是對每個候選數量跑 10-fold 交叉驗證, 算出 RMSECV(交叉驗證均方根誤差),畫成曲線。
8⚠️ 驗證的陷阱:隨機分折 vs 依「蛋」分組
這是整份教材最重要的觀念。回想資料結構:同一顆蛋被量了 22 次(第 0 天到第 21 天)。 同一顆蛋第 5 天和第 6 天的光譜長得非常像。
如果用隨機 10-fold,這兩筆很可能一筆落在訓練集、一筆落在驗證集。模型可能靠「認得這顆蛋」 來壓低誤差,而不是真的學會「判斷天數」。這種因資料切分不當而高估表現的現象,叫做資料洩漏 (data leakage)。
# 依蛋編號分組做交叉驗證 gkf = GroupKFold(n_splits=10) yhat_grp = cross_val_predict(PLSRegression(n_components=12, scale=True), Xp, y, cv=gkf, groups=groups)
| 驗證方式 | R² | RMSECV (天) | MAE (天) |
|---|---|---|---|
| 隨機 10-fold CV | 0.820 | 2.69 | 2.18 |
| 依蛋分組 GroupKFold | 0.811 | 2.75 | 2.22 |
9結果與解讀
模型可在 ±2.7 天的誤差內估出雞蛋年紀。圖 4 的擬合線斜率略小於 1(約 0.84),代表 PLS 常見的 向均值收縮:很新鮮(接近 0 天)的蛋會被略微高估、很舊(接近 21 天)的蛋會被略微低估。
10哪些波長最重要?(VIP 與迴歸係數)
建好模型後,我們想知道它「看了光譜的哪些位置」。VIP (Variable Importance in Projection) 衡量每個波長對 PLS 的貢獻,慣例以 VIP > 1 視為重要波長。
本例 VIP 最高的波長約為 788、821、1002–1007 nm。這與論文觀察一致:蛋在儲存過程中, 角質層含水量與蛋白質的 N–H/O–H 鍵結改變,使得 ~1000 nm 附近成為判斷天數的關鍵區域。 模型不是黑盒子 —— 它的依據可以對應到真實的化學變化。
PCA 探索(補充)
11用 Orange Data Mining 實作(不寫程式)
Orange Data Mining 是一套視覺化拖拉的資料分析軟體, 特別適合不熟程式的學生:把「元件 (widget)」拉到畫布上、用線連起來,就完成分析。
Options → Add-ons… → 勾選 Spectroscopy → 安裝後重啟。步驟:建立工作流程
storage_days=target、sample=meta、其餘為光譜特徵),開檔即可用。storage_days 畫散佈圖,重現圖 4。File → Data Table;File → Preprocess Spectra;
Preprocess Spectra → PLS;Preprocess Spectra → Test and Score(Data);
PLS → Test and Score(Learner);
Preprocess Spectra → Predictions、PLS → Predictions;Predictions → Scatter Plot。egg_storage_orange.tab;
② 在 Preprocess Spectra 內加入 SNV 與 SavGol(.ows 提供骨架,前處理步驟請依步驟 3 自行加入);
③ 確認 PLS components=12、Test and Score 選 10-fold。sample,可改用
Cross validation by feature(依特徵分組)並選 sample,即等同 Python 的 GroupKFold —— 對照第 8 節。12用 Google Colab 跑 Python(免安裝)
Google Colab 是免費的雲端 Python 環境,學生只要有 Google 帳號、用瀏覽器即可執行,無需安裝任何東西。
開啟方式(任選一種)
筆記本第一個資料格會跳出上傳視窗,請選擇 dataset_egg_storage.csv(可從本頁上方「下載」區或 Mendeley 連結取得),
接著由上到下逐格執行 (Shift+Enter) 即可。
▶ 在 Google Colab 開啟筆記本 ⬇ 下載資料 CSV
13Python vs Orange:兩種工具怎麼選?
| 面向 | Python (scikit-learn) | Orange Data Mining |
|---|---|---|
| 操作方式 | 寫程式碼 | 拖拉元件、連線 |
| 學習曲線 | 較陡(需懂語法) | 平緩(視覺化、即時預覽) |
| 彈性 / 客製 | 極高(任何流程、自訂函式) | 受限於現有元件 |
| 可重現 / 自動化 | 強(腳本、版本控制) | 適合互動探索 |
| 適合對象 | 需要客製分析、寫論文 | 教學入門、快速試驗、概念理解 |
14動手練習
- 換前處理:把二階微分改成 SNV 或原始光譜,重跑第 6 節,比較 RMSECV 差多少。
- 改 LV 數量:手動設成 2、5、19 個 LV,觀察圖 4 與過/欠擬合現象。
- 窗寬實驗:改 Savitzky–Golay 的
window(如 7、25),看二階微分的雜訊如何變化。 - 改成分類題:依「室溫最多放 14 天」,把問題改成「新鮮 (≤7 天) vs 不新鮮 (>7 天)」二元分類, 用 PLS-DA 或邏輯迴歸做做看。
- 波長篩選:只用 VIP > 1 的波長重建模型,準確度會變好還是變差?對應論文的「波長選擇」步驟。
- 雙工具對照:用 Orange 跑出 R²/RMSE,和 Python 的結果比對,思考為何可能有小差異(亂數種子、分折方式)。
📚參考資料與檔案清單
論文與資料
- Coronel-Reyes, J. et al. (2018). Determination of egg storage time at room temperature using a low-cost NIR spectrometer and machine learning techniques. Computers and Electronics in Agriculture, 145, 1–10. doi:10.1016/j.compag.2017.12.030
- 資料集:Mendeley Data — 6hn67h2trb (v2)
- Wold, S. et al. (2001). PLS-regression: a basic tool of chemometrics. Chemometrics Intell. Lab. Syst.
本教材附帶檔案(點擊下載)
- dataset_egg_storage.csv — 原始資料(Python / Colab 用)
- PLS_egg_freshness_colab.ipynb — Python 教學筆記本(Google Colab)
- egg_storage_orange.tab — Orange 專用資料(已標欄位角色,波長欄位為純數字)
- egg_pls_orange.ows — Orange 工作流程(七元件已接好)
- run_analysis.py — 產生本頁所有圖的完整 Python 腳本