引言
在上一篇《入门机器学习——从线性回归开始理解机器学习》中,我们大概讲述了机器学习的一般流程和通用的框架,并以线性回归为例子一步一步实现了让模型自动“回归”的能力。事实上,逻辑回归和线性回归非常相似,逻辑回归本质上就是把线性回归的值域通过 l o g i s t i c − f u n c t i o n logistic-function logistic−function放缩到了 [ 0 , 1 ] [0,1] [0,1]之间。每个样本将得到一个概率值,整体样本将得到样本类别的概率分布,根据这个概率分布我们就可以得到一个样本是某个类别的概率。理想情况下,一个样本属于其正确类别的概率为1,属于其他类别的概率为0。如果我们通过机器学习可以使得模型无限接近这个概率分布,那么就可以对分类任务进行拟合学习了。
所以,划一个重点:分类任务是通过拟合一条直线(或一个超平面),进而拟合一个概率分布。这也是为什么逻辑回归是用来做分类任务的原因。你也可以理解为,我们要向一个概率分布回归。
接下来,笔者还是以理论+实践的方式,一起梳理一下逻辑回归。希望读者阅读完后,能明白为什么逻辑回归是一个分类器。
数据生成
假设我们的特征有两个维度 ( x 1 , x 2 ) (x_1,x_2) (x1,x2),由这两个特征构建起两个类,一类标记为0,一类标记为1。为了简单起见,我们以 ( x 1 , x 2 ) (x_1,x_2) (x1,x2)维上的两个点为圆心,在一定半径内随机生成一些样本点,最终我们将生成两簇不重叠的样本集。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import random
def generate_data(c1,c2,r,size):
'''
c1,c2 : 以两个点为中心,生成两簇数据,一簇为正样本,一簇为负样本
r : 半径
size : 生成数据量
'''
x1_plus,x2_plus = np.random.uniform(0,r,size=size),np.random.uniform(0,r,size=size)
cluster1_x1,cluster1_x2 = x1_plus+c1[0],x2_plus+c1[1]
y1 = np.ones(size)
cluster2_x1,cluster2_x2 = x1_plus+c2[0],x2_plus+c2[1]
y2 = np.zeros(size)
return (np.hstack((cluster1_x1,cluster2_x1)),np.hstack((cluster1_x2,cluster2_x2)),np.hstack((y1,y2)))
样本在二维平面上的分布如下:
x1,x2,y =generate_data((1,5),(5,1),3,50)
plt.scatter(x1[:50],x2[:50])
plt.scatter(x1[50:],x2[50:])
样本在三维空间中的分布如下:
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x1[:50],x2[:50],y[:50])
ax.scatter(x1[50:],x2[50:],y[50:])
我们的目标就是希望能找到一个三维空间中的超平面
y
=
w
1
x
1
+
w
2
x
2
+
b
y=w_1x_1+w_2x_2+b
y=w1x1+w2x2+b,使得这个超平面可以将整个空间的样本集一分为二, 区分出蓝色样本集和红色样本集。
判别函数
上篇文章中我们用了比较通俗易懂的公式,描述什么是线性模型:
y
=
θ
0
+
∑
i
=
1
n
θ
i
x
i
y = \theta_0 + \sum_{i=1}^{n}\theta_ix_i \newline
y=θ0+i=1∑nθixi
其实,再大部分书籍或文档中,我们看到的线性模型的数学表达往往是这样的:
y
=
f
(
x
;
w
)
=
w
T
x
y = f(\boldsymbol{x};\boldsymbol{w}) = \boldsymbol{w}^T\boldsymbol{x} \\
y=f(x;w)=wTx
其中,
w
=
[
w
1
,
.
.
.
,
w
D
,
b
]
T
\boldsymbol{w} = [w_1,...,w_D,b]^T
w=[w1,...,wD,b]T为
D
+
1
D+1
D+1维的增广向量,前
D
D
D维表示权值,
b
b
b为偏置;
x
=
[
x
1
,
.
.
.
,
x
D
,
1
]
T
\boldsymbol{x} = [x_1,...,x_D,1]^T
x=[x1,...,xD,1]T。
在回归问题中,我们可以直接通过
f
(
x
;
w
)
f(\boldsymbol{x};\boldsymbol{w})
f(x;w)来预测目标值
y
y
y。但是在分类问题中,我们需要预测的目标值往往是离散的标签,比如二分类识别猫狗问题,我们用
−
1
-1
−1表示猫,用
1
1
1表示狗。因此,我们需要在线性模型的基础之上,引入一个判别函数
g
(
f
(
x
;
w
)
)
g(f(x;w))
g(f(x;w)),对值域进行收缩。我们可以用符号函数来作为二分类问题的判别函数:
g
(
f
(
x
;
w
)
)
=
s
g
n
(
f
(
x
;
w
)
)
=
{
−
1
f(x;w)<0
1
f(x;w)>0
g(f(x;w)) = sgn(f(x;w)) = \begin{cases} -1 & \text{f(x;w)<0} \\ \ 1 & \text{f(x;w)>0} \end{cases}
g(f(x;w))=sgn(f(x;w))={−1 1f(x;w)<0f(x;w)>0
这个判别函数就将线性模型所有的值归为了两类,所有小于
0
0
0的归为负类,所有大于
0
0
0的归为正类。但是
s
g
n
函
数
sgn函数
sgn函数有个最大的问题是对梯度下降法不友好。可以很容易看出来,
s
g
n
函
数
sgn函数
sgn函数对其自变量的梯度为
0
0
0,这将导致参数无法更新,进而无法一步步向着解的方向优化。
Logsitic函数
逻辑回归(Logistic Regression),之所以称为“逻辑”,就是因为用了Logistic函数作为线性模型的判别函数。我们先来看看,Logistic函数的数学定义,方便起见,我们下面用
δ
\delta
δ代替
L
o
g
s
i
t
i
c
Logsitic
Logsitic。:
δ
(
x
)
=
1
1
+
e
−
x
∂
δ
∂
x
=
δ
(
1
−
δ
)
\delta(x) = \frac{1}{1+e^{-x}} \\ \frac{\partial\delta}{\partial x} = \delta(1-\delta)
δ(x)=1+e−x1∂x∂δ=δ(1−δ)
L
o
g
s
i
t
i
c
Logsitic
Logsitic函数将线性模型的值域放缩到了
[
0
,
1
]
[0,1]
[0,1]之间,大于
0.5
0.5
0.5的属于正类,小于
0.5
0.5
0.5的属于负类。相比
s
g
n
sgn
sgn函数,
L
o
g
s
i
t
i
c
Logsitic
Logsitic函数的导数性质就比较好,连续可导且导函数也比较简单,计算量小。所以,现在我们的模型就是线性模型再经过一个Logsitic函数:
g
(
f
(
x
;
w
)
)
=
L
o
g
i
s
t
i
c
(
f
(
x
;
w
)
)
=
{
0
f(x;w)<0.5
1
f(x;w)>0.5
g(f(x;w)) = Logistic(f(x;w)) = \begin{cases} 0 & \text{f(x;w)<0.5} \\ 1 & \text{f(x;w)>0.5} \end{cases}
g(f(x;w))=Logistic(f(x;w))={01f(x;w)<0.5f(x;w)>0.5
def logistic(x):
return 1/(1+np.e**(-x))
def gf(w):
# 增广向量 w = [w* b] , x = [x* 1]
def f_(x):
return logistic(w @ x)
return f_
深度学习的基石
事实上,Logsitic回归模型已经初具深度学习中神经元的雏形,只是在深度学习中,
l
o
g
i
s
t
i
c
logistic
logistic函数被称为“激活函数”。我们常听的
s
i
g
m
o
i
d
sigmoid
sigmoid函数就是
l
o
g
i
s
t
i
c
logistic
logistic函数,除了
s
i
g
m
o
i
d
sigmoid
sigmoid,出名的激活函数还有
t
a
n
h
tanh
tanh,
r
e
l
u
relu
relu等。
交叉熵损失函数
上一篇文章中,我们提及Loss函数是一种评价模型好坏的指标函数,我们称之为“损失函数”。在回归问题中,我们常用的Loss函数是MSE(均方误差),因为我们想要拟合一条最能描述样本趋势的直线,那么我们希望所有样本点到这条直线距离的平均误差越小越好。同样道理,在分类问题中,我们也需要一个损失函数来描述模型预测的概率分布和真实概率分布之间的误差。这个函数就是交叉熵损失函数,下面是二分类交叉熵损失函数公式:
L
o
s
s
c
r
o
s
s
_
e
n
t
r
o
p
y
=
−
1
N
∑
i
=
1
N
(
y
(
i
)
l
o
g
y
^
(
i
)
+
(
1
−
y
(
i
)
)
l
o
g
(
1
−
y
^
(
i
)
)
)
Loss_{cross\_entropy} = -\frac{1}{N}\sum_{i=1}^{N}(y^{(i)}log\hat{y}^{(i)}+(1-y^{(i)})log(1-\hat{y}^{(i)}))
Losscross_entropy=−N1i=1∑N(y(i)logy^(i)+(1−y(i))log(1−y^(i)))
交叉熵损失函数为什么就能定义分类问题的模型的好坏呢?这个公式乍一看很难理解,估计又是那个天才发现的。事实上,还真是天才发现的,这个天才叫“香农”,就是那位“信息论之父”。想要理解交叉熵,我们需要先理解信息熵。信息熵的公式如下:
∑
P
i
log
2
P
i
−
1
\sum P_i \log_2{P_i^{-1}}
∑Pilog2Pi−1
知乎上有个不错的讲解,这里贴出来供读者学习,这里不在赘述。
https://www.zhihu.com/question/22178202
熵描述了事物的混乱程度,熵越小表示越有序,而信息熵描述了信息的不确定性,信息熵越小表示信息越准确。如果根据一个信息得到某个结果的概率为100%,那么根据信息熵公式,熵为0,信息非常准确。那么,如果根据一个信息得到某个结果的概率为0,信息熵为多少呢?这个时候需要对信息熵求趋于
0
+
0^+
0+的极限。
f
(
x
)
=
x
l
o
g
2
x
−
1
lim
x
−
>
0
+
f
(
x
)
=
lim
y
−
>
+
∞
f
(
y
)
=
lim
y
−
>
+
∞
l
o
g
2
y
y
=
lim
y
−
>
+
∞
1
y
l
n
2
=
0
,
y
=
1
x
f(x) = xlog_2x^{-1}\\ \lim_{x->0^+}f(x) = \lim_{y->+\infty}f(y) = \lim_{y->+\infty}\frac{log_2y}{y}=\lim_{y->+\infty}\frac{1}{yln2}=0,y=\frac{1}{x}
f(x)=xlog2x−1x−>0+limf(x)=y−>+∞limf(y)=y−>+∞limylog2y=y−>+∞limyln21=0,y=x1
也就是说,当根据一个信息得到某个结果的概率为0时,就相当于根据这个信息可以得到不是某个结果的概率为100%,这个信息也是非常准确的,熵也为0。只有当根据一个信息可能得到某个结果,但又不是非常确定时,信息熵才是比较大的 。交叉熵公式中
y
^
(
i
)
\hat{y}^{(i)}
y^(i)就表示模型预测是
y
(
i
)
y^{(i)}
y(i)的概率。如果预测非常准确,即
y
^
(
i
)
=
y
(
i
)
=
1
\hat{y}^{(i)}=y^{(i)}=1
y^(i)=y(i)=1或
y
^
(
i
)
=
y
(
i
)
=
0
\hat{y}^{(i)}=y^{(i)}=0
y^(i)=y(i)=0,那么正类的信息熵即为0,负类的信息熵也为0,总熵为0;如果预测非常不准确,当
y
^
(
i
)
=
0
,
y
(
i
)
=
1
\hat{y}^{(i)}=0,y^{(i)}=1
y^(i)=0,y(i)=1时,正类熵为
+
∞
+\infty
+∞,负类熵为0,总熵为
+
∞
+\infty
+∞;同理当
y
^
(
i
)
=
1
,
y
(
i
)
=
0
\hat{y}^{(i)}=1,y^{(i)}=0
y^(i)=1,y(i)=0,总熵也为
+
∞
+\infty
+∞。这就是为什么交叉熵可以用来衡量分类模型的好坏的原因,交叉熵越小就表示预测的越准确。
求导过程
根据链式求导法则,因为多了一个判别函数,因此需要先对
δ
\delta
δ求偏导,在对
w
\boldsymbol{w}
w求偏导,求导的过程稍微复杂,但是最终的形式却很简洁。
∂
L
o
s
s
∂
y
^
=
−
1
N
∑
i
=
1
N
(
y
(
i
)
1
y
^
(
i
)
+
(
1
−
y
(
i
)
)
−
1
1
−
y
^
(
i
)
)
∂
y
^
(
i
)
∂
w
∂
y
^
(
i
)
∂
w
=
y
^
(
i
)
(
1
−
y
^
(
i
)
)
x
(
i
)
∂
L
o
s
s
∂
w
=
−
1
N
∑
i
=
1
N
(
y
(
i
)
−
y
^
(
i
)
)
x
(
i
)
\frac{\partial{Loss}}{\partial\hat y} = -\frac{1}{N}\sum_{i=1}^{N}(y^{(i)}\frac{1}{\hat{y}^{(i)}}+(1-y^{(i)})\frac{-1}{1-\hat{y}^{(i)}})\frac{\partial \hat{y}^{(i)}}{\partial w} \\ \frac{\partial \hat{y}^{(i)}}{\partial w} = \hat{y}^{(i)}(1-\hat{y}^{(i)}) \boldsymbol{x^{(i)}} \\ \frac{\partial Loss}{\partial w} = -\frac{1}{N}\sum_{i=1}^{N}(y^{(i)}-\hat{y}^{(i)})x^{(i)}
∂y^∂Loss=−N1i=1∑N(y(i)y^(i)1+(1−y(i))1−y^(i)−1)∂w∂y^(i)∂w∂y^(i)=y^(i)(1−y^(i))x(i)∂w∂Loss=−N1i=1∑N(y(i)−y^(i))x(i)
def cross_entropy(y,y_hat):
N = y_hat.shape[0]
return -(1/N)*np.sum(y*np.log(y_hat)+(1-y)*np.log(1-y_hat))
def cross_entropy_grad(y,y_hat,x):
N = y_hat.shape[0]
grad = -(1/N)*np.sum((y-y_hat)*x,axis=1)
return grad.T
def gradient_descent(arg,lr,grad):
return arg-lr*grad
这里需要特别注意的地方是维度的问题。
w
w
w的维度为
(
1
,
3
)
(1,3)
(1,3),而输入
x
x
x的维度为
(
3
,
100
)
(3,100)
(3,100),
y
^
\hat{y}
y^和
y
y
y的维度为
(
1
,
100
)
(1,100)
(1,100)。我们在模型
f
(
x
)
f(x)
f(x)中求
w
@
x
w @ x
w@x时,输出自然是
(
1
,
100
)
(1,100)
(1,100)。但是在计算梯度时,我们希望的输出形状是
(
1
,
3
)
(1,3)
(1,3),(y-y_hat)*x
的输出形状为
(
3
,
100
)
(3,100)
(3,100),我们希望np.sum
按照行进行求和,最终每行的100个值相加得到三行一列的数据格式,就是梯度更新后的
w
w
w。
模型训练与验证
知道了模型,Loss函数,就可以使用梯度下降法对模型参数进行梯度更新求优了。对机器学习通用框架步骤不了解的读者可以看看我的第一篇文章。
class logistic_model():
def __init__(self,max_iter=100,lr=0.01):
self.max_iter = max_iter
self.lr = lr
def fit(self,data):
x,y = data
# 初始化权值向量w和模型
w = np.hstack((np.random.uniform(-1,1,2),np.array([1.0]))).reshape(1,3)
print(w,w.shape)
f = gf(w)
for i in range(self.max_iter):
y_hat = f(x)
loss = cross_entropy(y,y_hat)
print(loss)
# 计算梯度,并更新梯度
w_grad = cross_entropy_grad(y,y_hat,x)
w = gradient_descent(w,self.lr,w_grad)
# 重新生成模型
f = gf(w)
return f,w
x = np.vstack((x1,x2,np.ones(100)))
data = (x,y)
model = logistic_model(max_iter=300)
f,w = model.fit(data)
最终,模型训练完成后,我们可以得到一个期望的超平面,这个超平面将两簇数据样本集一分为二,在超平面下方的是蓝色样本集,在超平面上方的是红色样本集。
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x1[:50],x2[:50],y[:50])
ax.scatter(x1[50:],x2[50:],y[50:])
# 平面生成
X = np.arange(0,10,0.25)
Y = np.arange(0,10,0.25)
print(X.shape,Y.shape,w.shape)
X,Y = np.meshgrid(X,Y)
print(X.shape,Y.shape,Z.shape)
print(w.shape)
O = np.ones(40*40).reshape(40,40)
R = np.dstack([X,Y,O])
print("R:",R.shape)
ZR = (R @ w.T).reshape(40,40)
ax.plot_surface(X,Y,ZR)
总结
通过第一篇的线性回归和这一篇的逻辑回归,我们可以得到一些启发思考。
- 模型代表着拟合能力。在逻辑回归中,通过加入一个判别函数,我们可以让模型去拟合一个概率分布。相比线性回归,逻辑回归模型的能力略微强了一些。
- 损失函数代表着拟合导向。在逻辑回归中,我们想要的超平面是将样本集一分为二;而在线性回归中,我们想要的超平面是拟合样本集分布的趋势。指导模型学习方向的就是能评价模型好坏的损失函数。
- 梯度下降代表着拟合方法。有了拟合的能力和方向,如何一步步向着目标优化,这是梯度下降在机器学习中担当的角色。
这不就是人类学习的过程吗?人类的可塑性(智商,天赋等)就好比模型,有天赋的人没有正确的引导和目标(家庭,社会,个人内在驱动)也终将一事无成,而一步一步的勤奋就是学习最根本的方法,或许别人的方法略好,走的更快,但是都必须一步一步的优化,没有那个优化方法可以一步登天。所以,我得继续加油,你们也加油!!!