文章目录
逻辑回归原理
逻辑回归主要用于解决二分类问题,给定一个输入样本
x
x
x,输出该样本属于1对应类别的预测概率
y
^
=
P
(
y
=
1
∣
x
)
\hat{y} = P(y=1|x)
y^=P(y=1∣x)。
与线性回归相比,逻辑回归增加了非线性函数,如Sigmoid函数,使得输出值在[0,1]的区间中,并设定阈值进行分类。
逻辑回归的主要参数
- 输入特征向量: x ∈ R n x \in R^n x∈Rn(表示输入样本具有 n n n个特征值), y ∈ 0 , 1 y \in {0,1} y∈0,1(表示样本的标签)
- 逻辑回归的权重与偏置: w ∈ R n w \in R^n w∈Rn, b ∈ R b \in R b∈R
- 输出的预测结果:
y
^
=
σ
(
w
T
x
+
b
)
=
σ
(
w
1
x
1
+
w
2
x
2
+
.
.
.
+
w
n
x
n
+
b
)
\hat{y} = \sigma(w^Tx+b)=\sigma(w_1x_1+w_2x_2+...+w_nx_n+b)
y^=σ(wTx+b)=σ(w1x1+w2x2+...+wnxn+b)
( σ \sigma σ一般为Sigmoid函数)
逻辑回归的流程
- 准备好训练数据 x ∈ R n × m x \in R^{n \times m} x∈Rn×m( m m m表示样本个数, n n n表示每个样本的特征值),训练标签数据 y ∈ R m y \in R^m y∈Rm
- 初始化权重与偏置 w ∈ R n w \in R^n w∈Rn, b ∈ R b \in R b∈R
- 数据前向传播 y ^ = σ ( w T x + b ) \hat{y} = \sigma(w^Tx+b) y^=σ(wTx+b),通过极大似然损失函数计算损失
- 采用梯度下降法更新权重 w w w和偏置 b b b,最小化损失函数
- 采用预测得到的权重与偏置进行数据前向传播,设定分类阈值,实现二分类任务
逻辑回归的损失函数
线性回归中采用平方误差作为损失函数,而逻辑回归中一般采用极大似然损失函数衡量预测结果与真实值之间的误差。
对于单个样本的损失值计算公式如下:
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^)
- 若样本属于标签1,则 L ( y ^ , y ) = − log y ^ L(\hat{y},y)=-\log\hat{y} L(y^,y)=−logy^,预测值越接近1, L ( y ^ , y ) L(\hat{y},y) L(y^,y)的值越小
- 若样本属于标签0,则 L ( y ^ , y ) = − log ( 1 − y ^ ) L(\hat{y},y)=-\log(1-\hat{y}) L(y^,y)=−log(1−y^),预测值越接近0, L ( y ^ , y ) L(\hat{y},y) L(y^,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的步长
基于链式法则的梯度计算
在梯度下降法中介绍了参数更新的公式,可以看到参数更新涉及梯度的计算,本小节将详细介绍梯度计算的流程。
简单起见,作以下假设:
- 输入数据 x ∈ R 3 × 1 x \in R^{3 \times 1} x∈R3×1(1个样本,3个特征值)
- 标签 y ∈ R y \in R^{} y∈R
- 权重 w ∈ R 3 × 1 w \in R^{3 \times1} w∈R3×1
- 偏置 b ∈ R b \in R b∈R
为方便理解,采用流程图对逻辑回归的数据流动进行表示:
1. 计算 J J J关于 z z z的导数
- ∂ J ∂ z = ∂ J ∂ y ^ ∂ y ^ ∂ z \frac{\partial{J}}{\partial{z}}=\frac{\partial{J}}{\partial{\hat{y}}}\frac{\partial{\hat{y}}}{\partial{z}} ∂z∂J=∂y^∂J∂z∂y^
- ∂ J ∂ y ^ = − y y ^ + 1 − y 1 − y ^ \frac{\partial{J}}{\partial{\hat{y}}}=\frac{-y}{\hat{y}}+\frac{1-y}{1-\hat{y}} ∂y^∂J=y^−y+1−y^1−y \quad \quad ∂ y ^ ∂ z = y ^ ( 1 − y ^ ) \frac{\partial{\hat{y}}}{\partial{z}}=\hat{y}(1-\hat{y}) ∂z∂y^=y^(1−y^)
- ∂ J ∂ z = ∂ J ∂ y ^ ∂ y ^ ∂ z = − y ( 1 − y ^ ) + ( 1 − y ) y ^ = y ^ − y \frac{\partial{J}}{\partial{z}}=\frac{\partial{J}}{\partial{\hat{y}}}\frac{\partial{\hat{y}}}{\partial{z}}=-y(1-\hat{y})+(1-y)\hat{y}=\hat{y}-y ∂z∂J=∂y^∂J∂z∂y^=−y(1−y^)+(1−y)y^=y^−y
2. 计算 z z z关于 w w w和 b b b的导数
- ∂ z ∂ w 1 = x 1 \frac{\partial{z}}{\partial{w_1}}=x1 ∂w1∂z=x1 \quad \quad ∂ z ∂ w 2 = x 2 \frac{\partial{z}}{\partial{w_2}}=x2 ∂w2∂z=x2 \quad \quad ∂ z ∂ w 3 = x 3 \frac{\partial{z}}{\partial{w_3}}=x3 ∂w3∂z=x3
- ∂ z ∂ b = 1 \frac{\partial{z}}{\partial{b}}=1 ∂b∂z=1
3. 计算 J J J关于 w w w和 b b b的导数
- ∂ J ∂ w 1 = ∂ J ∂ z ∂ z ∂ w 1 = ( y ^ − y ) x 1 \frac{\partial{J}}{\partial{w_1}}=\frac{\partial{J}}{\partial{z}}\frac{\partial{z}}{\partial{w_1}}=(\hat{y}-y)x_1 ∂w1∂J=∂z∂J∂w1∂z=(y^−y)x1
- ∂ J ∂ w 2 = ∂ J ∂ z ∂ z ∂ w 2 = ( y ^ − y ) x 2 \frac{\partial{J}}{\partial{w_2}}=\frac{\partial{J}}{\partial{z}}\frac{\partial{z}}{\partial{w_2}}=(\hat{y}-y)x_2 ∂w2∂J=∂z∂J∂w2∂z=(y^−y)x2
- ∂ J ∂ w 3 = ∂ J ∂ z ∂ z ∂ w 3 = ( y ^ − y ) x 3 \frac{\partial{J}}{\partial{w_3}}=\frac{\partial{J}}{\partial{z}}\frac{\partial{z}}{\partial{w_3}}=(\hat{y}-y)x_3 ∂w3∂J=∂z∂J∂w3∂z=(y^−y)x3
- ∂ J ∂ b = ∂ J ∂ z ∂ z ∂ b = ( y ^ − y ) \frac{\partial{J}}{\partial{b}}=\frac{\partial{J}}{\partial{z}}\frac{\partial{z}}{\partial{b}}=(\hat{y}-y) ∂b∂J=∂z∂J∂b∂z=(y^−y)
向量化实现梯度计算
在上一节中,针对单个数据样本,介绍了如何计算梯度。但是实际过程中,数据样本不可能只有一个,因此要计算基于全体数据样本损失函数的梯度。本节将采用向量化的方式实现多数据样本的梯度计算,向量化的方式相对于采用for循环的方式,可以节省大量的时间,提高运算的效率。
首先声明下数据的结构:
- 输入数据 X ∈ R n × m X \in R^{n \times m} X∈Rn×m(m个样本,n个特征值)
- 标签 Y ∈ R m × 1 Y \in R^{m \times 1} Y∈Rm×1
- 权重 W ∈ R n × 1 W \in R^{n \times1} W∈Rn×1
- 偏置 b ∈ R b \in R b∈R
多样本向量化梯度下降的过程如下:
- Z = W T X + b Z=W^TX+b Z=WTX+b \quad Z ∈ R 1 × m Z\in R^{1\times m} Z∈R1×m
- Y ^ = σ ( Z ) \hat{Y}=\sigma(Z) Y^=σ(Z) \quad Y ^ ∈ R 1 × m \hat{Y}\in R^{1\times m} Y^∈R1×m
- ∂ J ∂ z = Y ^ − Y \frac{\partial{J}}{\partial{z}}=\hat{Y}-Y ∂z∂J=Y^−Y \quad ∂ J ∂ z ∈ R 1 × m \frac{\partial{J}}{\partial{z}}\in R^{1\times m} ∂z∂J∈R1×m
- ∂ J ∂ W = 1 m X ( Y ^ − Y ) T \frac{\partial{J}}{\partial{W}}=\frac{1}{m}X(\hat{Y}-Y)^T ∂W∂J=m1X(Y^−Y)T \quad ∂ J ∂ W ∈ R n × 1 \frac{\partial{J}}{\partial{W}}\in R^{n\times 1} ∂W∂J∈Rn×1
- ∂ J ∂ b = 1 m n p . s u m ( Y ^ − Y ) \frac{\partial{J}}{\partial{b}}=\frac{1}{m}np.sum(\hat{Y}-Y) ∂b∂J=m1np.sum(Y^−Y) \quad ∂ J ∂ b ∈ R \frac{\partial{J}}{\partial{b}}\in R ∂b∂J∈R
- W = W − α ∂ J ∂ W W = W-\alpha \frac{\partial{J}}{\partial{W}} W=W−α∂W∂J \quad b = b − α ∂ J ∂ b b= b-\alpha \frac{\partial{J}}{\partial{b}} b=b−α∂b∂J
逻辑回归代码实现
获取二分类数据
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%。因此逻辑回归虽然简单容易理解,模型的可解释性非常好,但是由于模型的形式比较简单,无法很好地拟合数据的真实分布,所以准确性往往不是很高。