核磁共振,讀出食品的分子含量

用真實的白酒 ¹H-NMR 資料,學會兩個化學計量學核心方法:
PCA 探索化學結構、PLS 回歸定量蘋果酸含量。

40白酒樣本
8712NMR ppm 點
1380.04 ppm 分桶
R²=0.93蘋果酸預測
為什麼是 NMR

最貼近分子細節的量測工具

核磁共振(Nuclear Magnetic Resonance, ¹H-NMR)依氫核所處的化學環境,輸出對應化學位移(ppm)的訊號強度。不同化合物在固定的 ppm 位置出現——乙醇在 1.17 / 3.65 ppm,蘋果酸在 ~2.96 ppm。一次量測,即可拿到食品樣本的完整化學指紋,而不需事先指定量什麼成分。

非破壞性

樣品可回收,量測快速(分鐘級),適合批次品質控管。

化學位移指紋

每種分子在固定 ppm 出現,一條光譜包含上百種成分資訊。

但是…

一條光譜是 8712 個 ppm 點,必須靠化學計量學才能從中提取含量與結構資訊。
  酒樣本 ──▶ ¹H-NMR 量測 ──▶ 8712 個 ppm 點光譜 ──▶ bucketing → 138 桶 ──▶ 面積標準化 ──▶ PCA / PLS
本課兩步走: 先用 PCA(非監督)探索光譜的化學結構,再用 PLS 回歸(監督)建立定量蘋果酸含量的模型。
資料集

白酒 ¹H-NMR(公開資料)

KU Quality & Technology(ucphchemometrics.com)公開的 Wine NMR 資料:40 支白酒,以 ¹H-NMR 量得從 6.0 ppm 到 0.5 ppm 共 8712 個點的光譜,並附有包含蘋果酸在內的 17 項化學參考值。屬公開學術資料。

白酒 ¹H-NMR 原始光譜
幾支白酒的 ¹H-NMR 原始光譜(8712 點)。乙醇 CH₃ 在 1.17 ppm、CH₂ 在 3.65 ppm 是最高峰——人眼難以直接比較不同酒的細微差異。

前處理:bucketing + 面積標準化

¹H-NMR 光譜的 8712 個點,彼此高度相關且峰位可能有輕微偏移。先做 bucketing:把每 0.04 ppm 寬的區段合併成一個桶,共得 138 個桶——消除偏移、降維。再做 面積標準化(每支酒的桶加總縮放為 100),消除因樣品稀釋濃度不同帶來的整體強度差異。最後做 mean-centering,讓每個桶以自身均值為基準。

bucketing + 面積標準化後光譜
bucketing(0.04 ppm,138 桶)加面積標準化後,酒與酒之間的相對訊號差異更清晰,乙醇主峰仍可見。
項目數值說明
樣本數40白酒
原始 ppm 點87126.0–0.5 ppm
分桶後維度1380.04 ppm / 桶
PLS 目標值蘋果酸 malicAcid0.04–3.12 g/L(連續值)
參考值數量17 項乙醇、甘油、有機酸、pH 等
方法一 · 非監督

PCA:把 8712 個 ppm 點壓成幾個方向

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

教學影片(一)PCA · 約 4.7 分 · 本地播放
$X \approx T\,P^{\top}$  —  分數 $T$ 是樣本位置、負荷量 $P$ 是 ppm 桶如何組成新方向

要保留幾個主成分?

PCA 陡坡圖
陡坡圖:PC1 = 78.5%——NMR 光譜高度相關(乙醇主導)。前 3 個成分累積 95.7%,2–3 個成分已足以看見化學結構。

分數圖:依蘋果酸濃度著色

PCA 分數圖(依蘋果酸濃度著色)
每個點是一支酒,顏色對應蘋果酸含量(viridis 色標)。PCA 從不知道含量,但酒沿蘋果酸濃度梯度在 PC1 方向有明顯分佈——結構自己說話。
核心觀念: PCA 是非監督的——只看光譜、不用任何標籤,卻能讓化學結構自己浮現。它擅長探索、分群、看化學梯度,但不會直接告訴你「這支酒的蘋果酸是幾 g/L」。

負荷量回連 ppm 化學區

PCA 負荷量 vs ppm
PC1(teal)與 PC2(coral)的負荷量曲線(ppm 軸反向)。負荷量高峰對應的 ppm,正是主導化學結構分佈的訊號區——乙醇 CH₃ (1.17 ppm)、CH₂ (3.65 ppm),以及有機酸、糖的 ppm 位。
方法二 · 監督回歸

PLS 回歸:用光譜「定量」蘋果酸含量

偏最小二乘回歸(Partial Least Squares Regression)給模型看過蘋果酸含量的標準值,找出的潛在變量 同時解釋光譜的變異、又對齊目標含量的梯度。這就是它和 PCA 的關鍵差異:PCA 只看 X,PLS 一手抓 X、一手抓連續目標 y。

教學影片(二)PLS 回歸 · 約 4.5 分 · 本地播放
$\max\ \operatorname{cov}(Xw,\ y)$  —  每個潛在變量(LV)最大化光譜與蘋果酸含量 $y$ 的共變異

選幾個潛在變量?用交叉驗證 RMSECV

RMSECV vs 潛在變量數
RMSECV(交叉驗證均方根誤差)vs 潛在變量數 LV。1 個 LV 時 RMSECV = 0.47 g/L;在 7 個 LV 時達到最低 RMSECV = 0.19 g/L。停在轉折點,不過度擬合。

成果:預測 vs 真實(交叉驗證)

PLS 預測 vs 真實蘋果酸
40 支酒的交叉驗證預測對上真實蘋果酸含量。點越靠近對角線越準。R²cv = 0.93,RMSECV = 0.19 g/L(蘋果酸範圍 0.04–3.12 g/L),NMR 光譜可解釋九成三的含量變異。

R²cv = 0.93

交叉驗證,93% 的蘋果酸變異可由 NMR 解釋。

7 個 LV

RMSECV 在此達到轉折最低,夠用就好。
0.19
g/L RMSECV,在 0.04–3.12 g/L 範圍內精準預測。

哪些 ppm 在預測蘋果酸?

PLS 係數 vs ppm
PLS 係數曲線(ppm 軸反向)。~2.96 ppm 係數最大——正是蘋果酸 CH₂ 的化學位移;5.9 ppm 附近亦有貢獻。模型學到的是蘋果酸自身的 NMR 訊號,不是統計巧合。
應用: 把蘋果酸換成其他目標(乙醇、甘油、pH…),同一套 PLS 回歸就是食品 NMR 定量的主力——成分定量、品質控管、真偽與產地鑑別。NMR 給指紋,PLS 回歸給數字。
怎麼算的

Python 程式(scikit-learn)

整套分析用 numpy / pandas / scipy / scikit-learn 完成,可重現。完整檔案見 python/ 目錄。

前處理 — bucketing + 面積標準化(python/nmr_utils.py

def bucket(X, ppm, width=0.04):
    """Sum NMR intensities into width-ppm bins (standard NMR bucketing)."""
    edges = np.arange(ppm.min(), ppm.max() + width, width)
    idx = np.digitize(ppm, edges)
    cols = []
    for b in range(1, len(edges)):
        sel = idx == b
        if sel.sum(): cols.append(X[:, sel].sum(1))
    return np.array(cols).T   # shape (n_samples, n_buckets)

def preprocess(Xb):
    """Area normalization — remove dilution effect, x100."""
    Xv = np.asarray(Xb, float)
    tot = Xv.sum(1, keepdims=True); tot[tot == 0] = 1.0
    return Xv / tot * 100.0

PCA — 主成分分析(python/02_pca.py

from sklearn.decomposition import PCA

meta, Xb, ctr = load()
Xp = preprocess(Xb.values)
Xc = Xp - Xp.mean(0)           # mean-centering
pca = PCA(n_components=8).fit(Xc)
T = pca.transform(Xc)           # scores: each wine's position
# evr[0] = 78.5%  (PC1 dominates — ethanol structure)
# top 3 PCs cumulate 95.7% variance

PLS 回歸 — 預測蘋果酸含量(python/03_pls.py

from sklearn.cross_decomposition import PLSRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, FunctionTransformer
from sklearn.model_selection import KFold, cross_val_predict
from sklearn.metrics import r2_score, mean_squared_error

y = meta["malicAcid"].values.astype(float)   # 連續目標:蘋果酸 g/L

def pipe(k):
    return Pipeline([("norm", FunctionTransformer(preprocess)),
                     ("center", StandardScaler(with_std=False)),
                     ("pls", PLSRegression(n_components=k))])

cv = KFold(10, shuffle=True, random_state=42)
# best_k = 7 LV -> RMSECV = 0.19 g/L, R²cv = 0.93
yhat = cross_val_predict(pipe(7), Xb.values, y, cv=cv).ravel()
▶ 如何在本機重跑
pip install numpy pandas scipy scikit-learn matplotlib
cd python
python 01_explore.py     # NMR 原始光譜 + bucketing 圖(nm01, nm02)
python 02_pca.py         # 陡坡 / 分數 / 負荷量(nm03, nm04, nm05)
python 03_pls.py         # RMSECV / 預測 vs 真實 / 係數(nm06, nm07, nm08)
# 所有圖輸出到 python/figures/
不寫程式的版本

用 Orange Data Mining 親手拉一遍

課堂可用 Orange 的拖拉式 widget,不寫一行程式就重現上面的 PCA 與 PLS 回歸結果。 開啟 orange/wine_nmr_workflow.ows,把 File 指向 data/wine_nmr_orange.tab 即可。

          ┌─ Data Table           (檢視分桶後光譜)
          │
 File ────┼─ PCA ──Transformed Data──▶ Scatter Plot    ← PCA 分支(Color = malicAcid)
(wine_    │
 nmr_     └─ PLS ──▶ Test and Score ──▶ Predictions ──▶ Scatter Plot
 orange)                (R²/RMSE)                      (predicted vs malicAcid)

關於 Orange PLS widget:Orange 的 PLS widget(Orange.widgets.model.owpls.OWPLS)做的正是數值回歸——和本課的 PLS 回歸完全對應。把 malicAcid 設成 target(連續值),直接用 PLS widget 即可輸出 R²/RMSE,不需任何替代方案。

對應概念Python 圖Orange widget
NMR 原始光譜nm01File → Data Table
主成分數量nm03PCA 視窗內變異曲線
分數圖(依蘋果酸上色)nm04PCA → Scatter Plot(Color=malicAcid)
選 LV / RMSECVnm06PLS widget 的 Components 參數 + Test&Score RMSE
預測 vs 真實nm07PLS Model → Predictions → Scatter Plot(x=malicAcid, y=predicted)
哪些 ppm 在預測nm08PLS widget 的 Coefficients 輸出
總結

PCA 與 PLS 回歸,一張表看懂

面向PCAPLS 回歸
學習方式非監督(只看 X)監督回歸(用 X 與連續目標 y)
目的探索結構、看化學分佈定量預測(蘋果酸、乙醇…)
找方向的準則最大化 X 的變異最大化 X 與目標 y 的共變異
本課結果PC1=78.5%,前 3 個佔 95.7%7 LV,R²cv=0.93,RMSECV=0.19 g/L
關鍵圖陡坡圖、分數圖、負荷量RMSECV 曲線、預測 vs 真實、係數圖
何時用先用它「看」資料化學結構確定要定量時用它建模
帶走兩句話: PCA 把 8712 個化學位移點壓縮成看得懂的方向;PLS 回歸一手抓光譜、一手抓含量標準值,讓 NMR 直接說出「這支酒,有幾克蘋果酸」。