逻辑回归介绍

逻辑回归(Logistic Regression, LR)模型其实仅在线性回归的基础上,套用了一个逻辑函数。因此,LR模型可以被认为是一个被Sigmoid函数(logistic方程)所归一化后的线性回归模型

为什么需要逻辑回归

首先要理解二分类问题,二分类问题是指预测的y值只有两个取值(0或1)。

例如:我们要做一个垃圾邮件过滤系统,X(i)是邮件的特征,预测的y值就是邮件的类别,是垃圾邮件还是正常邮件。对于类别我们通常称为正类(positive class)和负类(negative class),垃圾邮件的例子中,正类就是正常邮件,负类就是垃圾邮件。

如果我们继续使用线性回归来预测y的取值。这样做会导致y的取值并不为0或1。

逻辑回归使用一个函数来归一化y值,使y的取值在区间(0,1)内,称为Sigmoid函数:

$$
g(z)=\frac{1}{1+e^{-z}}
$$

当z趋近于无穷大时,g(z)趋近于1;当z趋近于无穷小时,g(z)趋近于0。

Sigmoid图形如下:

{cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import matplotlib.pyplot as plt
import numpy as np

def sigmoid(x):
return 1. / (1. + np.exp(-x))

x = np.arange(-10, 10, 0.1)
y = sigmoid(x)

plt.plot(x, y)
plt.axvline(0.0,ls = 'dotted', color='k')
plt.title(r'Sigmoid函数曲线', fontsize = 15)
plt.text(5,0.8,r'$y = \frac{1}{1+e^{-z}}$', fontsize = 18)

plt.show()

这里我们发现,函数中z无论取什么值,其结果y都在[0,-1]的区间内,回想一下,一个分类问题就有两种答案,一种是“是”,一种是“否”,那0对应着“否”,1对应着“是”,那又有人问了,你这不是[0,1]的区间吗,怎么做到只有0和1呢?

我们可以假设分类的阈值是0.5,超过0.5的归为1分类,低于0.5的归为0分类。阈值是可以自己设定的。

下面参考:逻辑回归 logistics regression 公式推导

我们不能直接拿了sigmoid函数就用,毕竟它连要训练的参数 $\theta$ 都没。我们结合sigmoid函数,线性回归函数,把线性回归模型的输出作为sigmoid函数的输入。于是最后就变成了逻辑回归模型:

$$
y=g(f({x}))=g\left({\theta}^{T} {x}\right)=\frac{1}{1+e^{-{\theta}^{T} {x}}}
$$

假设我们已经训练好了一组权值 $\theta^T$ 。只要把我们需要预测的 $\theta^T$ 代入到上面的方程,输出的y值就是这个标签为A的概率,我们就能够判断输入数据是属于哪个类别。

损失函数

逻辑回归的损失函数如下所示:

$$
\operatorname{cost}\left(h_{\theta}(x), y\right)=\left{\begin{array}{cc}{-\log \left(h_{\theta}(x)\right)} & {\text { if } y=1} \ {-\log \left(1-h_{\theta}(x)\right)} & {\text { if } y=0}\end{array}\right.
$$

当真实值为1分类时,用第一个方程来表示损失函数;当真实值为0分类时,用第二个方程来表示损失函数,为什么要加上log函数呢?可以试想一下,当真实样本为1是,但h=0概率,那么log0=∞,这就对模型最大的惩罚力度;当h=1时,那么log1=0,相当于没有惩罚,也就是没有损失,达到最优结果。所以数学家就想出了用log函数来表示损失函数,把上述两式合并起来就是如下函数:

$$
\begin{aligned} J(\theta) &=\frac{1}{m} \sum_{i=1}^{m} \operatorname{cost}\left(h_{\theta}\left(x^{(i)}\right), y^{(i)}\right) \ &=-\frac{1}{m}\left[\sum_{i=1}^{m} y^{(i)} \log h_{\theta}\left(x^{(i)}\right)+\left(1-y^{(i)}\right) \log \left(1-h_{\theta}\left(x^{(i)}\right)\right)\right] \end{aligned}
$$

最后按照梯度下降法一样,求解极小值点,得到想要的模型效果。

梯度下降

梯度下降(Gradient Descent)又叫作最速梯度下降,是一种迭代求解的方法,通过在每一步选取使目标函数变化最快的一个方向调整参数的值来逼近最优值。基本步骤如下:

  • 选择下降方向(梯度方向,$\nabla {J(\theta)}$)
  • 选择步长,更新参数 $\theta^i = \theta^{i-1} - \alpha^i \nabla {J(\theta^{i-1})}$
  • 重复以上两步直到满足终止条件

其中损失函数的梯度计算方法为:

$$
\frac{\partial{J}}{\partial{\theta}} = -\frac{1}{m}\sum_i (y^i - h_{\theta}\left(x^{(i)}\right))x^i
$$

沿梯度负方向选择一个较小的步长可以保证损失函数是减小的,另一方面,逻辑回归的损失函数是凸函数(加入正则项后是严格凸函数),可以保证我们找到的局部最优值同时是全局最优。

为什么可以用梯度下降法?

因为逻辑回归的损失函数L是一个连续的凸函数(conveniently convex)。这样的函数的特征是,它只会有一个全局最优的点,不存在局部最优。对于GD跟SGD最大的潜在问题就是它们可能会陷入局部最优。然而这个问题在逻辑回归里面就不存在了,因为它的损失函数的良好特性,导致它并不会有好几个局部最优。当我们的GD跟SGD收敛以后,我们得到的极值点一定就是全局最优的点,因此我们可以放心地用GD跟SGD来求解。

逻辑回归的优势

本部分参考:逻辑回归本质

  • 模型非常简单。应用到线上时,prediction的计算非常容易做。在O(1)的时间复杂度之内就能够给出模型的预测值,这对于线上数据暴风雨般袭来的时候非常有用。
  • 模型可解释性强。对于逻辑回归模型,每个特征$x_i$的参数$\theta_i$就是该特征的权重,$\theta_i$越大,则特征权重越大;越小,则特征权重越小。因此逻辑回归模型往往非常直观,而且容易debug,而且也容易手动修改。
  • 模型的输出平滑。由于Logistic function的作用,逻辑回归输出值是(0,1)之间的连续值,更重要的是,这个值能从某种角度上表示样本x是正例的可能性,输出值越接近1,则样本是正例的可能性就越大,输出值越接近0,样本是负例的可能性就越大。注意这里的用词,是样本x是正例的可能性,而不是样本x是正例的概率。

多分类问题

其实我们可以从二分类问题过度到多分类问题,有多种扩展逻辑回归使其成为多分类器的方法,比如One-Vs-All、One-Vs-One,下面简单介绍下One-Vs-All。

One-Vs-All

One-Vs-All(或者叫 One-Vs-Rest)的思想是把一个多分类的问题变成多个二分类的问题。

  1. 将类型class1看作正样本,其他类型全部看作负样本,然后我们就可以得到样本标记类型为该类型的概率p1。
  2. 然后再将另外类型class2看作正样本,其他类型全部看作负样本,同理得到p2。
  3. 以此循环,我们可以得到该待预测样本的标记类型分别为类型class i时的概率pi,最后我们取pi中最大的那个概率对应的样本标记类型作为我们的待预测样本类型。

逻辑回归建立步骤

我们按照以下三个基本步骤,来分析和建立逻辑回归模型:

  • 构造假设函数(即 H 函数)
  • 构造损失函数(即 J 函数)
  • 通过某种方法使得损失函数最小,并求得此时的参数θ

sklearn实现逻辑回归

1、简单实现

{cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from sklearn.datasets import load_iris   
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np

# 加载数据集
iris = load_iris()

# 数据特征:150行, 4列
features = iris['data']

# 对应的鸢尾花种类: 150个,三种鸢尾花分别用 0,1,2 表示
target = iris['target']

# 自定义4个特征的名称
feature_names = iris.feature_names
feature_names = ['花萼长度', '花萼宽度', '花瓣长度', '花瓣宽度']

# 自定义三种鸢尾花的名称
class_names = iris.target_names
class_names = ['山鸢尾花', '变色鸢尾花', '维吉尼亚鸢尾花']

# 把样本分成训练集和测试集两部分, 两者比例为: 7:3
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.3, random_state=42)

# 训练
lr = LogisticRegression()
lr.fit(X_train, y_train)

# 预测
output = lr.predict(X_test)

# 计算准确率
acc = np.mean(output == y_test)*100
print("The accuracy of the logistic regression classifier is: \t", acc, "%")
1
The accuracy of the logistic regression classifier is: 	 97.77777777777777 %

2、可视化分类边界

这里使用matplotlib下的pcolormesh()方法来直观表现出分类边界,有效解决了单纯的绘制散点图看不出分类的边界的问题。

注:为了可视化,我仅取花萼长度花萼宽度两个特征进行逻辑回归训练。

{cmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from sklearn.datasets import load_iris   
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np

# 加载数据集
iris = load_iris()

# 数据特征:150行, 4列
features = iris['data']

# 对应的鸢尾花种类: 150个,三种鸢尾花分别用 0,1,2 表示
target = iris['target']

# 自定义4个特征的名称
feature_names = iris.feature_names
feature_names = ['花萼长度', '花萼宽度', '花瓣长度', '花瓣宽度']

# 自定义三种鸢尾花的名称
class_names = iris.target_names
class_names = ['山鸢尾花', '变色鸢尾花', '维吉尼亚鸢尾花']

# 这里仅取花萼长度和花萼宽度做特征
X_train = features[:,:2]
y_train = target

# 训练
lr = LogisticRegression()
lr.fit(X_train, y_train)

# 画出分类边界
N, M = 500, 500 # 横纵各采样多少个值
x1_min, x2_min = X_train.min(axis=0)
x1_max, x2_max = X_train.max(axis=0)
t1 = np.linspace(x1_min, x1_max, N)
t2 = np.linspace(x2_min, x2_max, M)
x1, x2 = np.meshgrid(t1, t2) # 生成网格采样点
x_show = np.stack((x1.flat, x2.flat), axis=1) # 测试点
y_predict=lr.predict(x_show)

# 设置颜色
cm_light = mpl.colors.ListedColormap(['#A0FFA0', '#FFA0A0', '#A0A0FF'])
cm_dark = mpl.colors.ListedColormap(['g', 'r', 'b'])

# 绘制出分类图
plt.xlim(x1_min, x1_max)
plt.ylim(x2_min, x2_max)
plt.pcolormesh(x1, x2, y_predict.reshape(x1.shape),cmap=cm_light)
plt.scatter(X_train[:,0],X_train[:,1],c=y_train,cmap=cm_dark,marker='o',edgecolors='k')
plt.xlabel('花萼长度')
plt.ylabel('花瓣长度')
plt.title('鸢尾花分类')
plt.grid(True,ls=':')
plt.show()

参考

赞赏一杯咖啡
0%