跳至主要內容

    遮擋、梯度、Integrated Gradients 與 LRP

    同一個特徵,為什麼換一種解釋方法就可能得到不同答案?

    sunny 桑尼· · 38 MIN READ · 210 次瀏覽

    遮擋、梯度、Integrated Gradients 與 LRP
    本文目錄

    把某個特徵遮掉,模型分數沒有變;對那個特徵求梯度,結果也等於零。能不能因此說,模型完全不依賴它?

    先看你做了哪一種測試。單次遮擋比較指定替換前後的輸出,梯度看目前位置附近的微小變動;兩者都不必然回答「從某個基準走到這筆輸入,輸出的差額要怎麼分」。Integrated Gradients 沿選定路徑累積梯度,Layer-wise Relevance Propagation 則沿網路連線按指定規則分配 relevance。名稱不同,問題與前提也不同。

    上一篇 SHAP已看過,背景與缺失特徵的定義會改變歸因。本篇把問題轉到可微函數與神經網路,先用遮擋、梯度與平坦區理解差別,再手算 IG 與具體 LRP 規則。五張原圖提供來源方法與研究情境;完整 Python 小例只用手設函數,沒有訓練影像模型或執行解釋套件。

    先固定目標分數,再決定要問哪一種變化

    解釋前,先選定一個標量輸出 f(x)。對分類器而言,這可能是某類別的 logit、機率或其他分數;它們的尺度與梯度不同,不能混加。如果只想知道模型為何支持某類,應固定該類別,而不是在每個擾動點重新挑最高分的類別,最後卻把結果當成同一個函數。

    接著分辨四種問題:

    方法主要計算的量

    遮擋/替換
    把一個特徵或區域換成指定值後,f 改變多少?
    輸入梯度
    在目前 x 附近,該特徵微小變動時,f 有多敏感?
    Integrated Gradients,IG
    從指定 baseline 沿直線到 x,累積的輸出差額如何分到各維?
    LRP
    從選定輸出的 relevance 出發,按特定網路傳播規則分到下層與輸入

    這些量不必一致。分歧可能來自替換值、基準、飽和、特徵交互作用或規則本身,不一定是其中一支程式算錯。反過來,兩張熱圖看起來相近,也不能直接證明它們回答了相同問題。

    遮擋:一次替換造成的落差,不一定能相加

    對第 i 個特徵做替換,可以計算:

    drop_i = f(x) − f(把第i個特徵換成指定值後的x)
    

    影像裡,替換單位可以是一個像素、一塊區域或 superpixel;填值可能是零、灰色、均值或模糊後的值。文字裡,刪詞與換成佔位符也不相同。單位和替換方法都會改變你實際查詢的輸入。

    例如另設一個兩特徵函數:

    f(x1,x2) = x1 + 2x2 + x1x2
    目標 (1,1):分數4
    只把x1換成0:(0,1),分數2,drop1=2
    只把x2換成0:(1,0),分數1,drop2=3
    

    兩個 drop 相加是 5,大於原分數 4。因為兩次替換都包含交互作用消失的效果,這部分被重複計入了。單獨遮擋的落差不是自動滿足加總守恆的特徵分配。

    替換後的資料也可能不自然。黑色方塊可能引入強烈邊緣,刪詞可能破壞語法;分數下降可能同時反映資訊移除與人工輸入造成的分布差異。這不代表不能做遮擋,而是回報時必須說清楚它測的是哪種替換,不能把結果直接叫成現實世界的因果效果。

    梯度:在這裡怎麼變,不等於一路怎麼走到這裡

    輸入梯度的第 i 維是 ∂f/∂x_i。在可微位置、變動足夠小時,它描述每單位輸入變化帶來的局部輸出變化。若換了輸入單位或前處理尺度,梯度數字也會改;它不是沒有單位的通用重要性。

    gradient×input 再把梯度乘上 x_i。對零截距線性函數,這能對上各項輸出;對一般非線性函數,它仍只是當下梯度乘數值,不會因此自動變成精確輸出分解。若熱圖再取絕對值,正負方向也會被合併;亮度高並不直接等於「支持這個類別」。

    圖 1|來源自設分類函數的等高線示意。方框是待解釋點;左側黑箭頭是該點梯度,並不必然指向附近的決策邊界。右側三角形是邊界上的根點,黑箭頭改為根點梯度,紅箭頭表示相對該根點的逐維 Taylor 近似。右圖不是本篇 IG 計算,也不是 LRP 最終結果;此反例不表示梯度對局部敏感度毫無用途。來源:Sebastian Bach、Alexander Binder、Grégoire Montavon、Frederick Klauschen、Klaus-Robert Müller、Wojciech Samek,On Pixel-Wise Explanations for Non-Linear Classifier Decisions by Layer-Wise Relevance Propagation,PLOS ONE 10(7):e0130140,2015,Fig. 3,CC BY 4.0。本版主圖保留來源 PNG 位元組;手機先全圖,再分別放大完整左/右 panel,只重排原像素,沒有改寫箭頭或數據。

    圖中的問題是:當下的變化方向,能不能代表相對某個邊界的解釋?這與「梯度有沒有算對」不同。另一個常見落差來自飽和:函數在終點附近已經變平,即使從基準走來的過程曾經大幅上升,終點梯度仍可能很小。

    圖 2|來源 Inception v4/ImageNet 的 Goldfinch 案例。橫軸 α 是 baseline 到原影像的插值係數;縱軸為該類別的 softmax 機率,並不是 logit、梯度大小或 IG 值。曲線早期上升後大致變平,展示這個案例的飽和情形;不是所有模型都會沿路單調變化。來源:Pascal Sturmfels、Scott Lundberg、Su-In Lee,Visualizing the Impact of Feature Attribution Baselines,Distill,2020,DOI 10.23915/distill.00022,Figure 3 的自有圖表部分,依該文明示的 CC BY 4.0 使用。來源照片、下方選圖縮圖及照片提示框不在本次圖像內;本站未重跑影像模型。本版擷取原互動圖的純 chart;手機先全圖,再以兩個重疊的原橫軸視窗放大同一條曲線。細讀窗省略長標題與共同橫軸標籤,完整名稱、範圍與軸意義回看全圖;不是兩組新資料,沒有補畫刻度或曲線。

    這條曲線的斜率只描述沿所選插值方向的輸出變化,單靠曲線不能讀出每個像素的偏導數。也不要把「沿這個方向變平」讀成「模型一路都沒有用到輸入」。接下來用一個能完整手算的飽和函數,另外逐維計算梯度,把差別拆開。

    Integrated Gradients:沿選定路徑,把梯度累積起來

    IG 需要原輸入 x,以及 baseline b。標準直線路徑與第 i 維歸因為:

    路徑:gamma(alpha) = b + alpha × (x−b),alpha從0到1
    
    IG_i = (x_i−b_i) × integral from0to1:
                           ∂f(b+alpha×(x−b)) / ∂x_i  d alpha
    

    這不是只對終點求一次梯度。它在路徑上多個位置求梯度,再乘上該維從 baseline 到輸入的變化量。baseline 的數字要以模型真正接收的空間定義;前處理後的零向量,不一定對應原始黑色影像,也不自動表示「什麼資訊都沒有」。

    圖 3|基準 (r₁,r₂) 到輸入 (s₁,s₂) 的三條概念路徑,P₂ 是 IG 採用的直線;P₁、P₃ 提醒我們,相同起訖點不代表各維歸因必然相同。圖沒有顯示本篇小例的數值或積分結果。來源:Mukund Sundararajan、Ankur Taly、Qiqi Yan,Axiomatic Attribution for Deep Networks,Proceedings of the 34th International Conference on Machine Learning,PMLR 70:3319–3328,2017,Fig. 1/PDF p4,CC BY 4.0。本版從原 PDF 第4頁裁出完整圖與原 caption,沒有重繪路徑;手機保留同一完整圖。封面將全圖與 caption 等比置於白色 1600×900 畫布,沒有新增標題或圖元。

    考慮這個手設函數:

    s = x1 + 2x2
    f(x1,x2) = ReLU(s) − ReLU(s−1)
    
    s≤0:f=0
    0<s<1:f=s
    s≥1:f=1
    

    目標是 (1,1),f=1;零基準 (0,0) 的 f=0。在目標附近已飽和,所以梯度與 gradient×input 都是 (0,0)。只把 x1 換成零,s=2、f 仍為1;只把 x2 換成零,s=1、f 也仍為1,因此兩個遮擋 drop 都是0。剩餘特徵各自都足以維持輸出,並不表示它們在整個函數中完全沒作用。

    沿零基準直線,路徑是 (alpha,alpha),s=3alpha。只有前 1/3 路徑處於上升區,其梯度為 (1,2);之後梯度為0。因此:

    IG1 = 1 × [1 × (1/3)] = 1/3
    IG2 = 1 × [2 × (1/3)] = 2/3
    總和 = 1 = f(1,1)−f(0,0)
    

    零梯度、零單特徵替換 drop,以及非零 IG 可以同時成立。它們衡量的是不同的變化,不能只拿誰的數字較大就判斷誰比較正確。

    完整性與數值誤差,要和解釋品質分開

    在能使用路徑上的微積分基本定理的前提下,IG 的 completeness 表示:

    sum_i IG_i = f(x)−f(b)
    

    本例是連續、有限分段線性的函數,能在各段直接積分,再把結果接起來。這裡不把「幾乎處處可微」單獨當成對任意函數都足夠的保證,也不需要在 ReLU 折點替某個特定導數約定賦予積分質量。

    實作時通常用有限點近似積分。本篇使用 midpoint,並把「歸因總和減去輸出差」印成 delta:

    同一零基準路徑的 midpoint 步數IG 總和delta
    81.1250.125
    321.031250.03125
    1281.00781250.0078125
    5121.0019531250.001953125
    20481.000488281250.00048828125

    程式另外算 300 步,得到 (1/3,2/3)、delta=0。這個步數恰好讓分段位置與積分格子對齊;不能因此宣稱任何模型用 300 步都會精確。表格也只展示本例這些格點的收斂情況,不保證任意函數每加一步誤差都下降。

    delta 小,是必要時可用的數值檢查,但正負誤差可能抵銷,不能單憑總和吻合就證明每維歸因都已精確,更不能證明 baseline 合理、解釋符合人類語意或具有因果意義。

    換一個 baseline,分配的差額也會換

    保持模型與目標 (1,1) 不變,改用 b=(0.1,0)。baseline 的輸出已經是0.1,所以現在要分配的差額是0.9,而不是1。

    路徑 = (0.1+0.9alpha, alpha)
    s(alpha) = 0.1+2.9alpha
    到alpha=9/29時,s=1,此後飽和
    
    IG1 = 0.9 × 9/29 = 81/290 ≈ 0.279310345
    IG2 = 2 × 9/29 = 18/29 ≈ 0.620689655
    總和 = 0.9
    

    300 步 midpoint 的實際輸出是 (.279,.62),總和 .899、delta 約 −.001。既顯示了換 baseline 的分配差別,也顯示同樣 300 步不再恰好精確。

    baseline 不是一個只為讓程式能跑的參數。如果輸入某維剛好等於 baseline,同維的差值因子為0,其 IG 就會是0;這說的是相對此基準沒有變化,不保證該值對模型毫無含義。以黑色影像作基準,也可能讓本來就是黑色的物體區域沒有這種差值。

    Distill 的研究比較了常數、模糊、噪聲與基準分布,討論各自對 missingness 的假設;沒有給出適用所有任務的唯一最佳選項。本篇不重現其照片或整套評估,只保留圖 2 的自有曲線。使用真實模型時,應一起回報 baseline、輸出尺度、前處理、路徑與積分設定;路徑中間點也可能離開訓練資料分布,不能因為採直線就忽略這件事。

    LRP:先選 relevance 傳播規則,再談守恆

    IG 沿基準到輸入的路徑積分;LRP 從網路某個輸出的 relevance 出發,往輸入方向分配。它利用各層的連線與前向活動,但反向傳的是定義好的 relevance 訊息,不必然是普通反向傳播的導數。

    圖 4|左側是前向計算,右側沿相同拓樸反向分配 relevance。1–7 是來源的節點索引,不是各節點的數值;右側從 R₇=f(x) 開始。訊息如何分配必須另外指定規則,箭頭反向並不代表正在執行普通梯度反傳。來源:Sebastian Bach、Alexander Binder、Grégoire Montavon、Frederick Klauschen、Klaus-Robert Müller、Wojciech Samek,PLOS ONE 2015 原文,Fig. 2,CC BY 4.0。本版主圖保留來源 PNG 位元組;手機先全圖,再逐一放大完整前向/relevance panel。原方向與跨節點關係以全圖為準,沒有新增箭頭或數值。

    原文先給 relevance 與訊息的限制條件,再討論可採用的分配算法;不是只憑一張網路圖就唯一決定輸入分數。以最簡單的正權、非負輸入、無 bias、分母非零情況為例,可以採用:

    z_ij = a_i × w_ij
    R_(i←j) = z_ij / sum_k z_kj × R_j
    R_i = sum_j R_(i←j)
    

    這是本篇實作的簡化 z-rule。對每個上層節點 j,把 R_j 按各個前向輸入項 z_ij 的比例分配,比例加起來為1,就能在這一層保留 relevance 總量。若分母為0,就不能照此式硬除;遇到負項、bias 或不同激活函數,也需要重新交代規則與適用前提。

    單層例子是 input=(3,1)、weights=(2,1),輸出7。兩個前向項是6與1;從 R_out=7 開始,按6/7、1/7分配,得到 relevance=(6,1)。這是刻意簡單的正權線性情況,不是所有 LRP 都會得到同一種比例。

    分兩層傳一次:epsilon 穩定項會吸收什麼?

    圖 5|來源多層網路的前向與 relevance 反向示意。它描述分層訊息方向與符號,不是下文兩輸入/兩隱藏節點的數值網路,也沒有在圖上固定一套通用 LRP 規則。來源:Sebastian Bach、Alexander Binder、Grégoire Montavon、Frederick Klauschen、Klaus-Robert Müller、Wojciech Samek,PLOS ONE 2015 原文,Fig. 5,對應 neural-network relevance 的方法段,CC BY 4.0。本版主圖保留來源 PNG 位元組;手機先全圖,再分別放大完整前向/relevance panel,不是把兩個方向接成新的網路,沒有補畫層或連線。

    另設一個便於逐步核對的兩層正權、無 bias 網路:

    input = (1,2)
    h1 = 1×1 + 2×1 = 3
    h2 = 1×2 + 2×1 = 4
    output = 2h1 + 1h2 = 10
    

    這是另一個函數,不是先前的飽和模型,不能拿兩者的數值當成方法排名。兩個 hidden 值均為正;即使中間放 ReLU,也會原樣通過。本程式直接算這些明定的和,不呼叫神經網路框架。

    先從輸出 relevance=10 分到 h1/h2,得到6/4。再往下分:

    上層 relevance給 input1 的訊息給 input2 的訊息
    h1:6(1/3)×6=2(2/3)×6=4
    h2:4(2/4)×4=2(2/4)×4=2
    各輸入收到的總量46

    總量4+6=10。在這個特例裡,它也等於有效線性函數 (4x1+3x2) 的 gradient×input;不能據此說兩種方法在任意網路都相同。

    若分母很小,比例可能很不穩定。原 LRP 論文也討論 epsilon 穩定項,並明確指出 bias 與穩定項可能吸收 relevance。本例所有分母為正,加入 epsilon=1/2 後的規則簡化為:

    R_(i←j) = z_ij / (sum_k z_kj + epsilon) × R_j
    

    epsilon 在這裡只改 relevance 的分配,不改前向模型,也不是重新訓練的正則項。程式在兩次層間分配都加入同樣的 epsilon,沒有再分配被吸收的殘量。實際結果為:

    規則hidden relevanceinput relevance 總和
    epsilon=0 的 z-rule(6,4)10
    每層 epsilon=1/2(40/7,80/21)10960/1323 ≈ 8.284203

    epsilon 版的兩個 input relevance 是 4400/1323、6560/1323,輸出10減去它們的總和,還有 2270/1323。這是此穩定規則的吸收,不是程式忘了把一個神經元算進去。

    因此「LRP 必定完整分配全部輸出」不能脫離規則來說。若換成有 bias、帶符號項或其他 LRP 變體,殘量如何處理也必須重查。LRP 分配的還是選定模型輸出的 relevance,不會因為有守恆式,就升級成輸入對真實世界結果的因果效果。

    完整可跑程式:核對機制,不用下載影像模型

    以下程式保存的實跑環境為 Python 3.10.12,只用標準庫。它明列解析梯度,另在非折點位置用中央差分核對;沒有呼叫 autograd、Captum、IG 或 LRP 套件 API。ReLU 折點的導數在程式中約定為0,但本文使用的 midpoint 格點避開折點,積分結果不靠對折點導數的特殊取值。

    將完整程式存成 explanation_mechanics.py,執行:

    python3 explanation_mechanics.py
    
    #!/usr/bin/env python3
    """Hand-specified explanation mechanics; Python standard library only.
    
    No trained model, dataset, autograd framework, or attribution package is used.
    Saturated-score comparisons use one function. LRP uses a separate, explicitly
    positive, bias-free network. They are not a method-quality benchmark.
    """
    
    from fractions import Fraction as F
    import json
    import math
    
    
    def saturated_score(x1, x2):
        s = x1 + 2 * x2
        return max(s, 0) - max(s - 1, 0)
    
    
    def saturated_gradient(x1, x2):
        """Analytic derivative off s=0,1; choose zero at the two kinks.
    
        The chosen midpoint grids below never hit either kink. This convention
        does not assert differentiability at the kinks or emulate an autograd API.
        """
        s = x1 + 2 * x2
        slope = 1.0 if 0.0 < s < 1.0 else 0.0
        return slope, 2.0 * slope
    
    
    def midpoint_ig(baseline, target, steps):
        delta = tuple(t - b for b, t in zip(baseline, target))
        gradients = []
        for i in range(steps):
            alpha = (i + 0.5) / steps
            point = tuple(b + alpha * d for b, d in zip(baseline, delta))
            # Only this fixed toy/grid scope is supported by this script.
            s = point[0] + 2 * point[1]
            assert not math.isclose(s, 0.0, abs_tol=1e-12)
            assert not math.isclose(s, 1.0, abs_tol=1e-12)
            gradients.append(saturated_gradient(*point))
        attribution = tuple(delta[j] * sum(g[j] for g in gradients) / steps for j in range(2))
        output_change = saturated_score(*target) - saturated_score(*baseline)
        return {"steps": steps, "attribution": attribution,
                "sum": sum(attribution), "output_change": output_change,
                "completeness_delta": sum(attribution) - output_change}
    
    
    def central_difference(point, coordinate, step=1e-6):
        above, below = list(point), list(point)
        above[coordinate] += step
        below[coordinate] -= step
        return (saturated_score(*above) - saturated_score(*below)) / (2 * step)
    
    
    def positive_messages(inputs, weights, relevance, epsilon=F(0)):
        """Fixed positive no-bias z-rule, or positive-denominator epsilon variant.
    
        epsilon is added at every redistribution layer when requested. No
        residual is redistributed. This is not a general signed/bias LRP API.
        """
        if epsilon < 0 or any(v < 0 for v in (*inputs, *weights)):
            raise ValueError("This toy requires nonnegative inputs/weights/epsilon")
        terms = tuple(a * w for a, w in zip(inputs, weights))
        denominator = sum(terms, F(0)) + epsilon
        if denominator <= 0:
            raise ValueError("Positive nonzero denominator required")
        return tuple(term / denominator * relevance for term in terms)
    
    
    def two_layer_lrp(epsilon):
        x = (F(1), F(2))
        # Rows correspond to the two hidden units. ReLU leaves both positive h unchanged.
        hidden_weights = ((F(1), F(1)), (F(2), F(1)))
        output_weights = (F(2), F(1))
        hidden = tuple(sum((a * w for a, w in zip(x, row)), F(0)) for row in hidden_weights)
        output = sum((h * w for h, w in zip(hidden, output_weights)), F(0))
        hidden_r = positive_messages(hidden, output_weights, output, epsilon)
        input_messages = tuple(positive_messages(x, row, r, epsilon)
                               for row, r in zip(hidden_weights, hidden_r))
        input_r = tuple(sum((messages[j] for messages in input_messages), F(0)) for j in range(2))
        return {"epsilon_at_each_layer": str(epsilon), "x": list(map(str, x)),
                "hidden_weights_rows": [list(map(str, row)) for row in hidden_weights],
                "hidden_activations": list(map(str, hidden)),
                "output_weights": list(map(str, output_weights)), "output": str(output),
                "hidden_relevance": list(map(str, hidden_r)),
                "input_messages_by_hidden": [list(map(str, row)) for row in input_messages],
                "input_relevance": list(map(str, input_r)),
                "input_relevance_float": list(map(float, input_r)),
                "input_relevance_sum": str(sum(input_r, F(0))),
                "output_minus_input_relevance_sum": str(output - sum(input_r, F(0)))}
    
    
    def main():
        baseline, target = (0.0, 0.0), (1.0, 1.0)
        gradient = saturated_gradient(*target)
        ig = midpoint_ig(baseline, target, 300)
        score = saturated_score(*target)
        occlusion = tuple(score - saturated_score(*(0.0 if j == i else target[j] for j in range(2)))
                          for i in range(2))
        convergence = [midpoint_ig(baseline, target, n) for n in (8, 32, 128, 512, 2048)]
        nonzero_baseline = (0.1, 0.0)
        changed_baseline_ig = midpoint_ig(nonzero_baseline, target, 300)
        # s(alpha)=1/10+(29/10)alpha crosses 1 at alpha=9/29.
        analytic_nonzero = (F(81, 290), F(18, 29))
        fd_checks = []
        for point in ((0.25, 0.125), target):
            analytic = saturated_gradient(*point)
            finite = tuple(central_difference(point, j) for j in range(2))
            assert all(abs(a - f) < 1e-8 for a, f in zip(analytic, finite))
            fd_checks.append({"point": point, "analytic": analytic, "central_difference": finite})
    
        def interaction(x1, x2):
            return x1 + 2 * x2 + x1 * x2
    
        original = interaction(1, 1)
        interaction_drops = (original - interaction(0, 1), original - interaction(1, 0))
        one_layer_x, one_layer_w = (F(3), F(1)), (F(2), F(1))
        one_layer_r = positive_messages(one_layer_x, one_layer_w, F(7))
        z_rule, epsilon_rule = two_layer_lrp(F(0)), two_layer_lrp(F(1, 2))
    
        assert gradient == (0.0, 0.0) and occlusion == (0.0, 0.0)
        assert ig["attribution"] == (1 / 3, 2 / 3)
        assert abs(ig["completeness_delta"]) < 1e-12
        assert abs(convergence[-1]["completeness_delta"]) < abs(convergence[0]["completeness_delta"])
        assert abs(sum(analytic_nonzero) - F(9, 10)) == 0
        assert all(abs(a - float(b)) < 0.001 for a, b in zip(changed_baseline_ig["attribution"], analytic_nonzero))
        assert interaction_drops == (2, 3) and sum(interaction_drops) == 5 > original
        assert one_layer_r == (F(6), F(1))
        assert z_rule["hidden_relevance"] == ["6", "4"]
        assert z_rule["input_relevance"] == ["4", "6"]
        assert z_rule["input_relevance_sum"] == "10"
        assert epsilon_rule["input_relevance"] == ["4400/1323", "6560/1323"]
        assert F(epsilon_rule["input_relevance_sum"]) < F(epsilon_rule["output"])
    
        result = {
            "scope": "hand-set scalar mechanisms, not training/real-image attribution/causal evidence or a package benchmark",
            "saturated_function": "ReLU(x1+2*x2)-ReLU(x1+2*x2-1)",
            "saturation_zero_baseline": {"baseline": baseline, "target": target,
                "baseline_score": saturated_score(*baseline), "target_score": score,
                "analytic_target_gradient": gradient,
                "gradient_times_input": tuple(g * x for g, x in zip(gradient, target)),
                "single_feature_zero_occlusion_drops": occlusion,
                "analytic_straight_path_ig": ["1/3", "2/3"], "midpoint_ig": ig},
            "midpoint_resolution_comparison_same_zero_baseline": convergence,
            "nonzero_baseline_same_function_target": {"baseline": nonzero_baseline,
                "target": target, "baseline_score": saturated_score(*nonzero_baseline),
                "path_saturation_alpha": "9/29",
                "analytic_straight_path_ig": list(map(str, analytic_nonzero)),
                "analytic_ig_sum": str(sum(analytic_nonzero)), "midpoint_ig": changed_baseline_ig},
            "off_kink_gradient_central_difference_checks": fd_checks,
            "interaction_occlusion": {"function": "x1+2*x2+x1*x2", "target": [1, 1],
                "score": original, "single_feature_zero_occlusion_drops": interaction_drops,
                "sum_of_drops": sum(interaction_drops), "not_an_additive_decomposition": True},
            "single_positive_linear_z_rule": {"x": [3, 1], "weights": [2, 1],
                "output": 7, "relevance": list(map(str, one_layer_r))},
            "separate_two_layer_positive_no_bias_network": {"z_rule": z_rule,
                "epsilon_variant_no_residual_redistribution": epsilon_rule,
                "scope": "different function from saturated example; all weights/activations positive, no bias; epsilon=1/2 added in both layers"},
            "limits": ["all gradients are explicitly derived, not autograd",
                "straight paths and baselines are hand-chosen; not a causal intervention",
                "300 steps is exactly aligned for the zero-baseline toy; finite quadrature is not generally exact",
                "conservation of a relevance rule does not establish semantic quality or causality"]}
        print(json.dumps(result, ensure_ascii=False, indent=2))
    
    
    if __name__ == "__main__":
        main()
    

    輸出的 JSON 會列出零基準與非零基準、各步數 IG/delta、非折點梯度檢查、交互函數的遮擋落差,以及單層/兩層 relevance 訊息。LRP 部分使用 Fraction 保留精確分數,方便逐層查核;IG 部分保留浮點近似與誤差,不把小數顯示成完全精確的解析解。

    真正拿模型來解釋時,先把「要解釋哪個輸出」「移除或基準代表什麼」「使用哪套規則」「做了哪些數值與穩定性檢查」講清楚。遮擋落差大、IG 相加吻合或 relevance 守恆,都只回答各自的一部分問題。解釋是否忠實、是否受人工輸入影響、是否符合使用情境,仍須以相應測試與資料評估,不能只憑熱圖漂亮或亮在物體上就下結論。

    方法、原圖與實作來源

    圖像署名與改作聲明

    五組來源圖依各自明示的 CC BY 4.0 條件署名,原文與授權均在上方提供可點連結。以下保留完整圖像來源與本次改作聲明;它不把照片、第三方圖或模型結果一併納入。

    # DS58 image credits and reuse notes
    
    These illustrations reproduce the source-created diagrams and chart listed below. Their authors, individual grants and modifications are recorded here; no photograph or model/data rerun is included.
    
    ## PLOS ONE figures
    
    Sebastian Bach et al. (2015), Figures 2, 3, and 5, “On Pixel-Wise Explanations for Non-Linear Classifier Decisions by Layer-Wise Relevance Propagation,” *PLOS ONE* 10(7): e0130140, [doi:10.1371/journal.pone.0130140](https://doi.org/10.1371/journal.pone.0130140). The article states [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The main images are byte-copies of the complete source figures. The mobile derivatives show the complete figure first and stack complete source panels without adding marks, text, or data. The article's separate data-availability statement about some CC0 source images is not being treated as a blanket image grant.
    
    ## Integrated Gradients Figure 1
    
    Mukund Sundararajan, Ankur Taly, and Qiqi Yan (2017), Figure 1, “Axiomatic Attribution for Deep Networks,” *Proceedings of the 34th International Conference on Machine Learning*, PMLR 70, pp. 3319–3328, [PMLR article](https://proceedings.mlr.press/v70/sundararajan17a.html). The 2017 ICML author instructions linked the contemporaneous publication-permission form. Its §2 grants CC BY 4.0 and §3 requires citation and a PMLR link. No signed author-specific form copy is claimed. The source figure and caption are cropped from the original PDF page 4; the cover fits the complete crop proportionally on white.
    
    ## Distill chart
    
    Pascal Sturmfels, Scott Lundberg, and Su-In Lee (2020), Figure 3 chart, “Visualizing the Impact of Feature Attribution Baselines,” *Distill*, [doi:10.23915/distill.00022](https://doi.org/10.23915/distill.00022). The article-specific footer states that diagrams and text are CC BY 4.0 and excludes externally credited figures. The selected D3 chart is the authors' own visualization and has no separate third-party credit. The currently saved Distill FAQ links CC BY 2.0; this chart relies on the article-specific CC BY 4.0 statement, not on a claim about the FAQ. The selector thumbnails and photograph are excluded. The source's chart plots Goldfinch Softmax output probability against the interpolation constant in one Inception v4 example.
    
    The mobile chart shows the whole chart first, followed by two overlapping x-range crops. The crops omit the long title and x-axis label; readers should use the whole chart for those labels and the full range. No chart values or visual marks were redrawn or changed.
    

    留言0

    登入後就能留言。

    登入
    sunny 桑尼
    sunny 桑尼

    AI 資料科學 · 產品經理 · 蒙奇 D 桑尼

    專注於 AI 工具應用與資料科學教育,致力於讓每個人都能用 AI 提升工作效率。曾任職於科技業,現為全職教育創作者。

    讀更多文章 →想自己動手試?領免費 Starter Kit →