螢光 EEM,讀出橄欖油的老化指紋

用真實的橄欖油加速老化 EEM 螢光資料,學會兩個化學計量學核心方法:
PCA 探索老化結構、機器學習分類鑑別新鮮度。

24市售橄欖油 (EVOO)
10加速老化階段 (0–9)
728EEM 螢光指紋
55%前 2 主成分變異
為什麼是螢光 EEM

一個樣品,不是一條光譜,而是一張螢光地圖

激發–放射矩陣(Excitation-Emission Matrix, EEM)同時掃描「用哪個波長照」與「樣品放出哪個波長的光」, 得到一張二維螢光指紋。橄欖油裡天然就有會發螢光的成分——生育酚(維生素 E)、葉綠素、氧化產物, EEM 一次就把它們的整體輪廓拍下來。

靈敏

螢光對微量螢光物極敏感,適合追蹤抗氧化劑消耗與氧化產物生成。

二維指紋

不預設找誰,一次拍下整個激發 × 放射平面的螢光輪廓。

但是…

一張 EEM 是上百個彼此相關的數字,必須靠化學計量學才能解讀。
  橄欖油樣品 ──▶ 螢光光譜儀掃描(激發 × 放射) ──▶ 每個激發波長一條放射光譜 ──▶ 疊成「激發 × 放射」EEM 矩陣
本課兩步走: 先用 PCA(非監督)看老化過程裡有什麼結構,再用 分類模型(監督)鑑別 fresh / mid / late 三種新鮮度。
🎬 完整教學影片(中文旁白 · 約 4.7 分)· EEM 概念 → PCA 老化軌跡 → 新鮮度分類 · English version
資料集

橄欖油加速老化 EEM(公開資料)

資料來自 Mendeley Data 的 橄欖油老化螢光與 UV 光譜資料集(Venturini、Fluri & Baumgartner): 24 款市售特級初榨橄欖油,經 10 個加速老化階段(Aging Step 0–9),每個樣品量得一張 EEM 螢光指紋。屬公開資料(CC BY 4.0)。

新鮮橄欖油 EEM 熱圖
新鮮(Aging Step 0):~Ex 370 / Em 670 nm 亮區強,葉綠素類訊號明顯。
老化橄欖油 EEM 熱圖
老化(Aging Step 9):亮區重新分布、整體輪廓改變——老化在 EEM 上留下指紋。

圖中由左下往右上的對角亮線是散射(Rayleigh / Raman),屬光學假訊號、與化學無關;判讀時要避開它,只看真正的螢光亮區。 Python 解析後,每一列代表一個橄欖油 EEM 量測,欄位是分箱(binned)後的螢光特徵與幾個連回化學的區域特徵。

老化等級 aging_class量測數本課用途
fresh(Aging Step 0–2)144分類類別 A
mid-aged(Aging Step 3–6)296分類類別 B
late-aged(Aging Step 7–9)288分類類別 C
合計728PCA 探索 + 監督分類

前處理:補值 + 標準化

不同樣品、不同批次的整體強度會有高低差異。先 補值(Impute) 處理缺漏,再 標準化(Normalize / autoscale) 讓每個螢光特徵站上同一起跑點,避免少數高強度區域主導後續的 PCA 與分類——作用等同近紅外光譜裡的 SNV、質譜代謝體裡的 log + autoscale。

可解釋特徵

把螢光區域連回橄欖油的化學

EEM 的特徵不必是黑箱。把幾個有化學意義的激發 × 放射區域各自取平均, 就得到「看得懂」的特徵——而它們隨老化的變化,正好對應橄欖油裡真實發生的化學。

三個 EEM 區域特徵隨老化的變化
三個教學用區域特徵隨老化階段的變化(已正規化)。藍光區(生育酚類)前段快速下降、 綠/黃光區(氧化類)與紅光區(葉綠素類)後段明顯上升。

藍光區 · 生育酚類

Ex 300–340 / Em 320–380 nm。對應維生素 E 等天然抗氧化劑,老化前段被消耗而下降

綠/黃光區 · 氧化類

Ex 350–450 / Em 400–520 nm。氧化產物累積,隨老化逐步上升

紅光區 · 葉綠素類

Ex 350–460 / Em 650–750 nm。橄欖油特有色素訊號,隨老化重新分布。
教學重點: 特徵與化學對得上,模型才「可解釋」——抗氧化劑消耗、氧化產物生成,正是橄欖油老化的核心反應。 可解釋的特徵,讓 PCA 與分類的結果能被化學語言講清楚。
方法一 · 非監督

PCA:把上百個螢光特徵壓成幾個方向

主成分分析(Principal Component Analysis)在資料最分散的方向上建立新座標軸: PC1 是變異最大的方向、PC2 與它垂直且次大……少數幾個主成分,就能描述最重要的老化結構。

$X \approx T\,P^{\top}$  —  分數 $T$ 是樣本位置、負荷量 $P$ 是螢光特徵如何組成新方向

分數圖:老化軌跡自己浮現

橄欖油 EEM 的 PCA 分數圖
每個點是一個 EEM 量測,顏色為老化階段(0→9)。PCA 從沒看過老化標籤, 新鮮的樣本卻自己落在右下、老化的漂向左上,沿對角線形成一條老化軌跡(橘色箭頭)。 值得注意:老化方向與 PC2 的關聯(r≈0.44)比 PC1 更強——PC1 雖佔最大變異(42.0%),但主要反映的不是老化;左側遠離主群的點即可疑離群樣本。
核心觀念: PCA 是非監督的——只看螢光指紋、不用任何標籤,卻能讓老化結構自己浮現。它擅長探索、分群、找異常, 但不會直接告訴你「這支油是 fresh 還是 late」。
方法二 · 監督

分類:用螢光指紋鑑別新鮮度

想直接回答「fresh / mid / late」,就要給模型看過老化標籤——這是監督式學習。 把 EEM 特徵當輸入、aging_class 當目標,訓練分類器並用交叉驗證誠實評估。這就是它和 PCA 的關鍵差異: PCA 只看 X,分類模型一手抓 X、一手抓類別 y。

$\hat{y} = f(X)\ \in\ \{\text{fresh},\ \text{mid},\ \text{late}\}$  —  從螢光指紋 $X$ 學出新鮮度類別

比較多個模型,用交叉驗證選

用 Orange 的 Test & Score 同時跑 Random Forest、Logistic Regression、SVM, 以 5 折交叉驗證比較準確率;再用 Confusion Matrix 看哪些等級被搞混。

3 個老化等級

fresh / mid-aged / late-aged,由 Aging Step 分組而來。

多模型比較

RF / Logistic / SVM 一起評估,挑穩定又可解釋的。
相鄰最易混
fresh↔mid、mid↔late 邊界樣本最常被認錯——老化是連續的。
課堂討論: 老化是連續過程,硬切成三類,邊界附近本來就模糊。混淆矩陣若顯示「只混相鄰等級、不跨級亂跳」, 反而代表模型抓到了真實的老化順序——這比單看一個準確率數字更有教學價值。
怎麼算的

Python 程式(scikit-learn)

整套分析用 numpy / pandas / scikit-learn 完成,可重現:從 EEM 矩陣萃取特徵、做 PCA、再做監督分類。

特徵萃取 — 取有化學意義的 EEM 區域平均

import numpy as np

def eem_region_mean(eem, ex, em, ex_lo, ex_hi, em_lo, em_hi):
    """取 EEM 某個激發 × 放射矩形區域的平均強度,作為可解釋特徵。"""
    ex_sel = (ex >= ex_lo) & (ex < ex_hi)
    em_sel = (em >= em_lo) & (em < em_hi)
    return eem[np.ix_(em_sel, ex_sel)].mean()

# 三個連回化學的教學區域
tocopherol  = eem_region_mean(eem, ex, em, 300, 340, 320, 380)  # 生育酚(維生素E)藍光區
oxidation   = eem_region_mean(eem, ex, em, 350, 450, 400, 520)  # 氧化產物 綠/黃光區
chlorophyll = eem_region_mean(eem, ex, em, 350, 460, 650, 750)  # 葉綠素 紅光區

PCA — 主成分分析,看老化軌跡

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

X  = features[bin_columns].values           # 每列一個 EEM,欄為分箱螢光特徵
Xs = StandardScaler().fit_transform(X)      # 標準化,讓每個 bin 公平貢獻
pca = PCA(n_components=10).fit(Xs)
scores = pca.transform(Xs)                  # 樣本在新軸上的位置(分數)
evr = pca.explained_variance_ratio_ * 100
# PC1 = 42.0% | PC2 = 12.9% —— 老化軌跡沿對角線浮現(與 PC2 關聯較強,r≈0.44)

分類 — 鑑別 fresh / mid / late(5 折交叉驗證)

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_predict, StratifiedKFold
from sklearn.metrics import confusion_matrix

y = features["aging_class"]                 # fresh / mid-aged / late-aged
clf = RandomForestClassifier(n_estimators=300, random_state=42)
yhat = cross_val_predict(clf, Xs, y,
                         cv=StratifiedKFold(5, shuffle=True, random_state=42))
cm = confusion_matrix(y, yhat, labels=["fresh", "mid-aged", "late-aged"])
# 看混淆矩陣:誤判幾乎都落在「相鄰等級」之間
▶ 如何在本機重跑
pip install numpy pandas scipy scikit-learn matplotlib
# 1. 從 Raw_data 解析 EEM、萃取區域 + 分箱特徵 -> orange_olive_eem_features.csv
# 2. StandardScaler + PCA(10)            -> 陡坡 / 分數(依 aging_step 上色)
# 3. RandomForest + 5-fold CV            -> 混淆矩陣(fresh / mid / late)
# 圖輸出到 assets/plots/
不寫程式的版本

用 Orange Data Mining 親手拉一遍

課堂可用 Orange 的拖拉式 widget,不寫一行程式就重現上面的 PCA 與新鮮度分類。 File 指向 orange_olive_eem_features.csv,把 aging_class 設成 Target、sample_idsource_file 設成 Meta 即可。

Orange EEM 食品分析工作流程
Orange 工作流程:Python 特徵 → 視覺化工作流 → 模型評估,全程不寫程式。
              ┌─ Data Table        (檢視原始特徵)
              │
 File ────────┼─ PCA ──Transformed Data──▶ Scatter Plot       ← PCA 分支(Color = aging_step)
(olive_eem    │
 features)    └─ Select Columns ─ Preprocess ─┬─ Test & Score ──▶ Confusion Matrix
              (Target = aging_class)         └─(RF / Logistic / SVM 比較)
對應概念Python 圖Orange widget
EEM 螢光指紋eem_fresh / eem_lateFile → Data Table
區域特徵隨老化feature_trendsSelect Columns → Line Plot
老化軌跡(依階段上色)pca_olive_eemPCA → Scatter Plot(Color=aging_step)
新鮮度分類 + 評分Preprocess → Test & Score
哪些等級被搞混Confusion Matrix
總結

PCA 與監督分類,一張表看懂

面向PCA監督分類
學習方式非監督(只看 X)監督(用 X 與類別 y)
目的探索老化結構、分群、找異常鑑別新鮮度(fresh / mid / late)
找方向的準則最大化 X 的變異最小化分類錯誤、對齊類別分界
本課結果老化軌跡沿對角線浮現(與 PC2 關聯較強)RF / Logistic / SVM 比較,相鄰等級最易混
關鍵圖分數圖、負荷量Test & Score、混淆矩陣
何時用先用它「看」資料確定要鑑別時用它建模
帶走兩句話: PCA 把上百個螢光特徵壓縮成看得懂的老化方向;監督分類一手抓指紋、一手抓答案, 讓橄欖油的螢光 EEM 直接說出「這支油,有多新鮮」。
教學提醒: 本資料為示範用途。要做到出版品質,需再加上儀器校正、空白扣除、散射處理, 並依「油品身分」切分驗證集、保留獨立測試集,避免同一支油同時出現在訓練與測試而高估表現。