逻辑回归算法实现



算法原理

模型

逻辑回归模型是一个二分类的对数线性模型。令样本数据集为
{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,xiRt,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_ixiyiy_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=1xi)=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=0xi)=1P(yi=1xi)=1+e(ωTxi+b)e(ωTxi+b)
当类别yiy_iyi等于0时,p0p_0p0要尽可能的大。

可以将p1p_1p1p0p_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(yixi)=yip1+(1yi)p0={p1p0yi=1yi=0
这样,无论yiy_iyi是0还是1,都是要让ppp尽可能的大,即最大化。

整个数据集

对于单个样本数据(xix_ixiyiy_iyi),需要让ppp尽可能的大,那推广到整个数据集上,就是要求解
max⁡ω,b∏i=1nP(yi∣xi) \max_{\omega,b}\prod_{i=1}^nP(y_i|x_i) ω,bmaxi=1nP(yixi)
这里之所以将每个样本数据对应的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=1nP(yixi)ω,bmax[log(i=1nP(yixi))]ω,bmin[log(i=1nP(yixi))]ω,bmin[i=1n(logP(yixi))]
为了避免误会,箭头代表问题的等价转换。第一个箭头转换是因为对数函数是增函数,第二个箭头是因为添加负号,同时变成最小化问题,第三个箭头是根据对数的性质。进一步,我们就得到了逻辑回归的损失函数,也就是第三个箭头右边的公式的均值化,详细展开如下。
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=1n(logP(yixi))=n1i=1n[yilog(p1)+(1yi)log(p0)]=n1i=1n[yilog(1+e(ωTxi+b)1)+(1yi)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} bbλbL
其中,λ\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=1nxi(yif(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)) bL=n1i=1n(yif(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 yf(X):n1
其中,ω\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)

至此,逻辑回归算法讲解完毕,以上是个人对逻辑回归算法的一些理解,如有错误欢迎指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值