Logistic回归原理分析和实践

Logistic回归原理分析和实践

参考资料:

原理分析

线性回归

这里介绍Logisitic回归首先从线性回归讲起(logistic回归其实就是一种广义的线性回归)。

线性模型(linear model)试图学得一个通过属性的线性组合来进行的预测的函数(假设给定d个属性,$\pmb{x}=[x_1,x_2,…,x_d]$),即:

写成矩阵形式($\pmb{w}=[w_1,w_2,…,w_d]$):

“线性回归”(linear regression)试图学得一个线性模型以尽可能准确的预测实值输出。(其求解参数方法在此不做过多介绍)

线性模型虽然简单,但是却有着丰富的变化,假如我们所需要求解的输出可能在指数尺度上变化,那么我们可能需要以下的线性模型:

这就是“对数线性回归”(log-linear-regression),它实际上是在试图让$e^{\pmb{w^Tx}+b}$去逼近$y$,但是写成上述形式仍是线性回归,实际上已经是在求一种非线性关系。

更一般的,考虑单调可微函数$g(.)$,令:

这样得到模型称为“广义线性模型”(generalized linear model),其中$g(.)$称为“联系函数”(link function),上述对数线性函数就是广义线性函数当$g(.)=ln(.)$的一种特例。

逻辑回归

线性回归表现为输入与输出的一种线性关系,而逻辑回归直观上表现了0,1分类的形式(比如预测未来经济增长可能个线性回归问题,但是预测一张图片是猫咪还是狗狗就是一个逻辑回归问题)

但是我们可以使用上述提到的广义线性模型来实现。

考虑二分类任务,其输出标记$y\in\left\{ 0,1 \right\}$,而线性回归模型产生的预测值$z=\pmb{w^Tx}+b$是实值,于是我们只需要将实值z转化为0/1值,可以使用以下的“单位跃迁函数”(unit-step function)

即若预测值大于0就判正例,小于0则判反例,等于0可任意判别。

但是单位跃迁函数不连续,因此不能直接用作广义线性模型的$g(.)$,因此我们希望找到一定程度上近似单位跃迁函数的函数,并且希望它满足单调可微,对数几率函数(Logistic function)正好就满足(如下)。

Um1cdS.png

可以从上图中看出,对数几率函数是一种“Sigmoid函数”(即形如S的函数,对率函数就是其中最重要的代表),将对数几率函数带入广义线性模型可以得到下式:

将上式进行一些变形:

若将$y$视为样本$\pmb{x}$作为正例的可能性,则$1-y$是其反例可能性,二者的比值$\cfrac{y}{1-y}$称为几率(odds),反应了样本$\pmb{x}$作为正例的相对可能性,对几率取对数得到“对数几率”(log odds,亦称logit)$ln\cfrac{y}{1-y}$

由此可以看出逻辑回归实际上就是在用线性回归模型的预测结果去逼近真实标记的对数几率,因此其对应的模型称为“对数几率回归”(logistic regression),逻辑回归就是其音译的结果,中文“逻辑”和“logistic”的含义相差甚远。值得注意的是,虽然它的名字里面是“回归”,但它实际上是一种分类学习方法。

逻辑回归参数的求解

一些符号的说明:$a_i=\cfrac{1}{1+e^{\pmb{w^Tx_i}+b}}$,$z_i=\pmb{w^Tx_i}+b$,学习率:$\alpha$

1.损失函数

我们通过“极大似然法”来估计参数$\pmb{w}$和$b$,

我们首先定义最大似然函数(认为各个样本之间是相对独立的),

如果使用一般最大似然会出现概率连乘的表达式,容易造成数值溢出,因此在采用对数似然(并且更容易进行求导),将连乘结果转化为累加:

我们定义以下的损失函数:

之前的似然函数是取最大值的,我们定义的损失函数等于对数似然的相反数,所以损失函数是需要取最小值的(所以对应的是梯度下降法,你可能也听说过梯度上升法,其实是一回事,只是损失函数的定义不同罢了)。

2.1梯度下降法

对于一个样本,使用链式规则,依次求导:

对于m个样本,只需要求平均即可:

然后更新$\pmb{w}$和$b$

2.2 牛顿法

和梯度下降法类似,都是通过迭代算法来实现的。

首先我们来看这么一个问题,$f(x)=0$有近似根$x_k$,那么$f(x)$在点$x_k$处的泰勒展开式表示为:$f(x)\approx f(x_k) + f’(x_k)(x-x_k)$

令$f(x)=0$,那么就有$f(x_k)+f’(x-x_k)=0$,由此就可求解$x=x_k-\cfrac{f(x_k)}{f’(x_k)}$

对于凸函数而言,这样的一次迭代过程就会进一步的接近极小值,直观理解可以查看下图:

UuPHc4.png

由于我们的损失函数是一个高阶可导的连续凸函数(令$\pmb{\beta}=[\pmb{w}|b]$),所以我们令上述的$f(x)=J’(\pmb{\beta})$,当$J’(\pmb{\beta})=0$时即为最优解,所以我们就能够这样来更新参数:$\pmb{\beta} = \pmb{\beta}-\cfrac{\partial J}{\partial \pmb{\beta}} (\cfrac{\partial^2 J}{\partial \pmb{\beta}\partial \pmb{\beta^T}})^{-1}$

这就是牛顿法,其中$H=(\cfrac{\partial^2 J}{\partial \pmb{\beta}\partial \pmb{\beta^T}})^{-1}$被称为黑塞矩阵(Hessian Matrix),由于其不一定可逆(并且求逆的计算量比较大),所以一般使用的时拟牛顿法,拟牛顿法在这就不过多介绍了。(实践中牛顿法和拟牛顿法都没有使用)

Logistic回归实践

逻辑回归实践使用是Pima印第安人数据集,该数据集最初来自国家糖尿病/消化/肾脏疾病研究所。数据集的目标是基于数据集中包含的某些诊断测量来诊断性的预测 患者是否患有糖尿病。整个实践过程没有使用任何的机器学习库,需要完整代码可以点击这里,点击这里

python3开发环境说明:

  • numpy:1.15.4
  • matplotlib:2.2.3

1. 加载数据

数据集(Pima.csv)共有9个特征,分别如下:

  • Pregnancies:怀孕次数
  • Glucose:葡萄糖
  • BloodPressure:血压
  • SkinThickness:皮层厚度
  • Insulin:胰岛素 2小时血清胰岛素
  • BMI:体重指数 (体重/身高)^2
  • DiabetesPedigreeFunction:糖尿病谱系功能
  • Age:年龄 (岁)
  • Outcome:类标变量 (0或1)

将数据加载到矩阵中(将特征和最终的结果分开保存)

1
2
3
4
5
import numpy as np
import matplotlib.pyplot as plt
pimaForm = np.loadtxt('Pima.csv', dtype=np.float64, delimiter=',') # 加载整个表格
data = pimaForm[:,:8].T # 数据部分
label = pimaForm[:,8:].astype(np.int16) # 标签部分
1
print(data.shape, label.shape)

输出结果:

1
(8, 768) (768, 1)

2. 数据预处理

2.1 缺失值处理

我们简单的直接使用均值填充了缺失值

1
2
3
4
5
6
dataMean = data.mean(axis=1)    # 计算均值,用于缺失值填充
dataStd = data.std(axis=1) # 计算标准差,用于后续数据处理
for i in range(data.shape[0]):
for j in range(data.shape[1]):
if data[i][j] == 0: # 出现缺失值就用均值填充
data[i][j] = dataMean[i]

2.2 输出归一化

认为输入数据为正态分布,然后进行归一化处理

1
data = ((data.T - dataMean)/dataStd).T  # 使用z-score标准化方法

2.3 数据集划分

训练集和测试集按照 5:1 的比例划分

1
2
3
4
testData = data[:,:128]
testLabel = label[:128]
trainData = data[:,128:]
trainLabel = label[128:]
1
print(testData.shape, testLabel.shape, trainData.shape, trainLabel.shape)

输出结果:

1
(8, 128) (128, 1) (8, 640) (640, 1)

3. 初始化参数

对参数进行随机初始化:

1
2
3
4
def initParameter(feature_num):
w = np.random.rand(1, feature_num) # w:1xfeature_num, 用0-1随机数初始化
b = 0 # b初始化为0
return w,b
1
2
test_w, test_b = initParameter(8)   # 测试一下
print(test_w.shape, test_b)

输出结果:

1
(1, 8) 0

4. 向前传播

先计算线性模型,然后再带入sigmoid函数。

1
2
3
4
def forward(data, w, b):
z = np.dot(w, data) # 计算线性部分
a = 1/(1+np.exp(-z)) # 计算预测结果
return a.T
1
2
a = forward(trainData, test_w, test_b)
print(a.shape)

输出结果:

1
(640, 1)

5. 梯度下降

反向求导过程

1
2
3
4
def gradDescent(w, b, x , y, a, learning_rate):
w = w - learning_rate * np.dot(x, a-y).T / a.shape[0] # 更新参数w
b = b - learning_rate * (a-y).sum(axis=0)[0] # 更新参数b
return w,b
1
2
test_w2, test_b2 = gradDescent(test_w, test_b, trainData, trainLabel, a, 0.01)
print(test_w2.shape, test_b2)

输出结果:

1
(1, 8) -1.3182942043705952

6. 开始训练

就是不断循环向前传播和梯度下降的一个过程:

1
2
3
4
5
6
w, b = initParameter(8)   # 测试一下
cost = []
for i in range(200):
a = forward(trainData, w, b)
w, b = gradDescent(w, b, trainData, trainLabel, a, 0.4)
cost.append((-(trainLabel*np.log(a)+(1-trainLabel)*np.log(1-a))).sum(axis = 0)[0]/a.shape[0])
1
plt.plot(cost)

输出结果:

UJTwo6.png

然后进行预测

1
2
testPred = (forward(testData, w, b)>0.5).astype(np.int16)
print("测试集正确率:"+str((testPred==testLabel).astype(np.int16).reshape(-1).sum()/testPred.shape[0]))

输出结果:

1
测试集正确率:0.734375