算法原理
模型
逻辑回归模型是一个二分类的对数线性模型。令样本数据集为
{xi;yi}i=1n,xi∈Rt,yi∈{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}
其中,xix_ixi是样本的特征,yiy_iyi是样本的类别,限定为0或1。逻辑回归模型的数学表达式为
f(xi)=11+e−(ωTxi+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。但是在算法思想上两者却完全不同,感知机利用了错误驱动,通过错误分类集来构造损失函数,然后使用梯度下降法进行求解。而接下来我们会讲到,逻辑回归是通过构造概率模型并最大化概率的方式来进行分类。
原理
首先,模型的前提是所有样本数据都是独立同分布的。
单个样本xix_ixi
现在已知有一个样本数据(xix_ixi,yiy_iyi),我们的目的是希望将xix_ixi代入模型
f(xi)=11+e−(ωTxi+b)
f(x_i)=\frac{1}{1+e^{-(\omega^T x_i +b)}}
f(xi)=1+e−(ωTxi+b)1
得到的f(xi)f(x_i)f(xi)刚好等于yiy_iyi。所以在这里要先定义一个条件概率,表示在已知样本是xix_ixi的条件下,类别yiy_iyi是1的概率
p1=P(yi=1∣xi)=11+e−(ωTxi+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
当类别yiy_iyi等于1时,p1p_1p1要尽可能的大。由p1p_1p1的公式可以得到在已知样本是xix_ixi的条件下,类别yiy_iyi是0的概率为
p0=P(yi=0∣xi)=1−P(yi=1∣xi)=e−(ωTxi+b)1+e−(ωTxi+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)
当类别yiy_iyi等于0时,p0p_0p0要尽可能的大。
可以将p1p_1p1和p0p_0p0整合起来,令
p=P(yi∣xi)=yip1+(1−yi)p0={p1yi=1p0yi=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
这样,无论yiy_iyi是0还是1,都是要让ppp尽可能的大,即最大化。
整个数据集
对于单个样本数据(xix_ixi,yiy_iyi),需要让ppp尽可能的大,那推广到整个数据集上,就是要求解
maxω,b∏i=1nP(yi∣xi)
\max_{\omega,b}\prod_{i=1}^nP(y_i|x_i)
ω,bmaxi=1∏nP(yi∣xi)
这里之所以将每个样本数据对应的ppp相乘,是因为每个样本数据都是独立同分布的。另外,由于每个ppp的数值都在0和1之间,因此它们的积也是在0和1之间。为了将乘法拆开,需要做一个负对数的转换(一般采用自然对数)
maxω,b∏i=1nP(yi∣xi)→maxω,b[log(∏i=1nP(yi∣xi))]→minω,b[−log(∏i=1nP(yi∣xi))]→minω,b[−∑i=1n(logP(yi∣xi))]
\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)=−1n∑i=1n(logP(yi∣xi))=−1n∑i=1n[yilog(p1)+(1−yi)log(p0)]=−1n∑i=1n[yilog(11+e−(ωTxi+b))+(1−yi)log(e−(ωTxi+b)1+e−(ωTxi+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ω和bbb的值,不断迭代,从而最小化损失函数。
ω→ω−λ∂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∂ω=−1n∑i=1nxi(yi−f(xi))
\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=−1n∑i=1n(yi−f(xi))
\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ω和偏置参数bbb合并在一起进行计算。可转化成矩阵计算的形式如下图(*代表矩阵乘法)。
图中显示的各矩阵的维数分别为:
ω:(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ω包含了偏置bbb,所以需要再加上一维变成t+1t+1t+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)
至此,逻辑回归算法讲解完毕,以上是个人对逻辑回归算法的一些理解,如有错误欢迎指出。