0%

Python 机器学习算法二之逻辑回归的推导及实战

在线性回归模型中,我们实际上是建立了一个模型来拟合自变量和因变量之间的线性关系,但是在某些时候,我们要做的可能是一个分类模型,那么这里就可能用到线性回归模型的变种——逻辑回归,本节我们就逻辑回归来做一个详细的说明。

实例引入

我们还是以上一节的例子为例,张三、李四、王五、赵六都要贷款了,贷款时银行调查了他们的月薪和住房面积等因素,两个因素决定了贷款款项是否可以立即到账,下面列出来了他们四个人的工资情况、住房面积和到账的具体情况:

姓名

工资(元)

房屋面积(平方)

是否可立即到账

张三

6000

58

李四

9000

77

王五

11000

89

赵六

15000

54

看到了这样的数据,又来了一位孙七,他工资是 12000 元,房屋面积是 60 平,那的贷款可以立即到账吗?

思路探索

在这个例子中,我们不再是预测贷款金额具体是多少了,而是要对款项是否可以立即到账做一个分类,分类的结果要么是“否”要么是“是”,所以输出结果我们需要将其映射到一个区间内,让 0 代表“否”,1 代表“是”,这里我们就需要用一个函数来帮助我们实现这个功能,它的名字叫做 Sigmoid 函数,它的表达式是这样的: 它的函数图像是这样的: 它的定义域是全体实数,值域是 (0, 1),当自变量小于 -5 的时候,其值就趋近于 0,当自变量大于 5 的时候,其值就趋近于 1,当自变量为 0 的时候,其值就等于 1/2。 所以我们如果在线性回归模型的基础上加一层 Sigmoid 函数,结果不就可以被转化为 0 到 1 了吗?这就很自然而然地转化为了一个分类模型。 我们知道,线性回归模型的表达形式是这样的: 如果我们在它的基础上加一层 Sigmoid 函数,其表达式就变成了: 好,现在我们来考虑下这个表达式表达的什么意思。 我们举例来说吧,如果这个函数的输出结果为 1,那么我们肯定认为结果为“是”。如果输出结果为 0 呢?我们就当然认为结果为 “否”了,但如果输出结果为 0.5 呢?我们只能模棱两可,此时我们既可以判断为“是”,也可以判断为“否”,但恰好为 0.5 的概率太小了,多多少少也有点偏差吧。所以如果输出结果为 0.51,那么我们怎么认为?我们认为“是”的概率会更大,否的概率更小,概率是多少?0.49。如果输出结果是 0.95 呢?“是”的概率是多少?不用多说,必然是 0.95。 因此,我们可以看出来,函数输出的结果就代表了预测为“是”的概率,也就是真实结果为 1 的概率。我们用公式表达下,这里我们用 $ h{theta}(x) $ 表示预测结果,因此对于输入 x,分类类别为 1 和 0 的概率分别为: $$ \begin{cases} P(y = 1|x;\theta) = h{\theta}(x) \\\\ P(y = 0|x;\theta) = 1 - h{\theta}(x) \end{cases} J(\theta) = \dfrac{1}{2m}\sum{i=1}^{m}(h\theta(x^{(i)}) - y^{(i)})^2 Cost(h\theta(x), y) = \begin{cases} -log(h\theta(x)), y = 1 \\\\ -log(1- h\theta(x)), y = 0 \end{cases} Cost(h\theta(x), y) = -ylog(h\theta(x)) - (1-y)log(1-h\theta(x)) J(\theta) = \dfrac{1}{m}\sum{i=1}^{m}Cost(h\theta(x^{(i)}), y^{(i)})\\\\ = -\dfrac{1}{m}\sum{i=1}^{m}[y^{(i)}log(h\theta(x^{(i)})) + (1-y^{(i)})log(1-h\theta(x^{(i)}))] $$ 和线性回归类似,这次我们的目的就是找到 $ theta $,使得 $ J(theta) $ 最小。

推导过程

这次我们就不能向上次一样用直接求偏导数,然后将偏导数置 0 的方法来求解 $ theta $ 了,这样是无法求解出具体的值的。 我们可以使用梯度下降法来进行求解,也就是一步步地求出 $theta$ 的更新过程,公式如下: 这里面最主要的就是求偏导,过程如下: 因此: 这时候我们发现,其梯度下降的推导结果和线性回归是一样的!

编程实现

下面我们还是用 Sklearn 中的 API 来实现逻辑回归模型,使用的库为 LogisticRegression,其 API 如下:

1
class sklearn.linear_model.LogisticRegression(penalty=’l2’, dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1, class_weight=None, random_state=None, solver=’liblinear’, max_iter=100, multi_class=’ovr’, verbose=0, warm_start=False, n_jobs=1)

参数说明如下:

  • penalty:惩罚项,str 类型,可选参数为 l1 和 l2,默认为 l2。用于指定惩罚项中使用的规范。newton-cg、sag 和 lbfgs 求解算法只支持 L2 规范。L1G 规范假设的是模型的参数满足拉普拉斯分布,L2 假设的模型参数满足高斯分布,所谓的范式就是加上对参数的约束,使得模型更不会过拟合(overfit),但是如果要说是不是加了约束就会好,这个没有人能回答,只能说,加约束的情况下,理论上应该可以获得泛化能力更强的结果。
  • dual:对偶或原始方法,bool 类型,默认为False。对偶方法只用在求解线性多核(liblinear)的 L2惩 罚项上。当样本数量 > 样本特征的时候,dual 通常设置为 False。
  • tol:停止求解的标准,float类型,默认为1e-4。就是求解到多少的时候,停止,认为已经求出最优解。
  • c:正则化系数 λ 的倒数,float 类型,默认为 1.0。必须是正浮点型数。像 SVM 一样,越小的数值表示越强的正则化。
  • fit_intercept:是否存在截距或偏差,bool 类型,默认为 True。
  • intercept_scaling:仅在正则化项为”liblinear”,且fit_intercept 设置为 True 时有用。float 类型,默认为 1。
  • class_weight:用于标示分类模型中各种类型的权重,可以是一个字典或者’balanced’字符串,默认为不输入,也就是不考虑权重,即为 None。如果选择输入的话,可以选择 balanced 让类库自己计算类型权重,或者自己输入各个类型的权重。举个例子,比如对于0,1的二元模型,我们可以定义 class_weight={0:0.9,1:0.1},这样类型0的权重为 90%,而类型 1 的权重为 10%。如果 class_weight选择 balanced,那么类库会根据训练样本量来计算权重。某种类型样本量越多,则权重越低,样本量越少,则权重越高。
  • random_state:随机数种子,int类型,可选参数,默认为无,仅在正则化优化算法为 sag,liblinear时有用。
  • solver:优化算法选择参数,只有五个可选参数,即newton-cg, lbfgs, liblinear, sag, saga。默认为liblinear。solver 参数决定了我们对逻辑回归损失函数的优化方法,有四种算法可以选择,分别是:
    • liblinear:使用了开源的 liblinear 库实现,内部使用了坐标轴下降法来迭代优化损失函数。
    • lbfgs:拟牛顿法的一种,利用损失函数二阶导数矩阵即海森矩阵来迭代优化损失函数。
    • newton-cg:也是牛顿法家族的一种,利用损失函数二阶导数矩阵即海森矩阵来迭代优化损失函数。
    • sag:即随机平均梯度下降,是梯度下降法的变种,和普通梯度下降法的区别是每次迭代仅仅用一部分的样本来计算梯度,适合于样本数据多的时候。
    • saga:线性收敛的随机优化算法的的变重。
  • max_iter:算法收敛最大迭代次数,int 类型,默认为10。仅在正则化优化算法为 newton-cg, sag 和 lbfgs 才有用,算法收敛的最大迭代次数。
  • multi_class:分类方式选择参数,str 类型,可选参数为 ovr 和 multinomial,默认为 ovr。ovr 即前面提到的one-vs-rest(OvR),而 multinomial 即前面提到的 many-vs-many(MvM)。如果是二元逻辑回归,ovr 和 multinomial 并没有任何区别,区别主要在多元逻辑回归上。
  • verbose:日志冗长度,int 类型。默认为 0。就是不输出训练过程,1 的时候偶尔输出结果,大于 1,对于每个子模型都输出。
  • warm_start:热启动参数,bool 类型。默认为 False。如果为 True,则下一次训练是以追加树的形式进行(重新使用上一次的调用作为初始化)。
  • n_jobs:并行数。int 类型,默认为 1。1 的时候,用 CPU 的一个内核运行程序,2 的时候,用 CPU 的 2 个内核运行程序。为 -1 的时候,用所有 CPU 的内核运行程序。

属性说明如下:

  • coef_:斜率
  • intercept_:截距项

我们现在来解决上面的示例,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.linear_model import LogisticRegression

x_data = [
[6000, 58],
[9000, 77],
[11000, 89],
[15000, 54]
]
y_data = [
0, 0, 1, 1
]

lr = LogisticRegression()
lr.fit(x_data, y_data)
x_test = [[12000, 60]]
print('Intercept', lr.intercept_)
print('Coef', lr.coef_)
print('款项是否可以立即到账', lr.predict(x_test)[0])

这里我们的 y_data 数据就变了,变成了是非判断,这里 0 代表“否”,1 代表“是”。这里做逻辑回归时使用了 LogisticRegression 对象,然后调用 fit() 方法进行拟合,拟合完毕之后,使用 [12000, 60] 这条数据作为输入,让模型输出它的预测值。 运行结果:

1
2
3
Intercept [-0.03142387]
Coef [[ 0.00603919 -0.72587703]]
款项是否可以立即到账 1

结果中输出了截距项和系数项,然后预测了是否可以立即到账,结果为 1,这表明孙七进行贷款,款项可以立即到账。 以上就是逻辑回归的基本推导和代码应用实现。