[ML筆記]Adeline與梯度下降

Adaline 認知器

在上一篇中,我們知道 Rossenblatt 感知器的激勵函數是:

\begin{equation} \phi (z) = \phi (\textbf{w}^\mathrm{T} \cdot \textbf{x}) = \left\{ \begin{array}{ll} +1 & {z \geq \theta}\\ -1 & \textrm{otherwise}\\ \end{array} \right. \end{equation}

而這是 Adaline 認知器中的激勵函數:

\begin{equation} \phi (z) = \phi (\textbf{w}^\mathrm{T} \cdot \textbf{x}) = \textbf{w}^\mathrm{T} \cdot \textbf{x}=w_{1}x_{1}+w_{2}x_{2}+ \dots +w_{n}x_{n} \end{equation}

差別在於 Adaline 的激勵函數是一個連續的線性激勵函數(不像 Rossenblatt 是跳躍性的,非 $+1$ 就是 $-1$),我們也可說 Adaline 的激勵函數就示意整個淨輸入。另外 Adeline 演算法會在淨輸入到激勵函數後,把結果引入一個量化器(quantizer),對類別判斷之後再輸出,這是 Rossenblatt 感知器所沒有的。

梯度下降(Gradient Descent , GD)

數學推導

在統計學中,我們知道使用「誤差平方和」(SSE,Sum of Square Error),也就是下面這個式子來估算預估值和實際值之間的誤差是多少,其中的 $y_{i}$ 是實際值,而 $\hat{y_{i}}$ 則是預估值:

\begin{equation} \textrm{SEE(Sum of Square Error)} = \sum_{i}(y_{i}-\hat{y_{i}})^2 \end{equation}

而在 Adaline 裡,嘗試定義一個 $J(\textbf{w})$ 作為代價函數(Loss Function,也可以叫他損失函數)來評估學習後的預估值和實際值的誤差有多大。其實和 SSE 很像,只是為了把微分後把平方掉下來的 $2$ 給約掉,在式子前面加了常數 $12$ 。

\begin{equation} J(\textbf{w})=\frac{1}{2}\sum_{i}(y-\phi(z))^2 \end{equation}

透過梯度下降,我們基於代價函數 $J(\textbf{w})$ 延梯度方向 $\nabla J(\textbf{w})$ (微積分複習:梯度方向就是最陡的方向)把權重更新一次:

\begin{equation} \textbf{w}:=\textbf{w}+\Delta\textbf{w} \end{equation}

這個變動的權重是負梯度 $-\nabla J(\textbf{w})$ 乘上學習速率 $\eta$ ,因為和梯度相反的方向是函數下降最快的方向:

\begin{equation} \Delta\textbf{w}=-\eta\nabla J(\textbf{w}) \end{equation}

而這個梯度,其實就是對 $\textbf{w}$ 裡的所有座標偏微分(複習微積分葛),在這裡,我們是在算出所有的樣本累積誤差之後做加總($\sum$)再更新權重

其中的

\begin{equation} \begin{split} \frac{\partial J}{\partial w_{i}} &= \frac{\partial}{\partial w_{i}}\lgroup\frac{1}{2}\sum_{i}(y-\phi(z))^2\rgroup\\\\ &=\frac{1}{2}\frac{\partial}{\partial w_{i}}\sum_{i}(y-\phi(z))^2\\\\ &=\frac{1}{2}\sum_{i}2(y-\phi(z))\frac{\partial}{\partial w_{i}}(y-\phi(z))\\\\ &=\frac{1}{2}\sum_{i}2(y-\phi(z))\frac{\partial}{\partial w_{i}}(y-\phi(\textbf{w}^\mathrm{T} \cdot \textbf{x}))\\\\ &=\frac{1}{2}\sum_{i}2(y-\phi(z))\frac{\partial}{\partial w_{i}}(y-\phi(w_{1}x_{1}+w_{2}x_{2}+\dots+w_{n}x_{n}))\\\\ &=\frac{1}{2}\sum_{i}2(y-\phi(z))\frac{\partial}{\partial w_{i}}(y-(w_{1}x_{1}+w_{2}x_{2}+\dots+w_{n}x_{n}))\\\\ &=\sum_{i}(y-\phi(z))(-x_{i})\\\\ &=-\sum_{i}(y-\phi(z))x_{i} \end{split} \end{equation}

雛型

class AdalineGD(object):
    def __init__(self, eta = 0.01, n_iteration = 50):
        self.eta = eta
        self.n_iteration = n_iteration

    def fit(self, X, y):
       # 這裡的 X(矩陣)跟 y(清單)所蘊含的意義跟上次的 Rossenblatt 感知器一樣
        self.weight_ = np.zeros(1 + X.shape[1])
        self.cost_ = [] # 用來儲存代價函數(J(w))的輸出值,檢查是否收斂
        for i in range(self.n_iteration):
            net_input = self.net_input(X)
            output = self.activation(X)
            errors = (y - output)
            self.weight_[1:] = self.weight_[1:] + self.eta * X.T.dot(errors)
            self.weight_[0] = self.weight_[0] + self.eta * errors.sum() # 權重的更新
            cost = (errors**2).sum() / 2.0 # 就是J(w)
            self.cost_.append(cost)
        return self

    def net_input(self, X):
        # 淨輸入就是兩者內積
        return np.dot(X, self.weight_[1:]) + self.weight_[0]

    def activation(self, X):
        # Adaline 的激勵函數就是淨輸入
        return self.net_input(X)

    def predict(self, X):
        # 預測分類
        return np.where(self.activation(X) >= 0.0, 1, -1)

決定學習速率 η

考量何者的分類效率比較好,我們用 SSE(Sum of Square Error) 來比較。

fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))

# η = 0.01 的時候
ada1 = AdalineGD(n_iter=10, eta=0.01).fit(X, y)
ax[0].plot(range(1, len(ada1.cost_) + 1), np.log10(ada1.cost_), marker='o')
ax[0].set_xlabel('迭代次數')
ax[0].set_ylabel('log(SSE)')
ax[0].set_title('Adaline(η = 0.01)')

# η = 0.0001 的時候
ada2 = AdalineGD(n_iter=10, eta=0.0001).fit(X, y)
ax[1].plot(range(1, len(ada2.cost_) + 1), np.log(ada2.cost_), marker='o')
ax[1].set_xlabel('迭代次數')
ax[1].set_ylabel('log(SSE)')
ax[1].set_title('Adaline(η = 0.0001)')

plt.show()

會發現學習速率愈快的不一定會收斂到一個極限,反而在嘗試梯度「下降」的過程中跳過了局部最佳解,導致誤差愈來愈大。

標準化(Normalization)

讓資料呈現常態分佈(Normal Distribution)是一個很常用的方法,在對每一筆資料做標準化後,我們會得到平均值為 $0$,標準差為 $1$ 。標準化的公式是:

\begin{equation} z_{i} = \frac{x_{i}-\mu}{\sigma} \end{equation}

其中 $x_{i}$ 是原始資料,$\mu$ 是這比原始資料的平均值,$\sigma$ 是原始資料的標準差,而$z_{i}$ 則是標準化後的結果。

雛型

在 Numpy 套件裡,可以直接呼叫 std() 和 mean() 函式來處理標準化這件事情。

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()

這樣一來,就把原始資料的 X 矩陣標準化成為 X_std 了。接著,再以 $\eta=0.01$ 對 Adaline 進行訓練。

ada = AdalineGD(n_iter = 15, eta = 0.01)
ada.fit(X_std, y)

plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline(梯度下降)')
plt.xlabel('標準化後的花萼長度')
plt.ylabel('標準化後的花瓣長度')
plt.legend(loc='upper left')
plt.tight_layout()

plt.show()

和之前用 Rossenblatt 感知器分類的結果相比,我們會發現樣本數比較集中了,從鬆散的兩陀變得比較緊密的兩陀了。

而 SSE 呢?

plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Sum-squared-error')

plt.tight_layout()
# plt.savefig('images/02_14_2.png', dpi=300)
plt.show()

恩恩,看起來正常多惹。

隨機梯度下降(Stochastic Gradient Descent , SGD)

還在努力搞懂