NNs(Neural Networks,神經網絡)和Polynomial Regression(多項式回歸)等價性之思考,以及深度模型可解釋性原理研究與案例


1. Main Point

0x1:行文框架

  • 第二章:我們會分別介紹NNs神經網絡和PR多項式回歸各自的定義和應用場景。
  • 第三章:討論NNs和PR在數學公式上的等價性,NNs和PR是兩個等價的理論方法,只是用了不同的方法解決了同一個問題,這樣我們就形成了一個統一的觀察視角,不再將深度神經網絡看成是一個獨立的算法。
  • 第四章:討論通用逼近理論,這是為了將視角提高到一個更高的框架體系,通用逼近理論證明了所有的目標函數都可以擬合,換句話說就是,所有的問題都可以通過深度學習解決。但是通用逼近理論並沒有告訴我們具體用什么模型。
  • 第五章/第六章:討論NNs和PR都存在的兩個主要潛在缺陷:1)多重共線性;2)過擬合性。討論這2個缺陷的目的是為了讓我們更好的理解復雜網絡的深層原理,以及解決過擬合問題的通用底層思維,通過這樣的視角討論,我們會發現,dropout和正則化並沒有本質的區別,只是看問題的視角不同罷了。
  • 第七章:討論一個非常棒的學術研究成果,LIME,它提供了一種使用簡單復合函數(線性函數、決策樹等)來近似局部逼近深度學習模型的理論和方法,為我們更好的理解深度模型的底層邏輯提供了新的視角。

0x2:Main Academic Point 

  • 多項式回歸PR,和神經網絡NNs,在數學公式上具有近似等價性,都是是一個復合函數。
  • 對於任何單變量函數,只要基函數(神經元、一元線性單元)足夠多,神經網絡函數就能任意逼近它。
  • 對於任何多變量函數,一定可以被多個單變量函數的復合來逼近。
  • NNs的學習是個數據擬合(最小二乘回歸)的過程,本質上和PR的線性回歸分析是一樣的,擬合過程是在學習基函數的線性組合。
  • 具體的NNs應用過程中,選多少層、選多少基的問題在逼近論中就是沒有很好的解決方案,這是逼近論中就存在的問題,並不是深度網絡帶來的問題。也就是說,最優神經網絡的構建問題,是需要從逼近論這個層面去突破的,單純研究神經網絡幫助並不會很大。 

0x3:Main Engineering Point 

  • 需要選取多大的神經網絡(也就是選用什么樣的擬合函數)?具體地,網絡要多少層?每層多少節點?這個需要根據你要解決的具體問題而定,一般來說,問題越簡單,網絡的自由度就要越小,而目標問題越復雜,網絡的自由度就要適當放大。
  • 先建立一個較小的網絡來解決核心問題,然后一步一步擴展到全局問題。
  • 可視化你的結果!這樣有助於在訓練過程中發現問題。我們應該明確的看到這些數據:損失函數的變化曲線、權重直方圖、變量的梯度等。不能只看數值。

 

2. NNs and Polynomial Regression

這一章節,我么分別對NNs和PR進行簡要介紹,為下一章節討論它們二者之間的等價性進行一些鋪墊。

0x1:Polynomial Regression(多項式回歸)

1. 為什么我們需要多項式回歸

線性回歸模型是機器學習和數理統計中最簡單也最常見的模型,但是線性回歸有一個最重要的假設前提就是,響應變量和解釋變量之間的確存在着 線性關系,否則就無法建立有效(強擬合優度)的線性模型。
然而現實中的問題往往線性關系比較弱,甚至本來就不存在着線性關系。實際上,大部分的問題都是非線性關系,所以我們需要非線性模型的多項式回歸。

2. 多項式回歸形式化定義

多項式回歸就是把一次特征轉換成高次特征的線性組合多項式,下面用一元情況進行舉例,多元情況以此類推。
對於一元線性回歸模型如下:

一元線性回歸模型

擴展成一元多項式回歸模型(degree = d)就是:

一元多項式回歸模型

一般地,考慮 n 維特征(x1,x2,…,xn),d次冪的情況(n元d次冪多項式):

其中,

上式即為n元d次多項式的通用表達式,中間部分是一個排列組合公式的省略寫法。

從特征向量維度的角度來看,PolynomialFeatures(degree=d)將維度為n的原始特征(n元特征)擴展到了一個維度為的新特征空間。可以形象的理解為,將d個相同小球排成一排后,用n個隔板將其進行分割,每個隔間分配多少小球的問題,排列組合的結果為種方法。

值得注意的一點是,n元d次多項式在特征空間上具有兩個主要特點:

  • n元特征的權重的離散化分配
  • n元特征之間的特征組合:例如當原始特征為a,b,次冪為3時,不僅僅會將a3,b3作為新特征,還會添加a2b,ab2和ab。 
另一點值得注意的是,關於多項式特征空間擴展的這個特點,在帶來更強擬合能力的同時,也引入了過擬合的潛在風險,即“too many turn paramets problem”。

3. 多項式回歸代碼示例

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression


if __name__ == '__main__':
    # generate a random dataset
    np.random.seed(42)

    m = 100
    X = 6 * np.random.rand(m, 1) - 3
    y = 0.5 * X ** 2 + X + 2 + np.random.randn(m, 1)

    plt.plot(X, y, "b.")
    plt.xlabel("$x_1$", fontsize=18)
    plt.ylabel("$y$", rotation=0, fontsize=18)
    plt.axis([-3, 3, 0, 10])
    plt.show()

    # use Scikit-Learn PolynomialFeature class to constructing parameter terms.
    # a,b,degree=2: [a, b, a^2, ab, b^2]
    # a,b,degree=3: [a, b, a^2, ab, b^2, a^3, a^2b, ab^2, b^3]
    # a,b,c,degree=3: [a, b, c, a^2, ab, ac, b^2, bc, c^2, a^3, a^2b, a^2c, ab^2, ac^2, abc, b^3, b^2c, bc^2, c^3]
    poly_features = PolynomialFeatures(degree=2, include_bias=False)
    # fit the dataset with Polynomial Regression Function, and X_poly is the fitting X result
    X_poly = poly_features.fit_transform(X)
    print "X: ", X
    print "X_poly: ", X_poly

    lin_reg = LinearRegression()
    lin_reg.fit(X_poly, y)
    print(lin_reg.intercept_, lin_reg.coef_)

    # draw the prediction curve
    X_new = np.linspace(-3, 3, 100).reshape(100, 1)
    # fit the X_new dataset with Polynomial Regression Function, and X_new_poly is the fitting X result
    X_new_poly = poly_features.transform(X_new)
    y_new = lin_reg.predict(X_new_poly)
    plt.plot(X, y, "b.")
    plt.plot(X_new, y_new, "r-", linewidth=2, label="Predictions")
    plt.xlabel("$x_1$", fontsize=18)
    plt.ylabel("$y$", rotation=0, fontsize=18)
    plt.legend(loc="upper left", fontsize=14)
    plt.axis([-3, 3, 0, 10])
    plt.show()

代碼中有一個細節需要注意一下,PolynomialFeatures的參數d=2,即2次冪多項式。這里之所以選2次冪是因為我們事先知道了數據集的概率分布,形狀大致為一個二次函數。實際上,讀者朋友可以自己修改代碼,改為degree=3/4..,多項式依然可以擬合的很好,因為這個數據集中的噪聲點不是很多,不容易發生過擬合。

但是在實際的工程中,數據集的維度數十萬都是很正常的,我們不可能事先知道最適合的d次冪參數是多少。一個最常用的理論和方法就是設置一個相對較大的d次冪,即使用一個相對復雜的多項式函數去擬合數據,當然,d次冪參數也不能設置的過大,因為過於復雜的多項式函數會導致過擬合的發生。

Relevant Link:  

https://blog.csdn.net/tsinghuahui/article/details/80229299
https://www.jianshu.com/p/9185bc96bfa9
https://blog.csdn.net/qq_36523839/article/details/82924804

0x2:Neural Nets(NNs,神經網絡)

這個小節,我們以數學公式為探查視角,從簡單神經元到淺層神經網絡,逐步討論學習其公式形式,為接下來討論NNs和PR的近似等價性作准備。

1. 單神經元(感知機)單層神經網絡 

單個神經元是神經網絡的最基本組成單元(1層1神經元的神經網絡退化為感知機模型),單個的感知機的本質就是一個一元線性分類函數,用它可以划出一條線,把一維平面分割開:

但是,當面對更復雜的問題時,一元線性分類函數(一維平面)就無法解決了。例如”電路模擬中的XOR運算問題“。

在數字邏輯中,異或是對兩個運算元的一種邏輯分析類型,符號為XOR或EOR或⊕。與一般的或(OR)不同,當兩兩數值相同時為否,而數值不同時為真。異或的真值表如下:

XOR truth table
Input Output
A B
0 0 0
0 1 1
1 0 1
1 1

單層的神經元模型可以對“與/與非/或”等邏輯進行很好的模擬,但是唯獨無法解決異或問題,如下圖:

神經元模型對“與”、“與非”、“或”問題都可以找到一條完美的決策面。但是,對於XOR問題中的數據點,無論如何調整,都無法找到一個完美的決策面。

無法找出一條直線作為決策邊界,可以使(0,0)和(1,1)在一個區域,而(1,0)和(0,1)在另一個區域。

2. 多神經元(感知機)多層神經網絡 

單神經元單層網絡(一元線性分類模型)無法解決XOR問題的本質原因是,XOR問題中數據向量的秩為2,用秩為1的一元線性分類函數是無法線性表出XOR問題的數據向量的。按照線性代數的理論解釋,要線性表出一個秩為2的向量組,必須用大於等於2的向量組,也就是說必須用二元及二元以上的線性分類器,才能實現對XOR問題的分類。
現在,我們嘗試用一個包含2個神經元隱層的雙層感知機模型來解決XOR問題,
每一個神經元都由一個感知機模型表示,使用閾值函數作為它的激活函數。比特符號0和1,分別由0和+1表示。
頂部的神經元標注為“Neuron 1”,有: 

該隱藏神經元構造的決策邊界斜率等於-1,在下圖中給出其位置:

隱藏層底部的神經元標注為“Neuron 2”,有:

隱藏元構造的決策邊界方向和位置由下圖給出:

我們可以看到,兩個隱藏神經元已經各自完成了一半的分類任務,現在需要的是一個“復合決策函數”,將它們分類能力進行一個綜合,得到原來兩個隱藏神經元形成的決策邊界構造線性組合。

輸出層的神經元標注為“Neuron 3”,有:

底部隱藏神經元由一個興奮(正)連接到輸出神經元,而頂部隱藏神經元由一個更強的抑制(負)連接到輸出神經元。這樣,通過構造一個隱層(本質上是一個復合線性函數),我們成功地解決了XOR問題。

現在,我們將多神經元單層神經網絡抽象為一般數學公式,以一個包含3個神經元的輸入層和1個神經元的隱藏層神經網絡為例:

從復合線性函數的角度來分析,上圖所示的神經網絡等價於:

合並同類項后有:

可以看到,上式本質上是一個多元1次冪線性方程組

目前為止,看起來多神經元(感知機)神經網絡已經可以很好解決問題了,但其實它還遠遠不夠。我們繼續來看一個更復雜的案例。
假設我們有這個需求,要將下面的三角形和圓形點進行正確的分類,如下圖:
可以看到,數據集不同類別之間是彼此交錯的,任何單獨線性函數都無法完美分類,如下圖:

無論單條直線決策面如何調整,都是完成二分類任務

面對這種情況,就需要增加線性元的數量,改用多元復合線性函數來進行多元線性切割,如下圖:

從理論上說,如果允許復合線性函數中的單個線性函數彼此平行相交,則幾乎所有數據集都可以通過多元線性復合函數進行線性切割。換句話說,多元線性復合函數可以無限逼近任意概率分布(通用逼近理論)。

但是需要注意,對同一個分類任務來說,如果要實現完美分類,多元線性復合函數需要的函數元可能會很多,這就導致了維度爆炸問題,過於復雜的線性復合函數也間接增加了過擬合的風險。
延伸思考
未剪枝前的決策樹,本質上就是一個由感知機和階躍函數組合的多層神經網絡(DNN),這么說可能有些抽象,我們來看一個常規決策樹對特征空間的划分示意圖

決策樹的多層樹結構,本質就是感知機DNN的多層結構。從這個角度來看,決策樹和感知機DNN同樣都存在過擬合問題

延伸思考

以R2二維空間為例,屬於目標函數的點可能分布在空間中的任何位置,模型訓練的過程就是需要找到一個超分界面,將所有的點都分類到合適的類別中,這就是所謂的”模型記憶“。需要注意的是,以下兩個觀點都是錯誤的:

  • 有多少數據點就需要有多少神經元,每個神經元負責記憶一個數據點
  • 樣本點中有多少pattern,就需要多少神經元,每個神經元負責記憶一種pattern

正確的理解是:最少需要的神經元數量取決於目標函數概率分布的規律性,如果目標函數在特征空間中不同類別是彼此交錯分布的,那么為了正確地”切割“出一個合適的超平面,就需要遠大於pattern數的基函數,這樣切出來的超平面邊界會非常的鋸齒狀,相應的也可以想象,抗擾動能力也會相應下降。這也是為什么說越復雜的模型越容易過擬合的原因

3. 非線性激活函數神經網絡

上一小節留下的問題是,有沒有既能實現完美分類,同時又能有效控制函數元數量的復合函數呢?答案是肯定的,這就是我們接下來要討論的非線性復合函數(包含非線性激活函數的神經網絡)。

我們知道,使用階躍激活函數的多元感知機神經網絡,本質上是多個線性分界面的組合,如下圖:

上圖中,如果我們能構造出一個彎曲的決策超曲面,就可能實現用少量的非線性函數,直接對數據集進行分類。

非線性激活函數有很多,不同的數學公式形式帶來了不同的數學特性,這里我們以sigmoid函數為例:

單神經元后增加了一個非線性激活函數的神經網絡

 
3神經元后增加一個非線性激活函數作為輸入層,隱藏層由單個神經元組成,后面加一個非線性激活函數,得到一個非線性多神經元復合神經網絡
回到上面的例子,通過3個非線性分類函數可以實現完美分類,並且具有更好的泛化能力。

Relevant Link:  

https://www.cnblogs.com/LittleHann/p/6629069.html - Neural Networks and Deep Learning(神經網絡與深度學習) - 學習筆記

 

3. NNs和PR的等價性

這個章節我們來從神經網絡的觀點來看多項式擬合函數,並分析其等價性。

0x1:一元一次冪多項式函數和NNs的等價性

對於一元一次冪的逼近函數

可以看成為如下圖的三層神經網絡,

左:輸入層到隱層的權系數均為常值1

右:輸入層到隱層看成為“直接代入”(用虛線表示)

神經網絡中只有一個隱層。隱層上有個節點,激活函數分別為基函數(從這里我們將基函數稱為“激活函數”)。輸入層到隱層的權設為常值1(左圖),也可以看成為將輸入層的值“直接代入”到激活函數(右圖)。隱層到輸出層的權為基函數的組合系數

0x2:n元m次冪多項式函數和NNs的等價性

考慮一般的逼近函數。設中的一組基函數為。則函數可看成為如下圖的一個三層的神經網絡,

注意這里隱層的激活函數都是維函數,從輸入層到隱層也是直接代入。輸出層的各個分量共享隱層的激活函數。

一般地, 一個多元線性回歸方程,等價於一個3層人工神經網絡。也就是說,只要包含一個隱層的人工神經網絡,就可以等價所有多形式回歸模型。

更進一步地,如果給人工神經網絡加上非線性激活函數、增加網絡深度,這只是在增加神經網絡的自由度,多項式回歸依然能夠在一個限定的誤差ε內,近似地等價於該神經網絡。

而且在實際工程中,這個近似的程度還得具體目標分布有關(目標問題場景),如果目標分布較簡單,則在正則化稀疏學習的作用下,神經網絡會退化為一個多項式函數。這就是為什我們在某些簡單的問題上,用隨機森林和深度神經網絡的效果是差不多的,甚至傳統隨機森林效果還要更好。

Relevant Link:  

http://staff.ustc.edu.cn/~lgliu/Resources/DL/What_is_DeepLearning.html

 

4. Universal Approximation Theorems(通用逼近理論)

上一章節我們討論了NNs和PR的等價性,基本上來說,我們可以將NNs和PR視為同一種函數模型。這個章節我們就來討論一個對它們二者都使用的通用逼近理論(universal approximation theorems),通用逼近理論告訴我們,一定存在一個多層神經網絡或者多項式函數,可以在一定的誤差ε內,近似地逼近任意函數分布

雖然通用逼近定理並沒有給出如何找到這個NNs或PR,但是它從理論上證明了強存在性,這個存在性定理實在令人振奮,因為這意味着,在具體工程項目中,我們總可以應用深度神經網絡取得一個不錯的結果。

0x1:什么是逼近問題

在討論具體的理論之前,我們首先通過一個簡單的案例,對逼近問題建立一個直觀的感受。

我們先考慮最簡單的情形,即實數到實數的一元函數。假設通過實驗獲得了m個樣本點。我們希望求得反映這些樣本點規律的一個函數關系,如下圖所示。

1. 插值問題(Interpolation)

如果要求函數嚴格通過每個樣本點,即:,則求解函數的問題稱為插值問題(Interpolation)。插值問題一般需要針對樣本數據直接求解線性方程組的解。

插值問題更多僅限於理論分析,在實際的工程中,因為誤差和目標函數未知的緣故,幾乎不可能找到一個函數能完美通過所有的樣本點。所以,更多時候,我們需要討論逼近問題,而插值問題就是逼近問題的一個特例(誤差為0的逼近),相關討論,可以參閱這篇文章

2. 逼近問題(Approximation)

一般地,由於實驗數據帶有觀測誤差,因此在大部分情況下,我們只要求函數反映這些樣本點的趨勢,即函數靠近樣本點且誤差在某種度量意義下最小,稱為逼近問題(Approximation)。

若記在某點的誤差為,且記誤差向量為。逼近問題就是要求向量的某種范數最小。一般采用歐氏范數(范數)作為誤差度量的標准(均方誤差),即求如下極小化問題:

極小化問題,一般可通過極大似然估計或者矩估計的方法實現。

通用逼近理論討論的就是函數逼近問題,我們接下來圍繞這個主題展開討論。 

0x2:逼近函數模型分類

在科學技術的各領域中,我們所研究的事件一般都是有規律(因果關系)的,即自變量集合與應變量集合之間存在的對應關系通常用映射來描述,按照模型(函數)是否具備明確的函數表達式(概率分布函數),可以將模型大致分為兩類:

  • 生成式模型:有些函數關系可由理論分析直接推導得出(先驗),不僅為進一步的分析研究工作提供理論基礎,也可以方便的解決實際工程問題。比如,適合於宏觀低速物體的牛頓第二運動定律就是在實際觀察和歸納中得出的普適性力學定律。
  • 判別式模型:但是,很多工程問題難以直接推導出變量之間的函數表達式;或者即使能得出表達式,公式也十分復雜,不利於進一步的分析與計算。這時可以通過諸如采樣、實驗等方法獲得若干離散的數據(稱為樣本數據點),然后根據這些數據,希望能得到這些變量之間的函數關系(后驗),這個過程稱為數據擬合(Data fitting),在數理統計中也稱為回歸分析(Regression analysis)。回歸分析中有一類特殊情況,輸出的結果是離散型的(比如識別圖片里是人、貓、狗等標簽的一種),此時問題稱為分類(Classification)。

0x3:逼近函數方法

函數的表示是函數逼近論中的基本問題。在數學的理論研究和實際應用中經常遇到下類問題:在選定的一類函數中尋找某個函數,使它與已知函數(或觀測數據)在一定意義下為最佳近似表示,並求出用近似表示而產生的誤差。這就是函數逼近問題

1. 逼近函數類

在實際問題中,首先要確定函數的具體形式。這不單純是數學問題,還與所研究問題的運動規律及觀測數據有關,也與用戶的經驗有關。一般地,我們在某個較簡單的函數類中去尋找我們所需要的函數。這種函數類叫做逼近函數類

逼近函數類可以有多種選擇,一般可以在不同的函數空間(比如由一些基函數通過線性組合所張成的函數空間)中進行選擇。如下是一些常用的函數類。

1)多項式函數類

n次代數多項式,即由次數不大於n的冪基的線性組合的多項式函數:

其中為實系數。

更常用的是由n次Bernstein基函數來表達的多項式形式(稱為Bernstein多項式或Bezier多項式):

其中Bernstein基函數

2)三角多項式類

n階三角多項式,即由階數不大於n的三角函數基的線性組合的三角函數:

 

中為實系數。

 

這些是常用的逼近函數類。在逼近論中,還有許多其他形式的逼近函數類,比如由代數多項式的比構成的有理分式集(有理逼近);按照一定條件定義的樣條函數集(樣條逼近);徑向基函數(RBF逼近);由正交函數系的線性組合構成的(維數固定的)函數集等。

3)其他基函數類

在逼近論中,還有許多其他形式的逼近函數類,比如:

  • 由代數多項式的比構成的有理分式集(有理逼近)
  • 按照一定條件定義的樣條函數集(樣條逼近)
  • 徑向基函數(RBF逼近)
  • 由正交函數系的線性組合構成的(維數固定的)函數集等
  • GMM模型(高斯分布基函數)

2. 萬能逼近定理

在函數逼近論中,如果一組函數成為一組“基”函數,需要滿足一些比較好的性質,比如:

  • 光滑性(線性可微)
  • 線性無關性
  • 權性(所有基函數和為1)
  • 局部支集
  • 完備性:該組函數的線性組合是否能夠以任意的誤差和精度來逼近給定的函數(即萬能逼近性質)
  • 正性
  • 凸性等。其中, “完備性”是指,?

我們重點來討論一下完備性,即“萬能逼近定理”,

Weierstrass逼近定理

上的任意連續函數g,及任意給定的,必存在n次代數多項式,使得:

Weierstrass逼近定理表明,只要次數n足夠高,n次多項式就能以任何精度逼近給定的函數。具體的構造方法有Bernstein多項式或Chebyshev多項式等。

類似地,由Fourier分析理論(或Weierstrass第二逼近定理),只要階數足夠高,n階三角函數就能以任何精度逼近給定的周期函數,n階高斯函數就能組成GMM分布以逼近任意給定的概率分布函數。

這些理論表明,多項式函數類、三角函數類、高斯函數在函數空間是“稠密”的,這就保障了用這些函數類來作為逼近函數是“合理”的。

0x4:逼近函數選擇的最大挑戰

在一個逼近問題中選擇什么樣的函數類作逼近函數類,這要取決於被逼近函數本身的特點,也和逼近問題的條件、要求等因素有關。在實際應用中,存在着兩個最大的挑戰,

  • 選擇什么樣的逼近函數類?一般地,需要用戶對被逼近對象或樣本數據有一些“先驗知識”來決定選擇具體的逼近函數類。比如,
    • 如果被逼近的函數具有周期性,將三角函數作為逼近函數是個合理的選擇;
    • 如果被逼近的函數具有奇點,將有理函數作為逼近函數更為合理,等等。
  • 即使確定了逼近函數類,選擇多高的次數或階數?比如,如果選擇了多項式函數類,根據Lagrange插值定理,一定能找到一個次多項式來插值給定的個樣本點。但如果較大,則這樣得到的高次多項式很容易造成“過擬合”(Overfitting)。而如果選擇的過小,則得到的多項式容易造成“欠擬合”(Underfitting)。如下圖所示。過擬合或欠擬合函數在實際應用中是沒有用的,因為它們的泛化能力很差。

用不同次數的多項式擬合樣本點(藍色點)。

左:欠擬合;中:合適的擬合;右:過擬合。

這里需要提及的是,一個逼近函數“表達能力”體現在該函數的未知參數(例如多項式中的系數)與樣本點個數的差,也稱為“自由度”。

如果逼近函數的未知參數越多,則表達能力越強。然而,在實際的擬合問題中,逼近函數的擬合能力並非越強越好。因為如果只關注樣本點處的擬合誤差的話,非常強的表達能力會使得樣本點之外的函數值遠遠偏離期望的目標,反而降低擬合函數的預測性能,產生過擬合,如上圖(右)所示。擬合能力和過擬合規避之間的平衡,就是通過對自由度的控制來實現。

0x5:通用神經網絡

這一小節,我們來討論一下通用神經網絡,主要是探尋NNs是如何同時實現通用逼近和防止過擬合這2個目標的,PR和NNs是等價的,因此本章的討論對PR也同樣成立。

對於通用神經網絡來說,網絡的結構設置都存在着如下兩個主要挑戰:

  • 隱層中的節點中使用什么樣的激活函數(基函數)?(注意這里激活函數不是特指sigmoid那種激活函數,而是泛指整個神經元的最終輸出函數)
    • 依賴專家先驗經驗
  • 隱層中設置多少個節點(基函數的個數和次數)?
    • 雖然有些基函數的性質很好,但是次數或階數過高(比如多項式基或三角函數基),就會產生震盪,也容易產生過擬合,使得擬合函數的性態不好。

接下來我們來討論通用神經網絡是如何解決上述兩大挑戰的。

1. 使用簡單“元函數”作為激活函數

如何在沒有太多領域先驗的情況下,選擇合適的“基函數”的另一個策略是“原子化構建基礎,數據驅動結構生成”。

注意到,對於任意一個非常值的一元函數,這里我們稱為元函數,其沿着x方向的平移函數,以及沿着x方向的伸縮函數都與原函數線性無關。

也就是說,如果能有足夠多的元函數經過平移和伸縮變換,其線性組合所張成的函數空間就能有充分的表達能力(高秩矩陣)。所以接下來的問題就是,如何有效地得到

一個自然的想法就是,我們可以以這個作為激活函數,讓網絡自動地去學習這些激活函數的變換,來表達所需要的擬合函數呢?如下圖所示,

一元(單變量)函數的神經元結構

對單神經元來說,變量乘以一個伸縮,加上一個平移(稱為偏置“bias”),即變量的仿射變換,成為神經元的輸入,然后通過激活函數復合后成為該神經元的輸出

對於多變量的情形(多元函數),神經元的結構如下圖所示,

在多神經元網絡中,每一層的所有神經元都互相連接,變量的線性組合,加上一個平移(稱為偏置“bias”),即變量的仿射變換,成為神經元的輸入;然后通過激活函數復合后成為該神經元的輸出 

一個多元函數的神經網絡的結構如下圖所示,有一個輸入層,一個隱層及一個輸出層,

  • 輸入層除了變量外,還有一個常數節點1
  • 隱層包含多個節點,每個節點的激活函數都是,隱層的輸出就是輸入層節點的線性組合加偏置(即仿射變換)代入到激活函數的復合函數
  • 輸出層是這些復合函數的組合

這個網絡的所有權系數(層與層神經元之間的權),(偏置項)及作為這個神經網絡的參數變量,需要通過極小化損失函數來求解的。這個過程稱為“訓練”或“學習”。

和回歸分析類似,神經網絡的學習過程本質上就是在學習所有的系數參數。最后得到的擬合函數為一些基函數的線性組合表達。這些組合函數實質上就是表達函數的“基函數”。這樣就通過數據驅動的方式,得到了一個最優的基函數線性組合。

從這個觀點來看,神經網絡本質上就是傳統的逼近論中的逼近函數的一種推廣。它不是通過指定的理論完備的基函數(例如多項式,三角多項式等)來表達函數的,而是通過簡單的基元函數(激活函數)的不斷變換得到的“基函數”來表達函數的。

2. 使用超完備集實現萬能逼近

解決了基函數選擇的問題,我們還要問個問題:將函數經過充分多的平移和伸縮(包括它們的組合)所線性張成的函數空間,其表達能力足夠強嗎?這個函數空間是否在所有函數空間是稠密的?

如果結論是肯定的,那么就是說,對於任何一個給定的函數,總能找到函數的多次平移和縮放的函數,其線性組合能夠逼近給定的這個函數。也就是說,神經網絡只要隱層的節點數足夠多,該網絡所表達的函數就能逼近任意的函數

這個結論在大多數情況是成立的,由【萬能逼近定理】所保證。

為空間中的單位立方體,我們在這個定義域中來描述萬能逼近定理。記上的連續函數空間,上的可測函數空間,上相對測度μ的可積函數空間(即)。

設給定一元激活函數,首先給出如下定義,

【定義1】稱函數為壓縮函數,如果單調不減,且滿足

【定義2】稱函數為可分辨的,若對於有限測度μ,由

可得到

【定義3】記

為所有由激活函數變換及線性累加所構成的m維函數空間(即具有n個節點的單隱層神經網絡所表達的m維函數)。

由以上定義,有以下幾個定理(涉及實分析和泛函分析), 

【定理1】若是壓縮函數,則中一致稠密,在中按如下距離下稠密:

【定理2】若是可分辨的,則中按連續函數距離下稠密。

【定理3】若是連續有界的非常值函數,則中稠密。

【定理4】若是無界的非常值函數,則中稠密。

通俗地說就是:對任意給定的一個中的函數,只要項數足夠多,中就存在一個函數,使得在一定精度下逼近。也就是說,包含m個神經元的單隱層的神經網絡所表達的維函數能夠逼近中的任意一個函數 

基於萬能逼近定理,人工神經網絡往往會選擇一個超完備集神經元,即用大於目標函數維度的神經元數量,來構建一個復雜神經網絡,以保證近似逼近能力。

3. 使用稀疏學習在超完備集中選擇合適數量的基函數,以降低自由度

使用超完備集在獲得萬能逼近能力的同時,會帶來過擬合問題。在人工神經網絡中加入稀疏學習,可以有效避免該現象。

Relevant Link:    

http://staff.ustc.edu.cn/~lgliu/Resources/DL/What_is_DeepLearning.html
K. Hornik, et al. Multilayer feedforward networks are universal approximations. Neural Networks, 2: 359-366, 1989.
G. Cybenko. Approximation by superpositions of a sigmoidal function. Math. Control Signals System, 2: 303-314, 1989.
K. Hornik. Approximation capabilities of multilayer feedforward networks. Neural Networks, 4: 251-257, 1991.

 

5. PR(多項式回歸)and NNs(神經網絡)Overfitting

在實際工程項目中,不管是直接應用VGG-xx或者自己設計一種全新的網絡結構,網絡的參數動輒都上千萬,網絡越來越復雜,參數越來越多。

但需要注意的是,擬合函數所帶的參數的個數與樣本數據的個數之間的差代表着這個擬合函數的“自由度”。網絡越來越“深”后,擬合模型中的可調整參數的數量就非常大。因此,層數很大的深度網絡(模型過於復雜)能夠表達一個自由度非常大的函數空間,甚至遠高於目標函數空間(過完備空間),即自由度遠大於0。這樣就很容易導致過擬合(Overfitting),例如下圖所示,

過擬合可以使得擬合模型能夠插值所有樣本數據(擬合誤差為0!)。但擬合誤差為0不代表模型就是好的,因為模型只在訓練集上表現好;由於模型擬合了訓練樣本數據中的噪聲,使得它在測試集上表現可能不好,泛化性能差等。

為此,人們采取了不同的方法來緩解過擬合(無法完全避免),比如正則化、數據增廣、Dropout、網絡剪枝等。這些方法的底層原理,歸結為一句話都是:稀疏表達和稀疏學習

0x1:稀疏表達和稀疏學習 - 緩解overfitting的有效手段

緩解逼近函數過擬合的有效手段是,在函數公式中對回歸變量施加范數的正則項,例如L1/L2正則項,以達到對回歸變量進行稀疏化,即大部分回歸變量為0(少數回歸變量非0)。

這種優化稱為稀疏優化。也就是說,對回歸變量施加范數能夠“自動”對基函數進行選擇,值為0的系數所對應的基函數對最后的逼近無貢獻。這些非0的基函數反映了的樣本點集合的“特征”,因此也稱為特征選擇。

我們往往可以多選取一些基函數(甚至可以是線性相關的)及較高的次冪,使得基函數的個數比輸入向量的維數還要大,稱為“超完備”基(Over-complete basis)或過冗余基,在稀疏學習中亦稱為“字典”。然后通過對基函數的系數進行稀疏優化(稀疏學習/字典學習),選擇出合適(非0系數)的基函數的組合來表達逼近函數。

稀疏學習與最近十年來流行的壓縮感知(Compressive sensing)理論與方法非常相關,也是機器學習領域的一種重要方法。其理論基礎由華裔數學家陶哲軒(2006年國際數學家大會菲爾茲獎得主)、斯坦福大學統計學教授David Donoho(2018年國際數學家大會高斯獎得主)等人所建立,已成功用於信號處理、圖像與視頻處理、語音處理等領域。

Relevant Link:     

https://cosx.org/2016/06/discussion-of-sparse-coding-in-deep-learning/

 

6. Multicollinearity(多重共線性) 

所謂多重共線性,簡單來說,是指回歸模型中存在兩個或兩個以上的自變量彼此相關。其實本質上說,多重共線性和我們上一章討論的超完備基本質上是一樣的,因為超完備基常常是稀疏的,其內部往往存在較多線性相關的結構。

在NNs和PR中,也同樣存在多重共線性問題,所以這章我們也來討論這個問題,通過這些討論,我們能夠更加深刻理解NNs和PR的等價性。

0x1:造成多重共線性的原因

  • 解釋變量都享有共同的時間趨勢
  • 一個解釋變量是另一個的滯后,二者往往遵循一個趨勢
  • 由於數據收集的基礎不夠寬,某些解釋變量可能會一起變動
  • 某些解釋變量間存在某種近似的線性依賴關系

0x2:處理多重共線性的原則

  • 多重共線性是普遍存在的,輕微的多重共線性問題可不采取措施
  • 如果模型僅用於預測,則只要擬合程度好,可不處理多重共線性問題,存在多重共線性的模型用於預測時,往往不影響預測結果

0x3:多重共線性的負面影響

  • 變量之間高度相關,可能使回歸的結果混亂,甚至把分析引入歧途
  • 難以區分每個解釋變量的單獨影響
  • 變量的顯著性檢驗失去意義,模型的線性關系檢驗(F檢驗)顯著,但幾乎所有回歸系數bi的t檢驗卻不顯著
  • 對參數估計值的正負號產生影響,特別是估計系數的符號可能與預期的正相反,造成對回歸系數的解釋是危險的。比如:違約率應該和貸款余額是正相關的,但由於有其他因素的影響最終模型中貸款余額的系數為負,得到“貸款余額越大違約率越低”的危險解釋。可見,在建立回歸模型時,並不會特征變量越多越好,因為他們帶來問題比解決的問題可能更多
  • 回歸模型缺乏穩定性。樣本的微小擾動都可能帶來參數很大的變化,因為重復的特征變量很多,任何一個擾動都可能被放大很多倍
  • 影響模型的泛化誤差

0x4:方差擴大因子VIF(variance inflation factor):定量評估多重共線性程度

方差擴大(膨脹)因子法是通過考察給定的解釋變量被方程中其他所有解釋變量所解釋的程度,以此來判斷是否存在多重共線性的一種方法。
方程中的每一個解釋變量都有一個方差擴大(膨脹)因子(variance inflation factor,VIF),它反映的是多重共線性在多大程度上增大估計系數方差的指標。
統計上可以證明,解釋變量 、參數估計值 的方差可表示為:
式中, 是變量 的方差擴大因子,即,
   
這里的 是多個解釋變量輔助回歸的可決系數。 越大,說明變量間多重共線性越嚴重,方差膨脹因子 也就越大。
經驗表明, 時,說明解釋變量與其余解釋變量之間有嚴重的多重共線性。且這種多重共線性可能會過度地影響模型擬合結果。

0x5:多重共線性影響舉例

# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn import cross_validation


if __name__ == '__main__':
    # 首先捏造一份好的數據,樣本量為100,特征數為8,且滿足方程: y = 5x_0 + 6x_1 + 7x_2 + 8x_3 + 9x_4 + 10x_5 + 11x_6 + 12x_7 + b
    coef0 = np.array([5, 6, 7, 8, 9, 10, 11, 12])
    X1 = np.random.rand(100, 8)
    # 誤差項是期望為0,標准差為1.5的正態分布隨機變量。
    y = np.dot(X1, coef0) + np.random.normal(0, 1.5, size=100)

    training = np.random.choice([True, False], p=[0.8, 0.2], size=100)
    lr1 = LinearRegression()
    lr1.fit(X1[training], y[training])
    # 系數的均方誤差MSE
    print "lr1 MSE: ", (((lr1.coef_ - coef0) ** 2).sum() / 8)
    # 測試集准確率(R2)
    print "lr1 R2:", (lr1.score(X1[~training], y[~training]))
    # 平均測試集准確率
    print "lr1 MR2:", (cross_validation.cross_val_score(lr1, X1, y, cv=5).mean())

    # 基於上面構造數據,另外構造出兩份數據,
    # 1. X2: 第一份數據則增加兩個共線性特征,目的是顯著增加其VIF值
    # 2. X3: 第二份數據增加兩個隨機的特征用作對比
    X2 = np.column_stack([X1, np.dot(X1[:, [0, 1]], np.array([1, 1])) + np.random.normal(0, 0.05, size=100)])
    X2 = np.column_stack([X2, np.dot(X2[:, [1, 2, 3]], np.array([1, 1, 1])) + np.random.normal(0, 0.05, size=100)])
    X3 = np.column_stack([X1, np.random.rand(100, 2)])

    # 拿這兩份數據重新用線性回歸擬合模型
    lr2 = LinearRegression()
    lr2.fit(X2[training], y[training])
    # 系數的均方誤差MSE
    # 對於第二份共線性構造數據X2,因為多重共線性,可以看到MSE增加了很多,准確率也下降了0.2%:
    print "lr2 MSE: ", (((lr2.coef_[:8] - coef0) ** 2).sum() / 8)
    # 測試集准確率(R2)
    print "lr2 R2: ", (lr2.score(X2[~training], y[~training]))
    # 平均測試集准確率
    print "lr2 MR2: ", (cross_validation.cross_val_score(lr2, X2, y, cv=5).mean())

    lr3 = LinearRegression()
    lr3.fit(X3[training], y[training])
    # 系數的均方誤差MSE
    # X3沒有明顯變化
    print "lr3 MSE: ", (((lr3.coef_[:8] - coef0) ** 2).sum() / 8)
    # 測試集准確率(R2)
    print "lr3 R2: ", (lr3.score(X3[~training], y[~training]))
    # 平均測試集准確率
    print "lr3 MR2: ", (cross_validation.cross_val_score(lr3, X3, y, cv=5).mean())

    # show lr2 VIF result
    vif2 = np.zeros((10, 1))
    for i in range(10):
        tmp = [k for k in range(10) if k != i]
        lr2.fit(X2[:, tmp], X2[:, i])
        vifi = 1 / (1 - lr2.score(X2[:, tmp], X2[:, i]))
        vif2[i] = vifi

    vif3 = np.zeros((10, 1))
    for i in range(10):
        tmp = [k for k in range(10) if k != i]
        lr2.fit(X3[:, tmp], X3[:, i])
        vifi = 1 / (1 - lr2.score(X3[:, tmp], X3[:, i]))
        vif3[i] = vifi
    plt.figure()
    ax = plt.gca()
    ax.plot(vif2)
    ax.plot(vif3)
    plt.xlabel('feature')
    plt.ylabel('VIF')
    plt.title('VIF coefficients of the features')
    plt.axis('tight')
    plt.show()

可以看到,0、1、2、3、8、9個特征的VIF都過高,其中第9個是我們人工構造出了存在線性相關依賴的新特征變量。

0x6:NNs中普遍存在的多重共線性

在神經網絡中,同層的神經元和層與層之間的神經元之間都有可能存在多重共線性(線性相關),層內的多重共線性可以通過正則化進行緩解,相比之下,層與層神經元之間存在的多重共線性就無法避免了,它是普遍存在的。 

這里以一個“10 units”的全連接神經網絡為例,

計算層與層之間神經元的平均VIF結果如下:

可以看到隨着前向傳遞的進行,后面層的神經元的VIF越來越大,以層的視角來看,層與層之間的線性相關性逐漸提高,這個結論對PR也是同樣成立的。

這也從另一個層面看到,對於神經網絡來說,真正起作用的也只有最后一層隱層,雖然訓練過程是全網絡整體反饋調整的,但是最終輸出層的結果大部分由最有一層隱層的基函數決定。 

Relevant Link:     

https://baike.baidu.com/item/%E6%96%B9%E5%B7%AE%E6%89%A9%E5%A4%A7%E5%9B%A0%E5%AD%90 
https://www.jianshu.com/p/0925347c5066
https://www.jianshu.com/p/ef1b27b8aee0
https://arxiv.org/pdf/1806.06850v1.pdf

 

7. 深度模型可解釋性初探:用簡單局部線性函數近似逼近深度神經網絡

0x1:基本原理說明

1.  Interpretable Data Representations(可解釋性數據表征)

一般來說,模型的輸入層是可解釋性最強的,例如原始專家經驗特征、圖像像素矩陣、文本原始詞序列向量等。 

假設輸入層維度為d,可解釋性模型的維度d應該小於等於輸入層維度d,用”0/1“編碼來表征輸入層的每一個特征是否出現,即,

2. 保真性和解釋性平衡 - 構建似然估計函數

Local Interpretable Model-agnostic Explanations(局部線性逼近可解釋模型)需要同時平衡兩個對立的目標:

  • 保真性:可解釋性復合線性函數和目標函數的逼近誤差要盡量小
  • 可解釋性:可解釋性復合函數本身的復雜度要盡量低,基元函數數量越少,就越能從中理解到人類可讀的可解釋性

通過極大似然估計來獲得一個最優結果:

3. 基於局部擾動采樣的負反饋訓練過程

LIME捕獲局部線性特征的過程如下圖所示,

將目標函數f()看成是一個零先驗黑盒,通過不斷重復f(x)->add random noise to x->f(x),勾勒出目標函數的近似局部邊界,並將獲取的樣本作為打標數據輸入LIMIE模型進行負反饋訓練,這個做法和蒙特卡洛采樣的思想是類似的。

還有一點值得注意,LIMIE同時使用正則化來進行稀疏學習,進一步減少可解釋性單元,將可解釋單元集中在特定的一些重點特征上,提高人類可讀性。

0x2:隨機森林特征可解釋性

我們知道,隨機森林本質上是一個最優赫夫曼編碼函數。在隨機森林每棵樹中,特征節點的選擇和各個特征節點所處的位置,本身就包含了一個復合線性決策函數的能力。但是,我們最多也只能定性地了解有限特征的相對重要性,對每一個特征定量的重要性評估無法得知。

我們通過LIME對一個隨機森林模型進行“local linear approximation(局部線性近似)”,借助線性函數的強可解釋性,來定量研究隨機森林在預測中,各個特征向量各自起到了多少的貢獻(似然概率)。

# -*- coding: utf-8 -*-

import lime
import sklearn
import numpy as np
import sklearn
import sklearn.ensemble
import sklearn.metrics
from sklearn.datasets import fetch_20newsgroups
from lime import lime_text
from sklearn.pipeline import make_pipeline
from lime.lime_text import LimeTextExplainer

if __name__ == '__main__':
    # we'll be using the 20 newsgroups dataset.
    # In particular, for simplicity, we'll use a 2-class subset: atheism and christianity.
    categories = ['alt.atheism', 'soc.religion.christian']
    newsgroups_train = fetch_20newsgroups(subset='train', categories=categories)
    newsgroups_test = fetch_20newsgroups(subset='test', categories=categories)
    class_names = ['atheism', 'christian']

    # use the tfidf vectorizer, commonly used for text.
    vectorizer = sklearn.feature_extraction.text.TfidfVectorizer(lowercase=False)
    train_vectors = vectorizer.fit_transform(newsgroups_train.data)
    test_vectors = vectorizer.transform(newsgroups_test.data)

    # use random forests for classification.
    # It's usually hard to understand what random forests are doing, especially with many trees.
    rf = sklearn.ensemble.RandomForestClassifier(n_estimators=500)
    rf.fit(train_vectors, newsgroups_train.target)

    pred = rf.predict(test_vectors)
    print sklearn.metrics.f1_score(newsgroups_test.target, pred, average='binary')

    # Lime explainers assume that classifiers act on raw text,
    # but sklearn classifiers act on vectorized representation of texts.
    # For this purpose, we use sklearn's pipeline, and implements predict_proba on raw_text lists.
    c = make_pipeline(vectorizer, rf)
    print(c.predict_proba([newsgroups_test.data[0]]))

    # Now we create an explainer object. We pass the class_names a an argument for prettier display.
    explainer = LimeTextExplainer(class_names=class_names)

    # We then generate an explanation with at most 6 features for an arbitrary document in the test set.
    idx = 83
    exp = explainer.explain_instance(newsgroups_test.data[idx], c.predict_proba, num_features=6)
    print('Document id: %d' % idx)
    print('Probability(christian) =', c.predict_proba([newsgroups_test.data[idx]])[0, 1])
    print('True class: %s' % class_names[newsgroups_test.target[idx]])

    # The classifier got this example right (it predicted atheism).
    # The explanation is presented below as a list of weighted features.
    print "exp.as_list(): ", exp.as_list()

    # These weighted features are a linear model,
    # which approximates the behaviour of the random forest classifier in the vicinity of the test example.
    # Roughly, if we remove 'Posting' and 'Host' from the document ,
    # the prediction should move towards the opposite class (Christianity) by about 0.27 (the sum of the weights for both features).
    # Let's see if this is the case.
    print('Original prediction:', rf.predict_proba(test_vectors[idx])[0, 1])
    tmp = test_vectors[idx].copy()
    tmp[0, vectorizer.vocabulary_['Posting']] = 0
    tmp[0, vectorizer.vocabulary_['Host']] = 0
    print('Prediction removing some features:', rf.predict_proba(tmp)[0, 1])
    print('Difference:', rf.predict_proba(tmp)[0, 1] - rf.predict_proba(test_vectors[idx])[0, 1])

    # Visualizing explanations
    # The explanations can be returned as a matplotlib barplot:
    fig = exp.as_pyplot_figure()
    fig.show()
    exp.save_to_file('./oi.html')

可以看到,在將某一個document預測為“atheism”這一類別的時候,總共有“edu”、“NNTP”、“Posting”、“Host”、“There”、“hava”這些單詞起到了似然概率貢獻,並且它們各自的貢獻比是不同的。

0x3:InceptionV3圖像識別預測可解釋性分析

google的InceptionV3神經網絡模型,采用卷積網絡對圖像進行了預訓練。這節,我們用局部線性逼近方法,來對該網絡的局部可解釋性進行分析。

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt
import numpy as np
import os
import keras
import os, sys
try:
    import lime
except:
    sys.path.append(os.path.join('..', '..')) # add the current directory
    import lime
from lime import lime_image
from keras.applications import inception_v3 as inc_net
from keras.preprocessing import image
from keras.applications.imagenet_utils import decode_predictions
from skimage.io import imread
from skimage.segmentation import mark_boundaries
print('run using keras:', keras.__version__)


def transform_img_fn(path_list):
    out = []
    for img_path in path_list:
        img = image.load_img(img_path, target_size=(299, 299))
        x = image.img_to_array(img)
        x = np.expand_dims(x, axis=0)
        x = inc_net.preprocess_input(x)
        out.append(x)
    return np.vstack(out)


if __name__ == '__main__':
    # Using Inception
    # Here we create a standard InceptionV3 pretrained model,
    # and use it on images by first preprocessing them with the preprocessing tools
    inet_model = inc_net.InceptionV3()

    # Let's see the top 5 prediction for some image
    img_path = os.path.join('data', 'cat_and_mouse.jpg')
    print "img_path: ", img_path
    images = transform_img_fn([img_path])
    # I'm dividing by 2 and adding 0.5 because of how this Inception represents images
    plt.imshow(images[0] / 2 + 0.5)
    plt.show()
    preds = inet_model.predict(images)
    for x in decode_predictions(preds)[0]:
        print(x)

    # Explanation
    # Now let's get an explanation
    explainer = lime_image.LimeImageExplainer()
    # hide_color is the color for a superpixel turned OFF.
    # Alternatively, if it is NONE, the superpixel will be replaced by the average of its pixels.
    # Here, we set it to 0 (in the representation used by inception model, 0 means gray)
    explanation = explainer.explain_instance(images[0], inet_model.predict, top_labels=5, hide_color=0,
                                             num_samples=1000)

    # Now let's see the explanation for the Top class
    # We can see the top 5 superpixels that are most positive towards the class with the rest of the image hidden
    temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=True, num_features=5,
                                                hide_rest=True)
    plt.imshow(mark_boundaries(temp / 2 + 0.5, mask))
    plt.show()
    # Or with the rest of the image present:
    temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=True, num_features=5,
                                                hide_rest=False)
    plt.imshow(mark_boundaries(temp / 2 + 0.5, mask))
    plt.show()
    # We can also see the 'pros and cons' (pros in green, cons in red)
    temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=False, num_features=10,
                                                hide_rest=False)
    plt.imshow(mark_boundaries(temp / 2 + 0.5, mask))
    plt.show()
    # Or the pros and cons that have weight at least 0.1
    temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=False, num_features=1000,
                                                hide_rest=False, min_weight=0.1)
    plt.imshow(mark_boundaries(temp / 2 + 0.5, mask))
    plt.show() 

原始待預測打標圖像

通過局部線性逼近得到的top預測類(tabby)分界面(核心特征區)

with the rest of the image present

pros and cons' (pros in green, cons in red)

通過這個例子,我們可以更加深刻的認識到,卷積神經網絡是如何通過選取捕獲像素圖中特定區域,實現目標檢測與目標識別任務的。

0x4:Recurrent neural networks可解釋性可視化探索 

# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from keras.models import Sequential
from keras.layers import LSTM, Dropout, Dense
from keras.optimizers import Adam
from keras.utils import to_categorical
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import classification_report
from lime import lime_tabular


def reshape_data(seq, n_timesteps):
    N = len(seq) - n_timesteps - 1
    nf = seq.shape[1]
    if N <= 0:
        raise ValueError('I need more data!')
    new_seq = np.zeros((N, n_timesteps, nf))
    for i in range(N):
        new_seq[i, :, :] = seq[i:i+n_timesteps]
    return new_seq


if __name__ == '__main__':
    # We will use the CO2 dataset, which measures the concentration of CO2 above Mauna Loa every week since about 1960.
    # The classification task will be deciding if the concentration is rising,
    # this is a problem that needs recurrency to solve (since the answer comes from the derivative),
    # and is less trivial than it sounds because there is noise in the data.
    df = pd.read_csv('data/co2_data.csv', index_col=0, parse_dates=True)

    fig, (left, right) = plt.subplots(nrows=1, ncols=2, figsize=(13, 5))
    df[['co2']].plot(ax=left)
    df[['co2_detrended']].plot(ax=right)
    fig.show()

    # Reshaping the dataset to be appropriate for the model
    N_TIMESTEPS = 12  # Use 1 year of lookback
    data_columns = ['co2', 'co2_detrended']
    target_columns = ['rising']

    scaler = MinMaxScaler(feature_range=(-1, 1))
    X_original = scaler.fit_transform(df[data_columns].values)
    X = reshape_data(X_original, n_timesteps=N_TIMESTEPS)
    y = to_categorical((df[target_columns].values[N_TIMESTEPS:-1]).astype(int))

    # Train on the first 2000, and test on the last 276 samples
    X_train = X[:2000]
    y_train = y[:2000]
    X_test = X[2000:]
    y_test = y[2000:]
    print(X.shape, y.shape)

    # Define the model
    model = Sequential()
    model.add(LSTM(32, input_shape=(N_TIMESTEPS, len(data_columns))))
    model.add(Dropout(0.2))
    model.add(Dense(2, activation='softmax'))

    optimizer = Adam(lr=1e-4)
    model.compile(loss='binary_crossentropy', optimizer=optimizer)
    # train the model
    model.fit(X_train, y_train, batch_size=100, epochs=100,
              validation_data=(X_test, y_test),
              verbose=2)

    y_pred = np.argmax(model.predict(X_test), axis=1)
    y_true = np.argmax(y_test, axis=1)
    print(classification_report(y_true, y_pred))

    plt.plot(y_true, lw=3, alpha=0.3, label='Truth')
    plt.plot(y_pred, '--', label='Predictions')
    plt.legend(loc='best')
    plt.show()

    # Explain the model with LIME
    explainer = lime_tabular.RecurrentTabularExplainer(X_train, training_labels=y_train, feature_names=data_columns,
                                                       discretize_continuous=True,
                                                       class_names=['Falling', 'Rising'],
                                                       discretizer='decile')
    exp = explainer.explain_instance(X_test[50], model.predict, num_features=10, labels=(1,))
    print exp
    exp.show_in_notebook()

We can see that the most important features are the de-trended CO2 concentration several timesteps in the past. In particular, we see that if that feature is low in the recent past, then the concentration is now probably rising.

0x5:Webshell Random Foreast模型可解釋性可視化探索 

1. 加載webshell黑白樣本

100個黑/300個白

2. TF_IDF特征工程,隨機森林訓練

TF_IDF詞素似然概率

測試集預測結果

3. LIME局部特征逼近

LIME中各個子線性模型的似然概率占比

可以看到,對於這個判黑樣本來說,主要是'eval'、'base64_decode'、'gzinflate'這些關鍵詞起到了似然概率貢獻作用,這也和我們的安全領域先驗知識是吻合的。

同時,為了進一步理解線性基元對目標函數的局部線性近似逼近,我們手動disable掉2個top似然概率的基元函數,並觀察目標函數的預測結果,

去掉eval和base64_decode之后,目標函數的預測概率值等於這2個函數各自的基元函數的可解釋似然概率

這個實驗結果,證實了LIME基於隨機擾動負反饋的局部線性逼近的有效性,LIME的局部線性基元函數可以較好的代表目標函數的總體特征,局部匯總=總體,即1+1+1=3。

反過來,這種可解釋性也為webshell領域里的文本畸形變化對抗提供了理論依據,在文本詞維度的擾動可以干擾文本詞維度的檢測機制。作為防御方,要對抗這種對抗,就需要將模型抽象維度拉升到更高的維度,例如apicall、opcode、匯編代碼層等。

4. 可視化LIME可解釋模型

LIME基元函數中對判黑和判白的各自似然概率占比

Relevant Link:   

https://github.com/marcotcr/lime/blob/master/doc/notebooks/Tutorial%20-%20Image%20Classification%20Keras.ipynb  
https://arxiv.org/pdf/1602.04938.pdf

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM