iT邦幫忙

0

《DENG Cell Audit:VCC2026 白箱審計架構開發》#1 物理法則的緊箍咒:高維稀疏矩陣的「白箱計數算子」與 90% 歸零率迷思

  • 分享至 

  • xImage
  •  

Virtual Cell Challenge (VCC 2026) 是一個聚焦於生物學與機器學習的年度挑戰賽。

比賽結束來進行一下復盤結案,目前暫定5篇系列文,架構如圖:

https://ithelp.ithome.com.tw/upload/images/20261107/2018359674V3muNW3c.jpg

系列前言:在機器學習與生物資訊挑戰賽中,大多數的討論都圍繞在「換哪一個神經網路架構」或「怎麼做 Ensemble 刷分」。但在近期參與跨細胞背景的單細胞基因擾動預測(Single-Cell Perturbation Prediction)研究過程中,我們選擇了一條不同的路——在封關鎖定主力模型後,建立一套名為 DENG Cell Audit 的白箱審計工具鏈。本系列共 5 篇,將完整復盤我們如何透過算子追蹤、共變數去混淆、代數分解與無塵室開源工程,並進一步透過 E150 盲測與 Multi-Atlas 融合突破跨背景預測瓶頸,在複雜的高維稀疏矩陣中實踐嚴格的科學防禦。

🔗 開源專案連結:silver131-dev/deng-cell-audit (DENG Cell Audit — by ARiN)


一、 為什麼單細胞預測不能直接輸出浮點數?

在一般的表格數據或影像任務中,模型輸出一個浮點數向量 y ∈ R^G 就結束了。但在單細胞轉錄組(scRNA-seq)的世界裡,每一個觀測值 X_ig 代表第 i 顆細胞在第 g 個基因上的 UMI 分子計數(Unique Molecular Identifier counts)。

當我們要把從來源背景估計出的對數擾動效應向量 d ∈ R^G(經縮放因子 α 調整),作用到目標背景的控制組細胞矩陣 X ∈ ℤ_≥0^(N × G)(動輒數十萬顆細胞 × 18,533 個基因)時,預測輸出 Y 必須同時遵守三條鐵律(Invariants):

  1. 零支援集不擴張(Zero-Support Invariant):單細胞矩陣極度稀疏。在乘法縮放模型下,若控制組細胞原本在該基因的讀數為 0(X_ig = 0),轉換後必須嚴格保持為 0(Y_ig = 0),不能無中生有創造假訊號。
  2. Active-Pool 與全細胞 UMI 總預算守恆(Row-Sum UMI Conservation):每顆細胞的總測序深度(Library Size L_i = Σ_g X_ig)在轉換前後必須完全相等,且未被遮罩允許(Excluded)的基因座標不得被更動。
  3. 有界最大餘數整數化(Largest-Remainder Integerization):最終輸出的矩陣 Y 必須是 32 位元非負整數(int32),且每個座標的整數化誤差必須嚴格小於 1 個 count。

https://ithelp.ithome.com.tw/upload/images/20261107/20183596shEJtXif2y.png

如果沒有把這三條物理與工程限制寫成可逐層追蹤的「白箱算子」,一旦預測誤差(如 MSE 或 FID)不如預期,你根本無從得知: 到底是上游的效應估計不準,還是下游的整數化算子把訊號給切碎了?


二、 拆解四階段白箱算子 (operator.py)

為了確保轉換過程不會破壞這三條鐵律,我們設計了四個步驟的轉換管線:

[原整數 UMI] ➔ [1. 設定邊界] ➔ [2. 浮點縮放] ➔ [3. 總預算校正] ➔ [4. 餘數取整] ➔ [最終整數 UMI]
  • 第一步:設定安全邊界(Bound & Floor)
    防止 AI 預測出暴增幾萬倍或低於零的極端異常值,給數據加上合理的上下限。
  • 第二步:比例放大縮小(Scaling: X ➔ W)
    根據預測的效應,把原本的基因計數乘以縮放比例(這時候會產生小數)。原本是 0 的乘以任何數依然是 0,完美保護了鐵律一。
  • 第三步:總量配額校正(Conservation: W ➔ Z)
    把放大縮小後的所有基因數值相加,重新按比例拉回細胞原本的分子總預算,保護了鐵律二。
  • 第四步:最大餘數四捨五入(Integerization: Z ➔ Y)
    先幫大家取整數(地板函數|Z|)。這時會剩下一些沒分完的小數零頭。誰的小數點餘數最大,就把剩餘的 1 顆分子優先發給誰,直到總數完全對齊,保護了鐵律三。

為了讓算子的每一步變形都可被量測,我們在 deng_cell_audit/operator.py 中將轉換過程明確切分為四個可觀測的階段狀態(X ➔ W ➔ Z ➔ Y)。

https://ithelp.ithome.com.tw/upload/images/20261107/20183596wN1ZtCFv1O.png

以下是 deng-cell-audit 中的核心實作(純 NumPy、零黑盒依賴):

def trace_operator(matrix, effect, control_reference, allowed, alpha=1.0):
    x = counts(matrix)
    d, c = vector(effect, "effect"), vector(control_reference, "control reference")
    m = np.asarray(allowed)
    require(d.shape == c.shape == m.shape == (x.shape[1],) and m.dtype == np.dtype(bool), "operator axis or mask mismatch")
    require(np.all(c >= 0) and np.isfinite(alpha) and alpha >= 0, "invalid reference or alpha")
    with np.errstate(over="ignore"):
        requested = alpha * d
    require(np.isfinite(requested).all(), "effect overflow")
    requested[~m] = 0
    active = np.flatnonzero(np.abs(requested) > 1e-12)
    bounded = np.clip(requested, -2.0, 2.0)
    reference = np.maximum(c, .25)
    scale = np.clip(1 + np.expm1(bounded) * (1 + 1 / reference), .05, np.exp(2.0))
    w, z, y = x.astype(np.float64), x.astype(np.float64), x.copy()
    budgets = x[:, active].sum(axis=1, dtype=np.int64)
    if len(active):
        w[:, active] *= scale[active]
        for i, budget in enumerate(budgets):
            if budget == 0:
                continue
            weights = w[i, active]
            quota = weights * (float(budget) / float(weights.sum()))
            require(np.isfinite(quota).all(), "invalid quota")
            allocation = np.floor(quota).astype(np.int64)
            remainder = int(budget - allocation.sum())
            require(0 <= remainder <= len(active), "invalid rounding remainder")
            order = np.argsort(-(quota - allocation), kind="stable")
            allocation[order[:remainder]] += 1
            z[i, active], y[i, active] = quota, allocation
    require(np.all(y <= 2**31 - 1), "output count exceeds int32")
    require(np.array_equal(y.sum(axis=1), x.sum(axis=1)), "UMI conservation failed")
    require(np.array_equal(y[:, ~m], x[:, ~m]), "excluded coordinates changed")
    require(np.all(y[x == 0] == 0), "zero-support invariant failed")
    return {"X": x, "W": w, "Z": z, "Y": y.astype(np.int32), "requested": requested,
            "bounded": bounded, "scale": scale, "active": active, "budgets": budgets}

注意函式結尾的四行 require(...):每次轉換都會在記憶體中直接斷言 不溢位、全細胞 UMI 總和相等、遮罩外座標不變、以及零支援集不擴張 。任何一項違反,程式立刻拋出 ValueError 中止,絕不讓髒資料流向下游。


三、震撼真相!90% 的微調整歸零,訊號沒毀嗎?

當我們把這套演算法放到實際數據中測試時,看到了驚人的一幕:

在轉成整數後,有高達 82.5% ~ 99.4% 的微小浮點調整量,直接變成了 0!

第一時間大家都很恐慌:「天啊!是不是四捨五入把 AI 辛苦預測的訊號全部蒸發掉了?!」

但當我們去計算轉換前後的 總能量保留率( L2 能量) 時,卻發現它竟然高達 93% ~ 101%!

為什麼「90% 的位置歸零」與「100% 能量保留」可以同時發生?

我們進一步將活躍基因按請求強度切成 Top 10% 強效應基因 與 Bottom 90% 弱效應基因,答案瞬間明朗:

  • 單細胞擾動的能量高度集中在少數前段班基因上;
  • 大多數 Bottom 90% 基因分配到的浮點配額變動量遠小於 0.5 個 UMI count,在離散化過程中自然被向下取整歸零;
  • 結論:「大量座標位置歸零」不等於「大量訊號能量流失」。 整數化算子在數學本質上並不是一個隨機破壞者,而是一個「保留主幹強訊號、濾除微弱浮點尾端」的高閾值濾波器。

https://ithelp.ithome.com.tw/upload/images/20261107/20183596Z1u3rt6jYC.png

如果當時我們只看「歸零率高達 90%」單一指標,就會陷入第一個錯誤直覺:「一定是整數化把模型弄壞了,趕快改寫 rounding 規則!」——但因為我們同時追蹤了 collapse_rate 與 amplitude_ratio_L2,才守住了第一道防線。


四、 30 秒在本地跑起這個白箱算子

我們已將這套審計元件封裝為零外部資料依賴的 Python 套件 deng-cell-audit(支援 Python 3.12+)。你可以直接在 WSL 或 Linux 終端機執行合成示例驗證上述不變量:

git clone [https://github.com/silver131-dev/deng-cell-audit.git](https://github.com/silver131-dev/deng-cell-audit.git)
cd deng-cell-audit
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-tested.txt
python -m pip install -e . --no-deps

# 跑 49 項自動化數值與邊界測試
python -m pytest -q

# 執行內建 8-cell x 5-gene 合成審計 Demo
python -m deng_cell_audit demo --out results/demo

打開產出的 results/demo/method_demo.json,你就能直接看到 operator_conserves_umi: true 以及 counts_stage_comparison 中對 collapse_rate 與 amplitude_ratio_L2 的同步剖析。


📌 What's NEXT?

在第 2 篇中,我們會在特定細胞背景看到歸零率與預測誤差呈現高達 ρ = 0.853(Holm 校正 p = 0.0129)的超強相關——究竟這是演算法的漏洞,還是幕後有「隱藏的第三者」在作祟?


*提醒邦友,使用第三方服務/API 時,請務必評估資安風險與隱私保護
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言