A--利用梯度下降求解逻辑回归的python实现

旧时模样 提交于 2019-12-05 05:31:37
#导入必要的包
import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline

BGD求解逻辑回归

In [2]:
#⾸先定义联系函数sigmoid函数
def sigmoid(inX):
    return 1.0/(1+np.exp(-inX))

In [7]:

#自定义一个归一化函数(此函数的前提是我的数据是一个矩阵)
def regularize(xMat):
    inMat = xMat.copy()#创建一个副本,这样对inmat进行操作不会影响到xmat
    inMeans = np.mean(inMat,axis = 0) #求均值
    inVar = np.std(inMat,axis = 0) #求标准差
    inMat = (inMat - inMeans)/inVar #归一化
    return inMat

 

In [4]:
#编写批量梯度下降的自定义函数
def logisticReg_0(dataSet,eps=0.01,numIt=50000):
    xMat = np.mat(dataSet.iloc[:, :-1].values)
    yMat = np.mat(dataSet.iloc[:, -1].values).T
    xMat = regularize(xMat)
    m,n = xMat.shape
    weights = np.zeros((n,1))
    for k in range(numIt): 
        grad = xMat.T * (sigmoid(xMat * weights) - yMat) / m#加入了sigmoid函数
        weights = weights - eps * grad
    return weights
 
In [37]:
testSet = pd.read_table('testSet.txt', header=None)
testSet.head()#一个二分类数据集
Out[37]:
 012
0 -0.017612 14.053064 0
1 -1.395634 4.662541 1
2 -0.752157 6.538620 0
3 -1.322371 7.152853 0
4 0.423363 11.054677 0
In [12]:
ws = logisticReg_0(testSet, eps=0.01,numIt=500)
ws
Out[12]:
matrix([[ 0.08644437],
        [-1.26921488]])
 

计算准确率

In [13]:
#提取Xmat与Ymat
xMat = np.mat(testSet.iloc[:, :-1].values)
yMat = np.mat(testSet.iloc[:, -1].values).T
xMat = regularize(xMat)#对xmat归一化,ymat本身就是o和1 所以不用归一化了
 
In [14]:
(xMat * ws).A.flatten()#求得方程本身并转换成array再转换成行向量
Out[14]:
array([-2.05688038,  0.41967771, -0.04776864, -0.25878106, -1.20071802,
       -0.1069381 , -1.64582994, -0.26468857, -0.77631505, -1.06195274,
       -0.03362803, -1.71456551,  1.02134785, -0.82706158,  0.18285287,
        1.4363693 ,  0.11249424,  1.06201058,  1.66712589,  0.29874123,
        1.06389991,  1.83112447, -1.4738391 ,  2.23899711,  0.91209097,
       -0.74142665, -0.7494692 ,  1.97772498,  0.85942228, -0.73277762,
        0.67949527, -0.43292764, -1.28750584,  2.36442777,  0.53266104,
       -0.79675144, -0.72426019, -0.95718379, -1.64652946, -1.39006337,
        0.86512364,  0.20447648, -1.07659337,  1.73862742,  1.13032652,
       -1.1851674 ,  2.34784817,  0.1944565 , -1.4408959 , -1.17870617,
       -0.57766565, -1.05843461, -0.43883063, -1.90571443,  0.53484842,
       -0.06615649, -1.165083  , -0.18341635, -1.43918646,  0.47298352,
        0.61004349,  1.30172164, -0.68923266, -1.58831196, -1.54353632,
        2.14613556,  0.21291662, -1.2877214 , -1.46745076, -1.27794798,
       -1.21807298,  1.57582052, -1.98372337,  1.00328841, -0.87300219,
       -0.20031198,  1.68812137,  1.30544075,  0.57776419,  0.53883724,
        0.02686256,  2.59762059, -0.8853089 , -0.2801372 ,  1.25846702,
        2.21790839,  0.68382909, -1.56049535,  0.43216125,  1.79315117,
        2.00679837, -1.54676186, -0.85772193,  1.55979868,  1.41807387,
        1.15193462, -1.07653949,  1.60327969, -0.65872133, -2.22041914])
In [15]:
#进⼀步乘以sigmoid函数之后得到的是y取得1的概率⼤⼩
p = sigmoid(xMat * ws).A.flatten()
p
 
Out[15]:
array([0.113359  , 0.60340613, 0.48806011, 0.43566337, 0.23134751,
       0.47329092, 0.16167334, 0.4342115 , 0.31511462, 0.25693646,
       0.49159379, 0.15257249, 0.73523506, 0.30426674, 0.54558627,
       0.80789179, 0.52809394, 0.74307458, 0.84119225, 0.57413477,
       0.74343512, 0.86189563, 0.18635979, 0.90369721, 0.71342785,
       0.32269225, 0.32093697, 0.87843843, 0.70253994, 0.32458549,
       0.66362604, 0.39342746, 0.21627527, 0.91407421, 0.63010354,
       0.31072085, 0.32645555, 0.2774424 , 0.16157856, 0.19939764,
       0.70373002, 0.55094175, 0.25415123, 0.85051264, 0.75589915,
       0.23412436, 0.91276304, 0.54846151, 0.19140665, 0.23528491,
       0.3594699 , 0.25760872, 0.39201964, 0.12946308, 0.63061322,
       0.48346691, 0.23774491, 0.45427403, 0.19167136, 0.61608967,
       0.64795072, 0.78612459, 0.33420379, 0.16962153, 0.17602179,
       0.89530711, 0.55302897, 0.21623874, 0.18733039, 0.21789973,
       0.22827575, 0.82861179, 0.12092248, 0.73170463, 0.29462999,
       0.45008878, 0.84397694, 0.78674923, 0.64055278, 0.63154189,
       0.50671524, 0.93070829, 0.29207885, 0.43042014, 0.7787621 ,
       0.9018462 , 0.66459277, 0.17357558, 0.60638964, 0.85731319,
       0.88150902, 0.17555445, 0.29781552, 0.82632446, 0.80503628,
       0.75986411, 0.25416144, 0.83247627, 0.3410269 , 0.09793177])
 

enumerate(p) 函数:返回一个索引和值组成二元可迭代对象

In [16]:
 
#我们可令p>0.5时预测输出值为1,反之为0,得到最终y值预测结果
for i, j in enumerate(p):
    if j < 0.5: 
        p[i] = 0
    else:
        p[i] = 1
In [17]:
p
Out[17]:
array([0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 1., 1., 1.,
       1., 1., 1., 1., 1., 0., 1., 1., 0., 0., 1., 1., 0., 1., 0., 0., 1.,
       1., 0., 0., 0., 0., 0., 1., 1., 0., 1., 1., 0., 1., 1., 0., 0., 0.,
       0., 0., 0., 1., 0., 0., 0., 0., 1., 1., 1., 0., 0., 0., 1., 1., 0.,
       0., 0., 0., 1., 0., 1., 0., 0., 1., 1., 1., 1., 1., 1., 0., 0., 1.,
       1., 1., 0., 1., 1., 1., 0., 0., 1., 1., 1., 0., 1., 0., 0.])
In [18]:
testSet[3]=p
testSet

 . .

In [19]:
#首先封装一个准确率判断模型
def accuracyCalculation(dataSet):
    m = dataSet.shape[0]
    #如果后一列数据=前一列数据,对返回为True的行计数
    res = (dataSet.iloc[:, -1] == dataSet.iloc[:, -2]).value_counts()
    acc = res.loc[True] / m
    print("Model accuracy is :{}".format(acc) )
    return acc
 
In [20]:
accuracyCalculation(testSet) #准确率%92
Model accuracy is :0.92
Out[20]:
0.92
In [21]:
train_error = (np.fabs(yMat.A.flatten() - p)).sum()
train_error  #判断错误个数又8个
Out[21]:
8.0
In [22]:
train_error_rate = train_error / yMat.shape[0]
train_error_rate  #相对的 错误率为8
Out[22]:
0.08
In [24]:
#参数分别为  数据集   模型  学习率   迭代次数
def logisticAcc(dataSet, method, eps=0.01, numIt=50000):
    weights = method(dataSet,eps=eps,numIt=numIt) 
    p = sigmoid(xMat * ws).A.flatten()
    for i, j in enumerate(p):
        if j < 0.5: 
            p[i] = 0
        else:p[i] = 1
    train_error = (np.fabs(yMat.A.flatten() - p)).sum()
    trainAcc = 1 - train_error / yMat.shape[0]
    return trainAcc
 
In [26]:
import time
%time logisticAcc(testSet,logisticReg_0)
Wall time: 6 s
Out[26]:
0.92
 

SGD求解逻辑回归

In [32]:
def logisticReg_1(dataSet,eps=0.01,numIt=50000):
    dataSet = dataSet.sample(numIt, replace=True)
    dataSet.index = range(dataSet.shape[0])
    xMat = np.mat(dataSet.iloc[:, :-1].values)
    yMat = np.mat(dataSet.iloc[:, -1].values).T
    xMat = regularize(xMat) 
    m,n = xMat.shape
    weights = np.zeros((n,1))
    for i in range(m): 
        grad = xMat[i].T * (sigmoid(xMat[i] * weights) - yMat[i])
        weights = weights - eps * grad
    return weights
In [38]:
logisticReg_1(testSet)

Out[38]:

matrix([[ 1.31302158],
        [-6.52145044]])
In [39]:
%time logisticAcc(testSet,logisticReg_1)
Wall time: 7.46 s
Out[39]:
0.92
逻辑回归的Scikit-Learn实现
In [40]:
from sklearn.linear_model import LogisticRegression
clf = LogisticRegression()
clf.fit(testSet.iloc[:, :-1], testSet.iloc[:, -1])

Out[40]:

LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
          penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False)
In [41]:
clf.coef_
Out[41]:
array([[ 0.44732445, -0.58003724]])
In [42]:
clf.intercept_
Out[42]:
array([3.83513265])
In [43]:
clf.predict(testSet.iloc[:, :-1])
Out[43]:
array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
       0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1,
       1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1,
       1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0,
       1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0], dtype=int64)
In [44]:
from sklearn.metrics import accuracy_score
accuracy_score(testSet.iloc[:, -1], clf.predict(testSet.iloc[:, :-1]))
Out[44]:
0.96
 

注:梯度下降不适合求解回归方程,因为方程系数对线性回归是很重要的,我们可以根据系数直观的连接线性回归

1.参数讲解

 

1.1 概述

 

在scikit-learn中,与逻辑回归有关的常用的有2类。

LogisticRegression, LogisticRegressionCV,其中LogisticRegression和LogisticRegressionCV的主要区别是 LogisticRegressionCV使⽤了交叉验证来选择正则化系数C。⽽LogisticRegression需要⾃⼰每次指定 ⼀个正则化系数。除了交叉验证,以及选择正则化系数C以外, LogisticRegression和LogisticRegressionCV的使⽤⽅法基本相同。

 

1.2 参数详解

 

正则化参数penalty

 

LogisticRegression和LogisticRegressionCV默认就带了正则化项。penalty参数可选择的值

为"I1"和"I2".分别对应L1的正则化和L2的正则化,默认是L2的正则化。 在调参时如果我们主要的⽬的只是为了解决过拟合,⼀般penalty选择L2正则化就够了。但是如 果选择L2正则化发现还是过拟合,即预测效果差的时候,就可以考虑L1正则化。另外,如果模型 的特征⾮常多,我们希望⼀些不重要的特征系数归零,从⽽让模型系数稀疏化的话,也可以使⽤ L1正则化。 penalty参数的选择会影响我们损失函数优化算法的选择。即参数solver的选择,如果是L2正则 化,那么4种可选的算法{‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’}都可以选择。但是如果penalty是L1 正则化的话,就只能选择‘liblinear’了。这是因为L1正则化的损失函数不是连续可导的,⽽ {‘newton-cg’, ‘lbfgs’,‘sag’}这三种优化算法时都需要损失函数的⼀阶或者⼆阶连续导数。 ⽽‘liblinear’并没有这个依赖。

 

算法优化参数solver

 

solver参数决定了我们对逻辑回归损失函数的优化⽅法,有4种算法可以选择,

分别是: liblinear:使⽤了开源的liblinear库实现,内部使⽤了坐标轴下降法来迭代优化损失函数。 lbfgs:拟⽜顿法的⼀种,利⽤损失函数⼆阶导数矩阵即海森矩阵来迭代优化损失函数。 newton-cg:也是⽜顿法家族的⼀种,利⽤损失函数⼆阶导数矩阵即海森矩阵来迭代优化损失函 数。 sag:即随机平均梯度下降,是梯度下降法的变种,和普通梯度下降法的区别是每次迭代仅仅⽤ ⼀部分的样本来计算梯度,适合于样本数据多的时候。 从上⾯的描述可以看出,newton-cg, lbfgs和sag这三种优化算法时都需要损失函数的⼀阶或者 ⼆阶连续导数,因此不能⽤于没有连续导数的L1正则化,只能⽤于L2正则化。⽽liblinear通吃L1 正则化和L2正则化。 同时,sag每次仅仅使⽤了部分样本进⾏梯度迭代,所以当样本量少的时候不要选择它,⽽如果 样本量⾮常⼤,⽐如⼤于10万,sag是第⼀选择。但是sag不能⽤于L1正则化,所以当你有⼤量 的样本,⼜需要L1正则化的话就要⾃⼰做取舍了。要么通过对样本采样来降低样本量,要么回到 L2正则化。 从上⾯的描述,⼤家可能觉得,既然newton-cg, lbfgs和sag这么多限制,如果不是⼤样本,我们 选择liblinear不就⾏了嘛!错,因为liblinear也有⾃⼰的弱点!我们知道,逻辑回归有⼆元逻辑 回归和多元逻辑回归。对于多元逻辑回归常⻅的有one-vs-rest(OvR)和many-vs-many(MvM)两 种。⽽MvM⼀般⽐OvR分类相对准确⼀些。郁闷的是liblinear只⽀持OvR,不⽀持MvM,这样如 果我们需要相对精确的多元逻辑回归时,就不能选择liblinear了。也意味着如果我们需要相对精 确的多元逻辑回归不能使⽤L1正则化了。

 

分类⽅式选择参数

 

multi_class参数决定了我们分类⽅式的选择,有 ovr和multinomial两个值可以选择,默认是 ovr。

ovr即前⾯提到的one-vs-rest(OvR),⽽multinomial即前⾯提到的many-vs-many(MvM)。如果 是⼆元逻辑回归,ovr和multinomial并没有任何区别,区别主要在多元逻辑回归上。 OvR的思想很简单,⽆论你是多少元逻辑回归,我们都可以看做⼆元逻辑回归。具体做法是,对 于第K类的分类决策,我们把所有第K类的样本作为正例,除了第K类样本以外的所有样本都作为 负例,然后在上⾯做⼆元逻辑回归,得到第K类的分类模型。其他类的分类模型获得以此类推。 ⽽MvM则相对复杂,这⾥举MvM的特例one-vs-one(OvO)作讲解。如果模型有T类,我们每次在 所有的T类样本⾥⾯选择两类样本出来,不妨记为T1类和T2类,把所有的输出为T1和T2的样本放 在⼀起,把T1作为正例,T2作为负例,进⾏⼆元逻辑回归,得到模型参数。我们⼀共需要T(T- 1)/2次分类。 从上⾯的描述可以看出OvR相对简单,但分类效果相对略差(这⾥指⼤多数样本分布情况,某些 样本分布下OvR可能更好)。⽽MvM分类相对精确,但是分类速度没有OvR快。 如果选择了ovr,则4种损失函数的优化⽅法liblinear,newton-cg, lbfgs和sag都可以选择。但是 如果选择了multinomial,则只能选择newton-cg, lbfgs和sag了。

 

分类权重参数

 

class_weight参数⽤于标示分类模型中各种类型的权重,

可以不输⼊,即不考虑权重,或者说所 有类型的权重⼀样。如果选择输⼊的话,可以选择balanced让类库⾃⼰计算类型权重,或者我 们⾃⼰输⼊各个类型的权重,⽐如对于0,1的⼆元模型,我们可以定义class_weight={0:0.9, 1:0.1},这样类型0的权重为90%,⽽类型1的权重为10%。 如果class_weight选择balanced,那么类库会根据训练样本量来计算权重。某种类型样本量越 多,则权重越低,样本量越少,则权重越⾼。 那么class_weight有什么作⽤呢?在分类模型中,我们经常会遇到两类问题: 第⼀种是误分类的代价很⾼。⽐如对合法⽤户和⾮法⽤户进⾏分类,将⾮法⽤户分类为合法⽤户 的代价很⾼,我们宁愿将合法⽤户分类为⾮法⽤户,这时可以⼈⼯再甄别,但是却不愿将⾮法⽤ 户分类为合法⽤户。这时,我们可以适当提⾼⾮法⽤户的权重。 第⼆种是样本是⾼度失衡的,⽐如我们有合法⽤户和⾮法⽤户的⼆元样本数据10000条,⾥⾯合 法⽤户有9995条,⾮法⽤户只有5条,如果我们不考虑权重,则我们可以将所有的测试集都预测 为合法⽤户,这样预测准确率理论上有99.95%,但是却没有任何意义。这时,我们可以选择 balanced,让类库⾃动提⾼⾮法⽤户样本的权重。 提⾼了某种分类的权重,相⽐不考虑权重,会有更多的样本分类划分到⾼权重的类别,从⽽可以 解决上⾯两类问题。

 

样本权重参数

 

之前我们提到了样本不失衡的问题,由于样本不平衡,导致样本不是总体样本的⽆偏估计.

从⽽ 可能导致我们的模型预测能⼒下降。遇到这种情况,我们可以通过调节样本权重来尝试解决这个 问题。调节样本权重的⽅法有两种,第⼀种是在class_weight使⽤balanced。第⼆种是在调⽤fit 函数时,通过sample_weight来⾃⼰调节每个样本权重。 在scikit-learn做逻辑回归时,如果上 ⾯两种⽅法都⽤到了,那么样本的真正权重是class_weight*sample_weight.

 
 
 
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!