欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Lesson 6.4 逻辑回归手动调参实验

最编程 2024-02-09 14:15:25
...

文章目录


  • 一、数据准备与评估器构造
  • 1. 数据准备
  • 2. 构建机器学习流
  • 二、评估器训练与过拟合实验
  • 三、评估器的手动调参



在补充了一系列关于正则化的基础理论以及 sklearn 中逻辑回归评估器的参数解释之后,接下来,我们尝试借助 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


一、数据准备与评估器构造

  • 首先需要进行数据准备。为了更好的配合进行模型性能与各种方法效果的测试,此处先以手动创建数据集为例进行试验。


1. 数据准备


在 Lesson 5.1 的阅读部分内容中,我们曾介绍到关于逻辑回归的决策边界实际上就是逻辑回归的线性方程这一特性,并由此探讨了一元函数与二维平面的决策边界之间的关系。

据此我们可以创建一个满足分类边界为 y 2 = − x + 1.5

np.random.seed(24)
X = np.random.normal(0, 1, size=(1000, 2))
y = np.array(X[:,0]+X[:, 1]**2 < 1.5, int)

plt.scatter(X[:, 0], X[:, 1], c=y)


网络异常,图片无法展示
|


此时边界为 y 2 = − x + 1.5 而选取分类边界的哪一侧为正类哪一侧为负类(即不等号的方向),其实并不影响后续模型建模。

而利用分类边界来划分数据类别,其实也是一种为这个分类数据集赋予一定规律的做法。

同样,为了更好地贴近真实情况,我们在上述分类边界的规律上再人为增加一些扰动项,即让两个类别的分类边界不是那么清晰,具体方法如下:

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)

网络异常,图片无法展示
|

当然,如果要增加分类难度,可以选取更多的点随机赋予类别。

在整体数据准备完毕后,接下来进行数据集的切分:

X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, random_state = 42)


  • 至此,数据准备工作全部完成。

2. 构建机器学习流


接下来,调用逻辑回归中的相关类,来进行模型构建。


很明显,面对上述曲线边界的问题,通过简单的逻辑回归无法达到较好的预测效果,因此需要借助此前介绍的 PolynomialFeatures 来进行特征衍生,或许能够提升模型表现。

此外我们还需要对数据进行标准化处理,以训练过程稳定性及模型训练效率,当然我们还可以通过 Pipeline 将这些过程封装在一个机器学习流中,以简化调用过程。


我们知道,整个建模过程我们需要测试在不同强度的数据衍生下,模型是否会出现过拟合倾向,同时如果出现过拟合之后应该如何调整。


因此我们可以将上述 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 则是逻辑回归中控制正则化及惩罚力度的相关参数,该组参数能够很好的控制模型对于训练数据规律的挖掘程度。


当然,最终的建模目标是希望构建一个很好挖掘全局规律的模型,即一方面我们希望模型尽可能挖掘数据规律,另一方面我们又不希望模型过拟合。


上述过程有两点需要注意:


首先,复杂模型的建模往往会有非常多的参数需要考虑,但一般来说我们会优先考虑影响最终建模效果的参数(如影响模型前拟合、过拟合的参数),然后再考虑影响训练过程的参数(如调用几核心进行计算、采用何种迭代求解方法等),前者往往是需要调整的核心参数;


其次,上述实例化逻辑回归模型时,我们适当提高了最大迭代次数,这是一般复杂数据建模时都需要调整的参数。


合理的设置调参范围,是调好参数的第一步。



二、评估器训练与过拟合实验

  • 接下来进行模型训练,并且尝试进行手动调参来控制模型拟合度。
pl1 = plr()


  • 更多参数查看和修改方法
  • 当然,函数接口只给了部分核心参数,但如果想调整更多的模型参数,还可以通过使用此前介绍的 set_params 方法来进行调整:
pl1.get_params()

pl1.get_params()['polynomialfeatures__include_bias']
#False

# 调整PolynomialFeatures评估器中的include_bias参数
pl1.set_params(polynomialfeatures__include_bias=True)
#Pipeline(steps=[('polynomialfeatures', PolynomialFeatures(degree=1)),
#                ('standardscaler', StandardScaler()),
#                ('logisticregression',
#                 LogisticRegression(max_iter=1000000, penalty='none'))])

pl1.get_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.6985714285714286, 0.7066666666666667)


  • 我们发现,模型整体拟合效果并不好,我们可以借助此前定义的决策边界来进行一个更加直观的模型建模结果的观察。
  • 当然,由于此时我们是调用 sklearn 的模型,因此需要在此前的决策边界绘制函数基础上略微进行修改:
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)
• 1
• 2


f35ae6ed56754d92ba09f9db57ed5d01.png


  • 不难看出,逻辑回归在不进行数据衍生的情况下,只能捕捉线性边界,当然这也是模型目前性能欠佳的核心原因。当然,我们尝试衍生 2 次项特征再来进行建模:
pr2 = plr(degree=2)
pr2.fit(X_train, y_train)
pr2.score(X_train, y_train),pr2.score(X_test, y_test)
#(0.7914285714285715, 0.7866666666666666)

plot_decision_boundary(X, y, pr2)



a99d080b5bdb4cdca364bf5784c2a309.png

  • 能够发现,模型效果有了明显提升,这里首先我们可以通过训练完的逻辑回归模型参数个数来验证当前数据特征数量:
pr2.named_steps
#{'polynomialfeatures': PolynomialFeatures(include_bias=False),
# 'standardscaler': StandardScaler(),
# 'logisticregression': LogisticRegression(max_iter=1000000, penalty='none')}

pr2.named_steps['logisticregression'].coef_
#array([[-0.81012988,  0.04384694, -0.48583038,  0.02977868, -1.12352417]])


此处我们可以通过 Pipeline 中的 named_steps 来单独调用机器学习流中的某个评估器,从而能够进一步查看该评估器的相关属性,named_steps 返回结果同样也是一个字典,通过 key 来调用对应评估器。

当然该字典中的 key 名称其实是对应评估器类的函数(如果有的话)。

最后查看模型总共 5 个参数,对应训练数据总共 5 个特征,说明最高次方为二次方、并且存在交叉项目的特征衍生顺利执行。


而模型在进行特征衍生之后为何会出现一个类似圆形的边界?

其实当我们在进行特征衍生的时候,就相当于是将原始数据集投射到一个高维空间,而在高维空间内的逻辑回归模型,实际上是构建了一个高维空间内的超平面(高维空间的“线性边界”)在进行类别划分。

而我们现在看到的原始特征空间的决策边界,实际上就是高维空间的决策超平面在当前特征空间的投影。

而由此我们也知道了特征衍生至于逻辑回归模型效果提升的实际作用,就是突破了逻辑回归在原始特征空间中的线性边界的束缚。

而经过特征衍生的逻辑回归模型,也将在原始特征空间中呈现出非线性的决策边界的特性。


2ecebd6fc7d14d9cbc57672c91a93270.png 需要知道的是,尽管这种特征的衍生看起来很强大,能够帮逻辑回归在原始特征空间中构建非线性的决策边界。

但同时需要知道的是,这种非线性边界其实也是受到特征衍生方式的约束的,无论是几阶的特征衍生,能够投射到的高维空间都是有限的,而我们最终也只能在这些有限的高维空间中寻找一个最优的超平面。

过拟合倾向实验

当然,我们可以进一步进行 10 阶特征的衍生,然后构建一个更加复杂的模型:



pr3 = plr(degree=10)
pr3.fit(X_train, y_train)
pr3.score(X_train, y_train),pr3.score(X_test, y_test)

/Users/wuhaotian/opt/anaconda3/lib/python3.8/site-packages/sklearn/linear_model/_logistic.py:762: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of f AND g EVALUATIONS EXCEEDS LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  n_iter_i = _check_optimize_result(

#(0.8314285714285714, 0.78)



果在运行过程中显示上述警告信息,首先需要知道的是警告并不影响最终模型结果的使用,其次,上述警告信息其实是很多进行数值解求解过程都会面临的典型问题,就是迭代次数(max_iter)用尽,但并没有收敛到 tol 参数设置的区间。

解决该问题一般有三种办法:


其一是增加 max_iter 迭代次数,其二就是增加收敛区间,其三则是加入正则化项。

加入正则化项的相关方法我们稍后尝试,而对于前两种方法来说,一般来说,如果我们希望结果更加稳定、更加具有可信度,则可以考虑增加迭代次数而保持一个较小的收敛区间。


但此处由于我们本身只设置了 1000 条数据,较小的数据量是目前无法收敛止较小区间的根本原因,因此此处建议稍微扩大收敛区间以解决上述问题。


其实我们还可以通过更换迭代方法来解决上述问题,但限于逻辑回归模型的特殊性,此处不建议更换迭代方法。


而要求改 tol 参数,则可以使用前面介绍的 set_param 方法来进行修改: