1. 程式人生 > >NNs(Neural Networks,神經網路)和Polynomial Regression(多項式迴歸)等價性之思考,以及深度模型可解釋性原理研究與案例

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
InputOutput
AB
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%20Classifica