算法原理
模型
逻辑回归模型是一个二分类的对数线性模型。令样本数据集为
{
x
i
;
y
i
}
i
=
1
n
,
x
i
∈
R
t
,
y
i
∈
{
0
,
1
}
\{x_i;y_i\}^n_{i=1},x_i\in\mathbb{R}^t,y_i\in\{0,1\}
{xi;yi}i=1n,xi∈Rt,yi∈{0,1}
其中,
x
i
x_i
xi是样本的特征,
y
i
y_i
yi是样本的类别,限定为0或1。逻辑回归模型的数学表达式为
f
(
x
i
)
=
1
1
+
e
−
(
ω
T
x
i
+
b
)
,
i
=
1
,
2
,
.
.
.
,
n
f(x_i)=\frac{1}{1+e^{-(\omega^T x_i +b)}},i=1,2,...,n
f(xi)=1+e−(ωTxi+b)1,i=1,2,...,n
单单看这个表达式可以发现,它跟上节讲到的感知机模型是很类似的,感知机使用sign函数对线性表达式进行转化,而逻辑回归是用了sigmoid函数,这也是为什么感知机中的类别标签是1和-1,而逻辑回归的类别标签是0和1。但是在算法思想上两者却完全不同,感知机利用了错误驱动,通过错误分类集来构造损失函数,然后使用梯度下降法进行求解。而接下来我们会讲到,逻辑回归是通过构造概率模型并最大化概率的方式来进行分类。
原理
首先,模型的前提是所有样本数据都是独立同分布的。
单个样本 x i x_i xi
现在已知有一个样本数据(
x
i
x_i
xi,
y
i
y_i
yi),我们的目的是希望将
x
i
x_i
xi代入模型
f
(
x
i
)
=
1
1
+
e
−
(
ω
T
x
i
+
b
)
f(x_i)=\frac{1}{1+e^{-(\omega^T x_i +b)}}
f(xi)=1+e−(ωTxi+b)1
得到的
f
(
x
i
)
f(x_i)
f(xi)刚好等于
y
i
y_i
yi。所以在这里要先定义一个条件概率,表示在已知样本是
x
i
x_i
xi的条件下,类别
y
i
y_i
yi是1的概率
p
1
=
P
(
y
i
=
1
∣
x
i
)
=
1
1
+
e
−
(
ω
T
x
i
+
b
)
p_1=P(y_i=1|x_i)=\frac{1}{1+e^{-(\omega^T x_i +b)}}
p1=P(yi=1∣xi)=1+e−(ωTxi+b)1
当类别
y
i
y_i
yi等于1时,
p
1
p_1
p1要尽可能的大。由
p
1
p_1
p1的公式可以得到在已知样本是
x
i
x_i
xi的条件下,类别
y
i
y_i
yi是0的概率为
p
0
=
P
(
y
i
=
0
∣
x
i
)
=
1
−
P
(
y
i
=
1
∣
x
i
)
=
e
−
(
ω
T
x
i
+
b
)
1
+
e
−
(
ω
T
x
i
+
b
)
p_0=P(y_i=0|x_i)=1-P(y_i=1|x_i)=\frac{e^{-(\omega^T x_i +b)}}{1+e^{-(\omega^T x_i +b)}}
p0=P(yi=0∣xi)=1−P(yi=1∣xi)=1+e−(ωTxi+b)e−(ωTxi+b)
当类别
y
i
y_i
yi等于0时,
p
0
p_0
p0要尽可能的大。
可以将
p
1
p_1
p1和
p
0
p_0
p0整合起来,令
p
=
P
(
y
i
∣
x
i
)
=
y
i
p
1
+
(
1
−
y
i
)
p
0
=
{
p
1
y
i
=
1
p
0
y
i
=
0
p=P(y_i|x_i)=y_i p_1+(1-y_i) p_0=\begin{cases} p_1 & y_i=1 \\ p_0 & y_i=0 \end{cases}
p=P(yi∣xi)=yip1+(1−yi)p0={p1p0yi=1yi=0
这样,无论
y
i
y_i
yi是0还是1,都是要让
p
p
p尽可能的大,即最大化。
整个数据集
对于单个样本数据(
x
i
x_i
xi,
y
i
y_i
yi),需要让
p
p
p尽可能的大,那推广到整个数据集上,就是要求解
max
ω
,
b
∏
i
=
1
n
P
(
y
i
∣
x
i
)
\max_{\omega,b}\prod_{i=1}^nP(y_i|x_i)
ω,bmaxi=1∏nP(yi∣xi)
这里之所以将每个样本数据对应的
p
p
p相乘,是因为每个样本数据都是独立同分布的。另外,由于每个
p
p
p的数值都在0和1之间,因此它们的积也是在0和1之间。为了将乘法拆开,需要做一个负对数的转换(一般采用自然对数)
max
ω
,
b
∏
i
=
1
n
P
(
y
i
∣
x
i
)
→
max
ω
,
b
[
l
o
g
(
∏
i
=
1
n
P
(
y
i
∣
x
i
)
)
]
→
min
ω
,
b
[
−
l
o
g
(
∏
i
=
1
n
P
(
y
i
∣
x
i
)
)
]
→
min
ω
,
b
[
−
∑
i
=
1
n
(
l
o
g
P
(
y
i
∣
x
i
)
)
]
\max_{\omega,b}\prod_{i=1}^nP(y_i|x_i) \rightarrow\max_{\omega,b}[log(\prod_{i=1}^nP(y_i|x_i))] \rightarrow\min_{\omega,b}[-log(\prod_{i=1}^nP(y_i|x_i))] \rightarrow\min_{\omega,b}[-\sum_{i=1}^n(logP(y_i|x_i))]
ω,bmaxi=1∏nP(yi∣xi)→ω,bmax[log(i=1∏nP(yi∣xi))]→ω,bmin[−log(i=1∏nP(yi∣xi))]→ω,bmin[−i=1∑n(logP(yi∣xi))]
为了避免误会,箭头代表问题的等价转换。第一个箭头转换是因为对数函数是增函数,第二个箭头是因为添加负号,同时变成最小化问题,第三个箭头是根据对数的性质。进一步,我们就得到了逻辑回归的损失函数,也就是第三个箭头右边的公式的均值化,详细展开如下。
L
(
ω
,
b
)
=
−
1
n
∑
i
=
1
n
(
l
o
g
P
(
y
i
∣
x
i
)
)
=
−
1
n
∑
i
=
1
n
[
y
i
l
o
g
(
p
1
)
+
(
1
−
y
i
)
l
o
g
(
p
0
)
]
=
−
1
n
∑
i
=
1
n
[
y
i
l
o
g
(
1
1
+
e
−
(
ω
T
x
i
+
b
)
)
+
(
1
−
y
i
)
l
o
g
(
e
−
(
ω
T
x
i
+
b
)
1
+
e
−
(
ω
T
x
i
+
b
)
)
]
L(\omega,b) =-\frac{1}{n}\sum_{i=1}^n(logP(y_i|x_i)) =-\frac{1}{n}\sum_{i=1}^n[y_i log(p_1)+(1-y_i) log(p_0)] =-\frac{1}{n}\sum_{i=1}^n[y_i log(\frac{1}{1+e^{-(\omega^T x_i +b)}})+(1-y_i) log(\frac{e^{-(\omega^T x_i +b)}}{1+e^{-(\omega^T x_i +b)}})]
L(ω,b)=−n1i=1∑n(logP(yi∣xi))=−n1i=1∑n[yilog(p1)+(1−yi)log(p0)]=−n1i=1∑n[yilog(1+e−(ωTxi+b)1)+(1−yi)log(1+e−(ωTxi+b)e−(ωTxi+b))]
模型求解
采用梯度下降法求解,更新
ω
\omega
ω和
b
b
b的值,不断迭代,从而最小化损失函数。
ω
→
ω
−
λ
∂
L
∂
ω
\omega\rightarrow\omega-\lambda\frac{\partial L}{\partial\omega}
ω→ω−λ∂ω∂L
b
→
b
−
λ
∂
L
∂
b
b\rightarrow b-\lambda\frac{\partial L}{\partial b}
b→b−λ∂b∂L
其中,
λ
\lambda
λ是学习步长
∂
L
∂
ω
=
−
1
n
∑
i
=
1
n
x
i
(
y
i
−
f
(
x
i
)
)
\frac{\partial L}{\partial\omega}=-\frac{1}{n}\sum_{i=1}^{n}x_i(y_i-f(x_i))
∂ω∂L=−n1i=1∑nxi(yi−f(xi))
∂
L
∂
b
=
−
1
n
∑
i
=
1
n
(
y
i
−
f
(
x
i
)
)
\frac{\partial L}{\partial b}=-\frac{1}{n}\sum_{i=1}^{n}(y_i-f(x_i))
∂b∂L=−n1i=1∑n(yi−f(xi))
程序实现
sigmoid函数
def sigmoid(X):
y = 1 / (1+np.exp(-X))
return y
初始化函数
def __init__(self, alpha=0.01, iteration=1000):
"""
alpha 学习步长
w 权重向量
iteration 最大迭代次数
error 记录损失函数值
"""
self.alpha = alpha
self.iteration = iteration
训练函数
def fit(self, X, y):
[data_num, fea_num] = X.shape
X_ = np.vstack((np.ones(data_num).reshape(1,-1), X.T))
y_ = y.reshape(-1,1)
self.w = np.zeros(fea_num+1).reshape(-1,1)
self.error = []
i = 1
while i<self.iteration:
# 迭代w值
dif_y = y_ - sigmoid(np.dot(self.w.T,X_)).reshape(-1,1)
self.w = self.w + self.alpha * 1/data_num * np.dot(X_, dif_y)
# 记录损失
p1 = sigmoid(np.dot(self.w.T,X_).T)
temp = y_*np.log(p1) + (1-y_)*np.log(1-p1)
self.error.append(-temp.mean())
# 迭代次数
if (i+1)%100==0:
print('epoch--', i+1)
i += 1
训练函数的核心代码在第十一和第十二行,是参数
ω
\omega
ω的迭代更新公式,参考模型求解部分的公式,代码中将权重参数
ω
\omega
ω和偏置参数
b
b
b合并在一起进行计算。可转化成矩阵计算的形式如下图(*代表矩阵乘法)。
图中显示的各矩阵的维数分别为:
ω
:
(
t
+
1
)
∗
1
\omega:(t+1)* 1
ω:(t+1)∗1
X
:
(
t
+
1
)
∗
n
X_:(t+1)* n
X:(t+1)∗n
y
和
f
(
X
)
:
n
∗
1
y和f(X):n* 1
y和f(X):n∗1
其中,
ω
\omega
ω包含了偏置
b
b
b,所以需要再加上一维变成
t
+
1
t+1
t+1维。另外,逻辑回归不一定能将样本全部正确分类,所以需要设置迭代次数。
预测函数
def predict(self, X):
X_ = np.vstack((np.ones(X.shape[0]).reshape(1,-1), X.T))
sig = sigmoid(np.dot(self.w.T, X_))[0]
return (sig>=0.5)*1
整合全部代码
class LogisticRegression:
def __init__(self, alpha=0.01, iteration=1000):
"""
alpha 学习步长
w 权重向量
iteration 最大迭代次数
error 记录损失函数值
"""
self.alpha = alpha
self.iteration = iteration
def fit(self, X, y):
[data_num, fea_num] = X.shape
X_ = np.vstack((np.ones(data_num).reshape(1,-1), X.T))
y_ = y.reshape(-1,1)
self.w = np.zeros(fea_num+1).reshape(-1,1)
self.error = []
i = 1
while i<self.iteration:
# 迭代w值
dif_y = y_ - sigmoid(np.dot(self.w.T,X_)).reshape(-1,1)
self.w = self.w + self.alpha * 1/data_num * np.dot(X_, dif_y)
# 记录损失
p1 = sigmoid(np.dot(self.w.T,X_).T)
temp = y_*np.log(p1) + (1-y_)*np.log(1-p1)
self.error.append(-temp.mean())
# 迭代次数
if (i+1)%100==0:
print('epoch--', i+1)
i += 1
def predict(self, X):
X_ = np.vstack((np.ones(X.shape[0]).reshape(1,-1), X.T))
sig = sigmoid(np.dot(self.w.T, X_))[0]
return (sig>=0.5)*1
实例化演示
导入相关库和数据并查看数据分布
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data[:100,:2]
y = (iris.target[:100]==1)*1
plt.scatter(X[y==0,0], X[y==0,1], color='red')
plt.scatter(X[y==1,0], X[y==1,1], color='green')
实例化函数并画出划分平面
# 训练
clf=LogisticRegression()
clf.fit(X,y)
line1 = X[:,0]
line2 = -(clf.w[0]+clf.w[1]*line1)/clf.w[2]
# 画图
plt.scatter(X[y==0,0],X[y==0,1],color='red')
plt.scatter(X[y==1,0],X[y==1,1],color='green')
plt.plot(line1,line2)
至此,逻辑回归算法讲解完毕,以上是个人对逻辑回归算法的一些理解,如有错误欢迎指出。