Neural Network

1.PLA

重新回顧一下一開始學的PLA,preceptron learning Algorithm。PLA適用於二維及高維的線性可分的情況,如果是非線性可分的數據,如果使用PLA可能會無限循環。問題的答案只有同意或不同意:


10624272-cb9ea5eab2271df3.png

PLA要做的其實就找到一個x映射到y的f,使得這個f和數據的分佈一致,判斷結果一致。
如果是遇到了線性不可分的情況,就不能再要求完全正確了,所以在optimization的時候會要求只需要找到最少錯誤的分類就可以了,不再要求完全正確的分類。

2.Combine some PLA

把PLA按照aggregation model的想法結合起來:

10624272-6d0a49db831454c0.png

左邊是x = {X1, X2, X3, X4......},和右邊的w = {W1, W2, W3......}做內積之後就得到了n個值,就相當於PLA裏面WX的部分了。把得到WX的稱爲是g(x),最後再乘上α,其實就是把g(x)做了一個非線性的組合,也就是aggregation的blending模型。
10624272-7842c146aa974aa8.png
上圖實現的是g1,g2的and。如何用感知機模型實現and邏輯?一種方法是:
10624272-4f268d0f56512606.png

10624272-79698bcbf0d56158.png

g1,g2 = {-1, +1};g1 = -1, g2 = -1,那麼結果是就是-3,g1 = -1, g2 = +1, 結果就是-1,如果g1 = +1, g2 = +1,那麼結果就是還是正的,符合預期。
10624272-f1ee2716c04660d7.png

這個例子簡單說明了在經過一定的線性組合的情況下是可以做到非線性的分類的。除此之外,OR,XOR等等都是可以通過g(x)的線性組合得到的。
所以說,Neural Network是一種很powerful同上也是complicated的模型,另外,當hidden層神經元數量大的時候計算量會非常大。比如下面的一個例子,有一個圓形區域,裏面的+!外面是-1,這種是沒有辦法使用一個PLA切分開的,只能使用多個PLA了,如果是8個PLA的時候,大概是可以組成一個圓形,16個PLA的時候就要更接近了,因此,使用的PLA越多,得到的結果就會和數據的分佈越擬合:
10624272-0abeebdda0b9e6ba.png

之前說過,凸多邊形的VC dimension是無限大的,2的n次方,所以隨着PLA數量的增長vc 維是沒有限制的,容易造成過擬合。但是,數目較多總是可以得到一個更加平滑更加擬合的曲線,這也是aggregation model的特點。
但是,還是有單層preceptron線性組合做不到的事情,比如XOR操作:
10624272-2772bf150d105724.png

因爲XOR得到的是非線性可分的區域,如下圖所示,沒有辦法由g1和g2線性組合實現。所以說linear aggregation of perceptrons模型的複雜度還是有限制的。事實上,一層的perceptron其實就是一次transform,那麼既然一次transform不行,我們嘗試一下多層的perceptron,多次的transform。把XOR拆分開來,第一次是使用AND,第二次就是使用OR,如下:
10624272-6cc8605613c2ecfd.png

這樣就從簡單的單層aggregation提升到了multi-layer的多層感知機,模型的複雜度增加之後就更能解決一些非線性的問題了。 第一層的wx處理後再接到下一層wx處理。這其實就是有點接近神經網絡的模型了。

3.Neural Network

之前已經介紹過三種線性模型:linear classification,linear regression,logistic regression。那麼,對於OUTPUT層的分數s,根據具體問題,可以選擇最合適的線性模型。如果是binary classification問題,可以選擇linear classification模型;如果是linear regression問題,可以選擇linear regression模型;如果是soft classification問題,則可以選擇logistic regression模型。
如果根據上面的模型,每一層都是wx,那麼無論多少層疊加起來都是www....x而已,都是線性的,所以我們要在每一層結束的時候加上一個activity function。對於激活函數的選擇有很多,sigmoid,relu,tanh等。sigmoid函數由於會出現梯度消失的現象,所以現在已經很少用了,relu函數是比較常用的。這裏會使用tanh函數:

10624272-6762b2ab233a380f.png

tanh是一個平滑函數,當|s|比較大的時候近似於階梯函數,|s|比較小的時候會接近於線性函數。處處連續而且可導,計算也比較方便,而且求導的時候可以用上自身的值:
10624272-cb846a0a42eb51f0.png

反向傳播計算的時候是可以用上之前計算過的緩存下來的值,如果在神經元數量特別多的情況下是可以減少計算複雜度的。
10624272-a2730a2d29aa77a3.png

那麼下圖更新之後的Neural Network:
10624272-16461cd33236cbd6.png

10624272-731a14c2b137f7f7.png
指的就是第幾層,再看一下權值w:
10624272-bb9a52f6ecf7d96b.png
l表示第幾層,ij表示前一層輸出個數加上當前的項。那麼對於每一層的分數:
10624272-6262bbd3346f904e.png

再經過一層激活函數:
10624272-20e417d80568ba41.png

每一層的輸出就是這樣了。
10624272-55cd60787672636a.png

上圖中的每一層神經網絡其實就是一種transform的過程,而每一層轉換的關鍵就在於權值w,每一層網絡利用輸入x和權值w的乘積,在經過tanh函數之後,將得到改函數的輸出,從左到右,一層一層的進行,如果乘積得到的分數越大,那麼tanh(wx)就越接近於1了,表明擬合出來的分佈越接近於書記分佈。所以每一層的輸入x和權重w具有模式上的相似性,比較接近平行,那麼transform的效果就比較好。所以,神經網絡的核心就是pattern extraction,從數據本身開始查找蘊含的規律,通過一層一層的找到之後再擬合結果。所以,神經網絡是生成模型。
根據error function,我們只要計算Ein = (y - score)^2就可利用知道這單個樣本點的error了,所以只需要建立Ein與每一個權值之間的關係就可以了,最直接的方法就是使用Gradient Descent了,不斷優化w的值。
10624272-9aed69dd396c1f2d.png

先來看一下如何計算他們之間的關係:
10624272-a304cab32609bea0.png

我們的對象是w,自然就是對w求偏導:
10624272-9d5ad1f39e6a48cd.png

這是輸出層的偏導結果,也就是delta_output。
對於其他層,根據鏈式法則:
10624272-6fd5ebe09f6cc1fa.png

我們令第j層的神經元的偏導數即爲
10624272-44f069462e86ae84.png

10624272-f6f0a6e54dc37c64.png

後面的推導其實都很簡單了:
10624272-02817730837cecab.png

依照這種往上遞推的方式,可以把每一層的分數梯度算出來。
10624272-31fb82a4557449f6.png

上面採用的是SGD的方法,即每次迭代更新時只取一個點,這種做法一般不夠穩定。所以通常會採用mini-batch的方法,即每次選取一些數據,例如N/10,來進行訓練,最後求平均值更新權重w。這種做法的實際效果會比較好一些。

4.Optimization and Regularization

最終的目標還是要Ein最小,採用什麼error function只需要在推導上修正一下就好了。

10624272-71b094ede5bbddac.png

層數越多,說明模型的複雜度就會越大,很明顯,這樣得到的model肯定不是convex的,可能有很多的山峯,可能只是走到了其中一個而已,並沒有到最低的。解決這種情況首先就是對w權值就行隨機選擇,通過random從普遍概率上選擇初始值,避免了人爲干擾的因素,有更大的可能走到全局的最優。
10624272-c2b79a4784649a5f.png

從理論上看神經網絡,dvc = O(VD)。其中,V是神經網絡裏面神經元的個數,D表示所有權值的數量,所有如果V很大,那麼複雜度就會越大,很有可能會overfit,所以可以通過限制模型複雜度來防止過擬合。
防止overfit還有一個方法,就是regularization,L1或L2正則化。但是使用L2正則化有一個缺點,大的權值就減小很大,小權值基本不怎麼變,複雜度事實上還是沒有變。而L1正則化是可以使得權值稀疏,但是在某些地方是不可以微分的,使用也不適合。使用我們需要一個可以使得大權值減小很大,小權值減小到0的一個函數:
10624272-e0576b4dbd80c71f.png

10624272-8ee2565d2e8e8b4e.png

除此之外,訓練次數減少也是可以達到限制過擬合效果的。因爲訓練時間越長,對於尋找可能性就會越多,VC dimension就會越大,當t不大的時候是可以減低模型複雜度的。
10624272-83a83c72c16ca761.png

5.代碼實現

首先是生成數據,使用的是sklearn的make_moon:

def generator():
    np.random.seed(0)
    x, y = dataTool.make_moons(200, noise=0.2)
    plt.scatter(x[:, 0], x[:, 1], s = 40, c = y, cmap=plt.cm.Spectral)
    plt.show()
    return x, y
    pass

10624272-cf7174086a2a0f39.png

這是我們生成數據的分佈,可以看到線性分類器是無法區分開的。
然後就是網絡生成訓練的主要部分了:

class Network(object):
    def __init__(self, x, y):
        '''initialize the data'''
        self.x = x
        self.num_examples = len(x)
        self.y = y
        self.n_output = 2
        self.n_input = 2
        self.epsilon = 0.01
        self.reg_lambed = 0.01
        self.model = None
        pass

初始化數據。

    def calculate_loss(self, model):
        '''calculate the loss function'''
        w1, b1, w2, b2 = model['w1'], model['b1'], model['w2'], model['b2']
        z1 = self.x.dot(w1) + b1
        a1 = np.tanh(z1)
        z2 = a1.dot(w2) + b2
        exp_scores = np.exp(z2)
        probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
        corect_logprobs = -np.log(probs[range(self.num_examples), self.y])
        data_loss = np.sum(corect_logprobs)
        data_loss += self.reg_lambed / 2 * (np.sum(np.square(w1)) + np.sum(np.square(w2)))
        return 1.0/self.num_examples * data_loss

計算損失函數,最後使用的是softmax分類器,損失函數使用的是交叉熵:


10624272-36dc84f8297dea9c.png

在加上regularization即可。

    def predict(self, x):
        '''according to the model,predict the consequence'''
        w1, b1, w2, b2 = self.model['w1'], self.model['b1'], self.model['w2'], self.model['b2']
        z1 = x.dot(w1) + b1
        a1 = np.tanh(z1)
        z2 = a1.dot(w2) + b2
        exp_scores = np.exp(z2)
        probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
        return np.argmax(probs, axis=1)
        pass

預測,常規操作。

    def build_Network(self, n_hinden, num_pass = 20000, print_loss = True):
        loss = []
        np.random.seed(0)
        w1 = np.random.randn(self.n_input, n_hinden) / np.sqrt(self.n_input)
        b1 = np.zeros((1, n_hinden))
        w2 = np.random.randn(n_hinden, self.n_output) / np.sqrt(n_hinden)
        b2 = np.zeros((1, self.n_output))

        self.model = {}
        for i in range(num_pass):
            z1 = self.x.dot(w1) + b1
            a1 = np.tanh(z1)
            z2 = a1.dot(w2) + b2
            exp_scores = np.exp(z2)
            probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

            delta3 = probs
            delta3[range(self.num_examples), self.y] -= 1
            dw2 = (a1.T).dot(delta3)
            db2 = np.sum(delta3, axis=0, keepdims=True)
            delta2 = delta3.dot(w2.T)*(1 - np.power(a1, 2))
            dw1 = np.dot(self.x.T, delta2)
            db1 = np.sum(delta2, axis=0)
            dw2 += self.reg_lambed * w2
            dw1 += self.reg_lambed * w1

            w1 += -self.epsilon * dw1
            b1 += -self.epsilon * db1
            w2 += -self.epsilon * dw2
            b2 += -self.epsilon * db2

            self.model = {'w1':w1, 'b1':b1, 'w2':w2, 'b2':b2}
            if print_loss and i %200 == 0:
                print('Loss : ', (i, self.calculate_loss(model=self.model)))
                loss.append(self.calculate_loss(model=self.model))
        return loss

有使用的是softmax損失函數,對於softmax函數求導之後就是原函數-1,求w2梯度,對w2求導就只有a2了,根據剛剛的公式:


10624272-7f37297cb3334dc0.png

可以求出delta2,最後一次更新即可。

if __name__ == '__main__':
    Accuracy = []
    losses = []
    x, y = Tool.generator()
    for i in range(1, 13):
        mlp = Network(x, y)
        loss = mlp.build_Network(n_hinden=i)
        losses.append(loss)
        Tool.plot_decision_boundary(mlp.predict, x, y, 'Neutral Network when Hidden layer size is ' + str(i))
        predicstions = mlp.predict(x)
        a = sum(1*(predicstions == y)) / len(y)
        Accuracy.append(a)

    '''draw the accuracy picture'''
    plt.plot(range(len(Accuracy)), Accuracy, c = 'blue')
    plt.title('The Accuracy of the Neural Network')
    plt.xlabel('Hinden Layer Size')
    plt.ylabel('Accuracy')
    plt.show()


    '''draw the loss function picture'''
    for i, loss in enumerate(losses):
        plt.plot(range(len(loss)), loss, c = Tool.get_colors(i), label = 'the hindden layer size '+str(i))
    plt.title('Loss Function')
    plt.xlabel('time')
    plt.ylabel('loss score')
    plt.legend()
    plt.show()

主要的運行函數。
還需要看一個畫圖函數:

def plot_decision_boundary(pred_func, X, y, title):
    # Set min and max values and give it some padding
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    h = 0.01
    # Generate a grid of points with distance h between them
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # Predict the function value for the whole gid
    Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    # Plot the contour and training examples
    plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)
    plt.title(title)
    plt.show()

接下來運行一下看效果吧(有些顏色的代碼不全,GitHub上有):


10624272-e8edcc5b31cb25f3.png

10624272-2775fdc830d761f8.png

10624272-1e9af754c8f466ce.png

10624272-40cfc3da7a25c5de.png

10624272-59b17595006ffe7e.png

10624272-e3c572e369d13db3.png

接下來的效果應該都是類似的了。
隨着神經元數量的增加:


10624272-ff166aa370169356.png

準確率是越來越高的。
10624272-ca6a5032825bbfec.png

1個神經元和2個神經元的時候明顯是欠擬合,後面的效果其實都變化不大。

符上所以GitHub代碼:
https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/NeuralNetwork

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章