逻辑回归主要用于解决二分类问题,给定一个输入样本
x
x
x,输出该样本属于1对应类别的预测概率
y
^
=
P
(
y
=
1
∣
x
)
\hat{y} = P(y=1|x)
y^=P(y=1∣x)。
与线性回归相比,逻辑回归增加了非线性函数,如Sigmoid函数,使得输出值在[0,1]的区间中,并设定阈值进行分类。
线性回归中采用平方误差作为损失函数,而逻辑回归中一般采用极大似然损失函数衡量预测结果与真实值之间的误差。
对于单个样本的损失值计算公式如下:
L
(
y
^
,
y
)
=
−
y
log
y
^
−
(
1
−
y
)
log
(
1
−
y
^
)
L(\hat{y},y)=-y\log\hat{y}-(1-y)\log(1-\hat{y})
L(y^,y)=−ylogy^−(1−y)log(1−y^)
对于全体训练样本的损失值计算公式如下:
J
(
w
,
b
)
=
1
m
∑
i
=
1
m
L
(
y
^
(
i
)
,
y
(
i
)
)
J(w,b)=\frac{1}{m}\sum^m_{i=1}L(\hat{y}^{(i)},y^{(i)})
J(w,b)=m1∑i=1mL(y^(i),y(i))
梯度下降法的目的是最小化损失函数,函数的梯度指出了函数变化最快的方向。
如上图所示,假设
J
(
w
,
b
)
J(w,b)
J(w,b)是关于
w
w
w和
b
b
b的函数,
w
,
b
∈
R
w,b \in R
w,b∈R
计算初始点处的梯度,设置好学习率,对权重与偏置参数进行更新迭代,最终可以到达函数值最小点。
权重与偏置的更新公式为:
w
=
w
−
α
∂
J
(
w
,
b
)
∂
w
w=w-\alpha\frac{\partial{J(w,b)}}{\partial{w}}
w=w−α∂w∂J(w,b)
b = b − α ∂ J ( w , b ) ∂ b b=b-\alpha\frac{\partial{J(w,b)}}{\partial{b}} b=b−α∂b∂J(w,b)
注:其中 α \alpha α为学习率,即每次更新 w , b w,b w,b的步长
在梯度下降法中介绍了参数更新的公式,可以看到参数更新涉及梯度的计算,本小节将详细介绍梯度计算的流程。
简单起见,作以下假设:
为方便理解,采用流程图对逻辑回归的数据流动进行表示:
1. 计算 J J J关于 z z z的导数
2. 计算 z z z关于 w w w和 b b b的导数
3. 计算 J J J关于 w w w和 b b b的导数
在上一节中,针对单个数据样本,介绍了如何计算梯度。但是实际过程中,数据样本不可能只有一个,因此要计算基于全体数据样本损失函数的梯度。本节将采用向量化的方式实现多数据样本的梯度计算,向量化的方式相对于采用for循环的方式,可以节省大量的时间,提高运算的效率。
首先声明下数据的结构:
多样本向量化梯度下降的过程如下:
from sklearn.datasets import load_iris,make_classification from sklearn.model_selection import train_test_split import tensorflow as tf import numpy as np # 生成500个样本点,样本类别只有2种,每个样本的特征值有4个 X,Y=make_classification(n_samples=500,n_features=4,n_classes=2) # 取30%的数据作为测试集,70%数据作为训练集 x_train,x_test,y_train,y_test = train_test_split(X,Y,test_size=0.3) print("X:",X.shape) print("Y:",Y.shape) print("x_train:",x_train.shape) print("x_test:",x_test.shape) print("y_train:",y_train.shape) print("y_test:",y_test.shape)
输出结果为:
X: (500, 4) Y: (500,) x_train: (350, 4) x_test: (150, 4) y_train: (350,) y_test: (150,)
def initialize_with_zeros(shape): """ 创建一个形状为 (shape, 1) 的w参数和b=0. return:w, b """ w = np.zeros((shape, 1)) b = 0 return w, b
def basic_sigmoid(x): """ 计算sigmoid函数 """ s = 1 / (1 + np.exp(-x)) return s def propagate(w, b, X, Y): """ 参数:w,b,X,Y:网络参数和数据 Return: 损失cost、参数W的梯度dw、参数b的梯度db """ m = X.shape[1] # w (n,1), x (n, m) A = basic_sigmoid(np.dot(w.T, X) + b) # 计算损失 cost = -1 / m * np.sum(Y * np.log(A) + (1 - Y) * np.log(1 - A)) dz = A - Y dw = 1 / m * np.dot(X, dz.T) db = 1 / m * np.sum(dz) cost = np.squeeze(cost) grads = {"dw": dw, "db": db} return grads, cost
def optimize(w, b, X, Y, num_iterations, learning_rate): """ 参数: w:权重,b:偏置,X特征,Y目标值,num_iterations总迭代次数,learning_rate学习率 Returns: params:更新后的参数字典 grads:梯度 costs:损失结果 """ costs = [] for i in range(num_iterations): # 梯度更新计算函数 grads, cost = propagate(w, b, X, Y) # 取出两个部分参数的梯度 dw = grads['dw'] db = grads['db'] # 按照梯度下降公式去计算 w = w - learning_rate * dw b = b - learning_rate * db if i % 100 == 0: costs.append(cost) print("损失结果 %i: %f" %(i, cost)) params = {"w": w,"b": b} grads = {"dw": dw,"db": db} return params, grads, costs
def predict(w, b, X): ''' 利用训练好的参数预测 return:预测结果 ''' m = X.shape[1] y_prediction = np.zeros((1, m)) w = w.reshape(X.shape[0], 1) # 计算结果 A = basic_sigmoid(np.dot(w.T, X) + b) for i in range(A.shape[1]): if A[0, i] <= 0.5: y_prediction[0, i] = 0 else: y_prediction[0, i] = 1 return y_prediction
def model(x_train, y_train, x_test, y_test, num_iterations=2000, learning_rate=0.0001): """ """ # 修改数据形状 x_train = x_train.reshape(-1, x_train.shape[0]) x_test = x_test.reshape(-1, x_test.shape[0]) y_train = y_train.reshape(1, y_train.shape[0]) y_test = y_test.reshape(1, y_test.shape[0]) print(x_train.shape) print(x_test.shape) print(y_train.shape) print(y_test.shape) # 1、初始化参数 w, b = initialize_with_zeros(x_train.shape[0]) # 2、梯度下降 # params:更新后的网络参数 # grads:最后一次梯度 # costs:每次更新的损失列表 params, grads, costs = optimize(w, b, x_train, y_train, num_iterations, learning_rate) # 获取训练的参数 # 预测结果 w = params['w'] b = params['b'] y_prediction_train = predict(w, b, x_train) y_prediction_test = predict(w, b, x_test) # 打印准确率 print("训练集准确率: {} ".format(100 - np.mean(np.abs(y_prediction_train - y_train)) * 100)) print("测试集准确率: {} ".format(100 - np.mean(np.abs(y_prediction_test - y_test)) * 100)) return None
model(x_train, y_train, x_test, y_test, num_iterations=3000, learning_rate=0.01)
设置迭代次数为3000,学习率设置为0.01,运行将得到以下结果:
(4, 350) (4, 150) (1, 350) (1, 150) 损失结果 0: 0.693147 损失结果 100: 0.685711 损失结果 200: 0.681650 损失结果 300: 0.679411 损失结果 400: 0.678165 损失结果 500: 0.677465 损失结果 600: 0.677069 损失结果 700: 0.676843 损失结果 800: 0.676713 损失结果 900: 0.676639 损失结果 1000: 0.676595 损失结果 1100: 0.676570 损失结果 1200: 0.676556 损失结果 1300: 0.676547 损失结果 1400: 0.676542 损失结果 1500: 0.676539 损失结果 1600: 0.676538 损失结果 1700: 0.676537 损失结果 1800: 0.676536 损失结果 1900: 0.676536 损失结果 2000: 0.676535 损失结果 2100: 0.676535 损失结果 2200: 0.676535 损失结果 2300: 0.676535 损失结果 2400: 0.676535 损失结果 2500: 0.676535 损失结果 2600: 0.676535 损失结果 2700: 0.676535 损失结果 2800: 0.676535 损失结果 2900: 0.676535 训练集准确率: 60.57142857142857 测试集准确率: 56.0
损失函数值的变化如下图所示:
本文详细介绍了逻辑回归的原理,并利用Python实现了逻辑回归的案例。从运行结果可以看到,随着迭代次数的增加,损失函数的值并未一直下降到接近0的位置,而是稳定在0.6附近。同时,对于训练集的预测的准确性为 60.57%,对于测试集的预测的准确性为56%。因此逻辑回归虽然简单容易理解,模型的可解释性非常好,但是由于模型的形式比较简单,无法很好地拟合数据的真实分布,所以准确性往往不是很高。