神經網絡(1):使用python計算神經網絡中的Jacobian矩陣

2021-03-02 AI算法後丹修煉爐


一般來說,神經網絡是一個多元矢量值函數,如下所示:

函數


在訓練過程中,通常會在輸出中附加標量損失值-分類的典型是預測類概率上的交叉熵損失。使用這種標量損失時,M = 1,使用(隨機)梯度下降來學習參數,重複計算損失函數相對於

在推理階段,網絡的輸出通常是向量(例如,類別概率)。本文解釋了什麼是Jacobian,然後探討並比較了一些用Python完成的可能實現。

雅可比矩陣是什麼,為什麼要關心呢?

假設

這個矩陣告訴我們神經網絡輸入的局部擾動將如何影響輸出。 在某些情況下,此信息可能很有價值。 例如,在用於創作任務的ML系統中,讓系統向用戶提供一些交互式反饋,告訴他們修改每個輸入維度將如何影響每個輸出類別。
Tensorflow

嘗試用Tensorflow。先設計一個玩具網絡來玩。計算現有網絡

import numpy as np
N = 500  # Input size
H = 100  # Hidden layer size
M = 10   # Output size
w1 = np.random.randn(N, H)  # first affine layer weights
b1 = np.random.randn(H)     # first affine layer bias
w2 = np.random.randn(H, M)  # second affine layer weights
b2 = np.random.randn(M)     # second affine layer bias``

使用Keras實現以下網絡:

import tensorflow as tf
from tensorflow.keras.layers import Dense
sess = tf.InteractiveSession()
sess.run(tf.initialize_all_variables())
model = tf.keras.Sequential()
model.add(Dense(H, activation='relu', use_bias=True, input_dim=N))
model.add(Dense(O, activation='softmax', use_bias=True, input_dim=O))
model.get_layer(index=0).set_weights([w1, b1])
model.get_layer(index=1).set_weights([w2, b2])

現在計算該模型的雅可比矩陣。但是Tensorflow當前沒有提供開箱即用的計算Jacobian矩陣的方法。tf.gradients(ys,xs)方法為xs中的每個x返回sum(dy / dx),該方法包含Jacobian行總和的N維向量;不太符合我們的期望。但仍然可以通過計算每個

def jacobian_tensorflow(x):    
    jacobian_matrix = []
    for m in range(M):
        # We iterate over the M elements of the output vector
        grad_func = tf.gradients(model.output[:, m], model.input)
        gradients = sess.run(grad_func, feed_dict={model.input: x.reshape((1, x.size))})
        jacobian_matrix.append(gradients[0][0,:])
        
    return np.array(jacobian_matrix)

使用數值微分檢查來確保計算出的雅可比矩陣是正確的。下面的函數is_jacobian_correct()接受一個參數,該函數計算Jacobian函數和前饋函數

def is_jacobian_correct(jacobian_fn, ffpass_fn):
    """ Check of the Jacobian using numerical differentiation
    """
    x = np.random.random((N,))
    epsilon = 1e-5
    """ Check a few columns at random
    """
    for idx in np.random.choice(N, 5, replace=False):
        x2 = x.copy()
        x2[idx] += epsilon
        num_jacobian = (ffpass_fn(x2) - ffpass_fn(x)) / epsilon
        computed_jacobian = jacobian_fn(x)
        
        if not all(abs(computed_jacobian[:, idx] - num_jacobian) < 1e-3): 
            return False
    return True
def ffpass_tf(x):
    """ The feedforward function of our neural net
    """    
    xr = x.reshape((1, x.size))
    return model.predict(xr)[0]
is_jacobian_correct(jacobian_tensorflow, ffpass_tf)

輸出結果

>> True

看看此計算需要多長時間:

tic = time.time()
jacobian_tf = jacobian_tensorflow(x0, verbose=False)
tac = time.time()
print('It took %.3f s. to compute the Jacobian matrix' % (tac-tic))
>> It took 0.658 s. to compute the Jacobian matrix

在Macbook Pro 4核CPU上大約需要650毫秒。使用Tensorflow可能會更好; 但在編寫本文時,似乎並不能大幅度改善,因為Tensorflow需要在M個輸出上循環進行梯度計算(請注意,我此處並未嘗試使用GPU)。650毫秒對於這樣的示例來說太慢了,特別是如果考慮到在測試時進行交互使用的情況。

自動微分

autograd是一個很好的庫。特別是在Numpy上執行自動區分。要使用它,必須使用Autograd封裝的Numpy指定前饋函數

import autograd.numpy as anp
def ffpass_anp(x):
    a1 = anp.dot(x, w1) + b1   # affine
    a1 = anp.maximum(0, a1)    # ReLU
    a2 = anp.dot(a1, w2) + b2  # affine
    
    exps = anp.exp(a2 - anp.max(a2))  # softmax
    out = exps / exps.sum()
    return out

將其與之前的Tensorflow前饋函數ffpass_tf()進行比較,來檢查該函數是否正確。

out_anp = ffpass_anp(x0)
out_keras = ffpass_tf(x0)
np.allclose(out_anp, out_keras, 1e-4)

輸出

>> True

現在有相同的函數

from autograd import jacobian
def jacobian_autograd(x):
    return jacobian(ffpass_anp)(x)
is_jacobian_correct(jacobian_autograd, ffpass_np)

輸出

>> True

那麼需要多長時間?

%timeit jacobian_autograd(x0)

輸出

>> 3.69 ms ± 135 µs

所以Tensorflow實現花費了大約650毫秒,而Autograd需要3.7毫秒,速度提高了約170倍。當然,使用Numpy指定模型並不是很方便,因為Tensorflow和Keras提供了許多現成的有用函數和訓練工具……但是現在我們跨過了這一步,使用Numpy編寫了網絡, 也許可以使其更快?如果看一下Autograd的jacobian()函數的實現,事實證明它仍在映射函數輸出的維度。也許可以直接依靠Numpy更好的矢量化來改善結果。

Numpy

如果想要Numpy實現,則必須指定每個圖層的前進和後退路徑,以便自己實現反向傳播。我在下面針對玩具網絡包含的三種類型進行了affine,ReLU和softmax。此處各層的實現是非常通用的(如果僅關心這一網絡,則可以使其更加緊湊)。

反向傳播包含每個網絡輸出的梯度的矩陣(或者在通常情況下為張量),使用Numpy有效的矢量化操作:

def affine_forward(x, w, b):
    """
    Forward pass of an affine layer
    :param x: input of dimension (I, )
    :param w: weights matrix of dimension (I, O)
    :param b: biais vector of dimension (O, )
    :return output of dimension (O, ), and cache needed for backprop
    """
    out = np.dot(x, w) + b
    cache = (x, w)
    return out, cache
def affine_backward(dout, cache):
    """
    Backward pass for an affine layer.
    :param dout: Upstream Jacobian, of shape (M, O)
    :param cache: Tuple of:
      - x: Input data, of shape (I, )
      - w: Weights, of shape (I, O)
    :return the jacobian matrix containing derivatives of the M neural network outputs with respect to
            this layer's inputs, evaluated at x, of shape (M, I)
    """
    x, w = cache
    dx = np.dot(dout, w.T)
    return dx
def relu_forward(x):
    """ Forward ReLU
    """
    out = np.maximum(np.zeros(x.shape), x)
    cache = x
    return out, cache
def relu_backward(dout, cache):
    """
    Backward pass of ReLU
    :param dout: Upstream Jacobian
    :param cache: the cached input for this layer
    :return: the jacobian matrix containing derivatives of the M neural network outputs with respect to
             this layer's inputs, evaluated at x.
    """
    x = cache
    dx = dout * np.where(x > 0, np.ones(x.shape), np.zeros(x.shape))
    return dx
def softmax_forward(x):
    """ Forward softmax
    """
    exps = np.exp(x - np.max(x))
    s = exps / exps.sum()
    return s, s
    
def softmax_backward(dout, cache):
    """
    Backward pass for softmax
    :param dout: Upstream Jacobian
    :param cache: contains the cache (in this case the output) for this layer
    """
    s = cache
    ds = np.diag(s) - np.outer(s, s.T)
    dx = np.dot(dout, ds)
    return dx

現在已經定義了圖層,在前饋和反向傳播過程中使用它們:

def forward_backward(x):
    layer_to_cache = dict()  # for each layer, we store the cache needed for backward pass
    # Forward pass
    a1, cache_a1 = affine_forward(x, w1, b1)
    r1, cache_r1 = relu_forward(a1)
    a2, cache_a2 = affine_forward(r1, w2, b2)
    out, cache_out = softmax_forward(a2)
    # backward pass
    dout = np.diag(np.ones(out.size, ))  # the derivatives of each output w.r.t. each output.
    dout = softmax_backward(dout, cache_out)
    dout = affine_backward(dout, cache_a2)
    dout = relu_backward(dout, cache_r1)
    dx = affine_backward(dout, cache_a1)
    
    return out, dx

前饋輸出是否正確?

out_fb = forward_backward(x0)[0]
out_tf = ffpass_tf(x0)
np.allclose(out_fb, out_tf, 1e-4)

輸出

>> True

雅可比矩陣是否正確?

is_jacobian_correct(lambda x: forward_backward(x)[1], ffpass_tf)

輸出

>> True

最後:需要多長時間?

%timeit forward_backward(x0)
>> 115 µs ± 2.38 µs

在Autograd需要3.7 ms的情況下,現在只需要115 µs。好多了 :)

結論

探索了幾種在CPU上使用Tensorflow,Autograd和Numpy來計算Jacobian矩陣的方法。每種方法都有各自的優缺點。如果準備指定圖層的前饋和反向遍歷,則可以直接使用Numpy來獲得很多性能-對於我的玩具網絡和示例實現,約為5,000倍。當然,過程會因網絡架構而異。通常輸出維數M越大,就可以越過需要遍歷M個標量輸出的方法。

參考文獻

[1]: Atilim Gunes Baydin, Barak A. Pearlmutter, Alexey Andreyevich Radul, Jeffrey Mark Siskind. Automatic differentiation in machine learning: a survey. The Journal of Machine Learning Research, 18(153):1–43, 2018

完整代碼 https://github.com/hrzn/jacobianmatrix/blob/master/Jacobian-matrix-examples.ipynb

相關焦點

  • 圖神經網絡開發必備組件,NetworkX、稀疏矩陣、稀疏Tensor等
    由於網絡結構的複雜性、數據稀疏性以及數據量等問題,圖神經網絡的開發需要引入大量的工程策略和技巧。本文介紹圖神經網絡開發中幾個必備的組件。NetworkX目前已經成為圖神經網絡開發中數據組織的標準之一,Github上大量的圖神經網絡代碼都將網絡/圖數據轉換為NetworkX提供的數據結構,在此基礎上構建算法。
  • 基於Python建立深度神經網絡!你學會了嘛?
    初始化參數並定義超參數迭代次數在神經網絡中的L層的層數隱藏層大小學習率α3. 迭代循環正向傳播(計算電流損耗)計算成本函數反向傳播(計算電流損耗)升級參數(使用背景參數和梯度)4.使用訓練參數來預測標籤初始化更深層次的L-層神經網絡的初始化更為複雜,因為有更多的權重矩陣和偏置向量。下表展示了不同結構的各種層級。
  • 【神經網絡】神經網絡簡介
    [1]FFNN,Feedforward Neural Network信息傳播:朝著一個方向(反向傳播和這個不是同一個概念)前饋神經網絡是我們接觸到論文中最常用的一種神經網絡,簡稱前饋網絡。是實際應用中最常見的神經網絡結構,是人工神經網絡的一種,前饋描述的是網絡的結構,指的是網絡的信息流是單向的,不會構成環路。
  • 用Python實現多層感知器神經網絡
    激活函數有很多選項,但是在本文中我們僅涉及Sigmoid和softmax。圖1:感知器對於有監督的學習,我們稍後將輸入的數據通過一系列隱藏層轉發到輸出層。這稱為前向傳播。在輸出層,我們能夠輸出預測y。通過我們的預測y,我們可以計算誤差| y*-y | 並使誤差通過神經網絡向後傳播。這稱為反向傳播。
  • 一份完全解讀:是什麼使神經網絡變成圖神經網絡?
    二.為什麼很難在圖上定義卷積要回答這個問題,首先要理清一般使用卷積的動機,然後用圖術語描述「圖像上的卷積」,這將使「圖卷積」的過渡更加流暢。1. 為什麼卷積有用我們應該理解為什麼我們要注意到卷積,以及為什麼我們要用它來處理圖?與完全連接的神經網絡(NNS或MLP)相比,卷積網絡(CNN或Convnet)具有一定的優勢。
  • 深入淺出圖神經網絡實現方式,讓圖神經網絡不再難!
    文章《A Comprehensive Survey on Graph Neural Networks》[1]提供了一個全面的圖神經網絡(GNNs) 概述,並且將最新的圖神經網絡分為四類,即遞歸圖神經網絡(RecGNNs)、卷積圖神經網絡(ConvGNNs)、圖自動編碼器(GAEs)和時空圖神經網絡(STGNNs)。
  • 神經網絡初學者指南:基於Scikit-Learn的Python模塊
    Scikit-learn 在 Python 中設置神經網絡的方法,其最新版本現在已經內置支持神經網絡模型。| 神經網絡神經網絡是一個試圖模仿自然生物神經網絡的學習模式的機器學習框架。 生物神經網絡具有相互連接的神經元,神經元帶有接受輸入信號的樹突,然後基於這些輸入,它們通過軸突向另一個神經元產生輸出信號。 我們將嘗試通過使用人工神經網絡(ANN)來模擬這個過程,我們現在將其稱為神經網絡。
  • 邊緣計算中深度神經網絡剪枝壓縮的研究簡介
    深度神經網絡與其他很多機器學習模型一樣,可分為訓練和推理兩個階段。訓練階段根據數據學習模型中的參數(對神經網絡來說主要是網絡中的權重);推理階段將新數據輸入模型,經過計算得出結果。過參數化是指在訓練階段,網絡需要大量的參數來捕捉數據中的微小信息,而一旦訓練完成到了推理階段,就不需要這麼多的參數。基於這樣的假設,就可以在部署前對模型進行簡化。
  • 吳恩達深度學習(20)-激活函數的導數和神經網絡的梯度下降
    激活函數的導數(Derivatives of activation functions)在神經網絡中使用反向傳播的時候,你真的需要計算激活函數的斜率或者導數。>在神經網絡中a=g(z);g(z)'=d/dz g(z)=a(1-a)2) Tanh activation function其具體的求導如下: 公式2: g(z)=tanh(z)=(e^z-e^(-z))/(e^z+e^(-z) )在神經網絡中;3)Rectified Linear Unit
  • 床長人工智慧教程——基於矩陣計算神經網絡輸出的途徑
    在討論後向傳播之前,讓我們預熱一下計算神經網絡輸出的快速矩陣算法。我們實際上已經簡要的看到過這個算法 上一章的最後部分,但是我只是快速的描述了一下,因此現在值得再看看它的細節。這也是一個很好的方式,在熟悉的上下文中舒適的接受後向傳播的術語。讓我們以一個符號開始,它代表網絡中任意方式的權重信息。
  • 理清神經網絡中的數學知識
    神經網絡中,大家都希望最終的形式為矩陣乘以矩陣,不希望中間有任何向量的存在,這樣顯得更酷,實際上計算也更快。這很簡單,現在我們只差最後一步。當我們把所有數據放在一起,還是如上方有 這樣,在計算過程中,全部為不同形狀的矩陣。當然,大家也可以想想如果是列向量該是什麼形式。以上內容想說明的就是,無論是上方哪一種形式,都是正確的。關鍵看輸入的數據是什麼形式,形式決定了數據變換的順序,以及設計矩陣的形狀。通過以上的形式,其實神經網絡前向傳導和向量在不同維度間的連續線性變換及其相似。
  • 從零學習:從Python和R理解和編碼神經網絡(完整版)
    神經網絡的基本工作原理多層感知器及其基礎知識神經網絡具體步驟詳解神經網絡工作過程的可視化如何用Numpy實現NN(Python)如何用R語言實現NN反向傳播算法的數學原理如果你是一名開發者,或曾參與過編程項目,你一定知道如何在代碼中找bug。
  • 訓練神經網絡的五大算法
    每搜索一步,重新計算神經網絡模型的參數,損失值則相應地減小。我們先隨機初始化一組模型參數。接著,每次迭代更新這組參數,損失函數值也隨之減小。當某個特定條件或是終止條件得到滿足時,整個訓練過程即結束。現在我們就來介紹幾種神經網絡的最重要訓練算法。
  • GRNN神經網絡(Matlab)
    是一種使用徑向基函數作為激活函數的人工神經網絡。徑向基函數網絡的輸出是輸入的徑向基函數和神經元參數的線性組合,廣義回歸神經網是基於徑向基函數網絡一種改進。廣義回歸神經網絡是建立在數理統計基礎上的徑向基函數網絡,其理論基礎是非線性回歸分析。GRNN具有很強的非線性映射能力和學習速度,比RBF具有更強的優勢,網絡最後普收斂於樣本量集聚較多的優化回歸,樣本數據少時,預測效果很好,網絡還可以處理不穩定數據。
  • 利用TensorFlow構建前饋神經網絡
    在大數據與人工智慧時代,深度學習在處理大數據與訓練模型的能力上遠好於傳統的機器學習,通過多層神經網絡嵌套與大規模的迭代運算後得出的神經網絡也表現出優異的模型判別能力。圖1 機器學習vs深度學習(二) 從一個簡單的例子說起1.回歸問題&分類問題相信大家已經對線性回歸模型(Liner regression model)有了很深的理解。它的作用是給定一系列特徵值(x1,x2,x3,...
  • 零基礎入門深度學習 |最終篇:遞歸神經網絡
    循環神經網絡實現了前者,通過將長度不定的輸入分割為等長度的小塊,然後再依次的輸入到網絡中,從而實現了神經網絡對變長輸入的處理。遞歸神經網絡是一種表示學習,它可以將詞、句、段、篇按照他們的語義映射到同一個向量空間中,也就是把可組合(樹/圖結構)的信息表示為一個個有意義的向量。比如上面這個例子,遞歸神經網絡把句子"the country of my birth"表示為二維向量[1,5]。有了這個『編碼器』之後,我們就可以以這些有意義的向量為基礎去完成更高級的任務(比如情感分析等)。
  • 從零開始用 Python 構建循環神經網絡
    (給Python開發者加星標,提升Python技能)英文:Faizan Shaikh,翻譯:李文婧,轉自:數據派(ID:datapi)
  • 人工智慧之卷積神經網絡(CNN)
    CNN引入意義:  在全連接神經網絡中(下面左圖),每相鄰兩層之間的每個神經元之間都是有邊相連的。當輸入層的特徵維度變得很高時,這時全連接網絡需要訓練的參數就會增大很多,計算速度就會變得很慢。  2) 卷積層:卷積層是卷積核在上一級輸入層上通過逐一滑動窗口計算而得,卷積核中的每一個參數都相當於傳統神經網絡中的權值參數,與對應的局部像素相連接,將卷積核的各個參數與對應的局部像素值相乘之和,得到卷積層上的結果。一般地,使用卷積核進行特徵提取和特徵映射。
  • 圖解:卷積神經網絡的數學原理分析
    在過去,我們知道了稱為緊密連接的神經網絡。這些網絡的神經元被分為幾組以形成連續的層。每個這樣的神經元連接到相鄰層中的每個神經元。下圖顯示了此體系結構的示例。圖1. 密集連接的神經網絡結構 當我們基於有限的一組人為設計的特徵解決分類問題時,此方法非常有效。例如,我們根據足球運動員在比賽中的統計數據預測其位置。
  • 知識卡片 遞歸神經網絡
    not very good 是三個詞的組合在語義分類中對機器來說比較難的,傳統的詞向量分析對於分類的預測,採用詞袋模型使用貝葉斯和支持向量機算法,而通過使用word2vec將詞表示為一個低維的向量,使用遞歸神經網絡可以提高語義分類模型的效果