pythonML学习笔记ch2-Adaline

一、自适应神经元与学习收敛

        本节将讨论另外一种单层神经网络:自适应线性神经元(Adaline)

        Adaline算法特别有趣,因为它说明了定义最小化连续性代价函数的关键概念。这为理解如逻辑回归、支持向量机和回归模型等更高级的机器学习算法奠定了基础。

        Adaline规则(也称为威德罗 - 霍夫规则)和弗兰克.罗森布拉特的感知器之间的关键差异在于Adaline规则的权重更新是基于线性激活函数,而感知器是基于单位阶跃函数。Adaline的线性激活函数\Phi (z)是净输入的等同函数,即:

                                                ​​​​​​​        ​​​​​​​        \Phi (w^Tx) = w^Tx

感知器与自适应算法的主要区别图示如下:

pythonML学习笔记ch2-Adaline_第1张图片 图1-ppn与Adaline的区别

       

         1、梯度下降为最小代价函数

        有监督机器学习算法的一个关键是在学习过程中优化目标函数。该目标函数通常是要最小化的代价函数。对自适应先行神经元Adaline而言,可以把学习权重的代价函数 定义为在计算结果和真正的分类标签之间的误差平方和(SSE):

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​J(w) = \frac{1}{2}\sum_i(y^{(i)} - \Phi (z^{(i)}))^2

        接下来进行梯度下降来寻找权重,从而在鸢尾花数据集样本分类过程中最大限度地减小代价函数。如下图所示,可以把梯度下降背后的主要逻辑描述为走下坡路直到局部或全局最小点为止。

pythonML学习笔记ch2-Adaline_第2张图片 图2 - 梯度下降示意

        每次迭代都向梯度相反的方向上迈出一步,步幅由学习率以及梯度下降代价函数的 J(w) 的梯度\nabla J(w)朝反方向上迈出一步来更新权重:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​                w: = w + \Delta w

        其中把权重变化\Delta w定义为负的梯度乘以学习率\eta

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \Delta w = - \eta \nabla J(w)

        要计算代价函数的梯度需要分别用每个权重w_j来计算代价函数的偏导数:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \frac{\partial J}{\partial w_j} = -\sum_i(y^{(i)} - \phi(z^{(i)}))x_i^{(i)}

         这样就可以把权重w_j更新写为:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \Delta w_j = - \eta\frac{\partial J}{\partial w_j} = \eta \sum_i(y^{(i)} - \phi(z^{(i)}))x_j^{(i)}

注意:权重更新是基于训练集中的所有样本计算,而不是在每个样本之后逐步更新权重,这也就是这种方法被称为批量梯度下降的原因

二、用python实现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.
    random_state : int
      Random number generator seed for random weight
      initialization.


    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, random_state=1):
        self.eta = eta
        self.n_iter = n_iter
        self.random_state = random_state

    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

        """
        rgen = np.random.RandomState(self.random_state)
        self.w_ = rgen.normal(loc=0.0, scale=0.01, size=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 (as we will see later), 
            # we could change it to
            # a sigmoid function to implement a logistic regression classifier.
            output = self.activation(net_input)
            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 X

    def predict(self, X):
        """Return class label after unit step"""
        return np.where(self.activation(self.net_input(X)) >= 0.0, 1, -1)

        在这里添加了子激活函数(通过调用activation方法计算)来说明信息是如何通过单层神经网络流动的:来自输入数据、净输入、激活和输出的特征。

        实践中,常常需要一些实验来找到一个好的学习率η以达到最优收敛,以下选择了η=0.1和η=0.0001两个不同的学习率,把代价函数与迭代次数在图中画出以观察Adaline如何实现从训练数据中学习。

        代码如下:

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

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('Epochs')
ax[0].set_ylabel('log(Sum-squared-error)')
ax[0].set_title('Adaline - Learning rate 0.01')

ada2 = AdalineGD(n_iter=10, eta=0.0001).fit(X, y)
ax[1].plot(range(1, len(ada2.cost_) + 1), ada2.cost_, marker='o')
ax[1].set_xlabel('Epochs')
ax[1].set_ylabel('Sum-squared-error')
ax[1].set_title('Adaline - Learning rate 0.0001')

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

        图示如下:

pythonML学习笔记ch2-Adaline_第3张图片 图3-不同学习率的误差迭代趋势图

         左图显示选择太大的学习率所出现的情况。因为选择的全局最小值过低,无法最小化代价函数,误差经过每次迭代变得越来越大。另一方面,可以看到代价在右图降低,但所选择的学习率η=0.0001是如此之小,以至于算法需要经过多次迭代才能收敛到全局最低代价

        下图说明如果改变某个特定权重参数值来最小化代价函数J会发生什么情况。作图显示选择一个好的学习率,代价会逐渐降低,向全局最小的方向发展。然而右图显示如果选择的学习率太大,就会错过全局的最小值。

pythonML学习笔记ch2-Adaline_第4张图片 图4 - 不同学习率的代价收敛图

         三、通过调整特征大小改善梯度下降

        本节用一种被称为标准化的特征尺度调整方法来加快收敛,它可以使数据具有标准正态分布的特性,有助于梯度下降学习。标准化可以改变每个特征的平均值以使其居中为零,而且每个特征的标准偏差为1。例如标准化第j个特征:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        x_j = \frac{x_j - \mu_j}{\sigma_j }

        标准化有助于梯度下降学习的原因之一是优化器必须遍历几个步骤才能发现好的或者最优的解决方案(全局最小值),如下图所示:

pythonML学习笔记ch2-Adaline_第5张图片 图5-标准化与不标准化的收敛难度对比

         接下来对鸢尾花数据集特征进行标准化,代码如下:

# standardize features
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()

        接着再次训练Adaline,在学习率η = 0.01的条件下,经过几轮迭代后观察其现在的收敛情况,代码如下:

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

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.savefig('images/02_14_1.png', dpi=300)
plt.show()

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

        执行代码后可以看到下图所示的决策区域图以及代价下降图:

pythonML学习笔记ch2-Adaline_第6张图片 图6-经过标准化后的决策区域图与代价下降图

 可以看到,Adaline在学习率η = 0.01的情况下,经过训练已经开始收敛。然后需要注意,误差平方和SSE保持非零,即使所有的样本都可以分类正确

四、大规模机器学习与随机梯度下降

        对于批量梯度下降来说,当数据量十分庞大时,计算代价会非常昂贵,因为每向全局最小值走一步都需要重新评估整个训练集,接下来我们用随机梯度下降法来代替批量梯度下降,该方法并不是基于所有样本x^{(i)}的累计误差之和来更新权重,而是为每个训练样本逐渐更新权重:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \eta (y^{(i)} - \phi(z^{(i)}))x^{(i)}

        注意:由于每个梯度都是基于单个训练实例计算出来的,误差表面比梯度下降更大。要通过随机梯度下降获得满意的结果,很重要的一点是将训练数据以随机的顺序呈现出来,同时要把训练集重新洗牌以防止迭代循环。

        在随机梯度下降实现过程中,固定的学习率η经常被逐步下降的适应性学习率所取代,例如:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \frac{c1}{iternum + c2}

其中iternum为迭代数,c1、c2为常数,要注意随机梯度下降没有到达全局的最小值,但是在一个非常靠近这个点的区域。用适应性学习率可以把代价降到最低。

        随机梯度下降的另外一个优点是可以用它来做在线学习,使用在线学习,模型可以在数据到达时实时完成训练。这对累计大量数据的情况特别有用,系统可以立即适应变化,而且在存储空间有限的情况下,可以在更新模型后丢弃训练数据。

        接下来对标准化过后的鸢尾花数据集进行在线学习

代码如下:

class AdalineSGD(object):
    """ADAptive LInear NEuron classifier.

    Parameters
    ------------
    eta : float
      Learning rate (between 0.0 and 1.0)
    n_iter : int
      Passes over the training dataset.
    # 对数据进行洗牌
    shuffle : bool (default: True)
      Shuffles training data every epoch if True to prevent cycles.
    random_state : int
      Random number generator seed for random weight
      initialization.


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

        
    """
    def __init__(self, eta=0.01, n_iter=10, shuffle=True, random_state=None):
        self.eta = eta
        self.n_iter = n_iter
        # 对权重进行初始化
        self.w_initialized = False
        self.shuffle = shuffle
        self.random_state = random_state
        
    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._initialize_weights(X.shape[1])
        self.cost_ = []
        for i in range(self.n_iter):
            if self.shuffle:
                X, y = self._shuffle(X, y)
            cost = []
            for xi, target in zip(X, y):
                cost.append(self._update_weights(xi, target))
            avg_cost = sum(cost) / len(y) # 计算平均代价
            self.cost_.append(avg_cost)
        return self

    def partial_fit(self, X, y):
        """Fit training data without reinitializing the weights"""
        if not self.w_initialized:
            self._initialize_weights(X.shape[1])
        if y.ravel().shape[0] > 1:
            for xi, target in zip(X, y):
                self._update_weights(xi, target)
        else:
            self._update_weights(X, y)
        return self

    def _shuffle(self, X, y):
        """Shuffle training data"""
        r = self.rgen.permutation(len(y))
        return X[r], y[r]
    
    def _initialize_weights(self, m):
        """Initialize weights to small random numbers"""
        self.rgen = np.random.RandomState(self.random_state)
        self.w_ = self.rgen.normal(loc=0.0, scale=0.01, size=1 + m)
        self.w_initialized = True
        
    def _update_weights(self, xi, target):
        """Apply Adaline learning rule to update the weights"""
        output = self.activation(self.net_input(xi))
        error = (target - output)
        self.w_[1:] += self.eta * xi.dot(error)
        self.w_[0] += self.eta * error
        cost = 0.5 * error**2
        return cost
    
    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 X

    def predict(self, X):
        """Return class label after unit step"""
        return np.where(self.activation(self.net_input(X)) >= 0.0, 1, -1)
ada = AdalineSGD(n_iter=15, eta=0.01, random_state=1)
ada.fit(X_std, y)

plot_decision_regions(X_std, y, classifier=ada)
plt.title('Adaline - Stochastic Gradient Descent')
plt.xlabel('sepal length [standardized]')
plt.ylabel('petal length [standardized]')
plt.legend(loc='upper left')

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

plt.plot(range(1, len(ada.cost_) + 1), ada.cost_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Average Cost')

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

通过执行前面的示例代码可以得到以下两张图:

pythonML学习笔记ch2-Adaline_第7张图片 图7-adaline随机梯度下降

 正如上图可以看到的,平均代价降低得非常快,在15次迭代后,最终的决策边界看起来与批量梯度Adaline的下降结果类似。

你可能感兴趣的:(学习记录,学习,机器学习,人工智能)