2017年2月14日 星期二

機器學習(3)--適應線性神經元與梯度下降法(Adaline neuron and Gradient descent)

        這一節要介紹另一種類型的單層神經網路:適應線性神經元(Adaline)。並以梯度下降法
(Gradient descent)最小化成本函數。適應線性神經元(Adaline)在實務上是非常吸引人的,因為它清楚的說明了如何定義"成本函數",以及如何最小化"成本函數"的觀念,奠定了機器學習的理論基礎。在本文中主要還要介紹幾個機器學習或是深度學習裡都會出現的名詞,包括梯度下降法(Gradient descent)批次(batch)學習速率啟動函數(activation function)、以及偏誤(bias),在文末會以Python實作適應線性神經元(Adaline)。


     適應線性神經元(Adaline)可以算是Rosenblatt感知器改進方法,Adaline規則(也稱Widrow-Hoff規則)與Rosenblatt感知器之間最主要的差異在於,權重更新是基於"線性啟動函數"(Linear activation function),不像感知器是使用"單位階梯函數"(unit step function),在Adaline規則中它的線性啟動函數Φ(z)就是淨輸入,因此
並使用單位階梯函數量化器(quantizer)來預測類別標籤。可看以下圖示:
比較下圖差異,Adaline使用連續值的線性啟動函數來計算誤差及更新權重,而不是用它來預測二元類別標籤。

<圖一>適應線性神經元(Adaline)




<圖二>Rosenblatt感知器(Perceptron)



數學模型

適應線性神經元(Adaline)的淨輸入數學模式與Rosenblatt感知器(Perceptron)的數學模式類似,
在Rosenblatt感知器裡,首先我們定義輸入一組x值,及相對應的加權向量w,那淨輸入z:


那麼如果特定樣本xi的啟動函數Φ(z)所計算出來的值,大於一個事先定義的門檻值θ的話,我們就預測該樣本為正類(1),否則就預測為負類(-1)。在感知器演算法裡,啟動函數Φ()為一
"單位階梯函數"(unit step function)。如下:

為了方便程式,我們可以把門檻值θ移到等式左側,新增加一個加權w0=-θ,並定義x0=1(有些寫法會定義w0=θ,而x0=-1),這樣淨輸入z可以重新定義為:

啟動函數Φ()便可以重新定義為:


在此w0x0=--θ也可以說成是偏誤項(bias)。故上述淨輸入方程式就是等同以下較為常見的方
程式。
這也等同於一個線性方程式:Y=WX+b,我們之所以訓練神經元網路,所要找出最佳w權
重值,就是為了找出這一線性分割線,w即為該線斜率,而偏誤項(bias)b為線性方程式的
截距,其意義可以看成該分類線距離某分類項(如下圖正類或負類別)的距離,較遠即有較大的偏誤,較近即有較小的偏誤,但是過小的偏誤有可能產生過度適合(overfitting),而較大的偏誤則可能產生低度適合(underfitting)。有關過度適合(overfitting)低度適合(underfitting)之後會在別的文章討論。
     下圖說明了,淨輸入z是如何被啟動函數(左圖)壓縮成二元輸出(-1 or 1),而右圖則是說明淨輸入如何被二元分成兩類。



而在Adaline規則中與Rosenblatt感知器不一樣的地方,它的線性啟動函數Φ(z)就是淨輸入:

可想成該線性啟動函數Φ(z) 就如同Y=X 線性方程式,故它是通過原點的一條斜直線。
在此我們也可以用sigmoid 或tanH函數來取代,sigmoid 或tanH函數圖形上會是一條S型的曲線,該函數必須要可微分,因為接下來我們會定義成本函數(cost function)也有人說是loss function並求其梯度。

        而啟動函數Φ(z)實質的意義就是會把淨輸入的值轉換對應到該啟動函數的線上。例如Rosenblatt感知器使用的單位階梯函數,會把淨輸入z直接分成整數值1或-1。
        而在Adaline線性啟動函數該線上的值即是淨輸入z,它會是一實數,而且端看輸入的數值,有可會很大,超過1.0,故在Adaline會再使用二元分類的量化器分成正類及負類。整個計算流程像極Rosenblatt感知器,但是實質意義上不太一樣。
         而如果用sigmoid 函數,淨輸入的值Z會被轉成最大靠近1.0最小靠近0.0。
使用tanH函數淨輸入的值Z會被轉成最大靠近1.0最小靠近-1.0。
所以如果使用sigmoid 函數越靠近1.0的,我們可以把它分成正類,靠近0.0的分成另一類。tanH函數亦同。

這也是我們為什麼要使用可以微分的啟動函數,因為當把淨輸入的值轉換對應到該啟動函數的線上後,就可以使用微分求導求梯度的數學技巧來求最佳化。如下梯度下降法(Gradient descent)

以遞減梯度法最小化成本函數

      監督式學習演算法的一個關鍵要素是定義"目標函數",這個函數在處裡過程中被最佳化
,通常目標函數就是我們希望得到最小值的成本函數(cost function),在Adaline之下,我們可以將"運算結果"與"真實類別標籤"之間的誤差平方和定義為成本函數(cost function):
定義係數1/2只是為了方便導出梯度,也就是微分之後會被消去。Adaline使用的連續的線性啟動函數優點在於式可微的,而感知器所使用的單位階梯函數不行,另外該成本函數為一凹向上的函數,因此我們可以使用梯度下降法(Gradient descent),來找出成本函數最小的加權值。
如下圖所示:
        梯度下降法(Gradient descent) 利用爬下山的方式,找出區域最小值或是全域最小值,而在每一次的迭代中,由高處逐漸往下,步長則是由梯度斜率與學習速率η所決定

<圖三>梯度下降法(Gradient descent)


利用梯度下降法,我們可以一步步的使用成本函數J(w)中的梯度ΔJ(w)來更新加權。
這裡的加權變化Δw被定義為負梯度乘上學習速率η:
為了計算成本函數J(w)中的梯度ΔJ(w),需要計算成本函數J(w)相對於加權wj的偏導數:
j為某一w加權index。



<圖四>成本函數J(w)相對於加權wj的偏導數推導,底下E(w)即是J(w)




Adaline跟感知器得演算規則幾乎一樣,但是他們之間還是有一些差異,例如Adaline的淨輸入Φ(z)為一實數而非感知器的整數類別標籤(1,-1),另外最重要的是加權更新是基於訓練數據中所有樣本計算出來的,而不是針對每個樣本就做它的加權更新。這種方式也被稱為批次(batch)梯度下降法。

學習速率的實質意義:較小的學習速率訓練所需的迭代次數會較多花較多時間,但是過大的學習速率,則有可能衝全域最小值使得系統無法收斂,如下圖所示:




Python實作適應線性神經元(Adaline)

底下我們用Python來實作適應線性神經元(Adaline)與梯度下降法。我們將使用機器學習裡常使用的鳶尾花數據集,可從UCI Machine Learning Repository網站下載,該網站還有其他很多不同的數據集可以善加利用。鳶尾花數據集:https://archive.ics.uci.edu/ml/datasets/Iris

鳶尾花數據集共有150筆個樣本數,有三個類別標籤Iris-setosa、Iris-versicolor、Iris-virginica。
每一筆數據也就是每一類別有4個特徵值,Sepal length、Sepal width、Petal length、Petal width。在此範例我們將只擷取前100筆數據兩個類別標籤Iris-setosa、Iris-versicolor,做二元分類。每一類別各有50筆數據,並且只取Sepal length(花萼長)、Petal length(花瓣長)特徵當輸入。

import pandas as pd
df = pd.read_csv('iris.data', header=None)

利用pandas 讀進iris.data如下,行0及行2即為Sepal length(花萼長)、Petal length(花瓣長)特徵,
而行4即為類別標籤Iris-setosa、Iris-versicolor。


劃出散布圖視覺化資料,底下可以看出這是可線性分割,接著我們就用適應線性神經元(Adaline)來找出這一線性分割線。




下圖即為使用適應線性神經元(Adaline)與梯度下降法分類的結果,上圖為每一輪迭代成本下降的結果,而下圖即為正確的決策分布區域圖。



<完整範例程式:>

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import ListedColormap

#劃出分布圖及分類結果+
def plot_decision_regions(X, y, classifier, resolution=0.02):

 # setup marker generator and color map
 markers = ('s', 'x', 'o', '^', 'v')
 colors = ('red', 'green', 'lightgreen', 'gray', 'cyan')
 #np.unique =>Find the unique elements of an array
 cmap = ListedColormap(colors[:len(np.unique(y))])
 #len(np.unique(y))=2
 # plot the decision surface
 x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 #feature 1
 x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 #feature 2
 
 xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
         np.arange(x2_min, x2_max, resolution))
 Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
 Z = Z.reshape(xx1.shape)

 plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
 plt.xlim(xx1.min(), xx1.max())
 plt.ylim(xx2.min(), xx2.max())

 # plot class samples
 for idx, cl in enumerate(np.unique(y)):
  #idx=0,1  ;cl=-1,1
  print("X[y == cl, 0]=",X[y == cl, 0])
  print("X[y == cl, 1]=",X[y == cl, 1])
  plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1],
     alpha=0.8, c=cmap(idx),
     marker=markers[idx], label=cl)
#劃出分布圖及分類結果-

#適應線性神經元(Adaline)與梯度下降法+
class AdalineGD(object):
    """ADAptive LInear NEuron classifier.

    Parameters
    ------------
    eta : float
        Learning rate (between 0.0 and 1.0)
    n_iter : int
        Passes over the training dataset.

    Attributes
    -----------
    w_ : 1d-array
        Weights after fitting.
    cost_ : list
        Sum-of-squares cost function value in each epoch.

    """
    def __init__(self, eta=0.01, n_iter=50):
        self.eta = eta
        self.n_iter = n_iter

    def fit(self, X, y):
        """ Fit training data.

        Parameters
        ----------
        X : {array-like}, shape = [n_samples, n_features]
            Training vectors, where n_samples is the number of samples and
            n_features is the number of features.
        y : array-like, shape = [n_samples]
            Target values.

        Returns
        -------
        self : object

        """
        self.w_ = np.zeros(1 + X.shape[1])
        self.cost_ = []

        for i in range(self.n_iter):
            net_input = self.net_input(X)
            # Please note that the "activation" method has no effect
            # in the code since it is simply an identity function. We
            # could write `output = self.net_input(X)` directly instead.
            # The purpose of the activation is more conceptual, i.e.,  
            # in the case of logistic regression, we could change it to
            # a sigmoid function to implement a logistic regression classifier.
            output = self.activation(X)
            errors = (y - output)
            self.w_[1:] += self.eta * X.T.dot(errors)
            self.w_[0] += self.eta * errors.sum()
            cost = (errors**2).sum() / 2.0
            self.cost_.append(cost)
        return self

    def net_input(self, X):
        """Calculate net input"""
        return np.dot(X, self.w_[1:]) + self.w_[0]

    def activation(self, X):
        """Compute linear activation"""
        return self.net_input(X)

    def predict(self, X):
        """Return class label after unit step"""
        return np.where(self.activation(X) >= 0.0, 1, -1)
#-適應線性神經元(Adaline)與梯度下降法-

#使用pandas讀進iris.data' 格式為CSV+
df = pd.read_csv('iris.data', header=None)
#擷取出類別標籤
y = df.iloc[0:100, 4].values
#將類別標籤定義為-1及1兩類
y = np.where(y == 'Iris-setosa', -1, 1)
#擷取出花萼長sepal length and 及花瓣petal length
X = df.iloc[0:100, [0, 2]].values
#使用pandas讀進iris.data' 格式為CSV-
#標準化資料+
X_std = np.copy(X)
X_std[:, 0] = (X[:, 0] - X[:, 0].mean()) / X[:, 0].std()
X_std[:, 1] = (X[:, 1] - X[:, 1].mean()) / X[:, 1].std() 
#標準化資料-

#畫出每一輪的成本下降圖
plt.subplot(211)
#使用AdalineGD作分類器,學習速率eta=0.01,迭代最大為15輪,n_iter=15
ada = AdalineGD(n_iter=15, eta=0.01)
ada.fit(X_std, y)
plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Sum-squared-error')
plt.tight_layout()

#畫出決策區域圖
plt.subplot(212)
plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline - Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')
plt.tight_layout()
plt.show()




<參考資料>書名:Python機器學習,作者:Sebastian Raschka

加入阿布拉機的3D列印與機器人的FB專頁
https://www.facebook.com/arbu00/


<其他相關文章>
人工神經網路(1)--使用Python實作perceptron(感知器)
人工神經網路(2)--使用Python實作後向傳遞神經網路演算法(Backprogation artificial neature network)
深度學習(1)-如何在windows安裝Theano +Keras +Tensorflow並使用GPU加速訓練神經網路
機器學習(1)--使用OPENCV KNN實作手寫辨識
機器學習(2)--使用OPENCV SVM實作手寫辨識
演算法(1)--蒙地卡羅法求圓周率及橢圓面積(Monte carlo)