机器学习13(正则化)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录

简介

  • 这一节详细探讨关于正则化的相关内容并就 sklearn 中逻辑回归评估器的参数进行详细解释
  • 由于 sklearn 内部参数的一致性许多参数不仅是逻辑回归的参数也是大多数分类模型的通用参数
  • 逻辑回归作为一个诞生时间较早并且拥有深厚统计学背景的模型拥有非常多的变种应用方法虽然之前就逻辑回归的基本原理、基础公式以及分类性能进行了探讨但实际上逻辑回归算法的模型形态和应用方式远不止于此在 sklearn 中提供了非常丰富的逻辑回归的可选算法参数相当于提供了一个集大成者的逻辑回归模型
  • 第一个参数就是关于正则化的选项

正则化

  • 可以看一下逻辑回归的参数选项
    from sklearn.linear_model import LogisticRegression
    LogisticRegression?
    Init signature:
    LogisticRegression(
        penalty='l2',
        *,
        dual=False,
        tol=0.0001,
        C=1.0,
        fit_intercept=True,
        intercept_scaling=1,
        class_weight=None,
        random_state=None,
        solver='lbfgs',
        max_iter=100,
        multi_class='auto',
        verbose=0,
        warm_start=False,
        n_jobs=None,
        l1_ratio=None,
    )
    
  • penalty='l2' 也就是 L2 正则化接下来就正则化的相关内容展开讨论
  • 机器学习中正则化regularization的外在形式非常简单就是在模型的损失函数中加上一个正则化项regularizer有时也被称为惩罚项penalty term
  • 如下方程所示其中L为损失函数J为正则化项
  • 通常来说正则化项往往是关于模型参数的 1-范数 或者 2-范数当然也有可能是这两者的某种结合例如sklearn的逻辑回归中的弹性网正则化项
  • 模型加入 1-范数 的正则化也被称为 𝑙1 正则化加入 2-范数 的正则化也被称为 𝑙2 正则化
    1 N ∑ i = 1 N L ( y i , f ( x i ) ) + λ J ( f ) \frac{1}{N}\sum^{N}_{i=1}L(y_i,f(x_i))+\lambda J{(f)} N1i=1NL(yi,f(xi))+λJ(f)
  • 何时需要进行正则化呢
    • 正则化核心的作用是缓解模型过拟合倾向此外由于加入正则化项后损失函数的结构发生了变化因此也会影响损失函数的求解过程比如岭回归在加入了 𝑙1 正则化项之后会让损失函数的求解变得更加高效
    • 对于逻辑回归来说如果加入 𝑙2 正则化项损失函数就会变成严格的凸函数
    • 也就是说如果模型出现了过拟合就需要用正则化在了解正则化的具体原理之前需要引入两个非常重要的概念经验风险和结构风险

经验风险和结构风险

  • 在我们构建损失函数求最小值的过程其实就是依据以往经验训练数据追求风险最小以往数据上误差最小的过程
  • 而在给定一组参数训练后的w后计算得出的损失函数的损失值使用测试数据其实就是经验风险
  • 而所谓结构风险我们可以将其等价为模型复杂程度模型越复杂结构风险就越大
  • 而正则化后的损失函数在进行最小值求解的过程中其实是希望损失函数本身和正则化项都取得较小的值即模型的经验风险和结构风险能够同时得到控制
  • 换句话说就是正则项能够进一步降低损失值还能够简化模型结构
  • 模型的经验风险需要被控制不难理解因为我们希望模型能够尽可能的捕捉原始数据中的规律但为何模型的结构风险也需要被控制核心原因在于尽管在一定范围内模型复杂度增加能够有效提升模型性能但模型过于复杂可能会导致另一个非常常见的问题——过拟合
  • 关于模型过拟合的概念稍后会进行更加详细的介绍但总的来说一旦模型过拟合了尽管模型经验风险在降低、但模型的泛化能力会下降。因此为了控制模型过拟合倾向我们可以把模型结构风险纳入损失函数中一并考虑当模型结构风险的增速高于损失值降低的收益时我们就需要停止参数训练迭代
    1
  • 同时要求模型性能和模型复杂度都在一个合理的范围内其实等价于希望训练得到一个较小的模型同时具有较好的解释数据的能力捕捉全局规律的能力这也符合奥卡姆剃刀原则
  • 解释数据=捕捉数据全局规律

过拟合

  • 前面探讨过关于机器学习建模有效性的问题得出的结论是当训练数据和新数据具有规律的一致性时才能够进行建模而只有挖掘出贯穿始终的规律同时影响训练数据和新数据的规律模型才能够进行有效预测
  • 不过既然有贯穿始终的全局规律那就肯定存在一些只影响了一部分数据的局部规律
  • 一般来说由于全局规律影响数据较多因此更容易被挖掘而局部规律只影响部分数据更难被挖掘因此从较为宽泛的角度来看但伴随着模型性能提升也是能够捕获很多局部规律的
  • 但局部规律对于新数据的预测并不能起到正面的作用反而会影响预测结果此时就出现模型过拟合现象
  • 也就是说过拟合是指模型捕捉到了数据的局部规律过拟合会对新数据的预测产生负面影响
  • 通过如下实例进行说明
    • x 是一个0到1之间等距分布20个点组成的 ndarray
      # 设计随机数种子
      np.random.seed(123)
      
      # 创建数据
      n_dots = 20
      x = np.linspace(0, 1, n_dots)                        # 从0到1等宽排布的20个数
      y = np.sqrt(x) + 0.2*np.random.rand(n_dots) - 0.1    # 根号x + 扰动项
      
    • y = x + r y=\sqrt{x}+r y=x +rr 是人为制造的随机噪声在[-0.1,0.1]之间服从均匀分布
    • 借助 numpy 的 polyfit 函数来进行多项式拟合polyfit函数会根据设置的多项式阶数在给定数据的基础上利用最小二乘法进行拟合并返回拟合后各阶系数系数计算完成后常用 ploy1d 函数逆向构造多项式方程进而利用方程求解 y
    • 人为制造一个二阶多项式方程然后进行二阶拟合实验
      y0 = x ** 2
      np.polyfit(x, y0, 2)	# 2阶
      # array([ 1.00000000e+00, -2.00019564e-18, -3.29090297e-17])
      p = np.poly1d(np.polyfit(x, y0, 2))	# 方程
      print(p)	# 1 x^2 - 2e-18 x - 3.291e-17
      p(-1)	# 1.0
      
    • 接下来分别利用1阶x多项式、3阶x多项式和10阶x多项式来拟合y并利用图形观察多项式的拟合度
    • 首先定义一个辅助画图函数方便后续将图形画于一张画布中进而方便观察
      def plot_polynomial_fit(x, y, deg):
          p = np.poly1d(np.polyfit(x, y, deg))
          t = np.linspace(0, 1, 200)
          # 原始数据红点模型蓝曲线真实规律红虚线全局规律
          plt.plot(x, y, 'ro', t, p(t), '-', t, np.sqrt(t), 'r--')
      # 测试
      plot_polynomial_fit(x, y, 3)
      
      1
    • 将三个拟合结果放在一张图中
      plt.figure(figsize=(18, 4), dpi=200)
      titles = ['Under Fitting', 'Fitting', 'Over Fitting']
      for index, deg in enumerate([1, 3, 10]):
          plt.subplot(1, 3, index + 1)
          plot_polynomial_fit(x, y, deg)
          plt.title(titles[index], fontsize=20)
      
      2
    • 一阶多项式拟合的曲线即无法捕捉数据集的分布规律离数据集背后客观规律也很远
    • 三阶多项式在这两方面表现良好
    • 十阶多项式则在数据集分布规律捕捉上表现良好经过了大部分数据点但同样偏离红色曲线较远
    • 此时一阶多项式实际上就是欠拟合而十阶多项式则过分捕捉了噪声数据的分布规律局部规律而噪声之所以被称作噪声是因为其分布本身毫无规律可言或者其分布规律毫无价值因此就算十阶多项式在当前训练数据集上拟合度很高但其捕捉到的无用规律无法推广到新的数据集上因此该模型在测试数据集上执行过程将会有很大误差。即模型训练误差很小但泛化误差损失值很大
  • 接下来回到正则化看看它是如何降低风险的或者说如何解决过拟合问题的
    • 风险大的模型往往是过拟合的所以从很大程度上来说降低风险就是解决过拟合问题
    • 而正则化核心的作用就是缓解模型过拟合倾向
    • 至于如何提高预测的准确率就不是这里考虑的问题了当然风险大也有其他因素需要用其他方法解决这里先把握主要问题

正则化

  • 尝试在模型中加入正则化项来缓解10阶多项式的过拟合倾向
  • 为了更加符合 sklearn 的建模风格、从而能够使用sklearn的诸多方法我们将上述10阶多项式建模转化为等价的另一种形式即在原始数据中衍生出几个特征 x 2 x^2 x2 x 3 x^3 x3、…、 x 10 x^{10} x10然后代入线性回归方程进行建模
  • 换句话说就是之前用一个特征拟合到10阶现在用10个特征拟合但这10个特征其实是由一个特征衍生升次方出来的效果一样每一阶/每个特征都有系数转换形式而已
    x_l = []
    for i in range(10):
        x_temp = np.power(x, i+1).reshape(-1, 1)	# 搞成一列
        x_l.append(x_temp)
    # 最终搞成10列(20行20组训练数据)就是10个特征 x1...x10开始训练得到 w1...w10
    X = np.concatenate(x_l, 1)	# 还要有个bias
    
  • 上述衍生过程也可以通过sklearn中的PolynomialFeatures类实现
    from sklearn.preprocessing import PolynomialFeatures
    # 查看帮助文档
    PolynomialFeatures?
    
  • 围绕特征衍生后的新数据来进行线性回归建模
    from sklearn.linear_model import LinearRegression
    lr = LinearRegression()
    lr.fit(X, y)
    lr.coef_
    # 查看过拟合时MSE
    from sklearn.metrics import mean_squared_error
    mean_squared_error(lr.predict(X), y)	# 0.001172668222879593
    # 观察建模结果
    t = np.linspace(0, 1, 200)
    plt.plot(x, y, 'ro', x, lr.predict(X), '-', t, np.sqrt(t), 'r--')
    plt.title('10-degree')
    
  • 接下来尝试在线性回归的损失函数中引入正则化来缓解过拟合问题
  • 根据前面的讨论我们知道在线性回归中加入 l 2 l2 l2 正则化实际上就是岭回归Ridge而加入 l 1 l1 l1 正则化则变成了 Lasso
  • 因此我们分别考虑围绕上述模型进行岭回归和Lasso的建模
    # 导入岭回归和Lasso
    from sklearn.linear_model import Ridge,Lasso
    # alpha越大惩罚力度越大
    reg_rid = Ridge(alpha=0.005)
    reg_rid.fit(X, y)
    mean_squared_error(reg_rid.predict(X), y)	# 0.0021197020660901986
    # 观察惩罚效果
    t = np.linspace(0, 1, 200)
    plt.subplot(121)
    plt.plot(x, y, 'ro', x, reg_rid.predict(X), '-', t, np.sqrt(t), 'r--')
    plt.title('Ridge(alpha=0.005)')
    plt.subplot(122)
    plt.plot(x, y, 'ro', x, lr.predict(X), '-', t, np.sqrt(t), 'r--')
    plt.title('LinearRegression')
    
    1
  • 不难发现 l 2 l2 l2 正则化对过拟合倾向有较为明显的抑制作用
    reg_las = Lasso(alpha=0.001)	# 设置小一些
    reg_las.fit(X, y)
    reg_las.coef_
    # array([ 1.10845364, -0.        , -0.37211179, -0.        , -0.        ,
    #    0.        ,  0.        ,  0.        ,  0.        ,  0.05080217])
    
    mean_squared_error(reg_las.predict(X), y)	# 0.004002917874293844
    t = np.linspace(0, 1, 200)
    plt.subplot(121)
    plt.plot(x, y, 'ro', x, reg_las.predict(X), '-', t, np.sqrt(t), 'r--')
    plt.title('Lasso(alpha=0.001)')
    plt.subplot(122)
    plt.plot(x, y, 'ro', x, lr.predict(X), '-', t, np.sqrt(t), 'r--')
    plt.title('LinearRegression')
    
    2
  • 可以看到Lasso 的惩罚力度更强并且迅速将一些系数清零而这些被清零的参数则代表对应的特征在实际建模过程中并不重要从而达到特征筛选的目的
  • 在实际的建模过程中 l 2 l2 l2 正则化往往应用于缓解过拟合趋势 l 1 l1 l1 正则化往往被用于特征筛选
  • 特征重要性和特征对应系数大小并没有太大的关系判断特征是否重要的核心还是在于观察抛弃某些特征后建模结果是否会发生显著影响

  • l 1 l1 l1 正则化的运算结果说明上述10个特征中第一个、第三个和最后一个特征相对重要。而特征重要的含义其实是代表哪怕带入上述3个特征建模依然能够达到带入所有特征建模的效果
    # 验证
    # 挑选特征构建新的特征矩阵
    X_af = X[:, [0, 2, 9]]
    lr_af = LinearRegression()
    lr_af.fit(X_af, y)
    lr_af.coef_	# array([ 1.45261658, -0.93936141,  0.39449483])
    mean_squared_error(lr_af.predict(X_af), y)	# 0.0027510973386944155
    lr_af.predict(X_af)
    # 画图
    
    3
  • 可以看到哪怕删掉了70%的特征最终建模结果仍然还是未收到太大的影响从侧面也说明剩下70%的特征确实“不太重要”过拟合的趋势也略微有所好转
  • 继续加上 l 2 l2 l2 正则化呢
    # 特征减少可以适度放大alpha
    reg_rid_af = Ridge(alpha=0.05)
    reg_rid_af.fit(X_af, y)
    reg_rid_af.coef_	# array([ 1.02815296, -0.31070552,  0.06374435])
    mean_squared_error(reg_rid_af.predict(X_af), y)	# 0.004383156990375146
    # 画图
    t = np.linspace(0, 1, 200)
    plt.subplot(121)
    plt.plot(x, y, 'ro', x, reg_rid_af.predict(X_af), '-', t, np.sqrt(t), 'r--')
    plt.title('Ridge_af(alpha=0.05)')
    plt.subplot(122)
    # 和只使用Ridge相比
    plt.plot(x, y, 'ro', x, reg_rid.predict(X), '-', t, np.sqrt(t), 'r--')
    plt.title('Ridge(alpha=0.005)')
    
    4
  • 整体过拟合倾向被更进一步抑制整体拟合效果较好

建模策略

  • 小结一下
    • 当模型效果往往是线性模型多项式方程不佳时可以考虑通过特征衍生的方式来进行数据的“增强”
    • 如果出现过拟合趋势则首先可以考虑进行不重要特征的筛选过多的无关特征其实也会影响模型对于全局规律的判断此时可以考虑使用 l 1 l1 l1 正则化配合线性方程进行特征重要性筛选剔除不重要的特征
    • 对于过拟合趋势的抑制仅仅踢出不重要特征还是不够的对于线性方程类的模型来说 l 2 l2 l2 正则化则是缓解过拟合非常好的方法配合特征筛选能够快速的缓解模型过拟合倾向
  • 不进行特征筛选 l 2 l2 l2 正则化也可以帮助线性方程抑制过拟合但特征太多其实会影响 l 2 l2 l2 正则化的参数取值范围进而影响 alpha 参数惩罚力度的有效性
  • 上述参数的选取和过拟合倾向的判断其实还是主观判断成分较多选择看图一个更加严谨的流程是先进行数据集的划分然后选取更能表示模型泛化能力的评估指标然后将特征提取比如选 0/2/9、 l 2 l2 l2 正则化后的线性方程组成一个 Pipeline再利用网格搜索遍历确定一组最优的参数组合下一节
  • 需要强调的是并非所有模型都需要/可以通过正则化来进行过拟合修正典型的可以通过正则化来进行过拟合倾向修正的模型主要有线性回归、逻辑回归、LDA、SVM 以及一些 PCA 衍生算法如SparsePCA。而树模型则不用通过正则化来进行过拟合修正

逻辑回归

  • 官网给出的逻辑回归加入正则化后的损失函数表达式
    1
  • 该表达式和此前推导的交叉熵损失函数的表达式略有差异核心原因是 sklearn 在二分类时默认两个类别标签取值为 -1 和 1而不是 0 和 1可以参考前面的内容练习推导过程
  • 观察发现正则化后的损失函数有两处发生了变化
    • 在原损失函数基础上乘以了系数 C也是超参数需要人工输入用于调整经验风险部分和结构风险部分的权重C越大经验风险部分权重越大反之结构风险部分权重越大
    • 加入了正则化项在 l 2 l2 l2 正则化时采用的 w T w 2 \frac{w^Tw}{2} 2wTw 表达式其实相当于是各参数的平方和除以2在求最小值时本质上和 w 的 2-范数 起到的作用相同省去开平方是为了简化运算而除以 2 则是为了方便后续求导运算
  • 另外sklearn 中还提供了弹性网正则化方法通过 ρ \rho ρ 控制 l 1 l1 l1正则化和 l 2 l2 l2正则化惩罚力度的权重是一个更加综合的解决方案。不过代价是增加了一个超参数 ρ \rho ρ并且由于损失函数形态发生了变化导致部分优化方法无法使用

逻辑回归评估器

  • 进入正题sklearn 中逻辑回归评估器的参数函数形参解释
    2
  • 对偶问题是约束条件相反、求解方向也相反的问题一般在数据集过小而特征较多时使用
  • class_weight 代表各类样本在进行损失函数计算时的数值权重例如一个二分类问题0、1两类的样本比例是 2:1此时可以输入一个字典类型对象用于说明两类样本在进行损失值计算时的权重例如输入 {0:1, 1:3}则代表1类样本的每一条数据在进行损失函数值的计算时都会在原始数值上*3。而当我们将该参数选为balanced时则会自动将这个比例调整为真实样本比例的反比以达到平衡的效果
  • 其实除了最小二乘法和梯度下降以外还有非常多的关于损失函数的求解方法由 solver 参数决定
    • 当然也不是随便选的得看损失函数的形态
      3
    • liblinear这是一种坐标轴下降法并且该软件包中大多数算法都有C++编写运行速度很快支持OVR+L1或OVR+L2
    • lbfgs全称是L-BFGS牛顿法的一种改进算法一种拟牛顿法适用于小型数据集并且支持MVM+L2、OVR+L2以及不带惩罚项的情况
    • newton-cg同样也是一种拟牛顿法和 lbfgs 适用情况相同
    • sag随机平均梯度下降随机梯度下降的改进版类似动量法会在下一轮随机梯度下降开始之前保留一些上一轮的梯度从而为整个迭代过程增加惯性除了不支持L1正则化的损失函数求解以外包括弹性网正则化其他所有损失函数的求解
    • sagasag的改进版修改了梯度惯性的计算方法使得其支持所有情况下逻辑回归的损失函数求解
    • 对于逻辑回归来说决定损失函数形态的就是多分类问题时采用的策略以及加入的惩罚项所以大多数情况会优先根据多分类问题的策略及惩罚项来选取优化算法其次如果还有多个算法可选可以根据以下情况

    • Penalize the intercept (bad)如果要对截距项也进行惩罚那只能选取liblinear
    • Faster for large datasets如果需要对海量数据进行快速处理则可以选取sag和saga
    • Robust to unscaled datasets如果未对数据集进行标准化但希望维持数据集的鲁棒性迭代平稳高效则可以考虑使用liblinear、lbfgs和newton-cg三种求解方法
  • multi_class选用何种方法进行多分类问题求解
    • 可选 OVR 和 MVM默认情况是 auto此时模型会优先根据惩罚项和solver选择OVR还是MVM但一般来说MVM 效果会好于 OVR
  • 对于这些参数更深层次的理解则需要长期的积淀接下来进行一次综合调参练习

练习

  • 在补充了一系列关于正则化的基础理论以及 sklearn 中逻辑回归评估器的参数解释之后我们可以尝试进行包含特征衍生和正则化过程的建模试验同时探索模型经验风险和结构风险之间的关系
  • 一方面巩固此前介绍的相关内容同时也进一步加深对于Pipeline的理解和使用。也为后续的网格搜索调参做铺垫总结出更好的调参策略
    # 科学计算模块
    import numpy as np
    import pandas as pd
    
    # 绘图模块
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    
    # 自定义模块
    from ML_basic_function import *
    
    # Scikit-Learn相关模块
    # 评估器类
    from sklearn.preprocessing import StandardScaler
    from sklearn.preprocessing import PolynomialFeatures
    from sklearn.linear_model import LogisticRegression
    from sklearn.pipeline import make_pipeline
    
    # 实用函数
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score
    
  • 准备数据集
    • 逻辑回归的决策边界实际上就是逻辑回归的线性方程这一特性线性模型=
    • 创建一个满足分类边界为 y 2 = − x + 1.5 y^2=-x+1.5 y2=x+1.5 的分布
      np.random.seed(24)
      # 均值中心标准差宽度维度
      X = np.random.normal(0, 1, size=(1000, 2))	# 从正态高斯分布中抽取随机样本1000个坐标
      y = np.array(X[:,0]+X[:, 1]**2 < 1.5, int)
      plt.scatter(X[:, 0], X[:, 1], c=y)
      np.shape(y) # (1000,) 一行1000列和 (4,3) 4行3列 相反
      
      1
    • 选取分类边界的哪一侧为正类哪一侧为负类即不等号的方向并不影响后续建模
    • 再人为增加一些扰动项即让两个类别的分类边界不是那么清晰
      np.random.seed(24)
      for i in range(200):
          y[np.random.randint(1000)] = 1 # 一维数组
          y[np.random.randint(1000)] = 0
      plt.scatter(X[:, 0], X[:, 1], c=y)
      
      2
    • 数据集切分
      X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state = 42)
      
  • 构建机器学习流
    • 接下来调用逻辑回归中的相关类进行模型构建
    • 很明显面对上述曲线边界的问题通过简单的逻辑回归无法达到较好的预测效果因此需要借助此前介绍的 PolynomialFeatures 来进行特征衍生或许能够提升模型表现
    • 此外我们还需要对数据进行标准化处理增加训练过程稳定性及模型训练效率
    • 还可以通过Pipeline将这些过程封装在一个机器学习流中以简化调用过程
    • 整个建模过程我们需要测试在不同强度的数据衍生下模型是否会出现过拟合倾向如果出现过拟合之后应该如何调整
    • 我们将上述过程封装在一个函数中可以非常便捷地进行核心参数的设置同时也能够重复实例化不同的评估器以支持重复试验
      # 构建学习流
      def plr(degree=1, penalty='none', C=1.0):
          pipe = make_pipeline(PolynomialFeatures(degree=degree, include_bias=False), 
                               StandardScaler(), 
                               LogisticRegression(penalty=penalty, tol=1e-4, C=C, max_iter=int(1e6)))
          return pipe
      
    • degree 和数据增强的强度相关决定了衍生特征的最高阶数
    • penalty、C 是逻辑回归中控制正则化及惩罚力度的相关参数
    • 有两点需要注意首先复杂模型的建模往往会有非常多的参数需要考虑但一般来说我们会优先考虑影响最终建模效果的参数如影响模型前拟合、过拟合的参数这是根本然后再考虑影响训练过程的参数如调用几核心进行计算、采用何种迭代求解方法等往往是效率问题其次上述实例化逻辑回归模型时我们适当提高了最大迭代次数1e6这是一般复杂数据建模时都需要调整的参数

评估器训练与过拟合实验

  • 手动调参控制模型拟合度
    pl1 = plr()
    # 查看模型参数
    pl1.get_params()
    pl1.get_params()['polynomialfeatures__include_bias']
    # 调整参数
    # 调整PolynomialFeatures评估器中的include_bias参数
    pl1.set_params(polynomialfeatures__include_bias=True)
    
  • 观察建模结果与决策边界函数
    pr1 = plr()
    pr1.fit(X_train, y_train)
    # 效果并不好
    pr1.score(X_train, y_train),pr1.score(X_test, y_test)	# (0.79, 0.7733333333333333)
    
  • 绘制决策边界更加直观
    def plot_decision_boundary(X, y, model):
        """
        决策边界绘制函数
        """
        
        # 以两个特征的极值+1/-1作为边界并在其中添加1000个点
        x1, x2 = np.meshgrid(np.linspace(X[:, 0].min()-1, X[:, 0].max()+1, 1000).reshape(-1,1),
                             np.linspace(X[:, 1].min()-1, X[:, 1].max()+1, 1000).reshape(-1,1))
        
        # 将所有点的横纵坐标转化成二维数组
        X_temp = np.concatenate([x1.reshape(-1, 1), x2.reshape(-1, 1)], 1)
        
        # 对所有点进行模型类别预测
        yhat_temp = model.predict(X_temp)
        yhat = yhat_temp.reshape(x1.shape)
        
        # 绘制决策边界图像
        from matplotlib.colors import ListedColormap
        custom_cmap = ListedColormap(['#EF9A9A','#90CAF9'])
        plt.contourf(x1, x2, yhat, cmap=custom_cmap)
        plt.scatter(X[(y == 0).flatten(), 0], X[(y == 0).flatten(), 1], color='red')
        plt.scatter(X[(y == 1).flatten(), 0], X[(y == 1).flatten(), 1], color='blue')
    plot_decision_boundary(X, y, pr1)
    
    3
  • 在不进行数据衍生的情况下只能捕捉线性边界当然这也是模型目前性能欠佳的核心原因
    • 用直线将样本划开就是线性的
    • 线性模型可以是用曲线拟合样本但是分类的决策边界一定是直线的
    • 区分是否为线性模型主要是看一个乘法式子中 x 前的系数 w如果 w 只影响一个x那么此模型为线性模型 x n x_n xn 只受 w n w_n wn 影响才是线性
  • 尝试衍生特征再进行建模
    pr2 = plr(degree=2)	# 允许交叉项
    pr2.fit(X_train, y_train)
    pr2.score(X_train, y_train),pr2.score(X_test, y_test)	# (1.0, 0.9933333333333333)
    plot_decision_boundary(X, y, pr2)
    
    4
  • 模型效果有了明显提升非常接近自定义边界了
    # 可以列出当前实例化的所有评估器
    pr2.named_steps
    # {'polynomialfeatures': PolynomialFeatures(include_bias=False),
    # 'standardscaler': StandardScaler(),
    # 'logisticregression': LogisticRegression(max_iter=1000000, penalty='none')}
    # 查看参数
    pr2.named_steps['logisticregression'].coef_	# array([[-1201.44200673,   -17.05785225,   -20.11909011,    23.93051569,	-1903.66861834]])
    
  • 可以看到模型总共5个参数对应训练数据 5 个特征说明最高次方为二次方、并且存在交叉项当前5个特征为 x 1 x_1 x1 x 1 2 x_1^2 x12 x 2 x_2 x2 x 2 2 x_2^2 x22 x 1 x 2 x_1x_2 x1x2
    • 当我们在进行特征衍生的时候就相当于将原始数据集投射到一个高维空间而在高维空间内的逻辑回归模型实际上是构建了一个高维空间内的超平面高维空间的“线性边界”在进行类别划分
    • 而现在看到的原始特征空间的决策边界实际上就是高维空间的决策超平面截面在当前特征空间的投影
    • 由此我们也知道了特征衍生对于逻辑回归模型效果提升的实际作用就是突破了逻辑回归在原始特征空间中的线性边界的束缚 x n x_n xn 不再只受 w n w_n wn 的影响因为有交叉项也因为特征衍生源于同一个特征项
    • 经过特征衍生的逻辑回归模型也将在原始特征空间中呈现出非线性不是直线的决策边界的特性
    • 如图所示例如这种情况下在三维空间中两类样本的高度不同从而确定了截面
      4
  • 需要知道的是尽管特征衍生看起来很强大能够帮逻辑回归在原始特征空间中构建非线性的决策边界但这种非线性边界其实也是受到特征衍生方式的约束
    • 无论是几阶的特征衍生能够投射到的高维空间都是有限的而我们最终也只能在这些有限的高维空间中寻找一个最优的超平面再投影回来
  • 过拟合实验
    • 衍生到更高阶可以看出模型已经过拟合了偏离了我们定义的决策边界

      pr3 = plr(degree=10)
      pr3.set_params(logisticregression__tol=1e-2)
      # 修改收敛条件
      pr3 = plr(degree=10)
      pr3.set_params(logisticregression__tol=1e-2)	# 放宽
      pr3.fit(X_train, y_train)
      pr3.score(X_train, y_train),pr3.score(X_test, y_test)	# (1.0, 0.9833333333333333)
      plot_decision_boundary(X, y, pr3)
      

      5

    • 观察在提高模型复杂度的过程中训练误差和测试误差是如何变化的

      # 用于存储不同模型 训练准确率 与 测试准确率 的列表
      score_l = []
      # 实例化多组模型测试模型效果
      for degree in range(1, 21):
          pr_temp = plr(degree=degree)
          pr_temp.fit(X_train, y_train)
          score_temp = [pr_temp.score(X_train, y_train),pr_temp.score(X_test, y_test)]
          score_l.append(score_temp)
      plt.plot(list(range(1, 21)), np.array(score_l)[:,0], label='train_acc')
      plt.plot(list(range(1, 21)), np.array(score_l)[:,1], label='test_acc')
      plt.legend(loc = 4)
      

      6

    • 能够较为明显的看出伴随着模型越来越复杂特征越来越多训练集准确率逐渐提升但测试集准确率却在一段时间后开始下降

    • 说明模型经历了由开始的欠拟合到拟合再到过拟合的过程和上面说的 模型结构风险 伴随模型复杂度提升而提升的结论一致
      7

评估器的手动调参

  • 测试 l 1 l1 l1 正则化
    pl1 = plr(degree=10, penalty='l1', C=1.0)
    pl1.fit(X_train, y_train)
    # 更换求解器为saga
    pl1.set_params(logisticregression__solver='saga')
    pl1.fit(X_train, y_train)
    pl1.score(X_train, y_train),pl1.score(X_test, y_test)	# (0.9857142857142858, 0.9733333333333334)
    
  • 测试 l 2 l2 l2 正则化
    pl2 = plr(degree=10, penalty='l2', C=1.0).fit(X_train, y_train)
    pl2.score(X_train, y_train),pl2.score(X_test, y_test)	# 抑制了过拟合倾向
    
  • 我们采用一个非常朴素的想法来进行调参即使用degree、C和正则化选项l1或l2的不同组合来进行调参试图从中选择一组能够让模型表现最好的参数
    • l 1 l1 l1 正则化下最优特征衍生阶数3 阶为最佳
    • l 2 l2 l2 正则化下最优特征衍生阶数15 阶为最佳
    • 分别确定 C
      # 用于存储不同模型训练准确率与测试准确率的列表
      score_l1_3 = []
      # 实例化多组模型测试模型效果
      for C in np.arange(0.5, 2, 0.1):
          pr_temp = plr(degree=3, penalty='l1', C=C)
          pr_temp.set_params(logisticregression__solver='saga')
          pr_temp.fit(X_train, y_train)
          score_temp = [pr_temp.score(X_train, y_train),pr_temp.score(X_test, y_test)]
          score_l1_3.append(score_temp)  
      # 观察最终结果
      plt.plot(list(np.arange(0.5, 2, 0.1)), np.array(score_l1_3)[:,0], label='train_acc')
      plt.plot(list(np.arange(0.5, 2, 0.1)), np.array(score_l1_3)[:,1], label='test_acc')
      plt.legend(loc = 4)
      
      # 用于存储不同模型训练准确率与测试准确率的列表
      score_l2_15 = []
      # 实例化多组模型测试模型效果
      for C in np.arange(0.5, 2, 0.1):
          pr_temp = plr(degree=15, penalty='l2', C=C)
          pr_temp.fit(X_train, y_train)
          score_temp = [pr_temp.score(X_train, y_train),pr_temp.score(X_test, y_test)]
          score_l2_15.append(score_temp)
      # 观察最终结果
      plt.plot(list(np.arange(0.5, 2, 0.1)), np.array(score_l2_15)[:,0], label='train_acc')
      plt.plot(list(np.arange(0.5, 2, 0.1)), np.array(score_l2_15)[:,1], label='test_acc')
      plt.legend(loc = 4)
      
  • 通过蛮力搜索确定了一组能够让测试集准确率取得最大值的参数组合degree=15, penalty=‘l2’, C=1.0此时测试集准确率为0.9可能会因为数据集而略有不同
  • 尽管上述过程能够帮助我们最终找到一组相对比较好的参数最终建模结果略有提升但上述手动调参过程存在三个问题
      (1) 过程不够严谨诸如测试集中测试结果不能指导建模、参数选取及搜索区间选取没有理论依据等问题仍然存在
      (2) 执行效率太低如果面对更多的参数这是更一般的情况手动执行过程效率太低无法进行超大规模的参数挑选
      (3) 结果不够精确一次建模结果本身可信度其实并不高我们很难证明上述挑选出来的参数就一定在未来数据预测中拥有较高准确率
  • 而要解决这些问题需要了解关于机器学习调参的理论以及掌握更多更高效的调参工具
  • 正因如此接下来会学习关于机器学习调参的基本理论以及 sklearn 中的网格搜索调参工具而后我们再借助更完整的理论、更高效的工具解决这里的问题
  • 小结
    • 数据集准备——标准化——特征衍生——正则化——风险惩罚力度C
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 机器学习