[自学笔记]感知器与各种梯度下降

前言

【材料来自《Python机器学习》】
拟合函数 s = b + w 1 x 1 + w 2 x 2 + . . . + w n x n = b + w → ∙ x → T s = b + w_{1}x_{1} + w_{2}x_{2} + ... + w_{n}x_{n} = b + \overrightarrow{w} \bullet \overrightarrow{x}^{T} s=b+w1x1+w2x2+...+wnxn=b+w x T
代价函数 J = 1 2 ∑ i n ( y ( i ) − s ( i ) ) ) 2 J = \frac{1}{2} \sum_{i}^{n} (y^{(i)} -s^{(i)}))^{2} J=21in(y(i)s(i)))2

PS:规定上标表示数据组号,下标表示参数号

梯度下降

先从梯度下降开始。
每行数据后都更新一次 △ w j = η ( y ( i ) − s ( i ) ) ∗ x j ( i ) \bigtriangleup w_{j}=\eta (y^{(i)}-s^{(i)}) * x^{(i)}_{j} wj=η(y(i)s(i))xj(i)

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

class Perceptron(object):
    """Perceptron 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.
    errors_ : list
      Number of misclassifications (updates) 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_examples, n_features]
          Training vectors, where n_examples is the number of examples and
          n_features is the number of features.
        y : array-like, shape = [n_examples]
          Target values.

        Returns
        -------
        self : object

        """
        rgen = np.random.RandomState(self.random_state)  # 创建了一个随机数生成器对象
        '''
        生成一个服从正态分布的随机数序列
            loc 均值; scale 标准差; size 长度
        '''
        self.w_ = rgen.normal(loc=0.0, scale=0.01, size=1 + X.shape[1])
        self.errors_ = []

        for _ in range(self.n_iter):
            errors = 0    # 每次操作后的误差值,用于检验是否收敛
            for xi, target in zip(X, y):
                update = self.eta * (target - self.predict(xi)) # 变化的幅度
                self.w_[1:] += update * xi
                self.w_[0] += update
                errors += int(update != 0.0)
            self.errors_.append(errors)
        return self   # 可以支持连续调用多个方法,如a.A().B().C()

    def net_input(self, X):
        """Calculate net input"""
        # s = w0 + w1 * x1 + w2 * x2 + ...
        return np.dot(X, self.w_[1:]) + self.w_[0]

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



获取和绘制数据

# 获取数据
s = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
df = pd.read_csv(s, header=None, encoding='utf-8')
# print(df.tail())  # 检查最后五行


# 分出特征X和预测值y
y = df.iloc[0:100, 4].values
y = np.where(y == 'Iris-setosa', -1, 1)

# extract sepal length and petal length
X = df.iloc[0:100, [0, 2]].values

# 绘制
plt.scatter(X[:50, 0], X[:50, 1],
            color='red', marker='o', label='setosa')
plt.scatter(X[50:100, 0], X[50:100, 1],
            color='blue', marker='x', label='versicolor')

plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc='upper left')

plt.show()

在这里插入图片描述

训练,并绘制误差变化曲线

# ### 开始训练

ppn = Perceptron(eta=0.1, n_iter=10)

ppn.fit(X, y)

# 绘制误差变化情况,评估算法的性能和收敛速度
plt.plot(range(1, len(ppn.errors_) + 1), ppn.errors_, marker='o')
plt.xlabel('Epochs')
plt.ylabel('Number of updates')
plt.show()

在这里插入图片描述

将二维数据集可视化

def plot_decision_regions(X, y, classifier, resolution=0.02):
    # 实现二维数据集决策边界的可视化
    # setup marker generator and color map
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])

    # plot the decision surface
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    # 利用特征向量创建网格数组对xx1和xx2
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution),
                           np.arange(x2_min, x2_max, resolution))

    # 使用分类器(例如,支持向量机、逻辑回归等)对网格中的数据点进行预测,以获取对应的类别标签。
    # 通过将xx1和xx2转换为一维数组,并转置为列向量,构成一个样本集,
    # 然后使用分类器对这个样本集进行预测,得到预测的类别标签
    # 通常情况下,Z 将是一个包含预测结果的一维数组
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    
    # 将预测结果Z重新形状为与网格xx1相同的形状,以便后续在网格上绘制决策边界
    Z = Z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())

    # plot class examples
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0],
                    y=X[y == cl, 1],
                    alpha=0.8,
                    c=colors[idx],
                    marker=markers[idx],
                    label=cl,
                    edgecolor='black')



ppn = Perceptron(eta=0.1, n_iter=10)
ppn.fit(X, y)
plot_decision_regions(X, y, classifier=ppn)  # ppn作为分类器模型传入
plt.xlabel('sepal length [cm]')
plt.ylabel('petal length [cm]')
plt.legend(loc='upper left')
plt.show()

在这里插入图片描述

批量梯度下降

我们希望让代价函数取到最小值,采用批量梯度下降的方法
  1.令J对 w j w_{j} wj求偏导有: ∂ J ∂ w j = − ∑ i n ( y ( i ) − s ( i ) ) ∗ x j ( i ) \frac{\partial J}{\partial w_{j}} = -\sum_{i}^{n} (y^{(i)}-s^{(i)}) * x^{(i)}_{j} wjJ=in(y(i)s(i))xj(i)
  2.令J对 b b b求偏导有: ∂ J ∂ b = − ∑ i n ( y ( i ) − s ( i ) ) \frac{\partial J}{\partial b} = -\sum_{i}^{n} (y^{(i)}-s^{(i)}) bJ=in(y(i)s(i))
根据梯度下降, △ w = − η ▽ J ( w ) \bigtriangleup w = -\eta \bigtriangledown J(w) w=ηJ(w)
所 以       △ w j = − η ∂ J ∂ w j = η ∑ i n ( y ( i ) − s ( i ) ) ∗ x j ( i ) \bigtriangleup w_{j} = -\eta\frac{\partial J}{\partial w_{j}}=\eta\sum_{i}^{n} (y^{(i)}-s^{(i)}) * x^{(i)}_{j} wj=ηwjJ=ηin(y(i)s(i))xj(i)
其 中       η \eta η为学习速率

代码如下

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

    Parameters
    ----------
    X : {array-like}, shape = [n_examples, n_features]
      Training vectors, where n_examples is the number of examples and
      n_features is the number of features.
    y : array-like, shape = [n_examples]
      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)
        '''
        这里self.activation 是某个激活函数,用于引入非线性拟合,
        使得网络能够学习和表示复杂的函数映射。之后会引入sigmoid函数。
        '''
        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 activation(self, X):
    """Compute linear activation"""
    return X

随机梯度下降

采用逐步更新训练样本的权重,要对训练数据进行洗牌防止迭代循环

在随机梯度下降的过程中,固定的学习速率 η \eta η经常被随时间下降的自适应学习速率取代,例如: C 1 [ 迭代次数 ] + C 2 \frac{C1}{[迭代次数]+C2} [迭代次数]+C2C1   其中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 examples 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_examples, n_features]
          Training vectors, where n_examples is the number of examples and
          n_features is the number of features.
        y : array-like, shape = [n_examples]
          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)       # 计算平均MSE
            self.cost_.append(avg_cost)         # 保存平均MSE
        return self

    def partial_fit(self, X, y):
        """继续拟合训练数据,不重新初始化权重。
		这个方法的目的是实现在线学习和处理数据流,接收实时数据而不用重新初始化权重
		"""
        if not self.w_initialized:       # 如果权重尚未初始化,则初始化权重
            self._initialize_weights(X.shape[1])
        if y.ravel().shape[0] > 1:       # 如果y是多维数组,则遍历所有样本更新权重
            for xi, target in zip(X, y):
                self._update_weights(xi, target)
        else:                            # 如果y是一维数组,则直接更新权重
            self._update_weights(X, y)
        return self

    def _shuffle(self, X, y):
        """打乱训练数据"""
        r = self.rgen.permutation(len(y))   # 生成随机索引数组
        return X[r], y[r]    # # 通过随机索引打乱X和y
    
    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)

各种“梯度下降”的区别(以下为GPT回答)

梯度下降算法是机器学习和深度学习中用于寻找损失函数最小值的优化方法。主要分为三种类型:批量梯度下降(Batch Gradient Descent,BGD)、随机梯度下降(Stochastic Gradient Descent,SGD)以及介于两者之间的迷你批量梯度下降(Mini-Batch Gradient Descent,MBGD)。下面我将详细解释批量梯度下降和梯度下降(这里主要指的是批量梯度下降)的区别。

批量梯度下降(Batch Gradient Descent,BGD)
批量梯度下降每次使用整个训练数据集来计算损失函数的梯度,然后更新模型参数。其过程如下:

计算整个训练集的损失函数关于参数的梯度。
使用这个梯度来更新参数。
重复上述步骤,直到达到收敛或迭代次数上限。
优点:

在每次更新中,由于使用了所有训练数据,所以梯度的估计更加准确,这通常会导致更稳定的收敛。
缺点:

计算成本高,因为每次更新都需要遍历整个数据集。

对于大规模数据集,内存需求大。

更新过程慢,因为需要等待所有数据的梯度计算完成。

随机梯度下降(Stochastic Gradient Descent,SGD)
随机梯度下降每次只使用一个数据点来计算梯度和更新参数,其过程如下:

随机选择一个训练样本。

计算该样本的损失函数关于参数的梯度。

使用这个梯度来更新参数。

重复上述步骤,直到达到收敛或迭代次数上限。

优点:

更新速度快,每次只需要处理一个样本,适合大规模数据集。
由于每次更新都是基于单个样本,算法的收敛过程可能会更接近全局最小值,尤其是在非凸函数的情况下。
缺点:

梯度的估计可能不准确,因为只基于一个样本。
更新路径可能会有较大的波动,导致收敛过程不稳定。
总结
批量梯度下降和随机梯度下降的主要区别在于更新参数时使用的数据量不同。批量梯度下降使用所有数据,而随机梯度下降每次使用一个数据点。批量梯度下降提供了更准确但更慢的收敛,而随机梯度下降提供了更快但可能更不稳定的收敛。在实际应用中,迷你批量梯度下降通常是一个折中的选择,它使用数据集中的一小部分(一个“小批量”)来更新参数,平衡了速度和精确度。

为什么要激活函数(以下为GPT回答)

激活函数在神经网络中扮演着至关重要的角色,它们的使用是为了向网络模型中引入非线性特性。如果神经网络中没有激活函数,那么无论网络的结构有多复杂,它本质上只执行线性变换,这将极大地限制其学习和表达复杂功能的能力。以下是使用激活函数的几个主要原因:

引入非线性:如来源中提到,线性模型在处理线性可分数据集时可以工作得很好,但在处理非线性可分数据集时(如异或(XOR)问题)就无能为力了。激活函数的非线性特性使得神经网络可以对数据分布进行复杂的变换,从而学习到非线性关系。

模型拟合能力增强:通过引入非线性,神经网络可以拟合更复杂的函数,这意味着它可以学习到输入数据和输出数据之间更复杂的关系,从而提高模型的拟合能力和预测准确度。

梯度传播:激活函数影响梯度的传播,一些激活函数,如ReLU(Rectified Linear Unit),可以有效避免梯度消失问题,使得深度网络中的梯度能更好地反向传播,这有助于模型的训练和收敛。

计算效率:某些激活函数,如ReLU,其计算简单,可以加速神经网络的训练过程,减少计算资源的需求。

概率输出:在分类任务中,激活函数如Sigmoid或Softmax可以将神经元的输出转换为概率形式,这有助于模型给出分类预测时的置信度。

神经元激活:激活函数决定了神经元是否应该被激活(输出非零值)。例如,ReLU函数将所有负值输出为零,这有助于神经网络学习稀疏特征表示。

综上所述,激活函数是神经网络中不可或缺的组成部分,它们通过引入非线性特性,不仅增强了模型的学习能力和泛化能力,还促进了模型训练的高效性和稳定性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值