逻辑回归算法详解(附带实例,Python实现)
逻辑回归是机器学习中的一种分类算法,虽然名字中带有回归,但是它只是与回归之间有一定的联系。由于算法的简单和高效,在实际中应用非常广泛。
如果数据是有两个指标,可以用平面的点来表示数据,其中一个指标为 x 轴,另一个为 y 轴;如果数据有三个指标,可以用空间中的点表示数据;如果是 p 维(p>3),就是 p 维空间中的点。
从本质上来说,逻辑回归训练后的模型是平面的一条直线(p=2)或平面(p=3)或超平面(p>3)。并且这条线或平面把空间中的散点分成两半,属于同一类的数据大多数分布在曲线或平面的同一侧,如下图所示。

图 1 逻辑回归训练后的模型
如图 1 所示,其中点的个数是样本个数,两种颜色代表两种指标。直线可以看成经这些样本训练后得出的划分样本的直线,对于之后的样本的 p1 与 p2 的值,可以根据这条直线判断它属于哪一类。
这个函数就是 sigmoid 函数,形式为:
此处可以设函数为:
其中,xi 为测试集第 i 个数据,是 p 维列向量:
w 是 p 列向量 w=(w1w2…wp)T,为待求参数;b 是一个数,也是待求参数。
用以下 Python 代码实现 sigmoid 函数的图像绘制:

图 5 sigmoid曲线
对于样本 i,其类别为 yi∈(0, 1)。对于样本 i,可以把 h(xi) 看成是一种概率。yi 对应 1 时,概率是 h(xi),即 xi 属于 1 的可能性;yi 对应 0 时,概率是 1-h(xi),即 xi 属于 0 的可能性。构造极大似然函数:
其中,i 从 1~k 属于类别 1 的个数为 k;i 从 k+1~n 属于类别 0 的个数为 n-k。由于 y 是标签 0 或 1 ,所以上面的式子可写成:
这样不管 y 是 0 还是 1,其中始终有一项会变成 0 次方,也就是 1,和第一个式子是等价的。
为了方便,对式子取对数。因为是求式子的最大值,可以转换成式子乘以 -1,之后求最小值。同时对于 n 个数据,累加后值会很大,之后如果用梯度下降容易导致梯度爆炸。所以可以除以样本总数 n。
求最小值方法很多,机器学习中常用梯度下降系列方法,也可以采用牛顿法或求导数为零时 w 的数值等。
上式是比较直观的,即让预测函数 h(xi) 与实际的分类 1 或 0 越接近越好,也就是损失函数越小越好。
【实例 1】逻辑回归的实现。
【实例 2】利用 Python 中 sklearn 包进行逻辑回归分析。
1) 提出问题,根据已有数据探究“学习时长”与“是否通过考虑”之间关系建立预测模型。
2) 理解数据:
① 导入包和数据:
② 查看数据:
③ 绘制散点图查看数据分布情况:
从图中可以看出,当学习时间高于某一阈值时,一般都能够通过考试,下面利用逻辑回归方法建立模型。
3) 构建模型。
① 拆分训练集并利用散点图观察,效果如下图所示:

图 11 拆分训练集
② 导入模型:
4) 模型评估:
①模型评分(即准确率):
② 发指定某个点的预测情况:
③ 求出逻辑回归函数并绘制曲线:

图 12 逻辑回归曲线
④ 得到模型混淆矩阵:
⑤ 绘制模型 ROC 曲线,效果如图 13 所示。

图 13 ROC曲线
逻辑回归所处理的数据
逻辑回归是用来进行分类的。例如,我们给出一个人的[身高,体重]这两个指标,然后判断这个人是属于“胖”还是“瘦”。对于这个问题,可以先测量 n 个人的身高、体重以及对应的指标“胖”“瘦”,分别用 0 和 1 来表示“胖”和“瘦”,把这 n 组数据输入模型进行训练。训练之后再把待分类的一个人的身高、体重输入模型中,看这个人是属于“胖”还是“瘦”。如果数据是有两个指标,可以用平面的点来表示数据,其中一个指标为 x 轴,另一个为 y 轴;如果数据有三个指标,可以用空间中的点表示数据;如果是 p 维(p>3),就是 p 维空间中的点。
从本质上来说,逻辑回归训练后的模型是平面的一条直线(p=2)或平面(p=3)或超平面(p>3)。并且这条线或平面把空间中的散点分成两半,属于同一类的数据大多数分布在曲线或平面的同一侧,如下图所示。

图 1 逻辑回归训练后的模型
如图 1 所示,其中点的个数是样本个数,两种颜色代表两种指标。直线可以看成经这些样本训练后得出的划分样本的直线,对于之后的样本的 p1 与 p2 的值,可以根据这条直线判断它属于哪一类。
逻辑回归算法原理
首先处理二分类问题。由于分成两类,设其中一类标签为 0,另一类为 1。我们需要一个函数,对于输入的每一组数据,都能映射成 0~1 的数。并且如果函数值大于 0.5,就判定属于 1,否则属于 0。而且函数中需要待定参数,通过利用样本训练,使得这个参数能够对训练集中的数据有很准确的预测。这个函数就是 sigmoid 函数,形式为:

此处可以设函数为:

其中,xi 为测试集第 i 个数据,是 p 维列向量:

w 是 p 列向量 w=(w1w2…wp)T,为待求参数;b 是一个数,也是待求参数。
用以下 Python 代码实现 sigmoid 函数的图像绘制:
import matplotlib.pyplot as plt
import numpy as np
def sigmoid(z):
return 1.0/(1.0 + np.exp(-z))
z = np.arange(-10,10,0.1)
p = sigmoid(z)
plt.plot(z,p)
#画一条竖直线,如果不设定x的值,则默认是0
plt.axvline(x = 0,color = 'k')
plt.axhspan(0.0,1.0,facecolor = '0.7',alpha = 0.4)
#画一条水平线,如果不设定y的值,则默认是0
plt.axhline(y = 1,ls = 'dotted',color = '0.4')
plt.axhline(y = 0,ls = 'dotted',color = '0.4')
plt.axhline(y = 0.5,ls = 'dotted',color = 'k')
plt.ylim(-0.1,1.1)
#确定y轴的坐标
plt.yticks([0.0,0.5,1.0])
plt.ylabel('$\phi(z)$')
plt.xlabel('z')
ax = plt.gca()
ax.grid(True)
plt.show()
运行程序,效果如下图所示:
图 5 sigmoid曲线
逻辑回归求解参数
下面通过介绍两种方法求解逻辑回归的参数。1) 极大似然估计
极大似然估计是数理统计中参数估计的一种重要方法。其思想是如果一个事件发生了,那么发生这个事件的概率就是最大的。对于样本 i,其类别为 yi∈(0, 1)。对于样本 i,可以把 h(xi) 看成是一种概率。yi 对应 1 时,概率是 h(xi),即 xi 属于 1 的可能性;yi 对应 0 时,概率是 1-h(xi),即 xi 属于 0 的可能性。构造极大似然函数:

其中,i 从 1~k 属于类别 1 的个数为 k;i 从 k+1~n 属于类别 0 的个数为 n-k。由于 y 是标签 0 或 1 ,所以上面的式子可写成:

这样不管 y 是 0 还是 1,其中始终有一项会变成 0 次方,也就是 1,和第一个式子是等价的。
为了方便,对式子取对数。因为是求式子的最大值,可以转换成式子乘以 -1,之后求最小值。同时对于 n 个数据,累加后值会很大,之后如果用梯度下降容易导致梯度爆炸。所以可以除以样本总数 n。

求最小值方法很多,机器学习中常用梯度下降系列方法,也可以采用牛顿法或求导数为零时 w 的数值等。
2) 损失函数
逻辑回归中常用交叉熵损失函数。交叉熵损失函数和上面极大似然法得到的损失函数是相同的,这里不再赘述。另一种也可以采用平方损失函数(均方误差),即:
上式是比较直观的,即让预测函数 h(xi) 与实际的分类 1 或 0 越接近越好,也就是损失函数越小越好。
逻辑回归实战
前面已对逻辑回归所处理的数据、算法原理、求解参数等进行了介绍,下面通过两个实例演示如何利用 Python 实现逻辑回归。【实例 1】逻辑回归的实现。
# coding: utf-8
import numpy as np
import math
from sklearn import datasets
from collections import Counter
infinity = float(-2**31)
def sigmoidFormatrix(Xb, thetas):
params = - Xb.dot(thetas)
r = np.zeros(params.shape[0]) # 返回一个 np 数组
for i in range(len(r)):
r[i] = 1 / (1 + math.exp(params[i]))
return r
def sigmoidFormatrix2(Xb, thetas):
params = - Xb.dot(thetas)
r = np.zeros(params.shape[0]) # 返回一个 np 数组
for i in range(len(r)):
r[i] = 1 / (1 + math.exp(params[i]))
if r[i] >= 0.5:
r[i] = 1
else:
r[i] = 0
return r
def sigmoid(Xi, thetas):
params = - np.sum(Xi * thetas)
r = 1 / (1 + math.exp(params))
return r
class LinearLogisticRegression(object):
thetas = None
m = 0
# 训练
def fit(self, X, y, alpha = 0.01, accuracy = 0.00001):
# 插入第一列为1,构成Xb矩阵
self.thetas = np.full((X.shape[1]+1, 0.5))
self.m = X.shape[0]
a = np.full((self.m,1),1)
Xb = np.column_stack((a,X))
dimension = X.shape[1] + 1
# 梯度下降迭代
count = 1
while True:
oldJ = self.costFunc(Xb, y)
c = sigmoidFormatrix(Xb, self.thetas) - y
for j in range(dimension):
self.thetas[j] = self.thetas[j] - alpha * np.sum(c * Xb[:,j])
newJ = self.costFunc(Xb, y)
if newJ == oldJ or math.fabs(newJ - oldJ) < accuracy:
print("代价函数迭代到最小值,退出!")
print("收敛到:",newJ)
break
print("迭代第",count,"次!")
print("代价函数上一次的差:",(newJ - oldJ))
count += 1
# 预测
def costFunc(self, Xb, y):
sum = 0.0
for i in range(self.m):
yPre = sigmoid(Xb[i,:], self.thetas)
# print("yPre:",yPre)
if yPre == 1 or yPre == 0:
return infinity
sum += y[i] * math.log(yPre + (1 - y[i]) * math.log(1 - yPre))
return -1/self.m * sum
def predict(self, X):
a = np.full((len(X),1),1)
Xb = np.column_stack((a,X))
return sigmoidFormatrix2(Xb, self.thetas)
def score(self, X_test, y_test):
y_predict = myLogistic.predict(X_test)
re = (y_test == y_predict)
rel = Counter(re)
a = rel[True] / (rel[True] + rel[False])
return a
# if __name__ == "__main__":
from sklearn.model_selection import train_test_split
iris = datasets.load_iris()
X = iris['data']
y = iris['target']
X = X[y!= 2]
y = y[y!= 2]
X_train,X_test, y_train, y_test = train_test_split(X,y)
myLogistic = LinearLogisticRegression()
myLogistic.fit(X_train, y_train)
y_predict = myLogistic.predict(X_test)
print("参数:",myLogistic.thetas)
print("测试数据准确度:",myLogistic.score(X_test, y_test))
print("训练数据准确度:",myLogistic.score(X_train, y_train))
'''
sklearn 中的逻辑回归
'''
from sklearn.linear_model import LogisticRegression
print("sklearn 中的逻辑回归:")
logr = LogisticRegression()
logr.fit(X_train,y_train)
print("准确度:",logr.score(X_test,y_test))
运行程序,输出如下:
迭代第1次! 代价函数上一次的差:1.92938627909 迭代第2次! 代价函数上一次的差:0.572955749349 迭代第3次! 代价函数上一次的差:-4.91189833803 ... 迭代第239次! 代价函数上一次的差:-1.00784667506e-05 迭代第240次! 代价函数上一次的差:-1.0016786397e-05 代价函数迭代到最小值,退出! 收敛到:0.00339620832005 参数:[ 1.01342939e-03 -8.05476241e-01 -2.19136784e + 00 3.61585103e + 00 1.86218001e + 00] 测试数据准确度:1.0 训练数据准确度:1.0 sklearn中的逻辑回归: 准确度:1.0
【实例 2】利用 Python 中 sklearn 包进行逻辑回归分析。
1) 提出问题,根据已有数据探究“学习时长”与“是否通过考虑”之间关系建立预测模型。
2) 理解数据:
① 导入包和数据:
# 导入包
import warnings
import pandas as pd
import numpy as np
from collections import OrderedDict
import matplotlib.pyplot as plt
warnings.filterwarnings('ignore')
# 创建数据(学习时间与是否通过考试)
dataDict = {'学习时间': list(np.arange(0.50,5.50,0.25)),
'考试成绩':[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
dataOrDict = OrderedDict(dataDict)
dataDF = pd.DataFrame(dataOrDict)
dataDF.head()
| 学习时间 | 考试成绩 | |
|---|---|---|
| 0 | 0.50 | 0 |
| 1 | 0.75 | 0 |
| 2 | 1.00 | 0 |
| 3 | 1.25 | 0 |
| 4 | 1.50 | 0 |
② 查看数据:
# 查看数据具体形式 dataDF.head() # 查看数据类型及缺失情况 dataDF.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 20 entries, 0 to 19 Data columns (total 2 columns): 学习时间 20 non-null float64 考试成绩 20 non-null int64 dtypes: float64(1), int64(1) memory usage: 400.0 bytes # 查看描述性统计信息 dataDF.describe()
| 学习时间 | 考试成绩 | |
|---|---|---|
| count | 20.000000 | 20.000000 |
| mean | 2.875000 | 0.500000 |
| std | 1.47902 | 0.512989 |
| min | 0.500000 | 0.000000 |
| 25% | 1.68750 | 0.000000 |
| 50% | 2.87500 | 0.500000 |
| 75% | 4.06250 | 1.000000 |
| max | 5.250000 | 1.000000 |
③ 绘制散点图查看数据分布情况:
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = [u'SimHei']
plt.rcParams['axes.unicode_minus'] = False
#提取特征和标签
exam_X = dataDf['学习时间']
exam_y = dataDf['考试成绩']
#绘制散点图
plt.scatter(exam_X,exam_y,color = 'b',label = '考试数据')
plt.legend(loc = 2)
plt.xlabel('学习时间')
plt.ylabel('考试成绩')
plt.show()
如下图所示:
从图中可以看出,当学习时间高于某一阈值时,一般都能够通过考试,下面利用逻辑回归方法建立模型。
3) 构建模型。
① 拆分训练集并利用散点图观察,效果如下图所示:
#拆分训练集和测试集
from sklearn.cross_validation import train_test_split
exam_X = exam_X.values.reshape(-1,1)
exam_y = exam_y.values.reshape(-1,1)
train_X,test_X,train_y,test_y = train_test_split(exam_X,exam_y,train_size = 0.8)
print('训练集数据大小为',train_X.size,train_y.size)
print('测试集数据大小为',test_X.size,test_y.size)
训练集数据大小为16 16
测试集数据大小为4 4
#散点图观察
plt.scatter(train_X,train_y,color = 'b',label = '训练数据')
plt.scatter(test_X,test_y,color = 'r',label = '测试数据')
plt.legend(loc = 2)
plt.xlabel('小时')
plt.ylabel('分数')
plt.show()

图 11 拆分训练集
② 导入模型:
from sklearn.linear_model import LogisticRegression
modelLR = LogisticRegression()
#训练模型
modelLR.fit(train_X,train_y)
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)
4) 模型评估:
①模型评分(即准确率):
modelLR.score(test_X,test_y) 0.5
② 发指定某个点的预测情况:
#学习时间确定时,预测为0和1的概率分别为多少? modelLR.predict_proba(3) array([[0.28148368, 0.71851632]]) #学习时间确定时,预测能否通过考试? modelLR.predict(3) array([1],dtype = int64)
③ 求出逻辑回归函数并绘制曲线:
#先求出回归函数y = a + bx,再代入逻辑函数中pred_y = 1/(1 + np.exp(-y))
b = modelLR.coef_
a = modelLR.intercept_
print('该模型对应的回归函数为:1/(1 + exp-(% f + % f * x))'%(a,b))
该模型对应的回归函数为:1/(1 + exp-(-1.332918 + 0.756677 * x))
#画出相应的逻辑回归曲线,如图 12 所示。
plt.scatter(train_X,train_y,color = 'b',label = '训练数据')
plt.scatter(test_X,test_y,color = 'r',label = '测试数据')
plt.plot(test_X,1/(1 + np.exp(-(a + b * test_X))),color = 'r')
plt.plot(exam_X,1/(1 + np.exp(-(a + b * exam_X))),color = 'y')
plt.legend(loc = 2)
plt.xlabel('小时')
plt.ylabel('分数')
plt.show()

图 12 逻辑回归曲线
④ 得到模型混淆矩阵:
from sklearn.metrics import confusion_matrix
#数值处理
pred_y = 1/(1 + np.exp(-(a + b * test_X)))
pred_y = pd.DataFrame(pred_y)
pred_y = round(pred_y,0).astype(int)
#混淆矩阵
confusion_matrix(test_y.astype(str),pred_y.astype(str))
array([[1,2],
[0,1]],dtype = int64)
从混淆矩阵可以看出:
- 该模型的准确率 ACC 为 0.75;
- 真正率 TPR 和假正率 FPR 分别为 0.50 和 0.00,说明该模型对负例的甄别能力更强。
⑤ 绘制模型 ROC 曲线,效果如图 13 所示。
from sklearn.metrics import roc_curve,auc #计算roc和auc
fpr,tpr,threshold = roc_curve(test_y,pred_y) #计算真正率和假正率
roc_auc = auc(fpr,tpr) #计算auc的值
plt.figure()
lw = 2
plt.figure(figsize = (10,10))
plt.plot(fpr,tpr,color = 'r',
lw = lw,label = 'ROC curve(area = % 0.2f)' % roc_auc)
#假正率为横坐标,真正率为纵坐标做曲线
plt.plot([0,1],[0,1],color = 'navy',lw = lw,linestyle = '--')
plt.xlim([0.0,1.0])
plt.ylim([0.0,1.0])
plt.xlabel('误码率')
plt.ylabel('正码率')
plt.title('接收机工作特性')
plt.legend(loc = "lower right")
plt.show()
图 13 中的实线以下部分面积等于 0.75,即误将一个反例划分为正例。
图 13 ROC曲线
ICP备案:
公安联网备案: