逻辑回归 (Logistic Regression) 的算法,这是目前最流行使用最广泛的学习算法之一。
首先介绍几种分类问题:
- 垃圾邮件分类:垃圾邮件(是或不是)?
- 在线交易分类:欺诈性的(是或不是)?
- 肿瘤:恶性 / 良性
先从二元的分类问题开始讨论。将因变量(dependent variable)可能属于的两个类分别称为
- 负向类(negative class)和
- 正向类(positive class)
则 因变量 y ∈ { 0,1 } ,其中 0 表示负向类,1 表示正向类。
对于肿瘤分类是否为良性的问题:
对于二分类问题,y 取值为 0 或者1,但如果使用的是线性回归,那么 h 的输出值可能 远大于1,或者远小于。但数据的标签应该取值0 或者1。所以在接下来的要研究一种新算法逻辑回归算法,这个算法的性质是:它的输出值永远在0到 1 之间。
逻辑回归算法是分类算法。可能因为算法的名字中出现“回归”让人感到困惑,但逻辑回归算法实际上是一种分类算法,它适用于标签 y 取值离散的情况,如:1 0 0 1。
逻辑回归的输出变量范围始终在0和1之间。 逻辑回归模型的假设是: hθ = g(θTX) ,其中: X 代表特征向量 g 代表逻辑函数(Logistic function)。
逻辑函数是一个常用的逻辑函数为S形函数(Sigmoid function):
整合上式,得到:
逻辑函数的示意图:
Python实现:
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-z))
hθ(x) 的作用,给定输入变量,计算输出变量 = 1的可能性(estimated probability),即 hθ(x) = P(y=1 | x;θ)。
- 例如对一个肿瘤的样本,计算得到 hθ(x) = 0.7,也就是有70%的可能是恶性的。
- 当 hθ(x) >= 0.5 时,预测 y=1。
- 当 hθ(x) < 0.5 时,预测 y=0 。
可以总结为:
- z=0 时 g(z)=0.5
- z>0 时 g(z)>0.5
- z<0 时 g(z)<0.5
又 z=θTx ,即: θTx>=0 时,预测 y=1 θTx<0 时,预测 y=0
举个例子:
- 假设现在有一个模型,参数 θ 是向量[-3 1 1]。则当 -3+x1+x2 >= 0 ,即 x1+x2 >= 3 时,模型将预测 y=1 。我们可以绘制直线 x1+x2=3 ,这条线便是我们模型的分界线,将预测为1的区域和预测为0的区域分隔开。 如下图:
分类的示意图如下:
上面的例子还是很明显的,来一个复杂一点的:
上图中的数据需要用曲线才能分隔 y=0 区域和 y=1 区域。 y = 0 区域接近一个圆形,选用二次多项式: hθ(x)=g(θ0+θ1x1+θ2x2+θ3x12+θ4x22)。到这里还未讲过如何自动选取参数,先假设参数向量是[-1 0 0 1 1],则我们得到的判定边界恰好是圆点在原点且半径为1的圆形。
对于一个模型,如何选取参数 θ 了?
首先要定义用来拟合参数的优化目标或者叫代价函数,这便是监督学习问题中的逻辑回归模型的拟合问题。
对于线性回归模型,我们定义的代价函数是所有模型误差的平方和( J(θ)=1/(2m) Σ (hθ(x(i))-y(i))2 )。理论上来说,我们也可以对逻辑回归模型沿用这个定义,但是问题在于,当我们将 hθ(x)=(1+e-θTx)-1 带入到这样定义了的代价函数中时,我们得到的代价函数将是一个非凸函数(non-convex function)。
凸函数和非凸函数的示意如下:
加入代价函数 J 是非凸函数,意味着有许多局部最小值(见上图左图),这将影响梯度下降算法寻找全局最小值。
因此重新定义代价函数为:
其中 Cost() 的定义为:
hθ(x) 与 Cost(hθ(x),y) 之间的关系如下图所示:
这样构建的 Cost(hθ(x),y) 函数的特点是:当实际的 y=1 且 hθ(x) 也为1时误差为0,当 y=1 但 hθ(x) 不为1时误差随着 hθ(x) 变小而变大;当实际的 y=0 且 hθ(x) 也为0时代价为0,当 y=0 但 hθ(x) 不为0时误差随着 hθ(x) 的变大而变大。
将上式整合如下:
从而得到
对于这个代价函数,为了拟合 θ ,目标函数为:
对于新的样本 x,输出为:
Python代码计算Cost的示例,两种方法是同样的效果:
import numpy as np
def sigmoid(z):
return 1/(1 + np.exp(-z))
def cost1(theta, X, y): # 这是第一种方法
first = - y.T @ np.log(sigmoid(X @ theta))
second = (1 - y.T) @ np.log(1 - sigmoid(X @ theta))
return ((first - second) / (len(X))).item()
def cost2(theta, X, y): # 这是第二种方法
first = np.multiply(-y, np.log(sigmoid(X @ theta)))
second = np.multiply((1 - y), np.log(1 - sigmoid(X @ theta)))
return np.sum(first - second) / (len(X))
推导过程:
虽然得到的梯度下降算法表面上看上去与线性回归的梯度下降算法一样,但是这里 hθ = g(θTX) 的与线性回归中不同。另外,在运行梯度下降算法之前,进行特征缩放依旧是非常必要的。
然而梯度下降并不是唯一使用的算法,还有其他一些更高级、更复杂的算法。例如共轭梯度法(Conjugate gradient)、BFGS (变尺度法) 和L-BFGS (限制变尺度法) 就是其中更高级的优化算法,它们需要你计算代价函数 J(θ) 和导数项,然后会它们帮你最小化代价函数。
这Conjugate Gradient、BFGS、L-BFGS等算法有许多优点:
- 不需要手动选择学习率 α。只用给出计算导数项和代价函数的方法,因为算法内有一个智能的内部循环,称为线性搜索(line search)算法,它可以自动尝试不同的学习速率 ,并自动选择一个好的学习速率 ,它甚至可以为每次迭代选择不同的学习速率。
- 这些算法实际上在做更复杂的事情,不仅仅是选择一个好的学习速率,所以它们往往最终比梯度下降收敛得快多了,不过关于它们到底做什么的详细讨论,已经超过了这里讨论的范围。
下面是一个关于使用Conjugate Gradient的完整例子,并plot出training和prediction data
import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize
from mpl_toolkits.mplot3d import Axes3D
""" X是训练集的数据 """
X_train = np.array([[1., 1.],
[1., 2.],
[-1., -1.],
[-1., -2.]])
""" y是训练集的label """
y_train = np.array([1, 1, 0, 0])
""" 处理训练集X,补上x_0 """
X_train = np.hstack((np.ones((X_train.shape[0], 1)), X_train))
"""Sigmoid 函数公式 """
def sigmoid(z):
return 1/(1 + np.exp(-z))
""" 目标函数,也就是待最小化的 Cost function """
def cost(theta, X, y):
first = - y.T @ np.log(sigmoid(X @ theta))
second = (1 - y.T) @ np.log(1 - sigmoid(X @ theta))
return ((first - second) / (len(X))).item()
def hypothesis(X, theta):
return sigmoid(X @ theta)
def cost_wrapper(theta):
return cost(theta, X_train, y_train)
def hypothesis_wrapper(theta):
return hypothesis(X_train, theta)
""" 目标函数的梯度 """
def gradient(theta):
gradient_sum = (hypothesis_wrapper(theta) - y_train).T @ X_train
return gradient_sum / X_train.shape[0]
theta_train = np.array([1, 1.,2.])
theta_opt = optimize.minimize(cost_wrapper, theta_train,
method='CG', jac=gradient)
print(theta_opt)
""" 构造预测集数据 """
delta = 0.2
px = np.arange(-3.0, 3.0, delta)
py = np.arange(-3.0, 3.0, delta)
px, py = np.meshgrid(px, py)
px = px.reshape((px.size, 1))
py = py.reshape((py.size, 1))
pz = np.hstack((np.hstack((np.ones((px.size, 1)), px)), py))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
""" plot训练集 """
ax.scatter(X_train[:, 1], X_train[:, 2], y_train,
color='red', marker='^', s=200, label='Traning Data')
""" plot预测集, 二分类时在hypothesis外加上 np.around """
ax.scatter(px, py, (hypothesis(pz, theta_opt.x)),
color='gray', label='Prediction Data')
ax.legend(loc=2)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title('classification')
plt.show()
上述讨论都是针对二分类问题,那如何使用逻辑回归 (logistic regression) 解决多类别分类问题?具体来说,是一个叫做"一对多" (one-vs-all) 的分类算法。
-
例子1:假如现在需要一个学习算法自动地将邮件归到不同文件夹,或者说自动地加上标签。那么,我们就有了这样一个分类问题:其类别有四个,分别用 y=1、y=2、y=3、y=4 来代表。
-
例子2:是有关药物诊断的,如果一个病人因为鼻塞来到你的诊所,他可能并没有生病,用 y=1 这个类别来代表;或者患了感冒,用 y=2 来代表;或者得了流感用 y=3 来代表。
-
例子3:如果你正在做有关天气的机器学习分类问题,那么你可能想要区分哪些天是晴天、多云、雨天、或者下雪天,对上述的例子,可以取一个很小的数值,一个相对"谨慎"的数值,比如1 到3、1到4或者其它数值。
对于一个多类分类问题,我们的数据集或许看起来像下图的右图:
用3种不同的符号来代表3个类别,问题是给出3个类型的数据集,我们如何得到一个学习算法来进行分类呢?
下面将介绍如何进行一对多的分类工作,有时这个方法也被称为"一对余"方法。
如下图,训练集有3个类别,用三角形表示 y=1 ,方框表示 y=2 ,叉叉表示 y=3 。我们下面要做的就是使用一个训练集,将其分成3个二元分类问题。
我们先从用三角形代表的类别1开始,实际上我们可以创建一个,新的"伪"训练集,类型2和类型3定为负类,类型1设定为正类,我们创建一个新的训练集,如下图所示的那样,我们要拟合出一个合适的分类器。见下图:
这里的三角形是正样本,而圆形代表负样本。可以这样想,设置三角形的值为1,圆形的值为0,下面我们来训练一个标准的逻辑回归分类器,这样我们就得到一个正边界。
为了能实现这样的转变,我们将多个类中的一个类标记为正向类( y=1 ),然后将其他所有类都标记为负向类,这个模型记作 hθ(1)(x) 。接着,类似地第我们选择另一个类标记为正向类( y=2 ),再将其它类都标记为负向类,将这个模型记作 hθ(2)(x) ,依此类推。最后我们得到一系列的模型简记为: hθ(i)(x) = p(y=i|x;θ) 其中: i=(1,2,3....k)
最后,在做预测时,将所有的分类机都运行一遍,然后对每一个输入变量,选择最高可能性的输出变量。
现在要做的就是训练这个逻辑回归分类器: hθ(i)(x) ,其中 i 对应每一个可能的 y=i ,最后,为了做出预测,我们给出输入一个新的 x 值,用这个做预测。我们要做的就是在我们三个分类器里面输入 x ,然后我们选择一个让 hθ(i)(x) 最大的 i ,即 max hθ(i)(x) 。
在这之前,已经介绍了线性回归和逻辑回归,它们能够有效地解决许多问题。但当将它们应用到某些特定的机器学习问题时,可能遇到过拟合(over-fitting)的问题,过拟合可能会导致这些算法的效果很差。
如果我们有非常多的特征,我们通过学习得到的假设可能能够非常好地适应训练集(代价函数可能几乎为0),但是可能会不能推广到新的数据。下图是一个回归问题的例子:
上图中,第一个是线性模型,明显欠拟合(under-fitting),因为不能很好地适应训练集;第三个模型是一个四次方的模型,过于强调拟合原始数据,而丢失了算法的本质:预测新数据。可以看出,若给出一个新的值使之预测,它将表现的很差,是过拟合,虽然能非常好地适应训练集,但在新输入变量进行预测时可能效果不好;而中间的模型最合适。
分类问题中也存在这样的问题:
就以多项式理解, x 的次数越高,拟合的越好,但相应的预测的能力就可能变差。 问题是,如果我们发现了过拟合问题,应该如何处理?
- 丢弃一些不能帮助正确预测的特征。可以是手工选择保留哪些特征,或者使用一些模型选择的算法来处理(例如PCA)
- 正则化。保留所有的特征,但是减少参数的大小(magnitude)。
上面的回归问题中如果我们的模型是: hθ(x)=θ0+θ1x1+θ2x22+θ3x33+θ4x44。可以从之前事例中看出,是高次项导致过拟合,所以如果能让这些高次项的系数接近于0的话,就能很好的拟合了(避免过拟合)。所以要做的是在一定程度上减小参数 θ 的值,这是正则化的基本方法。我们决定要减少 θ3 和 θ4 的大小,要做的便是修改代价函数,在其中 θ3 和 θ4 设置一点惩罚。这样做的话,在尝试最小化代价时也需要将这个惩罚纳入考虑中,并最终导致选择较小一些的 θ3 和 θ4 。修改后的代价函数如下:
通过这个代价函数选择出的 θ3 和 θ4 对预测结果的影响就比之前要小许多。假如有非常多的特征,并不知道哪些特征我们要惩罚,可以对所有的特征进行惩罚,并且让代价函数最优化的软件来选择这些惩罚的程度。这样的结果是得到了一个较为简单的能防止过拟合问题的假设:
其中 λ 又称为正则化参数(RegularizationParameter)。注:根据惯例,不对 θ0 惩罚。经过正则化处理的模型与原模型的可能对比如下图所示:
如果选择的正则化参数 λ 过大,则会把所有的参数都最小化了,导致模型变成 hθ(x)=θ0 ,也就是上图中红色直线所示的情况,造成欠拟合。那为什么增加的一项 λ=Σnj=1θj2 可以使 θ 的值减小呢?
因为如果我们令 λ 的值很大的话,为了使CostFunction
尽可能的小,所有的 θ 的值(不包括 θ0 )都会在一定程度上减小。但若 λ 的值太大了,那么 θ (不包括 θ0 )都会趋近于0,这样我们所得到的只能是一条平行于 x 轴的直线。所以对于正则化,要取一个合理的 λ 值,才能更好的应用正则化。
回顾一下代价函数,为了使用正则化,让我们把这些概念应用到到线性回归和逻辑回归中去,那么我们就可以让他们避免过度拟合了。
对于线性回归的求解,我们之前推导了两种学习算法:一种基于梯度下降,一种基于正规方程。
正则化线性回归的代价函数为:
如果我们要用梯度下降法令这个代价函数最小化,因为我们不对 θ0 进行正则化,所以梯度下降算法将分两种情形:
上式可以调整为:
可以看出,正则化线性回归的梯度下降算法的变化在于,每次都在原有算法更新规则的基础上令值减少了一个额外的值。
我们同样也可以利用正规方程来求解正则化线性回归模型,方法如下所示:
图中的矩阵尺寸为 (n+1)*(n+1) 。
之前讲正规方程时讲过,要求 XTX 必须是可逆的,那对于不可逆的情况,可以用 pinv()
来计算伪逆。
而正则化刚好解决了这个问题,如果 λ > 0,可以证明 XTX + λI 是可逆的。
正定矩阵(positive defined matrix)有一个性质,所有的正定矩阵都是可逆的。因此我们可以通过证明 XTX + λI 是正定阵来证明其可逆。 (此外,正定矩阵特征值全部大于零(半正定矩阵特征值全部大于等于零)
-
XTX 是半正定的(positive semi-definited matrix):
根据半正定的定义,只需证明,对于不为零的向量 z,有 zT(XTX)z=(zTXT)(Xz)=(Xz)T(Xz) ≥ 0 ; Xz 记做 u,那么 (Xz)T(Xz) = uTu,根据向量点乘的性质得 uTu ≥ 0。
所以XTX 是半正定的。
-
XTX + λI 是正定的: 类似上面的证明 zT(XTX + λI) z = (zTXT)(Xz) + zTλIz。 根据第1点的证明,可知 (Xz)T(Xz) ≥ 0。 而对于非零向量z,有 zTλIz > 0。 所以 zT(XTX + λI) z > 0。 根据正定矩阵定义,XTX + λI 是正定的。所以 XTX + λI 是可逆的。
关于正定矩阵和半正定矩阵的几何理解,推荐看这里。
针对逻辑回归问题,我们在之前的课程已经学习过两种优化算法:
- 梯度下降法来优化代价函数 J(θ)
- 更高级的优化算法,这些高级优化算法需要你自己设计代价函数 J(θ)
例如对于下图的数据,当有很多features时,容易导致过拟合。
类似线性回归正则的处理,我们也给代价函数增加一个正则化的表达式,得到代价函数:
要最小化该代价函数,求导得梯度下降算法:
注:看上去同线性回归一样,但是知道 hθ(x)=g(θTX) ,所以与线性回归不同。
- 推荐访问Google Drive的共享,直接在Google Colab在线运行ipynb文件:
- 不能翻墙的朋友,可以访问GitHub下载: