机器学习

1、机器学习概述

  • 机器学习(Machine Learning, ML)主要研究计算机系统对于特定任务的性能,逐步进行改善的算法和统计模型。通过输入海量训练数据对模型进行训练,使模型掌握数据所蕴含的潜在规律,进而对新输入的数据进行准确的分类或预测。
  • 机器学习是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸优化、算法复杂度理论等多门学科。

1.1 人工智能、机器学习与深度学习

  • 人工智能(AI)是计算机科学的一个广泛领域,目标是让机器具备类人的“智能”,包括自然语言处理(NLP)、计算机视觉、机器人技术、专家系统等等;具体实现手段多种多样,包括规则系统、符号逻辑、统计方法、机器学习等。

  • 机器学习是AI的一个子领域,核心是通过数据驱动,让计算机进行学习并改进性能,自动发现规律(模式),并利用这些规律进行预测或决策。

  • 深度学习是机器学习的一个子领域,基于多层神经网络处理复杂任务。

1.2 发展历史

1.2.1 早期探索:20世纪50-70年代

  • 人工智能研究处于“推理期”,人们认为只要能赋予机器逻辑推理能力,机器就能具有智能。
    • 图灵提出“图灵测试”,定义机器智能的基本标准。
    • 弗兰克·罗森布拉特提出感知机算法,这是最早的人工神经网络模型之一,用于线性分类问题。
    • 亚瑟·李·塞谬尔提出“机器学习”一词,并设计了一个用于玩跳棋的学习算法。

1.2.2 知识驱动与专家系统:20世纪70-80年代

  • 随着研究推进,人们逐渐认识到,仅具有逻辑推理能力是远远实现不了人工智能的。部分人认为,要使机器具有智能,就必须设法使机器拥有知识。计算机硬件逐步发展,人工智能的研究进入以规则为主的“知识驱动”阶段。
    • 出现众多专家系统,如MYCIN(用于医疗诊断)。
    • 引入了决策树(如ID3算法)。
    • 统计学习理论开始成形,例如贝叶斯定理在机器学习中的应用。

1.2.3 数据驱动与统计学习:20世纪80年代-21世纪

  • 专家系统面临“知识工程瓶颈”。机器学习成为一个独立的学科领域,各种机器学习技术百花初绽。基于神经网络的连接主义逐步发展,但其局限性也开始凸显。统计学习登场并占据主流,代表技术是支持向量机。互联网的发展带来了大量数据,统计学方法逐渐成为机器学习的核心。
    • 决策树算法得到进一步发展(如C4.5算法)。
    • 支持向量机(SVM)被提出,成为一种强大的分类工具。
    • 无监督学习方法开始成熟,例如K-means聚类。
    • 随机森林和Boosting方法(如AdaBoost)引入,提升了集成学习的性能。

1.2.4 深度学习崛起:21世纪初

  • 随着计算能力(特别是GPU的发展)和数据规模的快速增长,深度学习技术得以崛起。深度神经网络主导,突破了图像、语音和文本领域的性能瓶颈。
    • 深度信念网络(DBN)被提出,标志着深度学习研究的复兴。
    • AlexNet在ImageNet图像分类竞赛中获胜,证明了卷积神经网络(CNN)的强大性能。
    • 生成对抗网络(GAN)被提出,用于生成图像和其他生成任务。
    • Transformer架构在论文《Attention is All You Need》中提出,彻底改变了自然语言处理领域。

1.2.5 大模型与通用人工智能:2020年代

  • 深度学习进入规模化应用阶段,大语言模型和多模态模型的出现推动了机器学习的新高潮。模型参数规模空前,技术以通用性和泛化能力为目标。
    • 自然语言处理:如OpenAI的GPT系列模型、Google的BERT。
    • 多模态模型:如OpenAI的CLIP、DeepMind的Gato。
    • 自监督学习成为重要方向,降低了对人工标注数据的依赖。
    • 强化学习和深度学习结合,应用于AlphaGo、AlphaFold等项目。

1.3 机器学习应用领域

  • 今天,在计算机科学的诸多分支学科领域中,无论是多媒体、图形学,还是网络通信、软件工程,乃至体系结构、芯片设计,都能找到机器学习技术的身影,尤其是在计算机视觉、自然语言处理等计算机应用技术领域,机器学习已成为最重要的技术进步源泉之一,并为许多交叉学科提供了重要的技术支撑。

1.4 基本术语

  • 数据集(Data Set):多条记录的集合。
    • 训练集(Training Set):用于训练模型的数据。
    • 验证集(Validation Set):用于调节超参数的数据。
    • 测试集(Test Set):用于评估模型性能的数据。
  • 样本(Sample):数据集中的一条记录是关于一个事件或对象的描述,称为一个样本。
  • 特征(Feature):数据集中一列反映事件或对象在某方面的表现或性质的事项,称为特征或属性。
  • 特征向量(Feature Vector):将样本的所有特征表示为向量的形式,输入到模型中。
  • 标签(Label):监督学习中每个样本的结果信息,也称作目标值(target)。
  • 模型(Model):一个机器学习算法与训练后的参数集合,用于进行预测或分类。
  • 参数(Parameter):模型通过训练学习到的值,例如线性回归中的权重和偏置。
  • 超参数(Hyper Parameter):由用户设置的参数,不能通过训练自动学习,例如学习率、正则化系数等。

2、机器学习基本理论

2.1 机器学习三要素

  • 机器学习的方法一般主要由三部分构成:模型、策略和算法,可以认为:机器学习方法 = 模型 + 策略 + 算法
    • 模型(model):总结数据的内在规律,用数学语言描述的参数系统。
    • 策略(strategy):选取最优模型的评价准则。
    • 算法(algorithm):选取最优模型的具体方法。

2.2 机器学习方法分类

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

    • 通常分类:按照有无监督,机器学习可以分为 有监督学习无监督学习半监督学习,除此之外还有 强化学习
    • 按模型分类:根据模型性质,可以分为概率模型/非概率模型,线性/非线性模型等。
    • 按学习技巧分类:根据算法基于的技巧,可以分为贝叶斯学习、核方法等。
  • 各种类型的机器学习方法可以用下图汇总展示:

2.3 建模流程

  • 我们可以以监督学习为例,考察一下机器学习的具体过程。

  • 可以看到,机器学习是由数据驱动的,核心是利用数据来“训练模型”;模型训练的结果需要用一定的方法来进行评估、优化,最终得到一个成熟的学习模型;最后就可以用这个模型来进行预测和解决问题了。

  • 总结监督学习建模的整体流程如下:

2.4 特征工程

2.4.1 什么是特征工程

  • 特征工程(Feature Engineering)是机器学习过程中非常重要的一步,指的是通过对原始数据的处理、转换和构造,生成新的特征或选择有效的特征,从而提高模型的性能。简单来说,特征工程是将原始数据转换为可以更好地表示问题的特征形式,帮助模型更好地理解和学习数据中的规律。优秀的特征工程可以显著提高模型的表现;反之,忽视特征工程可能导致模型性能欠佳。
  • 实际上,特征工程是一个迭代过程。特征工程取决于具体情境。它需要大量的数据分析和领域知识。其中的原因在于,特征的有效编码可由所用的模型类型、预测变量与输出之间的关系以及模型要解决的问题来确定。在此基础上,辅以不同类型的数据集(如文本与图像)则可能更适合不同的特征工程技术。因此,要具体说明如何在给定的机器学习算法中最好地实施特征工程可能并非易事。

2.4.2 特征工程的内容

特征选择

  • 从原始特征中挑选出与目标变量关系最密切的特征,剔除冗余、无关或噪声特征。这样可以减少模型的复杂度、加速训练过程、并减少过拟合的风险。
  • 特征选择不会创建新特征,也不会改变数据结构。
    • 过滤法(Filter Method):基于统计测试(如卡方检验、相关系数、信息增益等)来评估特征与目标变量之间的关系,选择最相关的特征。
    • 包裹法(Wrapper Method):使用模型(如递归特征消除 RFE)来评估特征的重要性,并根据模型的表现进行特征选择。
    • 嵌入法(Embedded Method):使用模型本身的特征选择机制(如决策树的特征重要性,L1正则化的特征选择)来选择最重要的特征。
  • 使用模型本身的特征选择机制(如决策树的特征重要性,L1正则化的特征选择)来选择最重要的特征。

特征转换

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

特征构造

  • 特征构造是基于现有的特征创造出新的、更有代表性的特征。通过组合、转换、或者聚合现有的特征,形成能够更好反映数据规律的特征。
    • 交互特征:将两个特征组合起来,形成新的特征。例如,两个特征的乘积、和或差等。例如,将年龄与收入结合创建新的特征,可能能更好地反映某些模式。
    • 统计特征:从原始特征中提取统计值,例如求某个时间窗口的平均值、最大值、最小值、标准差等。例如,在时间序列数据中,你可以从原始数据中提取每个小时、每日的平均值。
    • 日期和时间特征:从日期时间数据中提取如星期几、月份、年份、季度等特征。例如,将“2000-01-01”转换为“星期几”、“是否节假日”、“月初或月末”等特征。

特征降维

  • 当数据集的特征数量非常大时,特征降维可以帮助减少计算复杂度并避免过拟合。通过降维方法,可以在保持数据本质的情况下减少特征的数量。
    • 主成分分析(PCA):通过线性变换将原始特征映射到一个新的空间,使得新的特征(主成分)尽可能地保留数据的方差。
    • 线性判别分析(LDA):一种监督学习的降维方法,通过最大化类间距离与类内距离的比率来降维。
    • t-SNE(t-Distributed Stochastic Neighbor Embedding,t分布随机近邻嵌入):一种非线性的降维技术,特别适合可视化高维数据。
    • 自编码器(Auto Encoder):一种神经网络模型,通过压缩编码器来实现数据的降维。

2.4.3 常用方法

  • 对于一个模型来说,有些特征可能很关键,而有些特征可能用处不大。例如:
    • 某个特征取值较接近,变化很小,可能与结果无关。
    • 某几个特征相关性较高,可能包含冗余信息。
  • 因此,特征选择 在特征工程中是最基本、也最常见的操作。
  • 另外,在训练模型时有时也会遇到维度灾难,即特征数量过多。我们希望能在确保不丢失重要特征的前提下减少维度的数量,来降低训练模型的难度。所以在特征工程中,也经常会用到 特征降维 方法。

低方差过滤法

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    import numpy as np

    a = np.random.randn(100)
    print(a)
    # [ 0.36070071 2.13677203 1.36325329 -0.13294458 -0.0291686 -1.39060855
    # -1.94047182 -0.26953573 0.20210686 -0.7488366 0.02925943 -0.6766111
    # -0.22382015 1.28580848 2.50254282 1.22893359 -0.42289164 -0.87403858
    # -1.08375844 -1.90671471 -0.28077206 2.11113692 -2.69431733 -2.19873193
    # 0.94834015 1.35261029 1.34603252 1.18352071 -0.00978027 -0.43100757
    # -1.83186438 -1.04143962 -0.20326731 0.57799395 -0.06992466 -0.50471889
    # -1.12988679 0.92054355 0.4784427 -0.6216461 -1.16105255 2.07836037
    # -0.3373523 -0.66154948 0.41616453 -0.13273978 0.66291685 -0.11542286
    # -1.29489923 1.58016826 -0.93814983 -0.334354 0.08855323 0.03161845
    # 0.53178167 0.74066863 -0.93790566 0.69363144 -1.28656605 -0.49343728
    # -1.31372354 0.43915715 -0.97967482 -2.33227904 0.02242427 -0.60166846
    # -1.22613915 0.94458355 1.27186545 0.48074901 0.56433469 0.3749764
    # 0.00879591 0.84437485 0.28556304 0.04181647 0.25236129 -0.88177821
    # 0.09093931 0.55143042 0.41606487 -1.45551165 0.33277088 1.37883755
    # -0.82011486 0.75852977 -0.36216 -0.88499524 -0.92904442 -1.21827916
    # 0.70428296 0.2111302 0.50777054 0.68297524 1.73762063 0.61350689
    # -0.662046 0.64482729 -0.79694172 -1.22450193]
    print(np.var(a)) # 1.0687160020487072

    # b = np.random.randn(100) * 0.1
    b = np.random.normal(5, 0.1, size=100)
    print(b)
    # [5.02835072 4.80153458 4.96437936 5.09487385 5.00430856 4.94465199
    # 5.02297087 4.97117829 5.01987533 5.02892215 5.00614799 4.91924709
    # 5.06845345 4.98759698 4.83052256 4.94943499 4.80819656 4.94067108
    # 4.78703413 4.85777577 5.00745159 5.16792735 5.00128005 4.92322498
    # 5.09006184 4.99194233 4.98268243 5.03799814 5.00678407 4.77921707
    # 5.01180777 5.0082991 5.0032086 5.00175865 4.94052617 5.19813908
    # 5.05234313 5.13875251 5.02566037 4.83249639 5.01383185 4.99895764
    # ...
    # 5.05975641 4.79942041 5.0145954 4.78239676 5.04168793 5.20514153
    # 5.15149965 4.81288819 5.01306767 5.0257892 5.11006633 4.94729474
    # 4.95018672 5.07050675 4.93735832 4.78981386]
    print(np.var(b)) # 0.010221098345522504

    # 构建数据样本
    X = np.vstack((a,b)).T
    print(X.shape) # (100, 2)

    from sklearn.feature_selection import VarianceThreshold
    # 低方差过滤:删除方差低于 0.01 的特征
    var_thresh = VarianceThreshold(threshold=0.01)
    X_filtered = var_thresh.fit_transform(X)
    print(X_filtered.shape) # (100, 1)

相关系数法

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

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

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

      例如,现有一数据集包括不同渠道广告投放金额与销售额。使用pandas.DataFrame.corrwith(method=”pearson”)计算各个特征与标签间的皮尔逊相关系数。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      import pandas as pd

      advertising = pd.read_csv("data/advertising.csv")
      print(advertising.head())
      # Unnamed: 0 TV Radio Newspaper Sales
      # 0 1 230.1 37.8 69.2 22.1
      # 1 2 44.5 39.3 45.1 10.4
      # 2 3 17.2 45.9 69.3 9.3
      # 3 4 151.5 41.3 58.5 18.5
      # 4 5 180.8 10.8 58.4 12.9
      print(advertising.shape) # (200, 5)
      # 去掉第一列
      advertising.drop(advertising.columns[0], axis=1, inplace=True)
      print(advertising.shape) # (200, 4)
      print(advertising.head())
      # TV Radio Newspaper Sales
      # 0 230.1 37.8 69.2 22.1
      # 1 44.5 39.3 45.1 10.4
      # 2 17.2 45.9 69.3 9.3
      # 3 151.5 41.3 58.5 18.5
      # 4 180.8 10.8 58.4 12.9

      # 去掉空值
      advertising.dropna(inplace=True)
      # X是三个特征构成的矩阵
      X = advertising.drop(columns="Sales", axis=1)
      y = advertising["Sales"]
      # 计算皮尔逊相关系数
      print(X.corrwith(y, method="pearson"))
      # TV 0.782224
      # Radio 0.576223
      # Newspaper 0.228299
      # dtype: float64

      使用pandas.DataFrame.corr(method=”pearson”)计算皮尔逊相关系数矩阵。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      import seaborn as sns
      import matplotlib.pyplot as plt

      # 计算皮尔逊相关系数矩阵
      corr_matrix = advertising.corr(method="pearson")
      print(corr_matrix)
      # TV Radio Newspaper Sales
      # TV 1.000000 0.054809 0.056648 0.782224
      # Radio 0.054809 1.000000 0.354104 0.576223
      # Newspaper 0.056648 0.354104 1.000000 0.228299
      # Sales 0.782224 0.576223 0.228299 1.000000
      # 可视化热力图
      sns.heatmap(corr_matrix, annot=True, cmap="coolwarm", fmt=".2f")
      plt.title("Feature Correlation Matrix")
      plt.show()
      image-20250712141547220
    • 斯皮尔曼相关系数:斯皮尔曼相关系数(Spearman’s Rank Correlation Coefficient)的定义是等级变量之间的皮尔逊相关系数。用于衡量两个变量之间的单调关系,即当一个变量增加时,另一个变量是否总是增加或减少(不要求是线性关系)。适用于非线性关系或数据不符合正态分布的情况。

      • 是两个变量的等级之差。
      • n是样本数。
      • 斯皮尔曼相关系数的取值范围为[-1,1]:
        • P = 1:完全正相关(一个变量增加,另一个变量也总是增加)。
        • P = -1:完全负相关(一个变量增加,另一个变量总是减少)。
        • P = 0:无相关性。

      例如,现有一组每周学习时长与数学考试成绩的数据:

      按数值由小到大排出X、y的等级,并计算等级差:

      使用pandas.DataFrame.corrwith(method=”spearman”)计算斯皮尔曼相关系数。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      import pandas as pd

      # 每周学习时长
      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"))
      # 0 0.987879
      # dtype: float64
    • 主成分分析(PCA):主成分分析(Principal Component Analysis,PCA)是一种常用的降维技术,通过线性变换将高维数据投影到低维空间,同时保留数据的主要变化模式。

      使用sklearn.decomposition.PCA进行主成分分析。参数n_components若为小数则表示保留多少比例的信息,为整数则表示保留多少个维度。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      import numpy as np
      import matplotlib.pyplot as plt
      from sklearn.decomposition import PCA
      from sklearn.preprocessing import StandardScaler

      n_samples = 1000
      # 第1个主成分方向
      component1 = np.random.normal(0, 1, n_samples)
      # 第2个主成分方向
      component2 = np.random.normal(0, 0.2, n_samples)
      # 第3个方向(噪声,方差较小)
      noise = np.random.normal(0, 0.1, n_samples)
      # 构造3维数据
      X = np.vstack([component1 - component2, component1 + component2, component2 + noise]).T
      print(X.shape) # (1000, 3)

      # 标准化
      scaler = StandardScaler()
      X_standardized = scaler.fit_transform(X)

      # 应用PCA,将3维数据降维到2维
      pca = PCA(n_components=2)
      X_pca = pca.fit_transform(X_standardized)

      # 可视化
      # 转换前的3维数据可视化
      fig = plt.figure(figsize=(12, 4))
      # 代表子图有一行两列,操作子图1
      ax1 = fig.add_subplot(121, projection="3d")
      ax1.scatter(X[:, 0], X[:, 1], X[:, 2], c="g")
      ax1.set_title("Before PCA (3D)")
      ax1.set_xlabel("Feature 1")
      ax1.set_ylabel("Feature 2")
      ax1.set_zlabel("Feature 3")
      # 转换后的2维数据可视化
      ax2 = fig.add_subplot(122)
      ax2.scatter(X_pca[:, 0], X_pca[:, 1], c="g")
      ax2.set_title("After PCA (2D)")
      ax2.set_xlabel("Principal Component 1")
      ax2.set_ylabel("Principal Component 2")
      plt.show()

2.5 模型评估和模型选择(重点)

2.5.1 损失函数

  • 对于模型一次预测结果的好坏,需要有一个度量标准。

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

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

    • 0-1损失函数:

    • 平方损失函数:

    • 绝对损失函数:

    • 对数似然损失函数:

2.5.2 经验误差

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

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

  • 类似地,在测试数据集上平均误差,被称为测试误差或者 泛化误差(generalization error)。

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

2.5.3 欠拟合与过拟合

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

  • 欠拟合(Underfitting):是指模型在训练数据上表现不佳,无法很好地捕捉数据中的规律。这样的模型不仅在训练集上表现不好,在测试集上也同样表现差。

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

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

    • 欠拟合:模型在训练集和测试集上误差都比较大。模型过于简单,高偏差。
    • 过拟合:模型在训练集上误差较小,但在测试集上误差较大。模型过于复杂,高方差。
  • 产生原因与解决办法:

    • 欠拟合:
      • 产生原因:
        • 模型复杂度不足:模型过于简单,无法捕捉数据中的复杂关系。
        • 特征不足:输入特征不充分,或者特征选择不恰当,导致模型无法充分学习数据的模式。
        • 训练不充分:训练过程中迭代次数太少,模型没有足够的时间学习数据的规律。
        • 过强的正则化:正则化项设置过大,强制模型过于简单,导致模型无法充分拟合数据。
      • 解决办法:
        • 增加模型复杂度:选择更复杂的模型。
        • 增加特征或改进特征工程:添加更多的特征或通过特征工程来创造更有信息量的特征。
        • 增加训练时间:增加训练的迭代次数,让模型有更多机会去学习。
        • 减少正则化强度:如果使用了正则化,尝试减小正则化的权重,以让模型更灵活。
    • 过拟合:
      • 产生原因:
        • 模型复杂度过高:模型过于复杂,参数太多。
        • 训练数据不足:数据集太小,模型能记住训练数据的细节,但无法泛化到新数据。
        • 特征过多:特征太多,模型可能会“记住”数据中的噪声,而不是学到真正的规律。
        • 训练过长:训练时间过长,导致模型学习到训练数据中的噪声,而非数据的真正规律。
      • 解决办法:
        • 减少模型复杂度:降低模型的参数数量、使用简化的模型或降维来减小模型复杂度。
        • 增加训练数据:收集更多数据,或通过数据增强来增加训练数据的多样性。
        • 使用正则化:引入L1、L2正则化,避免过度拟合训练数据。
        • 交叉验证:使用交叉验证技术评估模型在不同数据集上的表现,以减少过拟合的风险。
        • 早停:训练时,当模型的验证损失不再下降时,提前停止训练,避免过度拟合训练集。
  • 代码演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split # 划分训练集和测试集
    from sklearn.linear_model import LinearRegression # 线性回归模型
    from sklearn.metrics import mean_squared_error # 均方误差损失函数

    """
    机器学习训练模型步骤:
    1. 读取数据(生成数据)
    2. 划分训练集和测试集
    3. 定义损失函数和模型
    4. 训练模型
    5. 预测结果,计算误差(测试误差)
    """
    # 配置matplotlib中全局绘图参数
    plt.rcParams["font.sans-serif"] = ["Songti SC"]
    plt.rcParams["axes.unicode_minus"] = False

    # 由一个向量X,生成degree列(degree个特征)的矩阵,[x^1,x^2,x^3,...,x^degree]
    def polynomial(x, degree):
    """构成多项式,返回 [x^1,x^2,x^3,...,x^n]"""
    return np.hstack([x**i for i in range(1, degree + 1)])

    # 1. 读取数据(生成数据)
    # 生成随机数据,扩展成二维矩阵表示
    X = np.linspace(-3, 3, 300).reshape(-1, 1)
    print(X.shape) # (300, 1)
    # 基于sinX叠加随机噪声
    y = np.sin(X) + np.random.uniform(-0.5, 0.5, 300).reshape(-1, 1)
    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")
    # 2. 划分训练集和测试集
    x_train, x_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
    )
    # 3. 定义模型
    # 创建线性回归模型
    model = LinearRegression()

    ## 一、欠拟合:1阶线性拟合,1个参数,复杂度过低
    x_train1 = x_train
    x_test1 = x_test
    # 4. 训练模型
    model.fit(x_train1, y_train)
    # 5. 预测结果,计算误差(测试误差)
    # 调用均方误差函数, 传入y的真实值和测试值
    test_loss1 = mean_squared_error(y_test, model.predict(x_test1))
    train_loss1 = mean_squared_error(y_train, model.predict(x_train1))
    # 画出拟合曲线,并标出误差
    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阶,转换成5个特征的线性拟合,复杂度正好
    x_train2 = polynomial(x_train, 5)
    x_test2 = polynomial(x_test, 5)
    # 4. 训练模型
    model.fit(x_train2, y_train)
    # 5. 预测结果,计算误差(测试误差)
    # 调用均方误差函数, 传入y的真实值和测试值
    test_loss2 = mean_squared_error(y_test, model.predict(x_test2))
    train_loss2 = mean_squared_error(y_train, model.predict(x_train2))
    # 画出拟合曲线,并标出误差
    ax[1].plot(X, model.predict(polynomial(X, 5)), color="r")
    ax[1].text(-3, 1, f"测试集均方误差:{test_loss2:.4f}")
    ax[1].text(-3, 1.3, f"训练集均方误差:{train_loss2:.4f}")

    ## 三、过拟合:20阶,转换成20个特征的线性拟合,复杂度过高
    x_train3 = polynomial(x_train, 20)
    x_test3 = polynomial(x_test, 20)
    # 4. 训练模型
    model.fit(x_train3, y_train)
    # 5. 预测结果,计算误差(测试误差)
    # 调用均方误差函数, 传入y的真实值和测试值
    test_loss3 = mean_squared_error(y_test, model.predict(x_test3))
    train_loss3 = mean_squared_error(y_train, model.predict(x_train3))
    # 画出拟合曲线,并标出误差
    ax[2].plot(X, model.predict(polynomial(X, 20)), color="r")
    ax[2].text(-3, 1, f"测试集均方误差:{test_loss3:.4f}")
    ax[2].text(-3, 1.3, f"训练集均方误差:{train_loss3:.4f}")
    plt.show()

    print(model.coef_)
    # [[ 1.64846422e+00 -3.93182879e-01 -3.96825551e+00 1.14356572e-01
    # 6.79221600e+00 7.23549510e-01 -5.69464460e+00 -8.68033595e-01
    # 2.63658242e+00 4.41330881e-01 -7.25535591e-01 -1.23985102e-01
    # 1.21766459e-01 2.07128401e-02 -1.22357992e-02 -2.05330235e-03
    # 6.76560318e-04 1.11699638e-04 -1.58307663e-05 -2.57060080e-06]]
    print(model.intercept_)
    # [0.09051985]
    • 当多项式次数较低时,模型过于简单,拟合效果较差。
    • 当多项式次数增加后,模型复杂度适中,拟合效果较好,训练误差和测试误差均较低。
    • 当多项式次数继续增加,模型变得过于复杂,过度学习了噪声,导致训练误差较低而测试误差较高。

2.5.4 正则化

  • 正则化(Regularization)是一种在训练机器学习模型时,在损失函数中添加额外项,来惩罚过大的参数,进而限制模型复杂度、避免过拟合,提高模型泛化能力的技术。

  • 如在平方损失函数中加入正则化项

    • 原损失函数的目的:更好的拟合数据集。
    • 正则化项的目的:减小参数的大小,从而降低模型的复杂度。
  • 这里的正则化系数,用来表示惩罚项的权重。正则化系数不属于模型的参数,无法通过训练学习得到,需要在模型训练开始之前手动设置,这种参数被称为“超参数”。

  • 两者相互平衡,在模型的拟合能力(偏差)和复杂度(方差)之间找到最佳折中。

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

    • L1正则化(Lasso回归)。L1正则化在损失函数中加入参数的绝对值之和:

      • L1正则化通过惩罚模型参数的绝对值,使得部分权重趋近0甚至变为0。这会导致特征选择,即模型会自动“丢弃”一些不重要的特征。L1正则化有助于创建稀疏模型(即许多参数为0)。在解决回归问题时,使用L1正则化也被称为“Lasso回归”。
      • 超参数控制着正则化的强度。较大的值意味着强烈的正则化,会使模型更简单,可能导致欠拟合。而较小的值则会使模型更复杂,可能导致过拟合。
    • L2正则化(Ridge回归,岭回归)。L2正则化在损失函数中加入参数的平方之和:

      • L2正则化通过惩罚模型参数的平方,使得所有参数都变得更小,但不会将参数强行压缩为0。它会使得模型尽量平滑,从而防止过拟合。
      • 在解决回归问题时,使用L2正则化也被称为“岭回归”。
    • **ElasticNet正则化(弹性网络回归)**。ElasticNet正则化结合了L1和L2正则化,通过调整两个正则化项的比例来取得平衡,从而同时具备稀疏性和稳定性的优点。

      • ,决定L1和L2的权重。
  • 正则化案例。同样以使用多项式在上拟合为例,分别不使用正则化、使用L1正则化、使用L2正则化进行拟合。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.model_selection import train_test_split # 划分训练集和测试集
    from sklearn.linear_model import LinearRegression, Lasso, Ridge # 线性回归模型,Lasso回归,Ridge回归
    from sklearn.metrics import mean_squared_error # 均方误差损失函数

    """
    机器学习训练模型步骤:
    1. 读取数据(生成数据)
    2. 划分训练集和测试集
    3. 定义损失函数和模型
    4. 训练模型
    5. 预测结果,计算误差(测试误差)
    """
    # 配置matplotlib中全局绘图参数
    plt.rcParams["font.sans-serif"] = ["Songti SC"]
    plt.rcParams["axes.unicode_minus"] = False

    # 由一个向量X,生成degree列(degree个特征)的矩阵,[x^1,x^2,x^3,...,x^degree]
    def polynomial(x, degree):
    """构成多项式,返回 [x^1,x^2,x^3,...,x^n]"""
    return np.hstack([x**i for i in range(1, degree + 1)])

    # 1. 读取数据(生成数据)
    # 生成随机数据,扩展成二维矩阵表示
    X = np.linspace(-3, 3, 300).reshape(-1, 1)
    print(X.shape) # (300, 1)
    # 基于sinX叠加随机噪声
    y = np.sin(X) + np.random.uniform(-0.5, 0.5, 300).reshape(-1, 1)
    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")
    # 2. 划分训练集和测试集
    x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    ## 过拟合:20阶,转换成20个特征的线性拟合,复杂度过高
    x_train = polynomial(x_train, 20)
    x_test = polynomial(x_test, 20)

    # 3. 定义模型
    # 创建线性回归模型
    model = LinearRegression()
    # 4. 训练模型
    model.fit(x_train, y_train)
    # 5. 预测结果,计算误差(测试误差)
    # 调用均方误差函数, 传入y的真实值和测试值
    test_loss1 = mean_squared_error(y_test, model.predict(x_test))
    # 画出拟合曲线,并标出误差
    ax[0, 0].plot(X, model.predict(polynomial(X, 20)), color="r")
    ax[0, 0].text(-3, 1, f"测试集均方误差:{test_loss1:.4f}")
    # 画出所有系数的柱状图
    ax[1, 0].bar(np.arange(20), model.coef_.reshape(-1))

    # L1正则化 -> Lasso回归
    lasso = Lasso(alpha=0.01)
    # 4. 训练模型
    lasso.fit(x_train, y_train)
    # 5. 预测结果,计算误差(测试误差)
    # 调用均方误差函数, 传入y的真实值和测试值
    test_loss2 = mean_squared_error(y_test, lasso.predict(x_test))
    # 画出拟合曲线,并标出误差
    ax[0, 1].plot(X, lasso.predict(polynomial(X, 20)), color="r")
    ax[0, 1].text(-3, 1, f"测试集均方误差:{test_loss2:.4f}")
    ax[0, 1].text(-3, 1.3, "Lasso回归")
    # 画出所有系数的柱状图
    ax[1, 1].bar(np.arange(20), lasso.coef_.reshape(-1))

    # L2正则化 -> Ridge回归
    ridge = Ridge(alpha=1)
    # 4. 训练模型
    ridge.fit(x_train, y_train)
    # 5. 预测结果,计算误差(测试误差)
    # 调用均方误差函数, 传入y的真实值和测试值
    test_loss3 = mean_squared_error(y_test, ridge.predict(x_test))
    # 画出拟合曲线,并标出误差
    ax[0, 2].plot(X, ridge.predict(polynomial(X, 20)), color="r")
    ax[0, 2].text(-3, 1, f"测试集均方误差:{test_loss3:.4f}")
    ax[0, 2].text(-3, 1.3, "Ridge回归")
    # 画出所有系数的柱状图
    ax[1, 2].bar(np.arange(20), ridge.coef_.reshape(-1))

    plt.show()

2.5.5 交叉验证

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

    • 简单交叉验证(Hold-Out Validation):将数据划分为训练集和验证集(如70%训练,30%验证)。结果受单次划分影响较大,可能高估或低估模型性能。

    • k折交叉验证(k-Fold Cross-Validation):将数据均匀分为k个子集(称为“折”),每次用k−1折训练,剩余1折验证,重复k次后取平均性能。充分利用数据,结果更稳定。

    • 留一交叉验证(Leave-One-Out,LOO):每次仅留一个样本作为验证集,其余全部用于训练,重复直到所有样本都被验证一次。适用于小数据集,计算成本极高。

2.6 模型求解算法

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

  • 这其实就是求解一个 最优化问题。代入训练集所有数据,要求最小值的目标函数就是模型中参数的函数。

  • 具体求解的算法,可以利用数学公式直接计算解析解,也可以使用迭代算法。

2.6.1 解析法

  • 如果模型损失函数的最小值可以通过数学公式进行严格推导,得到一个解析解,那么就直接得到了最优模型的全部参数。这种方法称作解析法。

  • 特点

    • 适用条件:目标函数必须可导,且导数方程有解析解。
    • 优点:直接且精确;计算高效;
    • 缺点:适用条件较为苛刻;特征维度较大时,矩阵求逆计算复杂度极高。
  • 应用示例

    • 线性回归问题:可以采用“最小二乘法”求得解析解。

    • L2正则化(Ridge回归,岭回归),可以得到解析解如下:

      由于加入的对角矩阵就像一条“山岭”,因此L2正则化也称作“岭回归”。

2.6.2 梯度下降法(重点)

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

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

    • 这里的是参数取值为时损失函数L的梯度,是每次迭代的“步长”,被称为“学习率”。学习率也是一个常见的超参数,需要手动设置,选择不当会导致收敛失败。
  • 特点:

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

    • 优点:适用性广;计算简单;

    • 缺点:收敛速度慢;可能陷入局部最优。

  • 分类:

    • 批量梯度下降(Batch Gradient Descent,BGD):每次迭代使用全部训练数据计算梯度。

      • 优点:稳定收敛。

      • 缺点:计算开销大。

    • 随机梯度下降(Stochastic Gradient Descent,SGD):每次迭代随机选取一个样本计算梯度。

      • 优点:速度快,适合大规模数据。

      • 缺点:梯度更新方向不稳定,优化过程震荡较大,可能难以收敛。

    • 小批量梯度下降(Mini-batch Gradient Descent,MBGD):每次迭代使用一小批样本(如32、64个)计算梯度。

      • 平衡了BGD的稳定性和SGD的速度,是最常用的方法。
  • 梯度下降法计算步骤:

    • 初始化参数:随机选择初始参数。
    • 计算梯度:在当前参数下,计算损失函数的梯度。
    • 更新参数:沿负梯度方向调整参数。
    • 重复迭代:直到满足停止条件(如梯度接近零、达到最大迭代次数等)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    # 目标函数
    def f(x):
    return x ** 2 + 6 * x + 4

    # 梯度函数(导函数)
    def gradient(x):
    return 2 * x + 6

    # 用列表记录所有点的轨迹
    x_list = []
    y_list = []

    # 1. 初始化参数(自变量)和学习率
    x = 1
    alpha = 0.1

    # 4. 重复迭代100次
    for i in range(100):
    y = f(x)
    print(f"x={x}\ty={y}")
    x_list.append(x)
    y_list.append(y)

    # 2. 计算梯度
    grad = gradient(x)

    # 3. 更新参数
    x = x - alpha * grad

    import matplotlib.pyplot as plt
    import numpy as np

    x = np.arange(-5, 1, 0.01)

    plt.plot(x, f(x))
    plt.plot(x_list, y_list, 'r') # 画出点的移动轨迹
    plt.scatter(x_list, y_list, color='red')

    plt.show()

    另一个单变量函数为例,介绍梯度下降法的代码实现。设,求x为何值时,。目标函数,原问题等价于求x为和值时目标函数取得最小值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    # 目标函数
    def J(x):
    return (x ** 2 - 2) ** 2

    # 梯度函数(导函数)
    def gradient(x):
    return 4 * x**3 - 8 * x

    # 用列表记录所有点的轨迹
    x_list = []
    y_list = []

    # 1. 初始化参数(自变量)和学习率
    x = 1
    alpha = 0.1

    # 4. 重复迭代100次
    for i in range(100):
    y = J(x)
    # # 当目标值小于1e-30时停止迭代
    # while (y:=J(x)) > 1e-30:
    print(f"x={x}\ty={y}")
    x_list.append(x)
    y_list.append(y)

    # 2. 计算梯度
    grad = gradient(x)

    # 3. 更新参数
    x = x - alpha * grad

    print(len(x_list))

    x = np.arange(0.9, 1.6, 0.01)

    fig, ax = plt.subplots(1, 2, figsize=(15, 5))
    ax[0].plot(x, J(x))
    ax[0].plot(x_list, y_list, 'r') # 画出点的移动轨迹
    ax[0].scatter(x_list, y_list, color='red')

    # 局部放大,去掉第一个点
    x_list = x_list[1:]
    y_list = y_list[1:]

    x = np.arange(1.399, 1.425, 0.001)
    ax[1].plot(x, J(x))
    ax[1].plot(x_list, y_list, 'r') # 画出点的移动轨迹
    ax[1].scatter(x_list, y_list, color='red')

    plt.show()

    当超参数alpha设置得更小例如0.01时:

  • 应用实例:

    • L1正则化(Lasso回归),梯度下降法求解的推导过程如下:

      可见L1正则化项的梯度是一个常数,当很小时会直接变成0,导致稀疏性。

    • L2正则化(Ridge回归,岭回归),梯度下降法求解的推导过程如下:

      可见L2正则化项的梯度是,相当于在每次更新时都对进行缩小,但不会直接变为0。

2.6.3 牛顿法和拟牛顿法(了解)

  • 牛顿法也是求解无约束最优化问题的常用方法,核心思想是利用目标函数的二阶导数信息,通过迭代逐渐逼近极值点。

    • 这里的表示损失函数L黑塞矩阵的逆在点的取值。
      • 优点:收敛速度快;精度高;
      • 缺点:计算复杂;可能发散。
    • 由于牛顿法中需要计算黑塞矩阵的逆,这一步比较复杂;所以可以考虑用一个n阶正定矩阵来近似代替它,这种方法称为“拟牛顿法”。
    • 牛顿法和拟牛顿法一般用于解决中小规模的凸优化问题。

2.7 模型评价指标

  • 对学习的泛化性能进行评估,不仅需要有效可行的实验估计方法,还需要有衡量模型泛化能力的评价指标,也叫做性能度量(performance measure)。

2.7.1 回归模型评价指标

  • 模型的评价指标用于衡量模型在训练集或测试集上的性能,评估结果反映了模型预测的准确性和泛化能力。

  • 对于回归问题,最常用的性能度量是“均方误差” (Mean Squared Error,MSE)。

    • 平均绝对误差(MAE):MAE对异常值不敏感,解释直观。适用于数据包含异常值的场景。

    • 均方误差(MSE):MSE会放大较大误差,对异常值敏感。适用于需要惩罚大误差的场景。

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

    • R²(决定系数):衡量模型对目标变量的解释能力,越接近1越好,对异常值敏感。

2.7.2 分类模型评价指标

  • 对于分类问题,最常用的指标就是“准确率”(Accuracy),它定义为分类器对测试集正确分类的样本数与总样本数之比。此外还有一系列常用的评价指标。

    • 混淆矩阵:混淆矩阵(Confusion Matrix)是用于评估分类模型性能的工具,展示了模型预测结果与实际标签的对比情况。对于二分类问题,混淆矩阵是一个2×2矩阵:

      例如,有10个样本。6个是猫,4个是狗。假设以猫为正例,模型预测对了5个猫,2个狗。

      使用sklearn.metrics.confusion_matrix查看混淆矩阵:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      import pandas as pd
      import seaborn as sns
      from sklearn.metrics import confusion_matrix

      labels = ["猫", "狗"] # 分类标签

      y_true = ["猫", "猫", "猫", "猫", "猫", "猫", "狗", "狗", "狗", "狗"] # 真实值
      y_pred = ["猫", "猫", "狗", "猫", "猫", "猫", "猫", "猫", "狗", "狗"] # 预测值

      matrix = confusion_matrix(y_true, y_pred, labels=labels)
      print(matrix)
      # [[5 1]
      # [2 2]]
      print(pd.DataFrame(matrix, columns=labels, index=labels))
      # 猫 狗
      # 猫 5 1
      # 狗 2 2
      sns.heatmap(matrix, annot=True, fmt='d', cmap='Greens')
    • 准确率(Accuracy):正确预测的比例。

      上述案例中,准确率 = (5+2)/10 = 0.7。

      1
      2
      3
      4
      5
      from sklearn.metrics import accuracy_score

      accuracy = accuracy_score(y_true, y_pred)

      print(accuracy) # 0.7
    • 精确率(Precision):预测为正例的样本中实际为正例的比例,也叫查准率。

      上述案例中,精确率 = 5/(5+2) = 0.7143。

      1
      2
      3
      4
      5
      from sklearn.metrics import precision_score

      precision = precision_score(y_true, y_pred, pos_label="猫")

      print(precision) # 0.7142857142857143
    • 召回率(Recall):实际为正类的样本中预测为正类的比例,也叫查全率。

      上述案例中,召回率 = 5/(5+1) = 0.8333。

      1
      2
      3
      4
      5
      from sklearn.metrics import recall_score

      recall = recall_score(y_true, y_pred, pos_label="猫")

      print(recall) # 0.8333333333333334
    • F1分数(F1 Score):精确率和召回率的调和平均。

      上述案例中,F1分数 =

      1
      2
      3
      4
      5
      from sklearn.metrics import f1_score

      f1 = f1_score(y_true, y_pred, pos_label="猫")

      print(f1) # 0.7692307692307693

      在代码中,我们可通过sklearn.metrics.classification_report生成分类任务的评估报告,包括精确率、召回率、F1分数等。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      from sklearn.metrics import classification_report

      print(classification_report(y_true, y_pred, labels=labels, target_names=None))
      # y_true:真实标签
      # y_pred:预测的标签
      # labels:可选,指定需要计算的类别列表(默认计算所有出现过的类别)
      # target_names:可选,类别名称(默认使用 labels 指定的类别号)

      # precision recall f1-score support

      # 猫 0.71 0.83 0.77 6
      # 狗 0.67 0.50 0.57 4

      # accuracy 0.70 10
      # macro avg 0.69 0.67 0.67 10
      # weighted avg 0.70 0.70 0.69 10
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      from sklearn.datasets import make_classification   # 自动生成分类数据集
      from sklearn.model_selection import train_test_split # 划分数据集
      from sklearn.linear_model import LogisticRegression # 逻辑回归(分类模型)
      from sklearn.metrics import classification_report


      # 1. 生成分类数据
      X, y = make_classification(n_samples=1000, n_features=20, n_classes=2,random_state=42)

      # 2. 划分数据集为训练集和测试集
      X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,random_state=42)

      # 3. 定义模型:逻辑回归
      model = LogisticRegression()

      # 4. 训练模型
      model.fit(X_train, y_train)

      # 5. 预测
      y_pred = model.predict(X_test)

      # 6. 生成分类报告
      report = classification_report(y_test, y_pred)
      print(report)
      # precision recall f1-score support

      # 0 0.82 0.88 0.85 145
      # 1 0.88 0.83 0.85 155

      # accuracy 0.85 300
      # macro avg 0.85 0.85 0.85 300
      # weighted avg 0.85 0.85 0.85 300
    • ROC曲线:ROC曲线(Receiver Operating Characteristic Curve,受试者工作特征)是评估二分类模型性能的工具,以假正例率(FPR)为横轴,以真正例率(TPR)为纵轴,展示不同阈值下模型的表现。绘制ROC曲线时,从高到低调整阈值,计算每个阈值的TPR和FPR并绘制所有阈值的点,形成ROC曲线。

      • 真正例率(TPR):实际为正例,被预测为正例的比例,即召回率。
      • 假正例率(FPR):实际为负例,被预测为正例的比例。
      • 阈值(Threshold):根据阈值将概率转换为类别标签。
    • AUC值:AUC值代表ROC曲线下的面积,用于量化模型性能。AUC值越大,模型区分正负类的能力越强,模型性能越好。AUC值=0.5表示模型接近随机猜测,AUC值=1代表完美模型。可通过sklearn.metrics.roc_auc_score计算AUC值。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      # 增加 AUC 指标
      from sklearn.metrics import roc_auc_score

      # 预测属于哪个类的概率值
      y_pred_proba = model.predict_proba(X_test)[:,1]
      # print(y_pred_proba)

      auc = roc_auc_score(y_test, y_pred_proba)

      # print(report)
      print(auc) # 0.9141
    • 绘制ROC曲线

      • 假设一个二分类模型的真实标签和模型输出概率如下:

      • 调整阈值,计算TPR和FPR:

      • 根据坐标点绘制ROC曲线:

3、KNN算法

3.1 KNN算法介绍

  • K近邻算法(K-Nearest Neighbors,KNN)是一种基本的分类与回归方法,属于监督学习算法。其核心思想是通过计算给定样本与数据集中所有样本的距离,找到距离最近的K个样本,然后根据这K个样本的类别或值来预测当前样本的类别或值。

3.1.1 工作原理

  • 计算距离:计算待分类样本与训练集中每个样本的距离。
  • 选择K个近邻:根据计算的距离,选择距离最近的K个样本。投票或平均:
    • 分类任务:统计K个近邻各类别的数量,将待分类样本归为数量最多的类别。
    • 回归任务:取K个近邻的平均值作为预测结果。

3.1.2 关键参数

  • 距离度量方法:选择合适的距离度量方法,常见的有欧氏距离、曼哈顿距离、切比雪夫距离、闵可夫斯基距离等。

  • K值:K值的选择对结果影响很大。K值过小容易过拟合,K值过大则可能欠拟合。

3.1.3 优缺点

  • KNN优点:

    • 简单直观,易于理解和实现。
    • 无需训练过程,直接利用训练数据进行预测。
  • KNN缺点:

    • 计算量大,尤其是训练集较大时。
    • 对噪声数据较敏感。

3.1.4 API使用

  • 分类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from sklearn.neighbors import KNeighborsClassifier
    import numpy as np

    # 定义数据
    X = np.array([[2, 1], [3, 1], [1, 4], [2, 6]])
    y = np.array([0, 0, 0, 1]) # 二分类

    # 定义模型
    knn = KNeighborsClassifier(n_neighbors=2, weights='distance')
    # 训练模型
    knn.fit(X, y)
    # 对新数据进行预测
    print(knn.predict([[4, 9]])) # [1]

    import matplotlib.pyplot as plt
    # 画图
    X1 = X[y==0]
    X2 = X[y==1]
    plt.scatter(X1[:, 0], X1[:, 1])
    plt.scatter(X2[:, 0], X2[:, 1])
    plt.scatter(4, 9)

    plt.show()
  • 回归

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from sklearn.neighbors import KNeighborsRegressor

    X = [[2, 1], [3, 1], [1, 4], [2, 6]]
    y = [0.5, 1.5, 4, 3.2]

    # 创建模型
    knn = KNeighborsRegressor(n_neighbors=2)

    # 训练
    knn.fit(X, y)

    print(knn.predict([[4, 9]])) # [3.6]

3.2 常见距离度量方法(了解)

3.2.1 欧氏距离

  • 欧几里得距离(Euclidean Distance)是指连接两点的线段的长度。

    • 之间的欧氏距离

3.2.2 曼哈顿距离

  • 曼哈顿距离(Manhattan Distance)是两点在标准坐标系上的绝对轴距之和。

    • 之间的曼哈顿距离

    • 曼哈顿距离得名于纽约曼哈顿的街道布局。由于曼哈顿的街道多为规则的网格状,车辆只能沿水平和垂直方向行驶,无法直接斜穿。因此,两点之间的实际行驶距离是沿街道行走的距离,而非直线距离。

3.2.3 切比雪夫距离

  • 切比雪夫距离(Chebyshev Distance)是两点各坐标数值差的最大值。

    • 之间的切比雪夫距离

    • 在国际象棋中,国王可以横向、纵向或斜向移动一格。其从起点到终点的最少步数就等于两点之间的切比雪夫距离。

3.2.4 闵可夫斯基距离

  • 闵可夫斯基距离(Minkowski Distance)是一种用于度量多维空间中两点间距离的通用方法,点之间的闵可夫斯基距离。p越小,对多个维度的差异更敏感;p越大,更关注最大维度的差异。
  • 通过调整参数p,闵可夫斯基距离可以退化为以下经典距离:
    • 曼哈顿距离:
    • 欧氏距离:
    • 切比雪夫距离:

3.3 归一化与标准化

3.3.1 归一化

  • 定义:将数据按比例缩放到一个固定范围(通常是[0,1]或[-1,1])。

  • 目的

    • 消除量纲差异:不同特征的单位或量纲可能差异巨大(例如身高以米为单位,体重以千克为单位),归一化可消除这种差异,避免模型被大范围特征主导。
    • 加速模型收敛:对于梯度下降等优化算法,归一化后特征处于相近的尺度,优化路径更平滑,收敛速度更快。
    • 适配特定模型需求:某些模型(如神经网络、K近邻、SVM)对输入数据的范围敏感,归一化能显著提升其性能。
  • 场景:归一化不改变原始分布形状,但对异常值比较敏感。当数据分布有明显边界(如图像像素值、文本词频),或模型对输入范围敏感时可以优先考虑归一化。

  • API使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from sklearn.preprocessing import MinMaxScaler

    X = [[2, 1], [3, 1], [1, 4], [2, 6]]

    scaler = MinMaxScaler(feature_range=(-1, 1))

    X_scaled = scaler.fit_transform(X)

    print(X_scaled)
    # [[ 0. -1. ]
    # [ 1. -1. ]
    # [-1. 0.2]
    # [ 0. 1. ]]

3.3.2 标准化

  • 定义:将数据调整为均值为0、标准差为1的标准正态分布。

    • 其中是平均值,是标准差。
  • 目的

    • 适应数据分布:将数据转换为均值为0、标准差为1的分布,适合假设数据服从正态分布的模型(如线性回归、逻辑回归)。
    • 稳定模型训练:标准化后的数据对异常值的敏感度较低(相比归一化),鲁棒性更强。
    • 统一特征尺度:与归一化类似,标准化也能消除量纲差异,但更关注数据的统计分布而非固定范围。
  • 场景:大多数场景下标准化更通用,尤其是数据分布未知或存在轻微异常值时。

  • API使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    from sklearn.preprocessing import StandardScaler

    # 标准化
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X)

    print(X_scaled)
    # [[ 0. -0.94280904]
    # [ 1.41421356 -0.94280904]
    # [-1.41421356 0.47140452]
    # [ 0. 1.41421356]]

    # 验证
    import numpy as np
    X = np.array(X)

    # 计算均值和标准方差
    mean = np.mean(X, axis=0)
    std = np.std(X, axis=0)

    print((X - mean)/std)
    # [[ 0. -0.94280904]
    # [ 1.41421356 -0.94280904]
    # [-1.41421356 0.47140452]
    # [ 0. 1.41421356]]

3.4 案例:心脏病预测

3.4.1 数据集说明

  • Heart Disease数据集https://www.kaggle.com/datasets/johnsmith88/heart-disease-dataset。
    • 年龄:连续值
    • 性别:0-女,1-男
    • 胸痛类型:1-典型心绞痛,2-非典型心绞痛,3-非心绞痛,4-无症状
    • 静息血压:连续值,单位mmHg
    • 胆固醇:连续值,单位mg/dl
    • 空腹血糖:1-大于120mg/dl,2-小于等于120mg/dl
    • 静息心电图结果:0-正常,1-ST-T异常,2-可能左心室肥大
    • 最大心率:连续值
    • 运动性心绞痛:1-有,2-无
    • 运动后的ST下降:连续值
    • 峰值ST段的斜率:1-向上,2-水平,3-向下
    • 主血管数量:0到3
    • 地中海贫血:一种先天性贫血,3-正常,6-固定缺陷,7-可逆缺陷
    • 是否患有心脏病:标签,0-否,1-是

3.4.2 加载数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import pandas as pd
from sklearn.model_selection import train_test_split, GridSearchCV # 划分数据集,网格搜索交叉验证
from sklearn.compose import ColumnTransformer # 列转换,做特征转换
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import OneHotEncoder, StandardScaler # 独热编码和标准化

import joblib # 用来保存对象到文件和加载对象

# 1. 加载数据集
heart_disease_data = pd.read_csv("data/heart_disease.csv")

# 数据清洗
heart_disease_data.dropna(inplace=True)

heart_disease_data.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 1025 entries, 0 to 1024
# Data columns (total 14 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 年龄 1025 non-null int64
# 1 性别 1025 non-null int64
# 2 胸痛类型 1025 non-null int64
# 3 静息血压 1025 non-null int64
# 4 胆固醇 1025 non-null int64
# 5 空腹血糖 1025 non-null int64
# 6 静息心电图结果 1025 non-null int64
# 7 最大心率 1025 non-null int64
# 8 运动性心绞痛 1025 non-null int64
# 9 运动后的ST下降 1025 non-null float64
# 10 峰值ST段的斜率 1025 non-null int64
# 11 主血管数量 1025 non-null int64
# 12 地中海贫血 1025 non-null int64
# 13 是否患有心脏病 1025 non-null int64
# dtypes: float64(1), int64(13)
# memory usage: 112.2 KB

print(heart_disease_data.head())
# 年龄 性别 胸痛类型 静息血压 胆固醇 空腹血糖 静息心电图结果 最大心率 运动性心绞痛 运动后的ST下降 峰值ST段的斜率 主血管数量 地中海贫血 是否患有心脏病
# 0 52 1 0 125 212 0 1 168 0 1.0 2 2 3 0
# 1 53 1 0 140 203 1 0 155 1 3.1 0 0 3 0
# 2 70 1 0 145 174 0 1 125 1 2.6 0 0 3 0
# 3 61 1 0 148 203 0 1 161 0 0.0 2 1 3 0
# 4 62 0 0 138 294 1 1 106 0 1.9 1 3 2 0

3.4.3 数据集划分

1
2
3
4
5
6
7
8
9
# 2. 数据集划分
# 定义特征
X = heart_disease_data.drop("是否患有心脏病", axis=1)
y = heart_disease_data["是否患有心脏病"]

x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)
# (717, 13) (308, 13) (717,) (308,)

3.4.4 特征工程

  • 特征转换。数据集中包含多种类型的特征:

    • 类别型特征(需要特殊处理)
      • 胸痛类型:4种分类(名义变量)
      • 静息心电图结果:3种分类(名义变量)
      • 峰值ST段的斜率:3种分类(有序变量)
      • 地中海贫血:4种分类(名义变量)
    • 数值型特征(可直接标准化):年龄、静息血压、胆固醇、最大心率、运动后的ST下降、主血管数量
    • 二元特征(保持原样):性别、空腹血糖、运动性心绞痛

    对于类别型特征,直接使用整数编码的类别特征会被算法视为有序数值,导致错误的距离计算(例如:会认为 胸痛类型=1 和 胸痛类型=2 之间的差异比 胸痛类型=1和 胸痛类型=3之间差异更小,而实际上它们都是类别)。使用 独热编码(One-Hot Encoding)可将类别特征转换为二元向量,消除虚假的顺序关系。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    from sklearn.preprocessing import StandardScaler, OneHotEncoder
    from sklearn.compose import ColumnTransformer

    # 3. 特征工程
    # 数值型特征
    numerical_features = ["年龄","静息血压","胆固醇","最大心率","运动后的ST下降","主血管数量"]
    # 类别型特征(多元分类)
    categorical_features = ["胸痛类型","静息心电图结果","峰值ST段的斜率","地中海贫血"]
    # 二元类别特征
    binary_features = ["性别","空腹血糖","运动性心绞痛"]

    # 创建列转换器
    transformer = ColumnTransformer(
    # (名称,操作,特征列表)
    transformers=[
    # 对数值型特征进行标准化
    ("num", StandardScaler(), numerical_features),
    # 对类别型特征进行独热编码,使用drop="first"避免多重共线性
    ("cat", OneHotEncoder(drop="first"), categorical_features),
    # 二元特征不进行处理
    ("bin", "passthrough", binary_features)
    ])

    # 执行特征转换
    x_train = transformer.fit_transform(x_train) # 计算训练集的统计信息并进行转换
    x_test = transformer.transform(x_test) # 使用训练集计算的信息对测试集进行转换

    print(x_train.shape, x_test.shape)
    # (717, 19) (308, 19)
  • 避免多重共线性。drop=”first”是独热编码中的一个参数,它的核心目的是避免多重共线性(Multicollinearity)。

    • 多重共线性是指特征之间存在高度线性相关关系的现象。例如特征胸痛类型包含4个类别(0、1、2、3),若直接进行独热编码会生成4个新列(胸痛类型_0、胸痛类型_1、胸痛类型_2、胸痛类型_3),此时这4列满足:胸痛类型_0+胸痛类型_1+胸痛类型_2+胸痛类型_3=1。

    • 这种完全线性相关关系会导致特征矩阵的列之间存在完美共线性。

    • 当特征矩阵存在多重共线性时,模型参数估计会变得不稳定(矩阵不可逆或接近奇异),导致系数估计值方差增大、模型可解释性下降、过拟合等问题。

    • 在独热编码时设置drop=”first”,会删除每个类别特征的第1列,从而打破完全共线性。比如特征胸痛类型会生成3列(胸痛类型_1、胸痛类型_2、胸痛类型_3),此时胸痛类型_1=0,胸痛类型_2=0,胸痛类型_3=0,隐含代表 胸痛类型_0=1。

    • 虽然KNN不直接受多重共线性影响(不像线性模型),但使用drop=”first”也能够减少冗余特征,提升计算效率。

3.4.5 模型训练与评估

1
2
3
4
5
6
7
8
9
10
# 4. 创建模型并训练
# 使用 KNN 进行二分类
from sklearn.neighbors import KNeighborsClassifier

# 使用K近邻分类模型,K=3
knn = KNeighborsClassifier(n_neighbors=3)
# 模型训练
knn.fit(x_train, y_train)
# 5. 测试,模型评估
print(knn.score(x_test, y_test)) # 0.9253246753246753

3.4.6 模型的保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6. 保存模型和加载模型
import joblib

# 保存模型对象到二进制文件
joblib.dump(knn, "knn_heart_disease.joblib")

# 从文件中加载模型
knn_load = joblib.load("knn_heart_disease.joblib")

# 7. 预测
print(x_test[100:101])
# [[ 1.39255596 -0.95819838 6.6179386 0.43693502 0.50431031 -0.72837545
# 0. 1. 0. 0. 0. 1.
# 0. 0. 0. 1. 0. 0.
# 0. ]]

y_pred = knn_load.predict(x_test[100:101])
print(y_pred, y_test.iloc[100]) # [1] 1

3.5 模型评估与超参数调优

3.5.1 网格搜索

  • 网格搜索(Grid Search)是一种系统化的超参数调优方法,通过遍历预定义的超参数组合,找到使模型性能最优的参数配置。通过自动化调参避免手动试错,提高效率。
  • 网格搜索通常嵌套交叉验证,与交叉验证结合以提高调参的可靠性:
    • 外层循环:遍历参数网格中的每个参数组合。
    • 内层循环:对每个参数组合使用交叉验证评估模型性能。

3.5.2 对心脏病预测模型进行超参数调优

  • 对模型训练与评估部分进行修改,使用sklearn.model_selection.GridSearchCV进行交叉验证和网格搜索:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    from sklearn.neighbors import KNeighborsClassifier
    from sklearn.model_selection import GridSearchCV

    knn = KNeighborsClassifier()
    # 网格搜索参数,K值设置为1到10
    param_grid = {"n_neighbors": list(range(1, 10))}
    # GridSearchCV(estimator=模型, param_grid=网格搜索参数, cv=k折交叉验证)
    knn = GridSearchCV(estimator=knn, param_grid=param_grid, cv=10)

    # 模型训练
    knn.fit(x_train, y_train)

    pd.set_option('display.max_columns', None)
    print(pd.DataFrame(knn.cv_results_)) # 所有交叉验证结果
    # mean_fit_time std_fit_time mean_score_time std_score_time \
    # 0 0.000536 0.000103 0.003207 0.003161
    # 1 0.000542 0.000041 0.001804 0.000599
    # 2 0.000511 0.000031 0.001455 0.000346
    # 3 0.000515 0.000026 0.001610 0.000992
    # 4 0.000591 0.000202 0.001680 0.000624
    # 5 0.000540 0.000052 0.004181 0.008246
    # 6 0.000560 0.000062 0.001372 0.000234
    # 7 0.000518 0.000017 0.001299 0.000177
    # 8 0.000517 0.000018 0.001362 0.000344
    # 9 0.000589 0.000148 0.001702 0.000606

    # param_n_neighbors params split0_test_score \
    # 0 1 {'n_neighbors': 1} 0.986111
    # 1 2 {'n_neighbors': 2} 0.944444
    # 2 3 {'n_neighbors': 3} 0.930556
    # 3 4 {'n_neighbors': 4} 0.902778
    # 4 5 {'n_neighbors': 5} 0.930556
    # 5 6 {'n_neighbors': 6} 0.916667
    # 6 7 {'n_neighbors': 7} 0.958333
    # 7 8 {'n_neighbors': 8} 0.944444
    # 8 9 {'n_neighbors': 9} 0.916667
    # 9 10 {'n_neighbors': 10} 0.888889

    # split1_test_score split2_test_score split3_test_score split4_test_score \
    # 0 1.000000 0.972222 0.986111 1.000000
    # 1 0.930556 0.916667 0.930556 0.958333
    # 2 0.833333 0.902778 0.861111 0.875000
    # 3 0.805556 0.888889 0.875000 0.888889
    # 4 0.805556 0.847222 0.916667 0.888889
    # 5 0.833333 0.888889 0.888889 0.888889
    # 6 0.875000 0.902778 0.930556 0.902778
    # 7 0.819444 0.888889 0.916667 0.902778
    # 8 0.819444 0.875000 0.902778 0.875000
    # 9 0.791667 0.861111 0.902778 0.875000

    # split5_test_score split6_test_score split7_test_score split8_test_score \
    # 0 0.986111 0.972222 0.985915 0.957746
    # 1 0.944444 0.902778 0.943662 0.901408
    # 2 0.916667 0.888889 0.845070 0.845070
    # 3 0.875000 0.875000 0.859155 0.816901
    # 4 0.902778 0.888889 0.901408 0.802817
    # 5 0.916667 0.902778 0.915493 0.802817
    # 6 0.902778 0.916667 0.915493 0.873239
    # 7 0.888889 0.930556 0.915493 0.845070
    # 8 0.875000 0.902778 0.887324 0.830986
    # 9 0.861111 0.875000 0.887324 0.845070

    # split9_test_score mean_test_score std_test_score rank_test_score
    # 0 0.943662 0.979010 0.016953 1
    # 1 0.845070 0.921792 0.031117 2
    # 2 0.774648 0.867312 0.043574 7
    # 3 0.774648 0.856182 0.040169 10
    # 4 0.760563 0.864534 0.054298 8
    # 5 0.760563 0.871498 0.051363 5
    # 6 0.788732 0.896635 0.042995 3
    # 7 0.816901 0.886913 0.042873 4
    # 8 0.816901 0.870188 0.034057 6
    # 9 0.802817 0.859077 0.034666 9

    print(knn.best_estimator_) # 最佳模型
    # KNeighborsClassifier(n_neighbors=1)

    print(knn.best_score_) # 最佳得分
    # 0.979010172143975

    # 使用最佳模型进行评估
    knn = knn.best_estimator_
    print(knn.score(x_test, y_test))
    # 0.9805194805194806

4、线性回归

4.1 线性回归简介

4.1.1 什么是线性回归

  • 线性回归(Linear Regression)是一种用于建模两个或多个变量之间线性关系的统计方法。它通过拟合一条直线(或超平面)来描述自变量(输入特征)与因变量(输出目标)之间的关联,并可用于预测或分析变量间的影响关系。

  • 假设因变量y与自变量之间的关系可以用如下线性方程表示:

    • :截距,模型在自变量全为0时的基准值。
    • :自变量的系数,表示每个自变量对因变量的影响程度。
  • 通过估计这些系数,使模型预测值尽可能接近真实值。

  • 一元线性回归。仅有一个自变量:

  • 多元线性回归。包含多个自变量:

4.1.2 线性回归应用场景

  • GDP预测:用历史数据(如投资、消费、出口)建立回归模型,预测GDP增长趋势。
  • 广告效果评估:量化不同渠道广告投入对销售额的影响,优化预算分配。
  • 药物剂量研究:分析药物剂量与患者生理指标(如血压、血糖)之间的关系。
  • 产品质量控制:通过生产参数(温度、压力、原料配比)预测产品性能(如强度、耐久)。
  • 政策效果评估:分析最低工资政策对就业率的影响,或环保法规对污染排放的抑制作用。
  • 气候变化建模:用工业排放量、森林覆盖率等变量预测全球气温变化趋势。

4.1.3 API使用

  • 例如,某中学教师想研究学生每周学习时间(小时)与数学考试成绩(0-100分)之间的关系,并预测学生成绩。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    from sklearn.linear_model import LinearRegression

    # 自变量,每周学习时长
    X = [[5], [8], [10], [12], [15], [3], [7], [9], [14], [6]]
    # 因变量,数学考试成绩
    y = [55, 65, 70, 75, 85, 50, 60, 72, 80, 58]
    # 实例化线性回归模型
    model = LinearRegression()
    # 模型训练
    model.fit(X, y)
    # 系数,每周每学习1小时,成绩会增加多少分
    print(model.coef_) # [2.87070855]
    # 截距
    print(model.intercept_) # 41.45069393718042
    # 预测,每周学习11小时,成绩可能是多少分
    x_new = [[11]]
    y_pred = model.predict(x_new)
    print(y_pred) # [73.02848795]

    # 画图
    import matplotlib.pyplot as plt
    import numpy as np

    # 输入数据散点图
    plt.scatter(X, y, color='blue')
    # 拟合直线
    x_line = np.arange(0, 18, 0.1).reshape(-1, 1)
    y_line = model.predict(x_line)
    plt.plot(x_line, y_line, color='red')
    # 画出预测点
    plt.scatter(x_new, y_pred, color='green')
    plt.show()

4.2 线性回归求解

4.2.1 损失函数

  • 模型的预测值与真实值之间存在误差。

    • 损失函数用于衡量模型预测值与真实值之间的误差,并通过最小化损失函数来优化模型参数。当损失函数最小时,得到的自变量系数就是最优解。
  • 均方误差(MSE)

    • :样本个数。
    • :第i个样本的真实值。
    • :第i个样本的预测值。

    均方误差是回归任务中最常用的损失函数。均方误差对应了欧氏距离。基于均方误差最小化来进行模型求解的方法称为“最小二乘法”。在线性回归中,最小二乘法就是试图找到一条直线(或超平面),使所有样本到直线(或超平面)上的欧氏距离之和最小。

    • 均方误差的特点

      • 均方误差对大误差比较敏感,因为平方项会放大较大的误差(如误差2→4,误差10→100)。
      • 均方误差是凸函数,存在全局的唯一最小值,平方项又使损失函数处处可导,便于求解最优参数。
      • 最小二乘法(最小化MSE)的解析解可通过矩阵运算直接求出(如)。
      • 若误差服从正态分布,则均方误差对应极大似然估计,是最优的损失函数。
    • 误差服从正态分布时,均方误差与极大似然估计的关系。假设因变量y与自变量x的关系为,为误差项。当误差独立且具有相同分布,并且都服从正态分布时:

      为使似然函数最大,需求解的最小值,发现其与均方误差直接相关。即最大化对数似然函数等价于最小化均方误差。

  • 平均绝对误差(MAE)

    • 平均绝对误差受异常值影响较小,但对小误差的惩罚较弱。适用与数据中存在显著异常值(如金融风险预测)的场景。

4.2.2 一元线性回归解析解

  • 对于一元线性回归,有

  • 求偏导:

  • 令偏导等于0:

  • 求偏导:

  • 以前述学生每周学习时间(小时)与数学考试成绩(0-100分)之间的关系为例:

    1
    2
    3
    4
    # 自变量,每周学习时长
    X = [[5], [8], [10], [12], [15], [3], [7], [9], [14], [6]]
    # 因变量,数学考试成绩
    y = [55, 65, 70, 75, 85, 50, 60, 72, 80, 58]

4.2.3 正规方程法

  • 正规方程法介绍:

    • 正规方程法(Normal Equation)是一种用于求解线性回归的解析解的方法。它基于最小二乘法,通过求解矩阵方程来直接获得参数值。
    • 将损失函数转换为矩阵形式:
    • 正规方程法适用于特征数量较少的情况。当特征数量较大时,计算逆矩阵的复杂度会显著增加,此时梯度下降法更为适用。
  • API使用

    1
    2
    3
    4
    5
    6
    7
    # fit_intercept: 是否计算偏置
    model = sklearn.linear_model.LinearRegression(fit_intercept=True)
    model.fit([[0, 3], [1, 2], [2, 1]], [0, 1, 2])
    # coef_: 系数
    print(model.coef_)
    # intercept_: 偏置
    print(model.intercept_)

4.2.4 梯度下降法

  • 更新公式

    • 梯度下降法(Gradient Descent)是一种用于最小化目标函数的迭代优化算法。核心是沿着目标函数(如损失函数)的负梯度方向逐步调整参数,从而逼近函数的最小值。梯度方向指示了函数增长最快的方向,因此负梯度方向是函数下降最快的方向。

    • 假设目标函数为,其中是模型参数。梯度下降的更新公式为:

      • :学习率(Learning Rate),用于控制步长。
      • :目标函数在处的梯度(偏导数组成的向量)。
  • 计算示例

    • 以前述学生每周学习时间(小时)与数学考试成绩(0-100分)之间的关系为例。
    • 自变量(学习时长)
    • 因变量(数学考试成绩)
    • 使得损失函数最小,为此需要求出损失函数相对的梯度。
    • 示例代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      import numpy as np

      def J(beta):
      """目标函数"""
      return np.sum((X @ beta - y) ** 2, axis=0).reshape(-1, 1) / n

      def gradient(beta):
      """梯度"""
      return X.T @ (X @ beta - y) / n * 2

      # 1. 定义数据
      X = np.array([[5], [8], [10], [12], [15], [3], [7], [9], [14], [6]]) # 自变量,每周学习时长
      y = np.array([[55], [65], [70], [75], [85], [50], [60], [72], [80], [58]]) # 因变量,数学考试成绩
      n = X.shape[0] # 样本数

      # 2. 数据处理,X增加一列1
      X = np.hstack([np.ones((n, 1)), X]) # X添加一列1,与偏置项相乘
      print(X.shape) # (10, 2)
      print(X)
      # [[ 1. 5.]
      # [ 1. 8.]
      # [ 1. 10.]
      # [ 1. 12.]
      # [ 1. 15.]
      # [ 1. 3.]
      # [ 1. 7.]
      # [ 1. 9.]
      # [ 1. 14.]
      # [ 1. 6.]]

      # 3. 初始化参数及超参数
      alpha = 0.01 # 学习率
      interations = 10000 # 迭代次数
      beta = np.array([[1], [1]]) # 初始化参数
      # while (j := J(beta)) > 1e-10 and (epoch := epoch + 1) <= 10000:
      # grad = gradient(beta) # 求解梯度
      # if epoch % 1000 == 0:
      # print(f"beta={beta.reshape(-1)}\tJ={j.reshape(-1)}")
      # beta = beta - alpha * grad # 更新参数
      beta0 = []
      beta1 = []
      while (np.abs(grad := gradient(beta)) > 1e-10).any() and (interations := interations - 1) >= 0:
      # 4. 计算梯度(略)
      # 5. 更新参数
      beta = beta - alpha * grad
      beta0.append(beta[0, 0])
      beta1.append(beta[1, 0])

      # 每迭代10轮打印一次当前的参数值和损失函数值
      if interations % 10 == 0:
      print(f"beta={beta.reshape(-1)}\tJ={J(beta).reshape(-1)}")

      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]
      # beta=[41.45069394 2.87070855] J=[2.98115413]

      # 画出beta0, beta1变化曲线
      import matplotlib.pyplot as plt
      plt.plot(beta0, beta1)
      plt.show()
  • 学习率的选择

    • 学习率过大:可能导致跳过最优解,甚至发散。
    • 学习率过小:收敛速度慢,易陷入局部极小。
    • 自适应学习率:高级优化器(如Adam、Adagrad)动态调整学习率以提升性能。
  • 梯度下降法常见问题

    • 特征缩放:通常需要提前对特征进行缩放(如标准化或归一化),以加快收敛速度。
    • 局部极小值、鞍点问题:
      • 可能陷入局部极小值(非全局最优解),或遇到鞍点(梯度为零但非极值点)。
      • 解决方案:使用动量(Momentum)、自适应优化器(如Adam)或二阶方法(如牛顿法)。
  • API使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from sklearn.linear_model import SGDRegressor

    model = SGDRegressor(
    loss="squared_error", # 损失函数,默认为均方误差
    alpha=1e-3, # 正则化参数,默认为1e-3
    fit_intercept=True, # 是否计算偏置
    learning_rate="constant", # 学习率策略
    eta0=0.001, # 初始学习率
    max_iter=10000, # 最大迭代次数
    tol=0.0001, # 损失值下降(变化)量小于tol时停止迭代
    )

    # 自变量,每周学习时长
    X = [[5], [8], [10], [12], [15], [3], [7], [9], [14], [6]]
    # 因变量,数学考试成绩
    y = [55, 65, 70, 75, 85, 50, 60, 72, 80, 58]

    model.fit(X, y)
    # coef_: 系数
    print(model.coef_) # [2.90041981]
    # intercept_: 偏置/截距
    print(model.intercept_) # [41.10974301]

4.3 案例:广告投放效果预测

4.3.1 数据集说明

4.3.2 使用线性回归预测广告投放效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import pandas as pd
from sklearn.preprocessing import StandardScaler # 标准化
from sklearn.model_selection import train_test_split # 划分数据集
from sklearn.linear_model import LinearRegression, SGDRegressor # 解析法和梯度下降法求解线性回归模型
from sklearn.metrics import mean_squared_error # 损失函数(性能指标),均方误差

# 1. 加载数据集
advertising = pd.read_csv("data/advertising.csv")
# 2. 数据预处理
advertising.drop(advertising.columns[0], axis=1, inplace=True)
advertising.dropna(inplace=True)
advertising.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 200 entries, 0 to 199
# Data columns (total 4 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 TV 200 non-null float64
# 1 Radio 200 non-null float64
# 2 Newspaper 200 non-null float64
# 3 Sales 200 non-null float64
# dtypes: float64(4)
# memory usage: 6.4 KB
print(advertising.head())
# TV Radio Newspaper Sales
# 0 230.1 37.8 69.2 22.1
# 1 44.5 39.3 45.1 10.4
# 2 17.2 45.9 69.3 9.3
# 3 151.5 41.3 58.5 18.5
# 4 180.8 10.8 58.4 12.9

# 3. 划分训练集与测试集
X = advertising.drop("Sales", axis=1)
y = advertising["Sales"]
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)
# (140, 3) (60, 3) (140,) (60,)

# 4. 特征工程:标准化
preprocessor = StandardScaler()
x_train = preprocessor.fit_transform(x_train) # 计算训练集的均值和标准差,并标准化训练集
x_test = preprocessor.transform(x_test) # 使用训练集的均值和标准差对测试集标准化

# 5. 创建模型并训练
# 5.1 使用正规方程法拟合线性回归模型
normal_equation = LinearRegression()
normal_equation.fit(x_train, y_train)
print("正规方程法解得模型系数:", normal_equation.coef_) # 正规方程法解得模型系数: [3.68334078 2.90625242 0.14194772]
print("正规方程法解得模型偏置:", normal_equation.intercept_) # 正规方程法解得模型偏置: 14.497142857142858
# 5.2 使用随机梯度下降法拟合线性回归模型
gradient_descent = SGDRegressor()
gradient_descent.fit(x_train, y_train)
print("随机梯度下降法解得模型系数:", gradient_descent.coef_) # 随机梯度下降法解得模型系数: [3.67642695 2.8895003 0.15873571]
print("随机梯度下降法解得模型系数:", gradient_descent.intercept_) # 随机梯度下降法解得模型系数: [14.47685173]

# 6. 测试(预测)
y_pred1 = normal_equation.predict(x_test)
y_pred2 = gradient_descent.predict(x_test)
print("正规方程法均方误差:", mean_squared_error(y_test, y_pred1)) # 正规方程法均方误差: 3.7967972367152303
print("随机梯度下降法均方误差:", mean_squared_error(y_test, y_pred2)) # 随机梯度下降法均方误差: 3.8056434634856102

5、逻辑回归

5.1 逻辑回归简介

5.1.1 什么是逻辑回归

  • 逻辑回归(Logistic Regression)是一种用于解决分类问题的统计方法,尤其适用于二分类问题。尽管名称中有“回归”,但它主要用于分类任务。

  • 逻辑回归通过将线性回归的输出映射到[0,1]区间,来表示某个类别的概率。

  • 常用的映射函数是sigmoid函数:,其导数

    • 逻辑回归结果可表示为:

    • 其中为为线性回归输出结果,P(y=1|x)表示输出为1类的概率。根据逻辑回归结果和阈值来确认最终预测结果,若逻辑回归结果大于阈值则输出为1类,反之输出为0类。

5.1.2 逻辑回归应用场景

  • 信用评分:预测客户是否会违约(违约/不违约)。
  • 欺诈检测:预测某笔交易是否是欺诈行为。
  • 垃圾邮件检测:预测一封邮件是否是垃圾邮件(垃圾邮件/非垃圾邮件)。
  • 广告点击预测:预测用户是否会点击某个广告(点击/不点击)。
  • 图像分类:将图像分类为不同的类别(如猫、狗、鸟等)。
  • 情感分析:将文本分类为正面、负面或中性情感。
  • 产品质量分类:预测产品是否合格。
  • 医学诊断:预测患者是否患有某种疾病(患病/未患病)。
  • 蛋白质功能预测:基于蛋白质序列和结构特征预测其功能类别。

5.1.3 逻辑回归损失函数

  • 逻辑回归的损失函数通常使用对数损失(Log Loss),也称为二元交叉熵损失(Binary Cross-Entropy Loss),用于衡量模型输出的概率分布与真实标签之间的差距。逻辑回归的损失函数来源于最大似然估计(MLE)。

5.1.4 损失函数的梯度(了解)

5.1.5 API使用

  • LogisticRegression参数说明:scikit-learn中的LogisticRegression类用于实现逻辑回归。它支持二分类和多分类任务,并提供了多种优化算法和正则化选项。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # solver: 优化算法
    # lbfgs: 拟牛顿法(默认),仅支持L2正则化
    # newton-cg: 牛顿法,仅支持L2正则化
    # liblinear: 坐标下降法,适用于小数据集,支持L1和L2正则化
    # sag: 随机平均梯度下降,适用于大规模数据集,仅支持L2正则化
    # saga: 改进的随机梯度下降,适用于大规模数据,支持L1、L2和ElasticNet正则化
    # penalty: 正则化类型,可选l1、l2和elasticnet
    # C: 正则化强度,C越小,正则化强度越大
    # class_weight: 类别权重,balanced表示自动平衡类别权重,让模型在训练时更关注少数类,从而减少类别不平衡带来的偏差
    model = sklearn.linear_model.LogisticRegression(solver="lbfgs", penalty="l2", C=1, class_weight="balanced")
  • 案例:心脏病预测。Heart Disease数据集https://www.kaggle.com/datasets/johnsmith88/heart-disease-dataset。

    • 年龄:连续值
    • 性别:0-女,1-男
    • 胸痛类型:1-典型心绞痛,2-非典型心绞痛,3-非心绞痛,4-无症状
    • 静息血压:连续值,单位mmHg
    • 胆固醇:连续值,单位mg/dl
    • 空腹血糖:1-大于120mg/dl,2-小于等于120mg/dl
    • 静息心电图结果:0-正常,1-ST-T异常,2-可能左心室肥大
    • 最大心率:连续值
    • 运动性心绞痛:1-有,2-无
    • 运动后的ST下降:连续值
    • 峰值ST段的斜率:1-向上,2-水平,3-向下
    • 主血管数量:0到3
    • 地中海贫血:一种先天性贫血,3-正常,6-固定缺陷,7-可逆缺陷
    • 是否患有心脏病:标签,0-否,1-是
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    import pandas as pd
    from sklearn.model_selection import train_test_split # 划分数据集
    from sklearn.compose import ColumnTransformer # 列转换,做特征转换
    from sklearn.preprocessing import OneHotEncoder, StandardScaler # 独热编码和标准化

    from sklearn.linear_model import LogisticRegression # 逻辑回归

    # 1. 加载数据集
    heart_disease_data = pd.read_csv("data/heart_disease.csv")

    # 数据清洗
    heart_disease_data.dropna(inplace=True)

    # heart_disease_data.info()
    # print(heart_disease_data.head())

    # 2. 数据集划分
    # 定义特征
    X = heart_disease_data.drop("是否患有心脏病", axis=1)
    y = heart_disease_data["是否患有心脏病"]

    x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    # print(x_train.shape, x_test.shape, y_train.shape, y_test.shape)

    # 3. 特征工程
    # 数值型特征
    numerical_features = ["年龄","静息血压","胆固醇","最大心率","运动后的ST下降","主血管数量"]
    # 类别型特征(多元分类)
    categorical_features = ["胸痛类型","静息心电图结果","峰值ST段的斜率","地中海贫血"]
    # 二元类别特征
    binary_features = ["性别","空腹血糖","运动性心绞痛"]

    # 创建列转换器
    transformer = ColumnTransformer(
    # (名称,操作,特征列表)
    transformers=[
    ("num", StandardScaler(), numerical_features),
    ("cat", OneHotEncoder(drop="first"), categorical_features),
    ("bin", "passthrough", binary_features)
    ])

    # 执行特征转换
    x_train = transformer.fit_transform(x_train)
    x_test = transformer.transform(x_test)

    print(x_train.shape, x_test.shape)
    # (820, 19) (205, 19)

    # 4. 创建逻辑回归模型并训练
    model = LogisticRegression(solver='lbfgs',max_iter=1000, penalty=None, class_weight='balanced')
    model.fit(x_train, y_train)

    # 5. 模型评估,计算准确率
    print(model.score(x_test, y_test)) # 0.8097560975609757

5.2 多分类任务(了解)

  • 逻辑回归通常用于二分类问题,但可以通过一对多(One-vs-Rest,OvR)以及Softmax回归(Multinomial Logistic Regression,多项逻辑回归)来扩展到多分类任务。

5.2.1 一对多(OVR)

  • 实现方式

    • 若有C个类别,则训练C个二分类逻辑回归分类器。

    • 每个分类器将一个类别作为正例、所有其他类别作为反例。

    • 预测时,计算C个分类器的输出概率,选取概率最高的类别。

    • 对于类别c:

  • 优缺点

    • 优点:简单易于实现,适用于类别数量较少的情况。
    • 缺点:每个类别训练1个分类器,当类别数量较多时,训练时间较长。
  • API

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from sklearn.linear_model import LogisticRegression

    model = LogisticRegression(multi_class="ovr")

    # 或

    from sklearn.multiclass import OneVsRestClassifier

    model = OneVsRestClassifier(LogisticRegression())

5.2.2 Softmax回归(多项逻辑回归)

  • 实现方式

    • 直接扩展逻辑回归到多分类问题,使用Softmax函数将模型输出转化为概率分布。

    • 只需训练1个逻辑回归模型。

    • 预测时用1个模型计算所有类别的概率,选择最大值。

    • 若有C个类别,模型将输出C个分数。

    • 对于类别c:

  • 优缺点

    • 优点:只训练1个模型,计算高效,分类一致性更好。
    • 缺点:计算Softmax需要对所有类别求指数,计算量较高。
  • API

    1
    2
    3
    4
    5
    6
    7
    from sklearn.linear_model import LogisticRegression

    model = LogisticRegression(multi_class="multinomial")

    # 对于多分类问题,LogisticRegression会自动使用multinomial,因此multi_class参数可省略

    model = LogisticRegression()

5.3 案例:手写数字识别

5.3.1 数据集说明

  • Digit Recognizer数据集:https://www.kaggle.com/competitions/digit-recognizer。

  • 文件train.csv中包含手绘数字(从0到9)的灰度图像,每张图像为28×28像素,共784像素。每个像素有一个0到255的值表示该像素的亮度。

  • 文件第1列为标签,之后784列分别为784个像素的亮度值。

  • 第x ✖️ 28 + y个像素表示图像第x行第y列的像素。

5.3.2 逻辑回归实现手写数字识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split # 划分数据集
from sklearn.preprocessing import MinMaxScaler # 归一化
from sklearn.linear_model import LogisticRegression # 逻辑回归

# 1. 加载数据集
digit = pd.read_csv('data/train.csv')

# 图片测试
# plt.imshow(digit.iloc[10, 1:].values.reshape(28, 28), cmap='gray')
# plt.show()

# 2. 划分训练集和测试集
X = digit.drop(['label'], axis=1)
y = digit['label']

x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 3. 特征转换:归一化
scaler = MinMaxScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)

# 4. 创建模型并训练
model = LogisticRegression(max_iter=1000)
model.fit(x_train, y_train)

# 5. 模型评估,计算准确率
print(model.score(x_test, y_test)) # 0.9186507936507936

# 6. 预测
plt.imshow(digit.iloc[1024, 1:].values.reshape(28, 28), cmap='gray')
plt.show()

print("预测数字为:", model.predict(digit.iloc[1024, 1:].values.reshape(1, -1)))
# 预测数字为: [1]

6、感知机

6.1 感知机的概念

  • 感知机(Perceptron)是二分类模型,接收多个信号,输出一个信号。感知机的信号只有0、1两种取值。下图是一个接收两个输入信号的感知机的例子:

  • x1,x2是输入信号,y是输出信号,w1,w2是权重,○称为神经元或节点。输入信号被送往神经元时,会分别乘以固定的权重。神经元会计算传送过来的信号的总和,只有当这个总和超过某个界限值时才会输出1,也称之为神经元被激活。这里将界限值称之为阈值θ。

  • 感知机的多个输入信号都有各自的权重,这些权重发挥着控制各个信号的重要性的作用,权重越大,对应信号的重要性越高。

6.2 简单逻辑电路

6.2.1 与门

  • 尝试使用感知机解决简单的问题,如实现逻辑电路中的与门(AND gate)。与门是具有两个输入和一个输出的门电路,仅在两个输入均为1时输出1,其他时候输出0。这种输入信号和输出信号对应表称为真值表。

  • 使用感知机表示与门需要确定能满足上述真值表的w1,w2,θ的值,满足条件的参数有无数个,如(w1,w2,θ)=(0.5,0.5,0.7)或(1.0,1.0,1.0)。

6.2.2 与非门

  • 再来考虑下与非门(NAND gate),与非门颠倒了与门的输出,仅当x1,x2同时为1时输出0,其他时候输出1。

  • 要表示与非门,满足条件的参数也有无数个,如(w1,w2,θ)=(-0.5,-0.5,-0.7)。实际上,只要把实现与门的参数值的符号取反即可。

6.2.3 或门

  • 再来看一下或门(OR gate)。或门只要有一个输入信号为1,输出就为1。

    • (w1,w2,θ)=(0.5,0.5,0)。

6.3 感知机的实现

6.3.1 简单实现

  • 先来实现与门,定义一函数AND接受x1和x2,在函数内部初始化参数w1,w2,θ。当输入的加权总和超过阈值时返回1,否则返回0。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def AND(x1, x2):
    w1, w2, theta = 0.5, 0.5, 0.7
    res = x1 * w1 + x2 * w2
    if res <= theta:
    return 0
    elif res > theta:
    return 1

    print(AND(0, 0)) # 0
    print(AND(1, 0)) # 0
    print(AND(0, 1)) # 0
    print(AND(1, 1)) # 1
  • 可以以同样的方式实现与非门和或门,不过在此之前先做一些修改。

6.3.2 导入权重和偏置

  • 考虑到以后的事情,我们将其修改为另外一种实现形式。将θ换为-b,表示感知机行为的式子变为如下形式:

    • b为偏置,w1,w2为权重。下面我们使用Numpy按上述形式分别实现与门、与非门和或门。
  • 与门:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import numpy as np

    def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    tmp = np.sum(w * x) + b
    if tmp <= 0:
    return 0
    else:
    return 1

    print(AND(0, 0)) # 0
    print(AND(1, 0)) # 0
    print(AND(0, 1)) # 0
    print(AND(1, 1)) # 1
    • 偏置b与权重w1,w2的作用是不同的,具体地说w1,w2是控制输入信号的重要性的参数,而偏置是调整神经元被激活的容易程度的参数。
  • 接下来实现与非门和或门:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import numpy as np

    def NAND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    tmp = np.sum(w * x) + b
    if tmp <= 0:
    return 0
    else:
    return 1

    print(NAND(0, 0)) # 1
    print(NAND(1, 0)) # 1
    print(NAND(0, 1)) # 1
    print(NAND(1, 1)) # 0

    def OR(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.2
    tmp = np.sum(w * x) + b
    if tmp <= 0:
    return 0
    else:
    return 1

    print(OR(0, 0)) # 0
    print(OR(1, 0)) # 1
    print(OR(0, 1)) # 1
    print(OR(1, 1)) # 1
    • 与门、与非门、或门是具有相同构造的感知机,区别仅在于权重参数的值。因此在与非门和或门的实现中,仅设置权重和偏置的值这一点和与门的实现不同。

6.4 感知机的局限

  • 现在来考虑一下异或门(XOR gate)。异或门仅当x1或x2中的一方为1时才会输出1。

  • 之前的感知机是无法实现这个异或门的,具体原因让我们来分析一下。

  • 感知机表示或门的式子如下:

    • 上式表示感知机会生成由直线-0.5+x_1+x_2=0划分的两个空间,其中一个空间输出1,另一个空间输出0,如图:

    • 如果要实现异或门,是无法使用一条直线将0和1分开的:

    • 但如果将直线这个限制条件去掉就可以了:

  • 感知机的局限性就在于它只能表示由一条直线划分的空间,而上图中的曲线无法用感知机表示。由曲线划分的空间称为非线性空间,由直线划分的空间称为线性空间。

6.5 多层感知机

  • 我们先来考虑一下如何使用与门、与非门和或门来组合出异或门。

    • 与门、与非门、或门用下图的符号表示:

    • 将其组合构成异或门:

  • 使用之前实现的与门、与非门和或门代码来实现异或门:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    import numpy as np

    def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    tmp = np.sum(w * x) + b
    if tmp <= 0:
    return 0
    else:
    return 1

    def NAND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([-0.5, -0.5])
    b = 0.7
    tmp = np.sum(w * x) + b
    if tmp <= 0:
    return 0
    else:
    return 1

    def OR(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.2
    tmp = np.sum(w * x) + b
    if tmp <= 0:
    return 0
    else:
    return 1

    def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1, s2)
    return y

    print(XOR(0, 0)) # 0
    print(XOR(1, 0)) # 1
    print(XOR(0, 1)) # 1
    print(XOR(1, 1)) # 0
  • 试着使用感知机的表示方法来表示这个异或门:

    • 如图所示,这是一种多层感知机结构。
    • 第0层的两个神经元接收输入信号,并将信号发送给第1层的神经元。第1层的神经元将信号发送给第2层的神经元。第2层的神经元输出结果。
    • 通过叠加层,感知机能进行更加灵活的表示。
    • 感知机是一种非常简单的算法,也是后续学习神经网络的基础。

7、其他监督学习算法(了解)

7.1 朴素贝叶斯法

7.1.1 朴素贝叶斯法简介

  • 朴素贝叶斯(naive Bayes)法是一种基于概率的机器学习算法。它基于贝叶斯定理,并假设特征之间相互独立(这就是“朴素”的来源)。朴素贝叶斯法实现简单,学习与预测的效率都很高,是一种常用方法,在许多场景下表现得非常好,如文本分类(垃圾邮件检测)、情感分析等。

  • 朴素贝叶斯法的核心是贝叶斯定理:

    • P(Y|X):后验概率,给定特征X时类Y的概率。
    • P(X|Y):条件概率,类Y包含特征X的概率。
    • P(Y):先验概率,类Y的概率。
    • P(X):特征X的概率。
  • 例如,判断一封邮件是不是垃圾邮件(Y=垃圾邮件,X=邮件内包含“免费”这个词):

    • P(Y|X):邮件包含“免费”时是垃圾邮件的概率。
    • P(X|Y):垃圾邮件中包含“免费”的概率。
    • P(Y):邮件是垃圾邮件的概率。
    • P(X):邮件包含“免费”的概率。
  • 朴素贝叶斯假设所有特征相互独立,这使得我们无需考虑特征之间复杂的依赖关系,极大简化了条件概率的计算:

7.1.2 极大似然估计

  • 在朴素贝叶斯法中,学习意味着估计先验概率和条件概率。可以应用极大似然估计法估计相应的概率。

  • 先验概率P(Y=)(Y为类)的极大似然估计:

  • 条件概率(Y为类时第j个特征)的极大似然估计:

    • 其中I为示性函数,取值为1或0。

7.1.3 贝叶斯估计

  • 使用极大似然估计可能会出现所要估计的概率值为0的情况,这会影响到后验概率的计算结果,使分类产生偏差。解决方法是改用贝叶斯估计。

  • 先验概率的贝叶斯估计:

  • 条件概率的贝叶斯估计:

    • 式中λ≥0,当λ=0时就是极大似然估计,常取λ=1,这时称为拉普拉斯平滑。

7.1.4 学习与分类过程

  • 学习时,计算先验概率:

  • 和条件概率:

  • 分类时,根据给定的实例x=(x_1,x_2,…,x_n )计算后验概率:

  • 并确定实例x属于哪一个类别:

7.2 决策树

7.2.1 决策树简介

  • 决策树(Decision Tree)是一种基于树形结构的算法,根据一系列条件判断逐步划分数据,缩小范围,最终得出预测结果。决策树由4部分组成:

    • 根节点:树的节点,包含所有数据。
    • 内部节点:表示特征上的判断条件。
    • 分支:根据判断条件分出的路径。
    • 叶节点:最终分类或回归的结果。
  • 决策树适用于需要规则化、可解释性和快速决策的场景,尤其在数据特征明确、样本量适中的情况下表现良好。在复杂任务中,它常作为基础模型,与集成学习结合(如随机森林、梯度提升树)以提升性能。

7.2.2 决策树工作过程

  • 决策树的学习通常包括3个步骤:特征选择、决策树的生成和决策树的剪枝。
  • 如果特征数量很多,可以在决策树学习之前对特征进行选择,只留下对训练数据有足够分类能力的特征。
  • 学习时通常是递归地选择最优特征,并根据该特征对训练数据进行划分,使得对各个子数据集有一个最好的分类。首先构建根结点,将所有训练数据都放在根结点。选择一个最优特征,按照这一特征将训练数据集划分成子集,使得各个子集有一个在当前条件下最好的分类。如果这些子集已经能够被基本正确分类,那么构建叶结点,并将这些子集分到所对应的叶结点中去;如果还有子集不能被基本正确分类,那么就对这些子集选择新的最优特征,继续对其进行划分并构建相应的结点。如此递归直至所有训练数据子集被基本正确分类,或者没有合适的特征为止。最后每个子集都被分到叶结点上,即都有了明确的类,这就生成了一棵决策树。决策树的每次划分都相当于在特征空间中引入一个超平面将当前空间一分为二。
  • 以上方法生成的决策树可能对训练数据有很好的分类能力,但对未知的测试数据却未必,即可能发生过拟合现象。因此需要对已生成的树自下而上进行剪枝,将树变得更简单,从而使它具有更好的泛化能力。具体地,就是去掉过于细分的叶结点,使其回退到父结点或更高的结点,然后将父结点或更高的结点改为新的叶结点。
  • 决策树的生成只考虑局部最优,决策树的剪枝则考虑全局最优。

7.2.3 特征选择与决策树生成

  • 特征选择在于选取对训练数据具有分类能力的特征,这样可以提高决策树学习的效率。如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的,经验上扔掉这样的特征对决策树学习的精度影响不大。通常特征选择的准则是信息增益或信息增益率。

  • 信息熵

    • 信息熵(Entropy)是表示随机变量不确定性的度量,设X是一个取有限个值的离散随机变量,其概率分布为:

    • 随机变量X的熵定义为:

    • 若pi=0,则定义0 log⁡0=0。通常式中对数以2或e为底。熵只依赖于X的分布,与X的取值无关。熵越大,随机变量的不确定性就越大。

    • 设有随机变量(X,Y),其联合概率分布为:

    • 条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import numpy as np
    import matplotlib.pyplot as plt

    p = np.arange(0.01, 1, 0.01)
    # 二值随机变量熵的计算
    H = - p * np.log2(p) - (1-p) * np.log2(1-p)

    # 画出函数图像
    plt.plot(p, H)
    plt.show()
  • 信息增益与ID3

    • 决策树学习应用信息增益(Information Gain)准则选择特征,给定训练集数据D和特征A,熵H(D)表示对数据集进行分类的不确定性,条件熵H(D|A)表示在特征A给定条件下对数据集D进行分类的不确定性。两者之差即信息增益:

    • 表示由于特征A而使得对数据集D的分类的不确定性减少的程度。对数据集D而言,信息增益依赖于特征,不同的特征具有不同的信息增益,信息增益大的特征具有更强的分类能力。在进行特征选择时,对训练数据集D计算每个特征的信息增益,并比较他们的大小,选择信息增益最大的特征。

    • 假设:

      • 训练数据集D有|D|个样本
      • 特征A有n个不同的取值{a1,a2,…,an},根据特征A将D划分为n个子集D1,D2,…,Dn,Di的样本个数为|Di |
      • 有K个类Ck,每个类有|Ck |个样本
      • 子集Di中属于类Ck的样本的集合为Dik (Dik=Di∩Ck),Dik的样本个数为|Dik |。
    • 信息增益计算方法如下:

      • 计算数据集D的熵H(D):

      • 计算特征A对数据集D的条件熵H(D|A):

      • 计算信息增益:

    • ID3算法在决策树各个节点上应用信息增益选择特征,递归地构建决策树。具体方法是:从根节点开始,计算所有可能的信息增益,选择信息增益最大的特征作为节点特征,由该特征的不同取值建立子节点,再依次对子节点进行上述操作,直到所有特征信息增益均很小或无特征可选为止,最终生成一颗决策树。

  • 信息增益率与C4.5

    • 使用信息增益划分训练数据集的特征,会倾向于选择取值较多的特征。而使用信息增益率(Information Gain Ratio)可以对这一问题进行校正,这是特征选择的另一个准则。

    • 特征A对训练数据集D的信息增益率定义为信息增益与训练数据集D关于特征A的值的熵之比:

    • 其中,n是特征A的取值个数。

    • C4.5算法与ID3算法相似,但对其进行了改进,使用信息增益率来选择特征。

  • 基尼指数与CART

    • 有K个类,样本属于第k类的概率为p_k,则概率分布的基尼指数:

    • 对于给定的样本集合D,其基尼指数:

    • 这里Ck是D中属于第k类的样本子集,K是类的个数。

    • 如果样本集合D根据特征A是否取某一可能值a被划分为两个子集D1,D2,则在特征A的条件下,集合D的基尼指数:

    • 基尼指数Gini(D)表示集合D的不确定性,基尼指数Gini(D,A)表示经A=a划分后集合D的不确定性。基尼指数越大样本集合的不确定性也越大,这与信息熵相似。

    • CART决策树是一棵二叉树,根据基尼指数生成决策树,对训练数据集D的每个特征A的每一个可能的取值a,计算A=a时的基尼指数,选择基尼指数最小的特征及其对应的切分点作为最优特征与最优切分点,并生成两个子节点,将训练数据集依特征分配到两个子节点中。重复上述过程,直到节点中样本数小于阈值、或样本集的基尼指数小于阈值、或没有个更多特征。

  • CART回归树

    • 一颗回归树对应着输入空间(特征空间)的划分,以及在划分上的输出值。将输入空间划分为M个单元R1,R2,…,RM,并且在每个单元Rm上有一个固定输出cm的回归树模型可表示为:

    • 在训练数据集所在的空间中,递归地将每个区域划分为两个子区域并决定每个子区域上的输出值,构建二叉决策树。每次划分的目标是找到一个特征j和该特征的一个切分点s,使得划分后的误差最小。在对输入空间进行划分时,将特征j和它的取值s(通常是连续特征取值的中间点)作为切分变量和切分点,定义两个区域:

    • 然后寻找最优切分变量j和最优切分点s。具体地,使用平方误差作为损失函数,求解:

    • 解出最优切分变量j和最优切分点s后,使用最优切分变量j和最优切分点s后划分两个区域。重复上述过程直到满足停止条件为止。最终叶节点的输出值为该区域内所有样本目标值的均值:

    • 由此生成一颗回归树,这样的回归树称之为最小二乘回归树。

7.2.4 决策树的剪枝

  • 决策树出现过拟合的原因在于学习时过多的考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。为了避免过拟合,可以对已生成的决策树进行剪枝,从决策树上裁掉一些子树或叶节点,并将其根节点或父节点作为新的叶节点,从而简化模型。决策树的剪枝通常分为预剪枝(Pre-Pruning)和后剪枝(Post-Pruning)。

  • 预剪枝是在决策树生成过程中,通过设置一些限制条件提前停止树的生长,避免过度分裂。常见的停止条件有:限制最大树深度、限制每个节点最小样本数、限制最小的误差减少量、限制最大叶节点数量等。但过于严格的停止条件可能导致欠拟合,并且可能难以确定最佳阈值,需要多次尝试。

  • 后剪枝是在决策树完全生成之后,基于某种评估准则从底部向上逐步判断是否移除分支。常见的后剪枝方法有代价复杂度剪枝(Cost-Complexity Pruning,CCP)和减少误差剪枝(Reduced Error Pruning,REP)等。

  • 代价复杂度剪枝需要首先计算子树的损失函数:

    • T为任意子树,|T|为子树的叶节点个数
    • C(T)为对训练数据的预测误差(如基尼指数)
    • α≥0为参数,权衡训练数据的拟合程度和模型的复杂度。α越大越倾向于剪掉子树,生成更简单的树。
    • 为参数是α时子树T的整体损失
    • 从α=0开始,逐渐增加α。对于每个α,选择剪掉使C_α (T)最小的子树。生成一系列剪枝后的树,使用交叉验证选择最优的α和对应的树。
  • 减少误差剪枝则是直接基于验证集的误差来判断是否剪枝。剪枝过程遍历每个内部节点,将该子树替换为一个叶节点并在验证集上比较替换前后的误差,如果替换后误差没有增加则剪掉该子树。

7.3 支持向量机

7.3.1 支持向量机简介

  • 支持向量机(Support Vector Machines,SVM)是一种二分类模型。其核心目标是寻找一个“间隔最大”的超平面将不同类别的数据点分隔开。这个超平面在二维空间中是一条直线,在三维空间中是一个平面,在更高维空间中则是一个超平面。

  • 间隔最大代表该超平面与最近的数据点之间的距离最大,这使得支持向量机有较强的泛化能力。

  • 支持向量机学习方法包括由简至繁的模型:线性可分支持向量机、线性支持向量机以及非线性支持向量机。

7.3.2 线性可分支持向量机-硬间隔

  • 硬间隔

    • 当训练样本线性可分时,此时可以通过最大化硬间隔来学习线性可分支持向量机。硬间隔是指超平面能够将不同类的样本完全划分开。距离超平面最近的几个样本点称为支持向量,它们直接决定超平面的位置和方向,只要支持向量不变,超平面就不会变。

    • 在样本空间中,超平面可表示为:

    • 其中w=(w1,w2,…,wn )为法向量,决定了超平面的方向;b为位移项,决定了超平面与原点之间的距离。将超平面记为(w,b)。

    • 相应的分类函数称为线性可分支持向量机:

  • 间隔与最大间隔

    • x’为超平面上一点,,样本空间中任一点x到超平面(w,b)的距离为:

    • 记每个样本点xi的类别为yi,该样本点的函数间隔,表示分类预测的正确性及确信度,若超平面(w,b)能将样本正确分类,则有:

    • 此时有:

    • 我们注意到对w,b进行缩放变换w→λw,b→λb时,不会改变超平面,也不会改变r的值,但函数间隔会随着缩放w和b而发生变化。也就是说可以通过缩放w和b来任意缩放函数间隔而不改变ri。

    • 因此令支持向量到超平面的函数间隔,此时支持向量到超平面的距离。两个异类支持向量到超平面的距离之和,γ被称为“间隔”。

    • 欲找到具有最大间隔的超平面,也就是求在约束下最大的γ:

    • 等价于:

    • s.t.为Subject to,意为约束。

    • 上式就是支持向量机的基本型。对上式使用拉格朗日乘子法得到其对偶问题,从对偶问题中解出拉格朗日乘子,进而解出w,b,即可得到具有最大间隔的超平面。

7.3.3 线性支持向量机-软间隔

  • 先前我们假定训练样本在样本空间中线性可分,但现实中很可能并非如此,此时我们无法找出一个合适的超平面将所有样本点完全正确划分。通常训练数据中会有一些特异点,如果将这些特异点去掉,剩下大部分样本点是线性可分的。这时我们可以放宽条件,允许某些样本分错,为此我们引入软间隔。

  • 线性不可分意味着某些样本点(xi,yi)不能满足约束条件。为解决这个问题,可以对每个样本点引进一个松弛变量ξi≥0,使得函数间隔加上松弛变量≥1。这时约束条件变为:

  • 同时为了在最大化间隔的时候使不满足约束的样本尽可能少,目标函数中引入对误分类的惩罚:

  • 这里C>0为惩罚系数,C值越大对误分类的惩罚越大。

  • 线性不可分的线性支持向量机的学习问题可表示如下:

  • 这就是软间隔支持向量机。

7.3.4 非线性支持向量机-核函数

  • 非线性分类问题是指通过利用非线性模型才能很好地进行分类的问题,如下图是无法直接使用超平面对其分类的:

  • 这时我们可以通过核函数将数据从原始空间映射到高维特征空间,使得数据在高维特征空间线性可分,将原本的非线性问题转换为线性问题。使用核技巧学习非线性支持向量机,等价于隐式地在高维特征空间中学习线性支持向量机。

    • ϕ(x)表示将x映射后的特征向量,在特征空间中超平面可表示为:

    • 并且有:

    • 将其转化为对偶问题并求解,但其对偶问题涉及到计算,这是样本xi和样本xj映射到特征空间之后的内积。由于特征空间维度很高,因此直接计算通常非常困难,为了避开这一障碍,设想这样一个函数:

    • κ就是核函数。显然若已知映射函数ϕ就可写出核函数,但实际任务中我们通常不知道ϕ的形式,那么如何判断给定的κ是否是核函数呢?

    • 实际上,只要一个对称函数所对应的核矩阵半正定,它就能作为核函数使用。这个定义在构造核函数时很有用,但对于一个具体函数κ,检验它是否为正定核函数并不容易,因此实际中往往使用已有的核函数。

    • 核函数的选择也是支持向量机最大的变数,若核函数选择不合适,意味着将样本映射到了一个不合适的特征空间,很可能导致性能不佳。下面是几种常用的核函数:

7.4 集成学习

  • 集成学习(Ensemble Learning)通过某种策略组合多个个体学习器的预测结果来提高整体的预测能力。只包含同种类型的个体学习器的集成称为同质集成,例如决策树集成中全是决策树,同质集成中的个体学习器亦称基学习器,相应的学习算法称为基学习算法。包含不同类型的个体学习器的集成称为异质集成,例如同时包含决策树和神经网络。
  • 集成学习有三大经典方法:Boosting、Bagging和Stacking。
    • Boosting(提升方法)按顺序训练模型,每个模型关注前一个模型的错误,通过加权调整来优化整体预测。如AdaBoost通过给错分的样本更大的权重,逐步改进;梯度提升树用梯度下降法优化损失函数;XGBoost和LightGBM是高效的梯度提升树变种。Boosting主要关注于降低偏差。
    • Bagging(Bootstrap Aggregating,自助聚合)从原始数据集中通过有放回的对样本采样生成多个子数据集,分别训练多个独立模型,最后通过投票(分类)或平均(回归)得到结果。随机森林则是在Bagging基础上随机选择特征子集训练每棵树。Bagging主要关注于降低方差。
    • Stacking(堆叠)训练多个不同类型的个体学习器,之后使用一个元模型综合多个个体学习器的预测。灵活性强,能结合多种模型的优势。

7.4.1 AdaBoost

  • 在概率近似正确学习的框架中,一个概念如果存在一个多项式的学习算法能够学习它,并且正确率很高,就称这个概念是强可学习的;一个概念如果存在一个多项式的学习算法能够学习它,但正确率仅比随机猜测略好,就称这个概念是弱可学习的。后来证明,强可学习与弱可学习是等价的。那么如果已经发现了弱学习算法,能否通过某种方式将其提升为强学习算法?

  • 对于分类问题而言,给定一个训练样本集,求比较粗糙的分类规则(弱分类器)要比求精确的分类规则(强分类器)容易的多。Boosting就是从弱学习算法出发,反复学习,得到一系列弱分类器,然后组合这些弱分类器构成一个强分类器。AdaBoost通常使用单层决策树作为基学习器,单层决策树也被称为决策树桩(Decision Stump)。

  • 大多数Boosting都是改变训练数据的概率分布(权重分布),针对不同的训练数据分布调用弱学习算法学习一系列弱分类器。AdaBoost(Adaptive Boosting,自适应提升)的做法是提高被前一轮弱分类器错误分类的样本的权重,降低被正确分类的样本的权重。这样一来后一轮弱学习器会更加关注那些没有被正确分类的数据。同时采用加权多数表决的方法,加大分类误差率小的弱分类器的权重,减小分类误差率大的弱分类器的权重。

  • AdaBoost工作流程如下:

    • 初始化训练数据权重分布,通常均匀分布:

    • 使用具有权重分布Dm的训练数据集学习,得到基本分类器:

    • 计算Gm (x)在训练数据集上的分类误差率:

    • 计算弱分类器Gm (x)的权重:

    • 更新训练数据集的权重分布:

    • 构建基本分类器的线性组合,得到最终分类器:

7.4.2 随机森林

  • 随机森林是Bagging的一个变体,在以决策树为基学习器构建Bagging集成的基础上,进一步在决策树训练过程中引入了随机属性选择。

  • 具体来说,传统决策树在选择划分特征时是在当前节点的特征集合(假定有d个特征)中选择最优特征。而在随机森林中,基决策树的每个节点先从该节点的特征集合中随机选择一个包含k个特征的子集,然后再从这个子集中选择一个最优特征用于划分。参数k控制着随机性的引入程度,若k=d,则基决策树的生成与传统决策树相同;若k=1,则随机选择一个属性用于划分。一般推荐k=log2⁡d。

  • 随机森林简单易实现,但在很多任务中都展现出了强大性能,被誉为“代表集成学习技术水平的方法”。Bagging中基学习器的多样性仅来自于样本扰动,而随机森林中基学习器的多样性不仅来自样本扰动,还来自特征扰动,这就使得最终集成的泛化性能可通过基学习器之间差异度的增加而进一步提升。

8、无监督学习

8.1 聚类

8.1.1 聚类简介

  • 聚类(Clustering)旨在将数据集中的样本分成若干个簇,使得同一个簇内的对象彼此相似,不同簇间的对象差异较大。聚类是一种无监督学习算法,不需要预先标记数据的标签,完全依赖数据本身内在结构和特征来进行分组,最终簇所对应的概念语义需由使用者来把握和命名。
  • 聚类的核心是“物以类聚”,具体通过以下步骤实现:
    • 定义相似性:选择一个度量标准(如欧氏距离,余弦相似度)来衡量对象之间的相似性或距离。
    • 分组:根据相似性将对象分配到不同的簇中。
    • 优化:通过迭代或直接计算,调整簇的划分,使簇内相似性最大化,簇间差异最大化。
  • 聚类应用场景:
    • 市场细分:将消费者按购买习惯分组。
    • 图像分割:将图像像素按颜色或纹理聚类。
    • 异常检测:识别不属于任何主要簇的异常点。
    • 生物信息:对基因表达数据进行分组。

8.1.2 常见聚类算法

K均值聚类

  • K均值聚类介绍:

    • K均值聚类(K-means)是基于样本集合划分的聚类方法,将样本集合划分为k个子集构成k个簇,将n个样本分到k个簇中,每个样本到其所属簇的中心的距离最小。每个样本只能属于一个簇,所以K均值聚类是硬聚类。

    • K均值聚类归结为样本集合的划分,通过最小化损失函数来选取最优的划分C^*。

    • 首先使用欧氏距离平方作为样本之间的距离:

    • 定义样本与其所属簇的中心之间的距离总和作为损失函数:

      • :第l个簇的中心。
      • C(i)=l:第i个样本是否属于簇l。
    • K均值聚类就是求解最优化问题:

    • 相似的样本被聚到同一个簇时损失函数最小。这是一个组合优化问题,n个样本分到k个簇,所有可能的分法数目,这个数目是指数级的,现实中采用迭代的方法求解。

  • K均值聚类工作流程:

    • 初始化,随机选择k个样本点作为初始簇中心。
    • 对样本进行聚类,计算每个样本到各个簇中心的距离,将每个样本分到与其最近的簇,构成聚类结果。
    • 计算聚类结果中每个簇中所有样本的均值,作为新的簇中心。
    • 使用新的簇中心重复上述过程,直到收敛或符合停止条件(例如划分不再改变)。
  • K均值聚类特点:

    • K均值聚类的初始中心的选择会直接影响聚类结果,并且不适合非凸形状簇。
    • K均值聚类需要事先指定簇个数k,而实际中最优的k值是不知道的,需要尝试使用不同的k值检验聚类结果质量,可以采用二分查找快速找到最优k值。聚类结果的质量可以用簇的平均直径来衡量,一般地,簇个数变小时平均直径会增加;簇个数变大超过某个值后平均直径会不变,而这个值正是最优的k值。

层次聚类

  • 层次聚类(Hierarchical Clustering)假设簇之间存在层次结构,将样本聚到层次化的簇中。层次聚类有自下而上的聚合方法和自上而下的分裂方法。因为每个样本只属于一个簇,所以层次聚类属于硬聚类。

  • 聚合聚类:开始将每个样本各自分到一个簇,之后将相距最近的两个簇合并,如此往复直至满足停止条件(例如达到预设的簇的个数、每个簇只包含一个样本、簇内样本相似性达到某个阈值等)。

  • 分裂聚类:开始将整个数据集视作一个整体,之后根据某种距离或相似性度量,选择一个现有的簇将其分裂成两个簇,使分裂后子簇内相似性高,子簇间差异大,如此往复直至满足停止条件。

密度聚类

  • 密度聚类(Density-Based Clustering)假设聚类结构能通过样本分布的紧密程度确定。通常情况下,密度聚类算法从样本密度的角度来考察样本之间的可连接性,并基于可连接样本不断扩展簇以获得最终聚类效果。

  • DBSCAN是一种著名的密度聚类算法,基于邻域参数来刻画样本分布的紧密程度。对于给定数据集D={x1,x2,…,xm },定义下列概念:

    • ε-邻域:对于xj∈D,其ε-邻域包含样本集D中与xj的距离不大于ε的样本。
    • 核心对象:若xj的ε-邻域至少包含MinPts个对象,则xj是一个核心对象。
    • 密度直达:若xj位于xi的ε-邻域中,且xi是核心对象,则称xj由xi密度直达。
    • 密度可达:对xi和xj,若存在样本序列p1,p2,…,pn,其中p1=xi,pn=xj,且p(i+1)由pi密度直达,则称xj由xi密度可达。
    • 密度相连:对xi和xj,存在xk使得xi和xj均由xk密度可达,则称xj由xi密度相连。
    • 噪声点:不属于任何簇的点,既不是核心对象也不在核心对象邻域内。
  • 基于这些概念,DBSCAN将簇定义为由密度可达关系导出的最大密度相连样本集合。DBSCAN先根据邻域参数(ε、MinPts)找出所有核心对象,再以任一核心对象为出发点找出由其密度可达的样本生成一个簇,直到所有核心对象均被访问过为止。

  • 密度聚类能识别任意形状的簇,可以自动识别并排除噪声点。但ε、MinPts的选择对密度聚类结果影响较大,且密度聚类难以适应密度变化较大的数据集。

8.1.3 K-means API使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import os
os.environ['OMP_NUM_THREADS'] = '2'

import matplotlib.pyplot as plt
from sklearn.cluster import KMeans # k均值聚类
from sklearn.datasets import make_blobs # 生成聚集分布的一组点

plt.rcParams['font.sans-serif'] = ["Songti SC"] # 宋体
plt.rcParams['axes.unicode_minus'] = False

# 1. 生成数据
X, y_true = make_blobs(n_samples=300, centers=3, cluster_std=2, random_state=42)

print(X.shape) # (300, 2)

# 先画出散点图
fig, ax = plt.subplots(2, figsize=(10, 10))

ax[0].scatter(X[:, 0], X[:, 1], c="gray", s=50, label="原始数据")
ax[0].set_title("原始数据")
ax[0].legend()

# 2. 创建模型并训练
kmeans = KMeans(n_clusters=3)
kmeans.fit(X)

# 获取簇中心点
centers = kmeans.cluster_centers_

# 3. 预测:每个样本点的 簇标签
y_cluster = kmeans.predict(X)

print(y_cluster)
# [1 1 2 0 1 0 2 0 2 2 2 0 2 2 1 2 1 0 2 2 2 2 0 1 2 1 1 0 0 2 2 2 1 2 1 2 1
# 0 1 0 0 2 1 0 2 2 1 0 1 0 0 1 1 2 1 0 1 2 0 2 1 0 0 1 1 0 0 1 1 2 0 1 1 2
# 2 1 1 0 2 0 2 2 1 2 0 1 1 2 0 2 1 2 1 2 2 1 1 2 1 1 0 2 0 2 2 2 2 2 0 1 0
# 2 2 2 2 0 1 0 1 0 0 0 2 1 1 1 1 2 1 1 2 2 2 2 2 0 0 1 2 1 2 2 1 2 0 0 0 2
# 0 2 2 1 0 1 2 0 0 1 1 2 2 1 1 1 2 1 0 2 2 2 2 2 0 2 0 0 0 2 0 0 1 2 1 0 0
# 1 0 2 0 0 1 1 0 1 0 0 0 0 2 1 2 2 0 0 2 0 1 1 0 2 2 1 0 0 1 1 1 1 2 1 1 0
# 1 1 2 0 1 1 0 2 2 1 2 1 0 0 1 0 1 1 1 0 0 2 1 0 0 0 1 0 1 0 1 0 0 1 0 2 1
# 2 2 2 1 2 0 0 1 0 0 2 2 0 0 0 1 1 1 2 2 2 0 0 0 0 1 0 1 0 0 1 2 0 0 2 1 2
# 0 2 1 1]
print(centers)
# [[ 4.85432791 2.04801886]
# [-6.88811639 -7.0878587 ]
# [-2.75726773 9.07285344]]

# 4. 画出聚类的散点图
ax[1].scatter(X[:, 0], X[:, 1], s=50, c=y_cluster)
ax[1].scatter(centers[:, 0], centers[:, 1], c="red", s=100, marker='o', label="簇中心")

ax[1].set_title("K-means聚类结果(K=3)")
ax[1].legend()
plt.show()

8.1.4 聚类模型评估(了解)

  • 由于聚类任务没有预定义的标签(不像监督学习有真实类别可供比较),所以需要依赖聚类结果和原始数据来衡量模型的好坏,主要关注簇内的紧凑性和簇间的分离性。

    • 轮廓系数(Silhouette Coefficient):计算每个样本到同簇其他样本的平均距离(内聚度ai)和到最近其他簇样本的平均距离(分离度bi),综合评价聚类紧密度和分离度。

      si的值越接近1,聚类效果越好。总体轮廓系数是所有si的平均值。

    • 簇内平方和(Within-Cluster Sum of Squares):衡量簇内数据点到簇中心的总距离平方和,常用于K-means。

      其中μk是第k个簇的中心。

    • 肘部法:肘部法用于确定最佳簇数K,在使用K-means时非常常见,它通过绘制簇数K和某个聚类质量指标(通常是簇内平方和)的关系曲线,找到一个拐点或“肘部”,即增加簇数带来的收益显著减少的点,这个点通常被认为是最佳的K值。

    • CH指数(Calinski-Harabasz Index):簇间和簇内分散度的比值,也称方差比准则。

      • BCSS:簇间平方和,nk是第k个簇的样本数,μk为第k个簇的中心,μ是所有样本的中心。
      • WCSS:簇内平方和。
    • 聚类评估API使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      import os
      os.environ['OMP_NUM_THREADS'] = '2'

      import matplotlib.pyplot as plt
      from sklearn.cluster import KMeans # k均值聚类
      from sklearn.datasets import make_blobs # 生成聚集分布的一组点

      from sklearn.metrics import silhouette_score, calinski_harabasz_score # 轮廓系数,CH指数

      plt.rcParams['font.sans-serif'] = ['Songti SC'] # 宋体
      plt.rcParams['axes.unicode_minus'] = False

      # 1. 生成数据
      X, y_true = make_blobs(n_samples=300, centers=3, cluster_std=2, random_state=42)

      print(X.shape) # (300, 2)

      # 2. 创建模型并训练
      kmeans = KMeans(n_clusters=3)
      kmeans.fit(X)

      # 获取簇中心点
      centers = kmeans.cluster_centers_

      # 3. 预测:每个样本点的 簇标签
      y_cluster = kmeans.predict(X)

      # print(y_cluster)
      # print(centers)

      # 4. 画出聚类的散点图
      plt.scatter(X[:, 0], X[:, 1], s=50, c=y_cluster)
      plt.scatter(centers[:, 0], centers[:, 1], c="red", s=100, marker='o', label="簇中心")

      plt.legend()
      plt.show()

      # 5. 打印评价指标
      print("簇内平方和:", kmeans.inertia_) # 簇内平方和: 2267.4382044976524
      print("轮廓系数: ", silhouette_score(X, y_cluster)) # 轮廓系数: 0.6996309397540692
      print("CH指数:", calinski_harabasz_score(X, y_cluster)) # CH指数: 1324.8383295497247

8.2 降维(了解)

8.2.1 奇异值分解

  • 奇异值分解简介

    • 奇异值分解(Singular Value Decompositon,SVD)是一种矩阵因子分解方法,用于将矩阵分解为更简单的形式,从而揭示数据的内在结构和特性。通过保留最大的几个奇异值及其对应的奇异向量,可以近似重构原始矩阵,减少数据维度,同时保留主要信息。主成分分析,潜在语义分析等都用到了奇异值分解。

    • 矩阵的奇异值分解是指将一个非零的实矩阵A∈R^(n×p)表示为三个矩阵的乘积(因子分解)的形式:

      • U是n阶正交矩阵:

      • V是p阶正交矩阵:

      • Σ是由降序排列的非负的对角元素组成的n×p矩形对角阵:

    • UΣV^T称为矩阵A的奇异值分解,σ_i称为矩阵A的奇异值,U的列向量称为左奇异向量,V的列向量称为右奇异向量。任一实矩阵一定存在奇异值分解,且奇异值分解不唯一。

  • 奇异值分解算法:矩阵A的奇异值分解可以通过求对称矩阵A^T A的特征值和特征向量得到。A^T A的单位化特征向量构成正交矩阵V的列;A^T A的特征值的平方根为奇异值σ_i,对其由大到小排列作为对角线元素构成对角矩阵Σ;求正奇异值对应的左奇异向量,再求扩充的A^T的标准正交基,构成正交矩阵U的列。具体过程如下:

    • 首先求A^T A的特征值和特征向量:

    • 得到特征值并将其由大到小排序:

    • 将特征值带入方程求出对应的特征向量,并将特征向量单位化,得到单位特征向量构成正交矩阵V:

    • 特征值的平方根构成n×p矩形对角阵Σ:

    • 对A的正奇异值计算U的列向量,A的秩为r:

    • 若n>r,则需要补充额外的正交向量使U成为n×n矩阵。求A^T的零空间的一组标准正交基

    • 以求的奇异值分解为例:

    • 得到齐次线性方程组:

    • 该方程组有非零解的充要条件是:

    • 解的λ1=10,λ2=0,代入线性方程组,得到对应的单位向量:

    • 奇异值,构造对角矩阵:

    • 基于A的正奇异值计算得到列向量u1

    • 列向量u2,u3是A^T^的零空间N(A^T^)的一组标准正交基,为此求解下面线性方程组:

    • 分别取(x2,x3)为(1,0)和(0,1)得到N(A^T^ )的基:

    • 正交基为:

    • 构造正交矩阵U:

    • 最终得到矩阵A的奇异值分解:

    • 上述过程只是为了说明计算的过程,实际应用中不会使用此算法。实际应用中虽然也会求解A^T^A的特征值,但不直接计算A^T^A。

  • 紧凑奇异值分解与截断奇异值分解

    • A=UΣV^T^又称矩阵的完全奇异值分解,实际常用的是奇异值分解的紧凑形式和截断形式。紧凑奇异值分解是与原始矩阵等秩的奇异值分解,截断奇异值分解是比原始矩阵低秩的奇异值分解。

    • 紧凑奇异值,r为矩阵A的秩。Ur∈R^(n×r)^取U的前r列,Σr是r阶对角矩阵,Vr∈R^(p×r)^取V的前r列。

    • 截断奇异值只取最大的k个奇异值对应的部分。Uk∈R^(n×k)^取U的前k列,Σk取Σ的前k个对角线元素,Vk∈R^(p×k)^取V的前k列。

8.2.2 主成分分析

  • 主成分分析简介

    • 主成分分析(Principal Component Analysis,PCA)是一种常用的无监督学习方法,旨在找到数据中“最重要的方向”,即方差最大的方向,并用这些方向重新表达数据。

    • 在主成分分析过程中,首先将数据的每个特征规范化为平均值为0方差为1,以消除不同特征之间量纲的差异,再使用正交变换把线性相关的原始数据转换为线性无关的新数据(主成分)。主成分彼此正交并且能够最大化地保留原始数据的方差信息。主成分分析主要用于降维和发现数据的基本结构。

    • 主成分分析可直观解释为对数据所在的原始坐标系进行旋转变换,将数据投影到新坐标系的坐标轴上,新坐标系的第一坐标轴、第二坐标轴等分别表示第一主成分、第二主成分等。数据在每一轴上的坐标值的平方表示相应变量的方差,并且这个坐标系是所有可能的新坐标系中,坐标轴上的方差的和最大的。

    • 在数据总体上进行的主成分分析称为总体主成分分析,在有限样本上进行的主成分分析称为样本主成分分析。在实际问题中,通常需要在观测数据上进行主成分分析,也就是样本主成分分析。

    • 传统的主成分分析通过协方差矩阵或相关矩阵的特征值分解进行。

    • 协方差矩阵描述变量之间的方差和协方差,适用于未标准化的数据;

    • 相关矩阵描述变量之间的标准化相关性,适用于标准化的数据。

    • 特征值分解的结果给出了主成分的方向(特征向量)和每个主成分的方差(特征值)。

    • 现在常用的方法是通过奇异值分解进行主成分分析。

  • 相关矩阵特征值分解实现主成分分析

    • 给定的样本数据每行为一个样本,每列为一个特征,对其进行规范化使其每列均值为0方差为1,得到X。对于标准化后的数据,其相关矩阵等于协方差矩阵。X的相关矩阵:

    • 求解特征方程|R-λI|=0,得到p个特征值λ1,λ2,…,λp,降序排列作为主成分的方差。将解得的特征值代入特征方程求出对应的单位特征向量v1,v2,…,vp,作为主成分方向。根据特征值计算每个主成分的方差贡献率:

    • 根据累计方差贡献率选择前k个主成分(如选择累计方差贡献率大于90%的前k个主成分),取V的前k列构成Vk。

    • 将数据投影到主成分方向,得到主成分Y=XVk

    • 下面是一个主成分分析的例子:

      • 给定样本数据X。当使用样本均值替代总体均值时,偏差平方和会系统性低估总体方差,因此使用样本标准差代替总体标准差,n-1提供无偏估计:

      • 计算相关矩阵,对于标准化后的数据,其相关矩阵等于协方差矩阵:

      • 对R进行特征值分解,得到特征值和特征向量:

      • 选择前两个作为主成分方向进行投影,得到第一、第二主成分:

  • 奇异值分解实现主成分分析

    • 通过奇异值分解实现主成分分析可以避免计算协方差矩阵或相关矩阵,可以提高计算效率。

    • 计算过程如下:

      • 对规范化后的样本矩阵X进行奇异值分解X=UΣV^T^,V的列向量就是X的主成分的方向,主成分矩阵Y=XV。Σ则与协方差矩阵的特征值相关。每个主成分的方差贡献率为,用于决定保留多少主成分。
    • 数学验证如下:

      • 给定的样本矩阵每行为一个样本,每列为一个特征,对其进行规范化使其每列均值为0方差为1,得到X。X的协方差矩阵为:

      • 代入奇异值分解X=UΣV^T^,因为U是正交矩阵,U^T^U=I:

      • 这正是协方差矩阵的特征值分解形式,其中V是特征向量,是特征值。

9、总结

  • 机器学习通常被分为哪几类?请介绍一下监督学习和无监督学习的区别。

    • 监督学习、无监督学习、半监督学习、强化学习。
    • 有监督学习是在有标签的数据集上进行训练的;而无监督学习是在没有给定明确标签的数据集上进行学习的过程,其目标是发现数据中隐藏的结构、模式或分组。
  • 什么是特征工程?主要包含哪些内容?

    • 特征工程(Feature Engineering)是机器学习过程中非常重要的一步,指的是通过对原始数据的处理、转换和构造,生成新的特征或选择有效的特征,从而提高模型的性能。简单来说,特征工程是将原始数据转换为可以更好地表示问题的特征形式,帮助模型更好地理解和学习数据中的规律。
    • 特征工程主要包括:特征选择、特征转换、特征构造、特征降维。
  • 什么是特征工程中的归一化、标准化?在处理图像像素值这样的数据时,一般优先采用哪种特征转换方式?对于类别型特征,一般会怎样进行处理?

    • 归一化将数据按比例缩放到一个固定范围[xmin,xmax](通常是[0,1]或[-1,1])。
    • 标准化将数据调整为均值为0、标准差为1的标准分布。
    • 当数据分布有明显边界(如图像像素值、文本词频),或模型对输入范围敏感时,可以优先考虑归一化。
    • 类别型特征:对于0-1取值的二元类别特征,可以保持原样;对于多类别特征,需要进行特殊编码,一般采用独热编码。
  • 请描述什么是欠拟合、过拟合。如果出现过拟合,可能是什么原因?怎样解决?

    • 欠拟合(Underfitting):是指模型在训练数据上表现不佳,无法很好地捕捉数据中的规律。这样的模型不仅在训练集上表现不好,在测试集上也同样表现差。
    • 过拟合(Overfitting):是指模型在训练数据上表现得很好,但在测试数据或新数据上表现较差的情况。过拟合的模型对训练数据中的噪声或细节过度敏感,把训练样本自身的一些特点当作了所有潜在样本都会具有的一般性质,从而失去了泛化能力。
    • 过拟合产生原因:
      • 模型复杂度过高:模型过于复杂,参数太多。
      • 训练数据不足:数据集太小,模型能记住训练数据的细节,但无法泛化到新数据。
      • 特征过多:特征太多,模型可能会“记住”数据中的噪声,而不是学到真正的规律。
      • 训练过长:训练时间过长,导致模型学习到训练数据中的噪声,而非数据的真正规律。
    • 解决办法:
      • 减少模型复杂度:降低模型的参数数量、使用简化的模型或降维来减小模型复杂度。
      • 增加训练数据:收集更多数据,或通过数据增强来增加训练数据的多样性。
      • 使用正则化:引入L1、L2正则化,避免过度拟合训练数据。
      • 交叉验证:使用交叉验证技术评估模型在不同数据集上的表现,以减少过拟合的风险。
      • 早停:训练时,当模型的验证损失不再下降时,提前停止训练,避免过度拟合训练集。
  • 机器学习中使用正则化技术的目的是什么?请列出常见的正则化技术(至少两种)。

    • 正则化(Regularization)的目的是在损失函数中添加额外项,来惩罚过大的参数,进而限制模型复杂度、避免过拟合,提高模型泛化能力。

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

    • L1正则化通过惩罚模型参数的绝对值,使得部分权重趋近0甚至变为0。这会导致特征选择,即模型会自动“丢弃”一些不重要的特征。L1正则化有助于创建稀疏模型(即许多参数为0)。

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

    • L2正则化通过惩罚模型参数的平方,使得所有参数都变得更小,但不会将参数强行压缩为0。它会使得模型尽量平滑,从而防止过拟合。

  • 采用交叉验证的目的是什么?请简要描述k折交叉验证。

    • 交叉验证(Cross-Validation)是一种评估模型泛化能力的方法,通过交叉验证能更可靠地估计模型在未知数据上的表现,亦能避免因单次数据划分不合理导致的模型过拟合或欠拟合。实际应用中,往往用交叉验证来进行模型选择和超参数调优。
    • k折交叉验证(k-Fold Cross-Validation):将数据均匀分为k个子集(称为“折”),每次用k−1折训练,剩余1折验证,重复k次后取平均性能。充分利用数据,结果更稳定。
  • 请写出至少两个常见的超参数,并说明其作用。

    • 正则化系数:用来表示正则化中,惩罚项的权重。
    • 学习率:在梯度下降法中,作为梯度值的系数,用来表示下降(学习)的速度。
    • ElasticNet正则化中的α:L1和L2正则化项权重系数。
    • KNN算法中的K值:选取K个近邻进行进行分类或预测。
    • K-means算法中的K值:聚类的簇的数量。
    • DBSCAN算法中的ε和MinPts:核心对象邻域的半径大小、至少包含的对象个数。
  • 什么是梯度下降法?请写出梯度下降法参数更新的迭代公式。

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

    • 参数更新的迭代公式:

  • 回归模型有哪些评价指标?请至少写出两个。

    • 平均绝对误差(MAE)

    • 均方误差(MSE)

    • 均方根误差(RMSE)

    • R²(决定系数)

  • 什么是分类问题的准确率、精确率、召回率?

    • 准确率(Accuracy):正确预测的比例。

    • 精确率(Precision):预测为正例的样本中实际为正例的比例,也叫查准率。

    • 召回率(Recall):实际为正类的样本中预测为正类的比例,也叫查全率。

  • 什么是ROC和AUC?AUC值=0.5和=1分别代表什么样的模型?

    • ROC曲线(Receiver Operating Characteristic Curve,受试者工作特征)是评估二分类模型性能的工具,以假正例率(FPR)为横轴,以真正例率(TPR)为纵轴,展示不同阈值下模型的表现。
    • AUC值代表ROC曲线下的面积,用于量化模型性能。AUC值越大,模型区分正负类的能力越强,模型性能越好。AUC值=0.5表示模型接近随机猜测,AUC值=1代表完美模型。
  • 请简要描述KNN算法的思想。

    • K近邻算法(K-Nearest Neighbors,KNN)是一种基本的分类与回归方法,属于监督学习算法。其核心思想是通过计算给定样本与数据集中所有样本的距离,找到距离最近的K个样本,然后根据这K个样本的类别或值来预测当前样本的类别或值。
  • 线性回归任务最常用哪种损失函数?为什么选择这种损失函数?

    • 线性回归一般使用均方误差损失函数。均方误差凸函数,存在全局的唯一最小值,平方项又使损失函数处处可导,便于求解最优参数,解析解可通过矩阵运算直接求出;若误差服从正态分布,则均方误差对应极大似然估计,是最优的损失函数。
  • 什么是“最小二乘法”?在线性回归中,最小二乘法有什么具体含义?

    • 基于均方误差最小化来进行模型求解的方法称为“最小二乘法”。在线性回归中,最小二乘法就是试图找到一条直线(或超平面),使所有样本到直线(或超平面)上的欧氏距离之和最小。
  • 梯度下降法中,学习率过大或过小,会有什么影响?

    • 学习率过大:可能导致跳过最优解,甚至发散。
    • 学习率过小:收敛速度慢,易陷入局部极小。
  • 逻辑回归一般用来解决什么任务?可以通过什么方法,扩展到多分类任务?

    • 逻辑回归(Logistic Regression)是一种用于解决分类问题的统计方法,尤其适用于二分类问题。尽管名称中有“回归”,但它主要用于分类任务。
    • 逻辑回归通常用于二分类问题,但可以通过一对多(One-vs-Rest,OvR)以及Softmax回归(Multinomial Logistic Regression,多项逻辑回归)来扩展到多分类任务。
  • 逻辑回归通常用什么映射函数将线性拟合结果转换为概率?一般用什么损失函数进行模型求解?

    • 逻辑回归常用的映射函数是sigmoid函数。逻辑回归的损失函数通常使用对数损失(Log Loss),也称为二元交叉熵损失(Binary Cross-Entropy Loss),用于衡量模型输出的概率分布与真实标签之间的差距。逻辑回归的损失函数来源于最大似然估计(MLE)。
  • 感知机有什么局限?可以怎样解决这个问题?

    • 感知机的局限性就在于它只能表示由一条直线划分的空间,即线性空间,而不能表示非线性空间划分,例如异或操作。通过叠加层级、构建多层感知机,就可以解决这个问题。
  • 决策树进行特征选择的准则有哪些?并列出相应的典型算法。

    • 决策树通常特征选择的准则是信息增益或信息增益率,还有基尼指数;对应的算法分别为ID3、C4.5、CART。
  • 请简要描述K-means和DBSCAN聚类算法。

    • K均值聚类(K-means)是基于样本集合划分的聚类方法,将样本集合划分为k个子集构成k个簇,将n个样本分到k个簇中,每个样本到其所属簇的中心的距离最小。
    • DBSCAN是一种著名的密度聚类算法,基于邻域参数来刻画样本分布的紧密程度,将簇定义为由密度可达关系导出的最大密度相连样本集合。DBSCAN先根据邻域参数(ε、MinPts)找出所有核心对象,再以任一核心对象为出发点找出由其密度可达的样本生成一个簇,直到所有核心对象均被访问过为止。