01. 机器学习概述

一、什么是机器学习

  机器学习(Machine Learning,ML)主要研究计算机对于特定任务的性能,逐步进行改善的算法和统计模型。通过输入海量训练数据对模型进行训练,是模型掌握数据所蕴含的潜在规律,进而对新输入的数据进行准确的分类和预测。机器学习专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

  我们可以在终端中使用 pip 安装 sklearn 机器学习库。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载

pip install scikit-learn -i https://mirrors.aliyun.com/pypi/simple

  国内常用的 pip 下载源列表:

二、机器学习与深度学习的关系

  机器学习与深度学习的关系如下:

人工智能与机器学习的关系

三、机器学习的方法

  机器学习的方法一般由三部分组成:

\[机器学习方法 = 模型 + 策略 + 算法 \]

  • 模型(model):总结数据的内在规律,用数学语言描述的参数系统。
  • 策略(strategy):选取最优模型的评价准则。
  • 算法(algorithm):选取最优模型的具体方法。

  机器学习的方法种类繁多,并不存在一个同一的理论体系能够覆盖所有内容。从不同的角度,可以将机器学习的方法进行不同的分类:

  • 通常分类:按照 有无监督,机器学习可以分为 有监督学习无监督学习半监督学习,除此之外还有 强化学习
    • 有监督学习:提供数据并提供数据对应结果的机器学习过程。
    • 无监督学习:提供数据并且不提供数据对应结果的机器学习过程。
    • 半监督学习:提供数据并且提供大部分数据对应结果的机器学习过程。
    • 强化学习:通过与环境交互并获取延迟返回进而改进行为的学习过程。
  • 按模型分类:根据 模型性质,可以分为 概率模型/非概率模型线性模型/非线性模型 等。
  • 按学习技巧分类:根据 算法基于的技巧,可以分为 贝叶斯学习核方法 等。

【1】、无监督学习

  无监督学习(Unsupervised Learning)算法采用 一组仅包含输入的数据,通过 寻找数据中内在结构 来进行 采样点的分组或聚集。无监督学习的算法从没有被标记或分类的测试数据中学习。无监督学习不响应反馈,而是要识别数据中的共性特征,对于一个新数据,可以通过判断其中是否存在这种特征,来做出相应的反馈。无监督学习的核心应用是统计学中的密度估计和聚类分析。

有监督机器学习分类

【2】、监督学习

  监督学习(Supervised Learning)算法构建了 包含输入和所需输出的一组数据的数学模型。这些数据称为 训练数据,由一组训练样本组成。监督学习主要包括分类和回归。当输出被限制为有限的一组值(离散数值)是使用 分类算法;当输出可以具有范围内的任何数值(连续数值)是使用 回归算法

  相似学习 是和回归和分类都密切相关的一类监督机器学习,它的目的是 使用相似性函数从样本中学习,这个函数可以度量两个对象之间的相似度或关联度。它在排名、推荐系统、视觉识别跟踪、人脸识别等方面有很好的应用场景。

无监督机器学习分类

【3】、半监督学习

  半监督学习 提供少量有标签数据和大量无标签数据,自动利用无标签数据提升学习性能。

半监督机器学习分类

【4】、强化学习

  强化学习 寻找一个智能体策略,使其在与动态环境交互的过程中产生最优的数据分布。

强化学习分类

四、建模流程

  1. 收集数据:收集用于训练和测试的数据集,确保数据代表了实际问题的不同方面。
  2. 数据清洗:对数据进行清洗,去除掉一些脏数据和不可用的数据。
  3. 特征工程:对数据进行转换和格式化,以确保它适合用于机器学习模型训练。
  4. 选择算法:选择合适任务类型(分类、回归、聚类等)和数据特征的机器学习算法。
  5. 模型训练:使用训练数据集来训练数据,让模型从数据中学习规律或模式。
  6. 模型评估:使用测试数据集评估模型的性能,确保其是否达到了预取的目标。
  7. 模型优化:在确保模型具有较好性能的基础上,进一步提高模型的效果。
  8. 模型部署:将训练好的模型部署到生产环境中,并实时检测其表现。

五、特征工程

  特征工程(Feature Engineering)是机器学习工程中非常重要的一步,指的是 通过对原始数据的处理、转换和构造,生成新的特征或选择有效的特征,从而提高模型的性能。简单来说,特征工程是将原始数据转换为可以更好地表示问题的特征形式,帮助模型更好地理解和学习数据中的规律。优秀的特征工程可以显著提高模型的表现。反之,忽视特征工程可能导致模型性能欠佳。

  实际上,特征工程是一个 迭代过程。特征工程取决于具体情境。它需要大量的数据分析和领域知识。其中的原因在于,特征的有效编码可由所用的模型类型、预测变量与输出之间的关系以及模型要解决的问题来确定。在此基础上,辅以不同类型的数据集则可能更适合不同的特征工程技术。

5.1、特征选择

  从原始特征中挑选处与目标变量关系最亲切的特征,剔除冗余、无关或噪声特征。这样可以减少模型的复杂度、加速训练过程,并减少过拟合的风险。特征选择不会创建新特征,也不会改变数据结果。常用的特征选择的方法如下:

  • 过滤法(Filter Method):基于统计测试(如方卡检验、相关系数、信息增益等)来评估特征与目标变量之间的关系,选择最相关的特征。
  • 包裹法(Wrapper Method):使用模型(如递归特消除 PRE)来评估特征的重要性,并根据模型的表现进行特征选择。
  • 嵌入法(Embedded Method):使用模型本身的特征选择机制(如决策树的特征重要性,L1 正则化的特征选择)来选择最重要的特征。

  常用的 特征选择 的方法如下:

【1】、低方差过滤法

  对于特征的选择,可以直接基于方差来判断,这是最简单的。低方差的特征意味着该特征的所有样本值几乎相同,对预测影响极小,可以将其去掉。

import numpy as np

from sklearn.feature_selection import VarianceThreshold

if __name__ == '__main__':
    # 构建特征
    a = np.random.randn(100)                                                    # 标准差为1长度为100的随机数
    b = np.random.normal(5, 0.1, size=100)                                      # 均值为5标准差为0.1长度为100的随机数

    # 构造特征向量输入数据
    X = np.vstack([a, b]).T

    # 低方差过滤,删除方差小于阈值的特征
    vt = VarianceThreshold(0.01)
    x_filtered = vt.fit_transform(X)

    print(x_filtered)

【2】、相关系数法

  通过计算特征与目标变量或特征之间的相关性,刷选出高相关性特征(与目标相关)或剔除冗余特征(特征间高度相关)。

(1)、皮尔逊相关系数

  皮尔逊相关系数用于衡量两个变量的线性相关性,取值范围 [-1, 1]。

\[r = \frac{\sum_{i = 1}^{n}{(x_{i} - \overline{i}) (y_{i} - \overline{y})}}{\sqrt{\sum_{i = 1}^{n}{(x_{i} - \overline{x})^{2}}}\sqrt{\sum_{i = 1}^{n}{(y - \overline{y})^{2}}}} \]

  • 正相关:值接近 1,说明特征随目标变量增加而增加。
  • 负相关:值接近 -1,说明特征随目标变量增加而减少。
  • 无关:值直接 0,说明特征和目标变量无明显关系。

  我们可以在终端中使用 pip 安装 pandas 库。

pip install pandas seaborn -i https://mirrors.aliyun.com/pypi/simple
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

if __name__ == '__main__':
    advertising = pd.read_csv("assets/data/advertising.csv")                    # 从CSV文件读取数据
    advertising.drop(advertising.columns[0], axis=1, inplace=True)              # 删除第一列(索引列)
    advertising.dropna(inplace=True)                                            # 删除缺失值

    X = advertising.drop("Sales", axis=1)                                       # 特征矩阵
    y = advertising["Sales"]                                                    # 目标变量

    corr_matrix = X.corrwith(y, method="pearson")                               # 使用皮尔逊相关系数计算特征与目标变量的相关系数
    
    sns.heatmap(corr_matrix.to_frame(), annot=True, cmap="coolwarm", fmt=".2f") # 将相关系数画成热力图
    plt.title("Correlation Matrix")                                             # 标题
    plt.show()                                                                  # 显示热力图

(2)、斯皮尔曼相关系数

  斯皮尔曼相关系数的定义是等级变量之间的皮尔逊相关系数。用于衡量两个变量之间的单调关系,即当一个变量增加时,另一个变量是否是增加或者减少(不要求是线性关系)。适用于非线性关系或者数据不符合正太分布的情况。

\[r_{s} = 1 - \frac{6\sum{d_{i}^{2}}}{n(n^{2} - 1)} \]

  其中,\(d_{i}\)两个变量的等级之差。n 是 样本数。斯皮尔曼相关系数的取值范围为 [-1, 1]

  • 正相关:值接近 1,说明特征随目标变量增加而增加。
  • 负相关:值接近 -1,说明特征随目标变量增加而减少。
  • 无关:值直接 0,说明特征和目标变量无明显关系。
import pandas as pd

if __name__ == '__main__':
    X = [[5], [8], [10], [12], [15], [3], [7], [9], [14], [6]]                  # 每周学习时长
    y = [55, 65, 70, 75, 85, 50, 60, 72, 80, 58]                                # 每周学习时长对应的成绩

    X = pd.DataFrame(X)
    y = pd.Series(y)
    print(X.corrwith(y, method="spearman"))

5.2、特征转换

  对数据进行数学或统计处理,使其变得更加适合模型的输入要求。常用的特征转换的方式如下:

  • 归一化(Normalization):将特征压缩到特征的范围(通常是 0 到 1 之间)。适用于对尺度敏感的模型(如 KNN、SVM)。
  • 标准化(Standardzation):通过减去均值并处以标准差,使特征的分布具有均值 0, 标准差 1。
  • 对数变化:对于有偏态的分布(如收入、价格等),对数转换可以将其转化为更接近正太分布的形式。
  • 类别变量的编码
    • 独热编码(One-Hot Encoding):将类别变量转换成二进制列,常用于无序类别特征。
    • 标签编码(Label Encoding):将类别型变量映射为整数,常用于有序列别特征。
    • 目标编码(Target Encoding):将类别变量的每个类别替换为其对应目标变量的平均值或其它统计量。
    • 频率编码(Frequency Encoding):将类别变量的每个类别替换为该类别再数据集的出现频率。

【1】、归一化

  不同特征的单位或量纲可能差异巨大(例如身高以米为单位,体重以千克为单位),我们可以使用归一化消除这种差异,避免模型被大范围的特征主导。归一化就是将数据按比例缩放到一个固定范围 \([x_{min}, x_{max}]\)(通常是 [0, 1] 或 [-1,1 ])。

\[x' = \frac{x - x_{min}}{x_{max} - x_{min}} \\ x' = 2 * \frac{x - x_{min}}{x_{max} - x_{min}} - 1 \]

  归一化不该案原始分布形状,但对异常值比较敏感。当数据分布有明显边界(如图像像素值,文本词频),或模型对输入范围敏感时可以优先考虑归一化。

import numpy as np

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(-1, 1))                                    # 归一化缩放器
X = np.array([[2, 1], [3, 1], [1, 4], [2, 6]])                                  # 特征
X_scaled = scaler.fit_transform(X)                                              # 归一化特征
print(X_scaled)

【2】、标准化

  标准化是将数据调整为 均值0标准差1 的标准分布。

\[x' = \frac{x - \mu}{\sigma} \]

  其中,\(\mu\)平均值\(\sigma\)标准差

  与归一化类似,标准化也能消除量纲差异,但它更加关注数据的统计分布而非固定范围。

import numpy as np

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()                                                       # 标准化缩放器
X = np.array([[2, 1], [3, 1], [1, 4], [2, 6]])                                  # 特征
X_scaled = scaler.fit_transform(X)                                              # 归一化特征
print(X_scaled)

【3】、独热编码

  对于类别型特征,直接使用整数编码的类别特征会被算法视为有序数值,导致错误的距离计算,因此,我们可以使用独热编码将类别特征转换为二元向量,消除虚假的顺序关系。

  在进行独热编码时,一般我们会使用 drop="first" 参数来避免多重共线性。多重共线性 是指特征之间存在高度线性相关关系的现象。例如特征包含 4 个类别,如直接进行独热编码会生成 4 个新列,此时这 4 列满足如下条件:

\[列1 + 列2 + 列3 + 列4 = 1 \]

  这种完全线性相关关系或导致特征矩阵的列之间存在完美共线性。当特征矩阵存在多重共线性时,模型估计会变得不稳定(矩阵不可逆或接近奇异)导出系数估计值方差增大、模型可解释性下降、过拟合等问题。在独热编码时设置 drop="first",会删除每个类别特征的第 1 列,从而打破完全共线性。此时:

\[列2 = 0, 列3=0, 列4 = 0 --隐含代表-->> 列1=1 \]

5.3、特征构造

  特征构造 是基于现有的特征创建出新的、更有代表性的特征。通过 组合转换 或者 聚合 现有的特征,形成能够更好反应数据规律的特征。

  • 交互特征:将两个特征组合起来,形成新的特征
  • 统计特征:从原始特征提取统计值。
  • 日期和时间特征:从日期和时间数据中提取如星期几、月份、年份、季度等特征。

5.4、特征降维

  当数据集的特征数量非常大时,特征降维可以帮助减少计算复杂度并避免过拟合。通过降维方法,可以在保持数据本质的情况下减少特征的数量。

  • 主成分分析(PCA):通过线性变化将原始特征映射到一个新的空间,使得新的特征(主成分)尽可能地保留数据的方差。
  • 线性判别分析(LDA):一种监督学习的降维方法,通过最大化类间距离与类内距离的比率来降维。
  • t-SNE(t-Distributed Stochastic Neighboor Enbedding,t 分布随机近邻嵌入):一种非线性的降维技术,特别适合可视化高维数据。
  • 自编码器(Auto Encoder):一种神经网络模型,通过压缩编码器来实现数据的降维。

  对于一个模型来说,有些特征可能很关键,而有些特征可能用处不大。因此,特征选择 在特征工程中是最基本、也最常见的操作。另外,在训练模型时有时也会遇到维度灾害,即特征数量过多。我们希望能在确保不丢失重要特征的前提下减少维度的数量,来降低训练模型的难度。所以在特征工程中,也经常会用到 特征降维 方法。

【1】、主成分分析

  主成分分析是一种常用的降维技术,通过线性变换将高维数据投影到低维空间,同时保留数据的主要变化模式。

sklearn.decomposition.PCA(
        n_components=None,
        *,
        copy=True,
        whiten=False,
        svd_solver="auto",
        tol=0.0,
        iterated_power="auto",
        n_oversamples=10,
        power_iteration_normalizer="auto",
        random_state=None,
    )

  其中,参数 n_components 如为浮点数则表示 保留多少比例的信息,为 整数 则表示 保留多少个维度

import numpy as np
import matplotlib.pyplot as plt

from sklearn.decomposition import PCA

if __name__ == '__main__':
    # 手动构造线性相关的三组特征数据
    n = 1000
    pc1 = np.random.normal(0, 1, n)                                             # 第一组特征,均值为0,标准差为1
    pc2 = np.random.normal(0, 0.2, n)                                           # 第二组特征,均值为0,标准差为0.2
    noise = np.random.normal(0, 0.05, n)                                        # 噪声,均值为0,标准差为0.05
    X = np.vstack((pc1 + pc2, pc1 - pc2, pc2 + noise)).T

    # 进行PCA进行降维,将三维数据将为二维
    pca = PCA(n_components=2)
    X_pca = pca.fit_transform(X)
    
    # 绘制原始数据和降维后的数据
    figure = plt.figure(figsize=(12, 4))
    ax = figure.add_subplot(121, projection='3d')
    ax.scatter(X[:, 0], X[:, 1], X[:, 2], c='b')
    ax.set_title('Original Data')
    ax.set_xlabel('Feature 1')
    ax.set_ylabel('Feature 2')
    ax.set_zlabel('Feature 3')

    ax2 = figure.add_subplot(122)
    ax2.scatter(X_pca[:, 0], X_pca[:, 1], c='b')
    ax2.set_title('PCA Reduced Data')
    ax2.set_xlabel('PC1')
    ax2.set_ylabel('PC2')

    plt.show()

六、模型评估与模型选择

6.1、损失函数

  对于模型一次预测结果的好坏,需要有一个度量标准。对于监督学习而言,给定一个输入 X,选取的模型就相当于一个 决策函数 f,它可以输出一个预测结果 f(x),而真实的结果(标签)记为 Y。f(x) 和 Y 之间可能会存有偏差,我们就用一个损失函数来度量预测偏差的程度,记作 L(Yf(X)))。

  • 损失函数用来衡量模型预测误差的大小,损失函数数值越小,模型就越好。
  • 损失函数是 f(X) 和 Y 的非负实值函数。

6.2、经验误差

  给定一个训练数据集,数据个数为 n:

\[T = \{(x_{1}, y_{1}), (x_{2}, y_{2}), ..., (x_{n}, y_{n})\} \]

  根据选取的损失函数,就可以计算出模型 f(X) 在训练集上的平均误差,称为 训练误差,也被称作 经验误差经验风险

\[R_{emp}(f) = \frac{1}{n} \sum_{i = 1}^{n}{L(y_{i}, f(x_{i})} \]

  类似着,在测试数据集上 平均误差,被称为测试误差或者 泛化误差

  一般情况下,对模型评估的策略,就是考察经验误差,当经验风险最小时,就认为取到了最优的模型。这种策略被称为 经验风险最小化

6.3、欠拟合与过拟合

  拟合 是指机器学习模型在训练数据山学习到规律并生成预测结果的过程。理想情况下,模型能够准确地捕捉训练数据的模式,并且在未见过的新数据(测试数据)上也有良好的表现,即模型具有良好的泛化能力。

  欠拟合 是指模型在训练数据上表现不佳,无法很好地捕捉数据或i性能数据上较差地情况。这样的模型不仅在训练集上表现不要,则测试集上也同样表现差。

  过拟合 是指模型在训练数据上表现得更好,但在测试数据或新数据上表现较差的情况。过拟合的模型对训练数据的噪声或细节过度敏感,把训练样本本身的一些特点当作了所有潜在潜在样本都会具有的一般性质,从而失去了泛化能力。

  产生欠拟合和过拟合的根本原因是 模型的复杂度过低或者过高,从而导致测试误差(泛化误差)偏大。

  • 欠拟合:模型在训练集和测试集上误差都比较差,模型过于简单,高偏差。
  • 过拟合:模型在训练集上误差较小,但在测试集上误差较大,模型过于复杂,高方差。

【1】、欠拟合

  欠拟合产生的根本原因如下:

  • 模型复杂度不足:模型过于简单,无法捕捉数据中的复杂关系。
  • 特征不足:输入特征不充分,或者特征选择不恰当,导致模型无法充分学习数据的模式。
  • 训练不充分:训练过程中迭代次数太少,模型没有足够的时间学习数据的规律。
  • 过强的正则化:正则化设置过大,强制模型过于简单,导致模型无法充分拟合数据。

  针对欠拟合,我们可以通过如下方式来解决:

  • 增加模型复杂度:选择更复杂的模型。
  • 增加特征或改进特征方程:添加更多的特征或通过特征工程来创建更有信息量的特征。
  • 增加训练时间:增加训练的迭代次数,让模型有更多机会去学习。
  • 减少正则化强度:如果使用了正则化,尝试减少正则化的权重,以让模型更灵活。

【2】、过拟合

  过拟合产生的根本原因如下:

  • 模型复杂度过高:模型过于复杂,参数太多。
  • 训练数据不足:数据集太小,模型能记住训练数据的细节,但无法泛化得到新数据。
  • 特征过多:特征太多,模型可能会记住数据中的噪声,而不是学到真正的规律。
  • 训练过长:训练时间过长,导致模型学习到训练数据中的噪声,而非数据的真正规律。

  针对过拟合,我们可以通过如下方式来解决:

  • 减少模型复杂度:降低模型的参数数量、使用简化的模型或降维来减少模型复杂度。
  • 增加训练数据:收集更多数据,或通过数据增强来增加需训练数据的多样性。
  • 使用正则化:引入 L1、L2 正则化,避免过度拟合训练数据。
  • 交叉验证:使用交叉验证技术评估模型在不同数据集上的表现,以减少过拟合的风险。
  • 早停:训练时,当模型的验证损失不再下降时,提前停止训练,避免过度拟合训练。
import numpy as np
import matplotlib.pyplot as plt

import sklearn.model_selection
import sklearn.metrics

from sklearn.linear_model import LinearRegression                               # 线性回归模型
from sklearn.preprocessing import PolynomialFeatures                            # 多项式特征

if __name__ == '__main__':
    # 字体设置
    plt.rcParams['font.sans-serif'] = ['KaiTi']                                 # 设置字体为楷体,解决中文显示问题
    plt.rcParams['axes.unicode_minus'] = False                                  # 显示负号

    # 1.生成数据
    X = np.linspace(-3, 3, 300).reshape(-1, 1)                                  # 生成-3到3之间的300个点
    y = np.sin(X) + np.random.uniform(-0.5, 0.5, size=300).reshape(-1, 1)       # 生成sin函数值并添加噪声

    # 2.划分训练集和数据集
    X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)

    # 欠拟合
    x_train1 = X_train
    x_test1 = X_test

    # 3.定义模型(线性回归模型)
    model = LinearRegression()

    # 4.训练模型
    model.fit(x_train1, y_train)                                                # 对欠拟合模型进行训练

    # 5.预测结果,计算测试集和训练集的均方误差
    # 欠拟合
    test_loss1 = sklearn.metrics.mean_squared_error(y_test, model.predict(x_test1))     # 计算测试集的均方误差
    train_loss1 = sklearn.metrics.mean_squared_error(y_train, model.predict(x_train1))  # 计算训练集的均方误差

    # 6.画出拟合曲线
    fig, ax = plt.subplots(1, 3, figsize=(15, 4))
    ax[0].scatter(X, y, color="y")
    ax[1].scatter(X, y, color="y")
    ax[2].scatter(X, y, color="y")

    ax[0].plot(X, model.predict(X), color="r")
    ax[0].text(-3, 1, f"测试误差: {test_loss1:.4f}")
    ax[0].text(-3, 1.3, f"训练误差: {train_loss1:.4f}")

    # 恰好拟合(5次多项式)
    poly5 = PolynomialFeatures(degree=5)                                        # 5次多项式特征
    x_train2 = poly5.fit_transform(X_train)                                     # 对训练集进行5次多项式特征变换
    x_test2 = poly5.transform(X_test)                                           # 对测试集进行5次多项式特征变换

    model.fit(x_train2, y_train)                                                # 对恰好拟合模型进行训练

    # 恰好拟合
    test_loss2 = sklearn.metrics.mean_squared_error(y_test, model.predict(x_test2))     # 计算测试集的均方误差
    train_loss2 = sklearn.metrics.mean_squared_error(y_train, model.predict(x_train2))  # 计算训练集的均方误差

    ax[1].plot(X, model.predict(poly5.fit_transform(X)), color="r")
    ax[1].text(-3, 1, f"测试误差: {test_loss2:.4f}")
    ax[1].text(-3, 1.3, f"训练误差: {train_loss2:.4f}")

    # 过拟合(20次多项式)
    poly20 = PolynomialFeatures(degree=20)
    x_train3 = poly20.fit_transform(X_train)
    x_test3 = poly20.transform(X_test)

    model.fit(x_train3, y_train)                                                # 对过拟合模型进行训练

    # 过拟合
    test_loss3 = sklearn.metrics.mean_squared_error(y_test, model.predict(x_test3))     # 计算测试集的均方误差
    train_loss3 = sklearn.metrics.mean_squared_error(y_train, model.predict(x_train3))  # 计算训练集的均方误差

    ax[2].plot(X, model.predict(poly20.fit_transform(X)), color="r")
    ax[2].text(-3, 1, f"测试误差: {test_loss3:.4f}")
    ax[2].text(-3, 1.3, f"训练误差: {train_loss3:.4f}")

    plt.show()

6.4、正则化

  正则化是一种在训练机器学习模型时,在损失函数中添加额外项,来惩罚过大的参数,进而限制模型复杂度、避免过拟合,提高模型泛化能力的技术。如果在平方损失函数中加入正则化 \(\lambda \sum_{i = 1}^{k}{w_{i}^{2}}\),则

\[Loss = \frac{1}{n}( \sum_{i=1}^{n}{f(x_{i} - y_{i})^{2}} + \lambda \sum_{i = 1}^{k}{w_{i}^{2}}) \]

  • 原损失函数 \(\sum_{i=1}^{n}{f(x_{i} - y_{i})^{2}}\) 的目的:更好的拟合数据集。
  • 正则化项 \(\lambda \sum_{i = 1}^{k}{w_{i}^{2}}\) 的目的:减少参数的大小,从而降低模型的复杂度。

  这里的 \(\lambda\)正则化系数,用来表示惩罚项的权重。正则化系数不属于模型的参数,无法通过训练学习得到,需要在模型训练开始之前手动设置,这种参数被称为 “超参数”。

  常见的正则化技术有 L1 正则化和 L2 正则化。

【1】、L1 正则化

  L1 正则化在损失函数中加入参数的绝对值之和:

\[Loss_{L_{1}} = 原Loss + \lambda \sum_{i = 1}^{k}{\abs{w_{i}}} \]

  L1 正则化通过惩罚模型参数的绝对值,使得部分权重趋近 0 甚至变为 0。这会导致特征选择,就模型会自动丢弃一些不重要的特征。L1 正则化有助于创建稀疏模型(即许多参数为 0)。在解决回归问题时,使用 L1 正则化也被称为 Lasso 回归

  \(\lambda\) 超参数控制着正则化的强度。较大的 \(\lambda\) 值意味着强烈的正则化,会使模型更简单,可能导致欠拟合。而较小的 \(\lambda\) 值会使模型更复杂,可能导致过拟合。

【2】L2 正则化

  L2 正则化在损失函数中加入参数的平方和:

\[Loss_{L_{2}} = 原Loss + \lambda \sum_{i = 1}^{k}{w_{i}^{2}} \]

  L2 正则化通过惩罚模型参数的平方,使得所有参数都变得更小,但不会将参数强行压缩为 0。它会使得模型尽量平滑,从而放置过拟合。在解决问题时,使用 L2 正则化也被称 岭回归

【3】、ElasticNet 正则化

  ElasticNet 正则化结合了 L1 和 L2 正则化,通过调整两个正则化向的比例来取得平衡,从而同时具备稀疏性和稳定性的优点。

\[Loss_{ElasticNet} = 原Loss + \lambda (\sum_{i=1}{n}{\abs{w_{i}}} + \frac{1 - \alpha}{2} \sum_{i=1}^{n}{w_{i}^{2}}) \]

  其中,\(\alpha \in [0, 1]\) ,决定 \(L_{1}\)\(L_{2}\) 的权重。

import numpy as np
import matplotlib.pyplot as plt

import sklearn.model_selection
import sklearn.metrics

from sklearn.linear_model import LinearRegression, Lasso, Ridge                 # 线性回归模型, Lasso 正则化, Ridge 正则化
from sklearn.preprocessing import PolynomialFeatures                            # 多项式特征

if __name__ == '__main__':
    # 字体设置
    plt.rcParams['font.sans-serif'] = ['KaiTi']                                 # 设置字体为楷体,解决中文显示问题
    plt.rcParams['axes.unicode_minus'] = False                                  # 显示负号

    # 1.生成数据
    X = np.linspace(-3, 3, 300).reshape(-1, 1)                                  # 生成-3到3之间的300个点
    y = np.sin(X) + np.random.uniform(-0.5, 0.5, size=300).reshape(-1, 1)       # 生成sin函数值并添加噪声

    # 2.划分训练集和数据集
    X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)

    # 过拟合(20次多项式)
    poly20 = PolynomialFeatures(degree=20)

    x_train = poly20.fit_transform(X_train)
    x_test = poly20.transform(X_test)

    # 3.定义模型(线性回归模型)
    model = LinearRegression()                                                  # 定义线性回归模型
    lasso = Lasso(alpha=0.1)                                                    # 定义 Lasso 正则化模型,正则化参数为 0.1
    ridge = Ridge(alpha=1)                                                      # 定义 Ridge 正则化模型,正则化参数为 0.1


    # 4.训练模型
    model.fit(x_train, y_train)                                                 # 对过拟合模型进行训练
    lasso.fit(x_train, y_train)                                                 # 对 Lasso 正则化模型进行训练
    ridge.fit(x_train, y_train)                                                 # 对 Ridge 正则化模型进行训练

    # 5.预测结果,计算测试集和训练集的均方误差
    # 过拟合
    test_loss1 = sklearn.metrics.mean_squared_error(y_test, model.predict(x_test))  # 计算测试集的均方误差
    test_loss2 = sklearn.metrics.mean_squared_error(y_test, lasso.predict(x_test))  # 计算测试集的均方误差
    test_loss3 = sklearn.metrics.mean_squared_error(y_test, ridge.predict(x_test))  # 计算测试集的均方误差

    # 6.画出拟合曲线
    fig, ax = plt.subplots(2, 3, figsize=(15, 8))
    ax[0, 0].scatter(X, y, color="y")
    ax[0, 1].scatter(X, y, color="y")
    ax[0, 2].scatter(X, y, color="y")

    ax[0, 0].plot(X, model.predict(poly20.fit_transform(X)), color="r")
    ax[0, 0].text(-3, 1, f"测试误差: {test_loss1:.4f}")
    ax[1, 0].bar(np.arange(21), model.coef_.reshape(-1))

    # Lasso 正则化
    ax[0, 1].plot(X, lasso.predict(poly20.fit_transform(X)), color="r")
    ax[0, 1].text(-3, 1, f"测试误差: {test_loss2:.4f}")
    ax[1, 1].bar(np.arange(21), lasso.coef_.reshape(-1))

    # Ridge 正则化
    ax[0, 2].plot(X, ridge.predict(poly20.fit_transform(X)), color="r")
    ax[0, 2].text(-3, 1, f"测试误差: {test_loss3:.4f}")
    ax[1, 2].bar(np.arange(21), ridge.coef_.reshape(-1))

    plt.show()

6.5、交叉验证

  交叉验证是一种评估模型泛化能力的方法,通过将数据集划分为多个子集,反复进行训练和验证,以减少因单次数据划分带来的随机性误差。通过交叉验证能更可靠地评估模型在未知数据上地表现,也能避免因单次数据划分不合理导致的模型过拟合或欠拟合。

【1】、简单交叉验证

  将数据划分成训练集和验证集,结果受单词划分影响较大,可能高估或低估模型性能。

【2】、k 折交叉验证

  将数据均匀分为 k 个子集,每次用 k - 1 折训练,剩余 1 折验证,重复 k 次后取平均性能。充分利用数据,结果更稳定。

【3】、留一交叉验证

  每次仅留一个样本作为验证集,其余全部用于训练,重复直到所有像本都被验证一次。适用于小数据集,计算成本极高。

七、模型求解算法

  正则化可以有效防止过拟合,增强模型的泛化能力。这时模型的评估策略,就是让结构化经验的风险最小,即增加了正则化项的损失函数最小,称为 结构风险最小化。

\[\min{\frac{1}{n}}(\sum_{i=1}^{n}L(y_{i}, f(x_{i})) + \lambda J(\theta)) \]

  这其实就是求解一个最优化问题。代入训练集所有数据 \((x_{i}, y_{i})\),要求最小值的目标函数就是模型中的参数 \(\theta\) 的函数。

7.1、解析法

  如果模型损失函数的最小值可以通过数学公式进行严格推导,得到一个解析解,那么就直接得到了最优模型的全部参数。这种方法称作为 解析法。解析法适用于 目标函数必须可导,且导数方程有解析解 的情况。

7.2、梯度下降法

  梯度下降法是一种常用的一阶优化方法,是求解无约束问题最简单、最经典的方法之一。梯度下降是迭代算法,基本思路就是先取一个适当的初始值 \(\theta_{0}\),然后沿着梯度方向或者负梯度方向,不停地更新参数,最终得到极小值。

  • 梯度方向:函数变化增长最快地方向(变量沿此方向变化时函数增加最快)。
  • 负梯度方向:函数变化减少最快地方向(变量沿此方向变化时函数较少最快)。

  因为损失函数是系统地函数,那么如果系数沿着损失函数的负梯度方向变化,此时损失函数减少最快,能够以最快速度下降到极小值。

\[\theta_{k+1} = \theta_{k} - \alpha \nabla L(\theta_{k}) \]

  这里的 \(\nabla L(\theta_{k})\) 是参数取值为 \(\theta_{k}\) 时,损失函数 L 的梯度,\(\alpha\) 是每次迭代的步长,被称为 学习率。学习率也是一种常见的超参数,需要手动设置,选择不当会导致收敛失败。

梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。

  梯度下降法可以分为以下几种:

  • 批量梯度下降:每次迭代使用全部训练数据计算梯度,它可以稳定收敛,但计算开销大。
  • 随机梯度下降:每次迭代随机选取一个样本计算梯度。它的速度块,适合大规模数据,但梯度更新方向不稳定,优化过程震荡较大,可能难以收敛。
  • 小批量梯度下降:每次的迭代使用一小批样本计算梯度。

  梯度下降的计算步骤如下:

  1. 初始化参数:随机选择初始参数。
  2. 计算梯度:在当前参数下,计算损失函数的梯度。
  3. 更新参数:沿负梯度方向调整参数。
  4. 重复迭代:直到满足条件停止(如梯度接近零,达到最大迭代次数等)。
import numpy as np
import matplotlib.pyplot as plt

# 定义目标函数
def f(x):
    return x ** 2

# 定义梯度函数
def gradient(x):
    return 2 * x

if __name__ == '__main__':
    # 字体设置
    plt.rcParams['font.sans-serif'] = ['KaiTi']                                 # 设置字体为楷体,解决中文显示问题
    plt.rcParams['axes.unicode_minus'] = False                                  # 显示负号

    # 用列表保存点的变化轨迹
    x_list = []
    y_list = []

    # 定义超参数和x的初始值
    alpha = 0.1
    x = 1

    # 重复迭代100次
    for i in range(100):
        y = f(x)
        
        # 记录当前点的坐标
        x_list.append(x)
        y_list.append(y)

        # 更新x的值
        x = x - alpha * gradient(x)

    x = np.arange(-1, 1, 0.01)
    plt.plot(x, f(x))
    plt.plot(x_list, y_list, "r")
    plt.scatter(x_list, y_list, c="r")
    plt.show()

7.3、牛顿法和拟牛顿法

  牛顿法 也是求解无约束最优化问题的常用方法,核心思想是利用目标函数的二阶导数信息,通过迭代逐渐逼近极值点。它的优点是速度快,精度高,但计算比较复杂,可能会发散。

\[\theta_{k+1} = \theta_{k} - K^{-1}(\theta_{k}).\nabla L(\theta_{k}) \]

  这里的 \(H^{-1}(\theta_{k})\) 表示损失函数 L 黑塞矩阵的逆在点 \(\theta_{k}\) 的取值。

  由于牛顿法中需要计算黑塞矩阵 \(H^{-1}(\theta_{k})\) ,这一步比较复杂,所有可以考虑用一个 n 阶正定矩阵来近似代替它,这种方法称为 拟牛顿法

八、模型评价指标

  对学习的泛化能力进行评估,不仅需要有效可行的实验估计方法,还需要有衡量模型泛化能力的评价指标,也叫做性能度量。模型的评价指标用于衡量模型在训练集或测试集上的性能,评估结果反映了模型预测的准确性和泛化能力。

8.1、回归模型评价指标

  对于回归问题,最常用的性能度量是 均方误差

【1】、平均绝对误差

\[MAE = \frac{1}{n}\sum_{i=1}^{n}\abs{f(x_{i}) - y_{i}} \]

MAE 对异常值不敏感,解释直观,适用于数据包含异常值的场景。

【2】、均方差误差

\[MSE = \frac{1}{n}\sum_{i=1}^{n}{(f(x_{i}) - y_{i})}^{2} \]

MSE 会方法较大误差,对异常值敏感,适用于需要惩罚大误差的场景。

【3】、均方根误差

\[RMSE = \sqrt{\frac{1}{n}\sum_{i=1}^{n}{(f(x_{i}) - y_{i})}^{2}} \]

与 MSE 类似,但量纲与目标变量一致,适用于需要直观误差量纲的场景,如果一味地视图降低 RMSE,可能会导致模型对异常值也拟合度很高,容易过拟合。

【4】、决定系数

\[R^{2} = 1 - \frac{\sum_{i=1}^{n}{(f(x_{i}) - y_{i})}^{2}}{\sum_{i=1}^{n}{(y_{i} - \overline{y})}^{2}} \]

衡量模型对目标变量的解释能力,越接近 1 越好,对异常值敏感。

8.2、分类模型评价指标

  对于分类问题,最常用的指标就是 准确率,它定义为分类对测试集正确分类的样本数与总样本数之比。

【1】、混淆矩阵

  混淆矩阵时用于评估分类模型性能的工具,展示了模型预测结果与解释标签的对比情况。

  对于二分类问题,混淆矩阵是一个 2 X 2 的矩阵:

真实值/预测值 正例 负例
正例 真正值(True Positive, TP) 假负例(False Negative, FN)
负例 假正例(False Positive, FP) 真负例(True Negative, TN)
import pandas as pd
import sklearn.metrics

labels = ["猫", "狗"]                                                           # 定义类别标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]             # 真实值
y_pred = ["猫", "猫", "猫", "狗", "狗", "狗", "狗", "猫", "猫", "狗"]             # 预测值

matrix = sklearn.metrics.confusion_matrix(y_true, y_pred, labels=labels)        # 计算混淆矩阵
print(pd.DataFrame(matrix, index=labels, columns=labels))

【2】、准确率

  正确的预测比例:

\[Accruacy = \frac{TP + TN}{TP + TN + FP + FN} \]

import sklearn.metrics

labels = ["猫", "狗"]                                                           # 定义类别标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]             # 真实值
y_pred = ["猫", "猫", "猫", "狗", "狗", "狗", "狗", "猫", "猫", "狗"]             # 预测值

matrix = sklearn.metrics.confusion_matrix(y_true, y_pred, labels=labels)        # 计算混淆矩阵
accuracy = sklearn.metrics.accuracy_score(y_true, y_pred)                       # 计算准确率
print(accuracy)

【3】、精确率

  预测为正例的样本中实际为正例的比例,也叫 查准率

\[Precision = \frac{TP}{TP + FP} \]

import sklearn.metrics

labels = ["猫", "狗"]                                                           # 定义类别标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]             # 真实值
y_pred = ["猫", "猫", "猫", "狗", "狗", "狗", "狗", "猫", "猫", "狗"]             # 预测值

matrix = sklearn.metrics.confusion_matrix(y_true, y_pred, labels=labels)        # 计算混淆矩阵
accuracy = sklearn.metrics.precision_score(y_true, y_pred, pos_label="猫")      # 计算准确率
print("精确率:", accuracy)

【4】、召回率

  实际为正例的样本中预测为正例的比例,也叫 查全率。

\[Recall = \frac{TP}{TP + FN} \]

import sklearn.metrics

labels = ["猫", "狗"]                                                           # 定义类别标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]              # 真实值
y_pred = ["猫", "猫", "猫", "狗", "狗", "狗", "狗", "猫", "猫", "狗"]              # 预测值

matrix = sklearn.metrics.confusion_matrix(y_true, y_pred, labels=labels)        # 计算混淆矩阵
accuracy = sklearn.metrics.recall_score(y_true, y_pred, pos_label="猫")         # 计算召回率
print("召回率:", accuracy)

【5】、F1 分数

  精确率和召回率的调和平均。

\[F1 Score = \frac{2 * Precision * Recall}{Precision + Recall} \]

import sklearn.metrics

labels = ["猫", "狗"]                                                           # 定义类别标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]              # 真实值
y_pred = ["猫", "猫", "猫", "狗", "狗", "狗", "狗", "猫", "猫", "狗"]              # 预测值

matrix = sklearn.metrics.confusion_matrix(y_true, y_pred, labels=labels)       # 计算混淆矩阵
f1 = sklearn.metrics.f1_score(y_true, y_pred, pos_label="猫")                  # 计算F1值
print("F1值:", f1)

  我们可以使用 sklearn.metrics.classification_report() 函数 获取分类评估报告

import sklearn.metrics

labels = ["猫", "狗"]                                                           # 定义类别标签
y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"]              # 真实值
y_pred = ["猫", "猫", "猫", "狗", "狗", "狗", "狗", "猫", "猫", "狗"]              # 预测值

matrix = sklearn.metrics.confusion_matrix(y_true, y_pred, labels=labels)        # 计算混淆矩阵
report = sklearn.metrics.classification_report(y_true, y_pred, labels=labels)   # 计算分类评估报告
print("分类评估报告:")
print(report)

【6】、ROC 曲线

  • 真实例率(TPR):实际为正例,被预测为正例的比例,即召回率,\(TPR = \frac{TP}{实际正例数} = \frac{TP}{TP + FN}\)
  • 假正例率(FPR):实际为负例,被预测为正例的比例,\(FPR = \frac{FP}{实际负例数} = \frac{FP}{FP + TN}\)
  • 阈值:根据阈值将概率转换为列表标签。

  ORC 曲线是评估二分类模型性能的工具,以 假正例率(FPR)为 横轴,以 真实例率(TPR)为 纵轴,展示不同阈值下模型的表现。绘制 ROC 曲线时,从高到低调整阈值,计算每个阈值的 TPR 和 FPR 并绘制所有阈值的点,形成 ROC 曲线。

【7】、AUC

  AUC 值代表 ROC 曲线下的面积,用于量化模型性能。AUC 值越大,模型区分正负类的能力越强,模型性能越好。AUC 值为 0.5 时,表示模型接近随机猜测,AUC 的值为 1 时,代表完美模型。

posted @ 2026-01-07 22:02  星光映梦  阅读(2)  评论(0)    收藏  举报