逻辑回归,原理及代码实现

Ⅰ.逻辑回归概述:
逻辑回归(LR,Logistic Regression)是传统机器学习中的一种分类模型,它属于一种在线学习算法,可以利用新的数据对各个特征的权重进行更新,而不需要重新利用历史数据训练。因此在实际开发中,一般针对该类任务首先都会构建一个基于LR的模型作为Baseline Model,实现快速上线,然后在此基础上结合后续业务与数据的演进,不断的优化改进。
由于LR算法具有简单、高效、易于并行且在线学习(动态扩展)的特点,在工业界具有非常广泛的应用。例如:评论信息正负情感分析(二分类)、用户点击率(二分类)、用户违约信息预测(二分类)、用户等级分类(多分类 )等场景。
Ⅱ.本次作业主要使用二元逻辑回归进行线性分类:
1.封装一个逻辑回归的类,用的损失函数是交叉熵。
2.以iris数据集为数据来源(需要处理),利用定义的train_test_split函数,将train和test进行分割,其中测试集占20%,训练集占80%。
3.实现逻辑回归,首先实例化一个类,然后通过训练集训练得到线性函数参数θ,画出决策边界,最后对测试集进行预测。
4.总结,根据本次实验和资料查找分析逻辑回归的优缺点。
注:由于逻辑回归在自然语言处理中有极大的运用,因此本次实验使用传统的逻辑回归(即二元逻辑回归)进行线性分类。

1.封装Logistics模型

注:构建的是二元逻辑回归类,适用于二维数据的线性分类
In [1]:
import numpy as np
from math import sqrt
class LogisticRegression:
    def __init__(self):
        """初始化logisticRegression模型"""
        self.coef_=None#系数
        self.intercept=None#系数
        self._theta=None# θ[θ0,θ1,θ2]参数
    def _sigmoid(self,t):
        return 1./(1.+np.exp(-t))#Sigmoid函数的输出表征了当前样本标签为1的概率;
    def fit(self ,X_train,y_train,eta=0.01,n_iters=1e4):#eta学习率,1e4是指1*10的4次方;
        assert X_train.shape[0]==y_train.shape[0]#assert函数可以在条件不满足程序运行的情况下直接返回错误,而
不必等待程序运行后出现崩溃的情况。
        def J(theta,X_b,y):#定义交叉熵损失函数,theta是θ参数,也是最后要优化的参数,X_b由多行[1,X1,X2]组成,y是实际值;
            y_hat=self._sigmoid(X_b.dot(theta))#首先X_b.dot(θ),然后sigmoid,得到线性判断边界即:
hθ(x)=sigmoid(θ0+θ1X1+θ2X2),其中hθ(x)就是预测值y_hat
            try : 
                return -np.sum(y*np.log(y_hat)+(1-y)*np.log(1-y_hat))/len(y)#这里交叉熵没有添加正则化项
            except:
                return float("inf")#返回无穷
        def dJ(theta,X_b,y):#根据交叉熵损失函数,对θ求导
            return X_b.T.dot(self._sigmoid(X_b.dot(theta))-y)/len(y)
        def gradient_descent(X_b,y,initial_theta,eta,n_iters=1e4,epsilon=1e-8): #梯度下降
         """四个超参数:eta学习率,theta_initial是θ初始值,epsilon差距判断值是为了判断梯度是否为0(梯度不可能为0或者说很难找到,所以用epsilon=1e-8表示交叉熵值变化幅度替代梯度为0),n_iters迭代下降次数"""
            theta=initial_theta
            cur_iter = 0#cur_iter用来计量迭代次数
            while cur_iter < n_iters: #开始迭代
                gradient = dJ(theta, X_b, y) #梯度等于交叉熵求导值
                last_theta = theta #上一个参数θ值
                theta = theta - eta * gradient#更换参数
                if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):#如果交叉熵变化幅度足够小于epsilon,就得到最优的参数θ,停止循环。
                    break
                cur_iter += 1#迭代次数加1
            return theta#返回的θ,就是需要求的最优参数
        X_b = np.hstack([np.ones((len(X_train), 1)), X_train])# np.hstack()进行的是列操作,将行数与X相同的一维全是1的数组与X矩阵进行列拼接,即:[1i,X1i,X2i]。
        initial_theta = np.zeros(X_b.shape[1])#初始化θ,构造出一组长度与x_b行数一样,全是零的数组
        self._theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters) #调用gradient_descent梯度下降函数
        self.intercept_ = self._theta[0]#得到θ0 
        self.coef_ = self._theta[1:]#得到θ1和θ2
        return self#返回的是它自己,可以连续调用它的方法
    def predict_proba(self, X_predict):
        """给定待预测数据集X_predict,返回表示X_predict的结果概率向量"""
        assert self.intercept_ is not None and self.coef_ is not None#θ0、θ1、θ2不为空;
        assert X_predict.shape[1] == len(self.coef_)#输入的X矩阵列数要与参数长度相同
        X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
        return self._sigmoid(X_b.dot(self._theta)) #经过sigmoid函数,输出的就是(0,1)之间的概率
    def predict(self, X_predict):
        """给定待预测数据集X_predict,返回表示X_predict的结果向量"""
        assert self.intercept_ is not None and self.coef_ is not None#θ0、θ1、θ2不为空;
        assert X_predict.shape[1] == len(self.coef_)#输入的X矩阵列数要与参数长度相同
        proba = self.predict_proba(X_predict) #调用predict_proba()函数
        return np.array(proba >= 0.5, dtype='int')#大于0.5就是1,反之是0,相当于分成0,1两类
    def accuracy_score(self, X,y):
        """根据待测数据集 X 、y和y_predict确定当前模型的准确度"""
        y_predict = self.predict(X) #预测出y_predict 
        assert len(y) == len(y_predict) #判断正确标签y与预测标签y_predict是否相同
        score=np.sum(y_test == y_predict) / len(y) #计算准确率
        return score#返回准确率
    def __repr__(self): #返回显示类的自定义属性信息
        return "LogisticRegression()" 

2.实现逻辑回归

2.1处理数据集

In [2]:
import matplotlib.pyplot as plt
from sklearn import datasets
iris = datasets.load_iris()#iris数据集是用来给花做分类的数据集,iris包含150个样本,对应着数据集的每行数据;
                      #每行数据包含每个样本的四个特征和样本的类别信息。
X = iris.data#X是(150,4)矩阵
y = iris.target#y是(150,)矩阵,其中值只有0,1,2;iris数据集是对三种花分类,所以有三种标签就可以。
"""******************这里用iris数据集,主要是利用数据集的数据,拼凑出符合实验的数据,个人认为这并不具有具体意义,但强求的说(通过两种特征进行预测标签是0或1的花品种)****************"""
X = X[y<2,:2] #计算出X中符合y<2条件的数量为100,则将X矩阵中满足条件的100行提取,然后切前两列的数据。
y = y[y<2]#计算出在y中符合y<2的数量,为100;使y与X数据保持同步。
plt.scatter(X[y==0,0],X[y==0,1],color="r")#横坐标:X[y==0,0]的行满足y=0条件,列是X第一列的所有数据Xi,
                                    #纵坐标:X[y==0,1]的行满足y=0条件,列是X第二列的所有数据y,将其得到的(Xi,y)点归为一类
plt.scatter(X[y==1,0],X[y==1,1],color="g")#横坐标:X[y==1,0]的行满足y=1条件,列是X的第一列的所有数据Xi,
                                    #纵坐标:X[y==1,1]的行满足y=1条件,列是X第二列的所有数据y,将其得到的(Xi,y)点归为另一类

2.2使用逻辑回归

In [3]:
def train_test_split(X,y,test_radio=0.2,seed=None):
“”“定义分割数据集函数,测试集占0.2,训练集占0.8;需要把数据要打乱,因为训练集可能是整理好的有序的;有可能在多次打乱情况下,还要指定的部分训练集, 所以需要numpy中的随机seed, 默认是不指定随机seed的”“”
    assert X.shape[0]==y.shape[0]
    assert 0.0<=test_radio<=1.0
    if seed:                # 是否使用随机种子,使随机结果相同,方便debug
        np.random.seed(seed)#seed( ) 用于指定随机数生成时所用算法开始的整数值。
    shuffled_indexes=np.random.permutation(len(X))#permutation()用来随机排列一个数组的。
    test_size=int(len(X)*test_radio)#测试集的大小
    test_indexes=shuffled_indexes[:test_size]#测试集的随机索引
    train_indexes=shuffled_indexes[test_size:]#训练集的随机索引,注意:切片的位置
    X_train=X[train_indexes] #train_indexes是个矩阵,
    """例子:若a=numpy.array[2,3,4,5],在python中不能出现a[[1,2,3]],会错误。而可以b=[1,2,3],a=[b],这返回是[3,4,5],是指b对应的是a的位置 """
    y_train=y[train_indexes]  #训练集的正确标签
    X_test=X[test_indexes] #得到的是测试集和训练集的具体矩阵
    y_test=y[test_indexes] #测试集的正确标签
    return X_test,X_train,y_train,y_test     

X_test,X_train,y_train,y_test=train_test_split(X,y,seed=666)#读入数据集
log_reg=LogisticRegression()#实例化
log_reg.fit(X_train,y_train)#训练集训练参数
Out[3]:
LogisticRegression()

2.3.绘图

In [4]:
def x2(x1):#已知log_reg.coef_和log_reg.intercept_参数(θ参数),可以定义逻辑回归的决策边界函数。
    return (-log_reg.coef_[0] * x1 - log_reg.intercept_) / log_reg.coef_[1]
x1_plot = np.linspace(4, 8, 1000)#将4到8分成1000个数据,赋值给x1_plot。
x2_plot = x2(x1_plot)#将x1_plot的数据,带入x2函数中,得出1000个x2_plot数据。
"""数据集被决策边界分成两类的情况"""
plt.scatter(X[y==0,0], X[y==0,1], color="r")#读入数据集y=0标签的离散点
plt.scatter(X[y==1,0], X[y==1,1], color="g")#读入数据集y=1标签的离散点
plt.xlabel("X1",color="b",size=12)#x轴上的名字
plt.ylabel("X2",color="b",size=12)#y轴上的名字
plt.title("train_test",size=18) #标题
plt.annotate("θ0+θ1X1+θ2X2=0",xy=(7,4),xytext=(+30,-30),textcoords='offset points',fontsize=16,
             arrowprops=dict(arrowstyle='->',connectionstyle='arc3,rad=.2')) #添加标记
plt.legend(['label:0','label:1'],loc="best") #标签
plt.plot(x1_plot, x2_plot) #绘画线性方程
plt.show()
"""测试集的数据被决策边界分成两类的情况"""
plt.scatter(X_test[y_test==0,0], X_test[y_test==0,1], color="r")#读入测试集y=0标签的离散点
plt.scatter(X_test[y_test==1,0], X_test[y_test==1,1], color="g")#读入测试集y=0标签的离散点
plt.xlabel("X1",color="b",size=12) #x轴上的名字
plt.ylabel("X2",color="b",size=12) #y轴上的名字
plt.title("test",size=18) #标题
plt.annotate("θ0+θ1X1+θ2X2=0",xy=(7,4),xytext=(+30,-30),textcoords='offset points',fontsize=16,
             arrowprops=dict(arrowstyle='->',connectionstyle='arc3,rad=.2')) #添加标记
plt.legend(['label:0','label:1'],loc="best") #标签;红色是标签0,绿色是标签1。
plt.plot(x1_plot, x2_plot) #绘画线性方程
plt.show()
Out[4]:

3.实验结果

In [5]:
a=log_reg.accuracy_score(X_test,y_test) #测试集准确率
b=log_reg.accuracy_score(X_train,y_train) #训练集准确率
c=log_reg.predict_proba(X_test)#预测测试集每个数据的y值的概率
d=log_reg.predict(X_test)#预测测试集每个数据的y值
e=y_test#测试集正确标签y值
print(" 测试集准确率:",a,"\n","训练集准确率:",b,"\n","预测值y概率:",c,"\n 预测值y:",d,"\n","真实值y:",e)      
 Out[5]:
测试集准确率: 1.0 
 训练集准确率: 0.9875 
 预测值y概率: [0.92972035 0.98664939 0.14852024 0.01685947 0.0369836  0.0186637
 0.04936918 0.99669244 0.97993941 0.74524655 0.04473194 0.00339285 0.26131273 
0.0369836  0.84192923 0.79892262 0.82890209 0.32358166 0.06535323 0.20735334] 
 预测值y: [1 1 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 0 0 0] 
 真实值y: [1 1 0 0 0 0 0 1 1 1 0 0 0 0 1 1 1 0 0 0]

4.总结:

以下结合本次实验以及资料查找:
逻辑回归优点:
模型:模型清晰,背后的概率推导经得住推敲。
输出:输出值自然地落在0到1之间,并且有概率意义 
参数:参数代表每个特征对输出的影响,可解释性强。
可扩展:可以使用online learning的方式更新轻松更新参数,不需要重新训练整个模型。
过拟合:解决过拟合的方法很多,如L1、L2正则化。
多重共线性:L2正则化就可以解决多重共线性问题。
逻辑回归缺点:
特征相关情况:因为它本质上是一个线性的分类器,所以处理不好特征之间相关的情况。
特征空间:特征空间很大时,性能不好。
精度:容易欠拟合,与其它分类算法(例如:SVM等等)相比,精度不高。
上一篇:【lssvm预测】基于飞蛾扑火算法改进的最小二乘支持向量机lssvm预测


下一篇:sklearn学习总结