欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

手写算法-python代码实现逻辑回归(带L1、L2正则项)

程序员文章站 2022-03-27 21:05:29
手写算法-python代码实现逻辑回归逻辑回归原理解析损失函数定义以及数学公式推导过程解释1:通俗易懂的手推损失函数:解释2:最大似然估计求解参数对损失函数推导梯度python代码实现逻辑回归逻辑回归原理解析前面我们系统性的介绍了线性回归,初学者建议把我前面的文章看完,再来看逻辑回归。写得应该算是容易看懂的了,且都有实例辅证,大家看的时候要自己跑一边代码,多动手、多思考。今天,我们来讲逻辑回归。逻辑回归是LogisticRegression的直译,它不是用来解决回归问题的,而是用来解决分类问题的,它...

逻辑回归原理解析

前面我们系统性的介绍了线性回归,初学者建议把我前面的文章看完,再来看逻辑回归。写得应该算是容易看懂的了,且都有实例辅证,大家看的时候要自己跑一边代码,多动手、多思考。

今天,我们来讲逻辑回归。

逻辑回归是LogisticRegression的直译,它不是用来解决回归问题的,而是用来解决分类问题的,它其实是在线性回归的基础上实现的。
我们知道,线性回归针对的是标签为连续值的机器学习任务,那怎样才可以用线性模型做分类任务呢?
例如二分类任务,标签值只有0和1两种。

思考:我们可以建立某种映射关系,将原先的连续值,转为0/1值,
现在请出sigmoid函数:
手写算法-python代码实现逻辑回归(带L1、L2正则项)
其函数图像如下:
手写算法-python代码实现逻辑回归(带L1、L2正则项)
长的很优雅!在自变量实数范围内,它的取值都在0-1之间,完美的映射了线性回归的连续值;
把线性回归的假设函数 z=X???? 作为x传入其中:

手写算法-python代码实现逻辑回归(带L1、L2正则项)
手写算法-python代码实现逻辑回归(带L1、L2正则项)
这就是逻辑回归的假设函数,预测函数。
实际上就是在线性回归线的结果上,加上sigmoid函数。

0.5作为分类的边界:
当z >= 0的时候 g(z) >= 0.5,其中z为线性回归函数 z=X????
最终类别为1;

当z <= 0的时候g(z) <= 0.5,其中z为线性回归函数 z=X????
最终类别为0;

z = 0是临界点!!!

例如下图:
手写算法-python代码实现逻辑回归(带L1、L2正则项)
-3 + x1 + x2 = 0这条线,就是临界线。

手写算法-python代码实现逻辑回归(带L1、L2正则项)
其中 h(x) 的值,是样本属于1类别的概率值,
z = 0时,概率值为0.5;
z > 0时,概率值大于0.5;
z < 0时,概率值小于0.5;

问题:逻辑回归和回归有没有关系?
回答:有关系,对于二分类任务来说,我们对逻辑回归做一个变形,就会发现它本质上是对数几率回归,
金融评分卡就是根据这个公式映射的,所以说逻辑回归是一种广义线性回归。

手写算法-python代码实现逻辑回归(带L1、L2正则项)

损失函数定义以及数学公式推导过程

有了假设函数,我们开始定义逻辑回归的损失函数,这里继续提出一个问题,用我们常用的最小二乘法作为损失函数,可不可以?

从理论上讲,可以。但是这个时候
手写算法-python代码实现逻辑回归(带L1、L2正则项)
,就没有办法用凸优化算法求解。
我们选用对数损失函数作为损失函数,凸函数,好优化。

解释1:通俗易懂的手推损失函数:

手写算法-python代码实现逻辑回归(带L1、L2正则项)

解释2:最大似然估计求解参数

手写算法-python代码实现逻辑回归(带L1、L2正则项)

对损失函数推导梯度

手写算法-python代码实现逻辑回归(带L1、L2正则项)

python代码实现逻辑回归

class LogisticRegression:
    
    #默认没有正则化,正则项参数默认为1,学习率默认为0.001,迭代次数为10001次
    def __init__(self,penalty = None,Lambda = 1,a = 0.001,epochs = 10001):
        self.W = None
        self.penalty = penalty
        self.Lambda = Lambda
        self.a = a
        self.epochs =epochs
        self.sigmoid = lambda x:1/(1 + np.exp(-x))
        
    def loss(self,x,y):
        m=x.shape[0]
        y_pred = self.sigmoid(x * self.W)
        return (-1/m) * np.sum((np.multiply(y, np.log(y_pred)) + np.multiply((1-y),np.log(1-y_pred))))
    
    def fit(self,x,y):
        lossList = []
        #计算总数据量
        m = x.shape[0]
        #给x添加偏置项
        X = np.concatenate((np.ones((m,1)),x),axis = 1)
        #计算总特征数
        n = X.shape[1]
        #初始化W的值,要变成矩阵形式
        self.W = np.mat(np.ones((n,1)))
        #X转为矩阵形式
        xMat = np.mat(X)
        #y转为矩阵形式,这步非常重要,且要是m x 1的维度格式
        yMat = np.mat(y.reshape(-1,1))
        #循环epochs次
        for i in range(self.epochs):
            #预测值
            h = self.sigmoid(xMat * self.W)
            gradient = xMat.T * (h - yMat)/m
            
            
            #加入l1和l2正则项,和之前的线性回归正则化一样
            if self.penalty == 'l2':
                gradient = gradient + self.Lambda * self.W
            elif self.penalty == 'l1':
                gradient = gradient + self.Lambda * np.sign(self.W)
          
            self.W = self.W-self.a * gradient
            if i % 50 == 0:
                lossList.append(self.loss(xMat,yMat))
		#返回系数,和损失列表
        return self.W,lossList

实例展示

下面我们继续用sklearn生成数据集,来看看效果

from sklearn.datasets import make_classification
from matplotlib import pyplot as plt

#生成2特征分类数据集
x,y =make_classification(n_features=2,n_redundant=0,n_informative=1,n_clusters_per_class=1,random_state=2043)

#第一个特征作为x轴,第二个特征作为y轴
plt.scatter(x[:,0],x[:,1],c=y)
plt.show()

手写算法-python代码实现逻辑回归(带L1、L2正则项)
数据分布如上,现在用我们写好的逻辑回归来做分类:

#默认参数
lr = LogisticRegression()
w,lossList = lr.fit(x,y)

#前面讲过,z=0是线性分类临界线
# w[0]+ x*w[1] + y* w[2]=0,求解y (x,y其实就是x1,x2)
x_test = [[-1],[0.7]]
y_test = (-w[0]-x_test*w[1])/w[2] 

plt.scatter(x[:,0],x[:,1],c=y)
plt.plot(x_test,y_test)
plt.show()

手写算法-python代码实现逻辑回归(带L1、L2正则项)

损失图像:

#画图 loss值的变化
n = np.linspace(0,10000,201)
plt.plot(n,lossList,c='r')
plt.title('Train')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.show()

手写算法-python代码实现逻辑回归(带L1、L2正则项)
损失随着迭代次数的增加,一直在减小,但是,很明显,当前迭代次数,并没有使得模型参数收敛。

迭代50000次,来看效果:

lr = LogisticRegression(epochs=50000)
w,lossList = lr.fit(x,y)

#前面讲过,z=0是线性分类临界线
# w[0]+ x*w[1] + y* w[2]=0,求解y (x,y其实就是x1,x2)
x_test = [[-1],[0.7]]
y_test = (-w[0]-x_test*w[1])/w[2] 

plt.scatter(x[:,0],x[:,1],c=y)
plt.plot(x_test,y_test)
plt.show()

手写算法-python代码实现逻辑回归(带L1、L2正则项)

#画图 loss值的变化
n=np.linspace(0,50000,1000)
plt.plot(n,lossList,c='r')
plt.title('Train')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.show()

手写算法-python代码实现逻辑回归(带L1、L2正则项)

此时模型基本上已经收敛,输出模型参数,计算模型分类效果:

print('模型参数是:\n',w,'\n')

#这里感觉其实处理x,y不应该放在封装好的类里面处理,应该拿出来,作为全局变量使用,优化点
m = x.shape[0]
X = np.concatenate((np.ones((m,1)),x),axis = 1)
xMat = np.mat(X)
y_pred = [1 if x >= 0.5 else 0 for x in lr.sigmoid(xMat*w)]

from sklearn.metrics import classification_report
print(classification_report(y,y_pred))

手写算法-python代码实现逻辑回归(带L1、L2正则项)

准确率、召回率、F1的值如图所示,整体分类效果还行。

sklearn对比

接下来,我们调用sklearn的逻辑回归库,来分类数据集:

from sklearn.linear_model import LogisticRegression as LR
clf = LR(penalty='none') #查看系数可知,默认带L2正则化,且正则项参数C=1,这里的C是正则项倒数,越小惩罚越大,这里也不用正则化
clf.fit(x,y)
print('sklearn拟合的参数是:\n','系数:',clf.coef_,'\n','截距:',clf.intercept_)

y_pred_1 = clf.predict(x)
print('\n')
print(classification_report(y,y_pred_1))

手写算法-python代码实现逻辑回归(带L1、L2正则项)
系数比较接近,分类效果也差不多。

plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False #用来正常显示负号

y_test_1 = (-clf.intercept_ - clf.coef_[0][0] * np.array(x_test))/clf.coef_[0][1]

fig =plt.figure()
ax1= fig.add_subplot()
ax1.scatter(x[:,0],x[:,1],c=y,label='样本分布')
ax1.plot(x_test,y_test,c='r',label='python代码拟合')
ax1.plot(x_test,y_test_1,c='k',label='sklearn拟合')
ax1.legend(prop = {'size':10}) #此参数改变标签字号的大小
plt.show()

手写算法-python代码实现逻辑回归(带L1、L2正则项)
两条分类线基本上重合了。

L1、L2正则化作比较

上面的sklearn逻辑回归中,当clf = LR()即默认L2正则化,C=1时,两个类别F1的值都是0.9。
正则化的比较这里就不展示了,大家可以自行去测试一下,原理和线性回归的正则化原理一样,效果也差不多。
只不过我们自己写的python代码里面Lambda越大,惩罚越强;
而sklearn里面,C越小,惩罚越强。

总结

逻辑回归作为线性回归的变种,它的用途很广,因此掌握它的原理是很有必要的,打好线性回归(逻辑回归也是广义线性回归)的基础,以后对我们学习其他算法大有裨益。

问题:
1、逻辑回归怎么处理多分类问题;
2、应用逻辑回归算法,怎么做样本不均衡的二分类模型;
3、怎么使用正则化;

这里给上刘建平博士的博客链接,写的很精炼:
链接: scikit-learn 逻辑回归类库使用小结

以上问题我们这里就暂不展开讨论了,手写算法系列,我们专注算法的底层原理和数学推导、python代码实现,来帮助大家更好的理解这些算法;
应用层面的问题,我会在随笔栏目中,慢慢补上。

本文地址:https://blog.csdn.net/weixin_44700798/article/details/110848711