逻辑回归的目的
逻辑回归是解决二分问题,判断样本属于正类的概率是多大,0-1之间
找到一组最佳的权重(w1,w2,w3,…) ,b,使得模型预测的概率 P(Y=1) 尽可能接近样本的真实标签(1 或 0)。
计算过程
前向传播过程如下:
假设有一个简单的神经网络层,包括输入 ( x )、权重 ( w )、偏置 ( b ) 和激活函数( σ),输出为 ( a )。损失函数为 ( L ),我们希望计算损失函数对权重 ( w ) 的梯度。
σ ( z ) = 1 1 + e − z \sigma(z) = \frac{1}{1 + e^{-z}} σ(z)=1+e−z1
损失函数,也就是误差
损失计算:L=Loss(a,y),其中 y 是真实标签,a是预测值
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)=−ylog(y^)−(1−y)log(1−y^)
最常见的损失函数就是最小二乘法损失函数:1/2∑(目标值 - 预测值)²。即E_{total}=\frac{1}{2}(T-Y)^{2}。通过最小化均方误差,可以求解出最优的模型参数W,使得模型的预测结果Y与目标值之间T的平方差最小化。这样的损失函数对异常值比较敏感,因为它会对差值进行平方,导致异常值的差异被放大。
训练的目的是把L(y^,y)趋近0,让损失最小,就是趋向于最低点
其有w与b两个变量
反向传播,更新权重W
这里的 α是学习率,是调整的幅度,w的优化就是通过减去w的偏导数来达成的
J和L是一样的含义,都是损失函数
偏导数如何求(计算梯度)
反向传播中的梯度计算
计算损失函数 L L L 对激活值 a a a 的梯度:
∂ L ∂ a \frac{\partial L}{\partial a} ∂a∂L
计算激活函数 σ \sigma σ 对 z z z 的梯度:
∂ a ∂ z = σ ′ ( z ) \frac{\partial a}{\partial z} = \sigma'(z) ∂z∂a=σ′(z)
计算 z z z 对权重 w w w 的梯度:
∂ z ∂ w = x \frac{\partial z}{\partial w} = x ∂w∂z=x
就是w使用链式法则计算损失函数 L L L 对权重 w w w 的梯度:
∂ L ∂ w = ∂ L ∂ a ⋅ ∂ a ∂ z ⋅ ∂ z ∂ w \frac{\partial L}{\partial w} = \frac{\partial L}{\partial a} \cdot \frac{\partial a}{\partial z} \cdot \frac{\partial z}{\partial w} ∂w∂L=∂a∂L⋅∂z∂a⋅∂w∂z
4中就是w关于L的偏导数
这是L
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)=−ylog(y^)−(1−y)log(1−y^)
这是a
σ ( z ) = 1 1 + e − z \sigma(z) = \frac{1}{1 + e^{-z}} σ(z)=1+e−z1
这是z
z = w ∗ x + b z=w*x+b z=w∗x+b
问题:计算出 J J J 关于 z z z 的导数
- ∘ d J d z = d J d a ∗ d a d z \circ\ \dfrac{dJ}{dz}=\dfrac{dJ}{da}*\dfrac{da}{dz} ∘ dzdJ=dadJ∗dzda
- ∘ d J d a = − y a + 1 − y 1 − a \circ\ \dfrac{dJ}{da} = -\dfrac{y}{a} + \dfrac{1 - y}{1 - a} ∘ dadJ=−ay+1−a1−y
- ∘ d a d z = a ( 1 − a ) \circ\ \dfrac{da}{dz} = a(1 - a) ∘ dzda=a(1−a)
d z = d J d a ⋅ d a d z = a − y dz = \frac{dJ}{da} \cdot \frac{da}{dz} = a - y dz=dadJ⋅dzda=a−y
d w 1 = d z d w 1 = ( a − y ) ⋅ x 1 dw_1 = \frac{dz}{dw_1} = (a - y) \cdot x_1 dw1=dw1dz=(a−y)⋅x1
d w 2 = d z d w 2 = ( a − y ) ⋅ x 2 dw_2 = \frac{dz}{dw_2} = (a - y) \cdot x_2 dw2=dw2dz=(a−y)⋅x2
d b = d z db = dz db=dz
在神经网络中,偏置项 b 是全局共享参数:,偏置项 b 影响所有样本的输出
计算梯度代码现
import numpy as np
# 激活函数及其导数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
#倒数
def sigmoid_derivative(x):
return sigmoid(x) * (1 - sigmoid(x))
# 假设数据
x = np.array([0.5]) # 输入
w = np.array([0.2]) # 权重
b = 0.1 # 偏置
y = np.array([0.7]) # 实际目标
# 前向传播
z = w * x + b
a = sigmoid(z)
# 计算损失(均方误差)
loss = 0.5 * (a - y) ** 2
print(f'损失对权重的梯度: {loss}')
# 反向传播(链式法则)
dL_da = a - y # 损失函数对激活值的梯度
da_dz = sigmoid_derivative(z) # 激活函数对 z 的梯度
dz_dw = x # z 对权重 w 的梯度
# 使用链式法则计算损失函数对权重的梯度
dL_dw = dL_da * da_dz * dz_dw
print(f'损失对权重的梯度: {dL_dw}')
#这个就是J对w的梯度,就是那个偏导数
完整代码实现
数学原理
正向传播
初始化,假设n个特征,m个样本
J = 0 J=0 J=0
W = n p . z e r o s ( [ n , 1 ] ) W=np.zeros([n,1]) W=np.zeros([n,1])
b = 0 b=0 b=0
Z = n p . d o t ( W T , X ) + b Z=np.dot(W^{T},X)+b Z=np.dot(WT,X)+b
A = σ ( Z ) A=\sigma(Z) A=σ(Z)反向传播
每个样本梯度计算过程为:
d Z = A − Y dZ = A - Y dZ=A−Y
d W = 1 m X d Z T dW = \frac{1}{m} XdZ^{T} dW=m1XdZT
d b = 1 m np.sum ( d Z ) db = \frac{1}{m} \text{np.sum}(dZ) db=m1np.sum(dZ)
更新
W : = W − α d W W := W - \alpha dW W:=W−αdW
b : = b − α d b b := b - \alpha db b:=b−αdb
- 各个函数
import numpy as np
#激活函数sigmoid()
def basic_sigmoid(x):
s = 1 / (1 + np.exp(-x))
return s
#初始化W,b
def initialize_weights(shape):
w=np.zeros((shape,1))
b=0
#这里w全为1,后面会迭代,1只是初始值
assert(w.shape == (shape,1))
assert(isinstance(b,float) or isinstance(b,int))
#assert检测类型是否正确,相当于异常检测
return w,b
#前向传播与反向传播,propagate传播
#用于得到 平均损失cost,dW均值,与db,便于后续优化optimize
def propagate(W,b,X,Y):
#W权重(n,1),b是偏置向量,X是输入的m个样本的(n,m)矩阵,Y是实际值用于反向传播
m=X.shape[1] #获取X的列数,即样本的个数
#前向传播
#np.dot()是矩阵乘法,W,T大写代表矩阵,+b是矩阵结果的每一项都+b
#得到的A是(1,m)的一维矩阵
A=basic_sigmoid(np.dot(W.T,X)+b)
#计算损失
#操作对象是矩阵,想达到向量化运算,用np
cost=-1/m*np.sum(np.log(A)+(1-Y)*np.log(1-A)) #求损失平均值,负的(-),越接近0正确率越高
##反向传播
#下面得到的是dW是w得平均值(n,1)矩阵
dz=A-Y #得到(1,m)
dW=1/m*np.dot(X,dz.T) #n*m M*1->(n,1)
db=1/m*np.sum(dz) #偏置向量怎么调?
assert(dW.shape ==W.shape)
assert(db.dtype == float)
#np.squeeze(cost)确保cost是一个标量(不是矩阵)
cost=np.squeeze(cost)
assert(cost.shape == ())
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的平均值梯度(n,1)矩阵,db
dW=grads['dW']
db=grads['db']
#根据优化公式更新
w=W-learning_rate*dW #权重
b=b-learning_rate*db #偏置
#记录cost的变化,每隔100个记录一次
if i % 100==0:
costs.append(cost)
print("损失结果 %i,%f" %(i,cost))
params={
"w":w,
"b":b
}#更新之后的线性变换的参数
grads={
"dW":dW,
"db":db
}#没变,还是grads,cost=propagate(W,b,X,Y)
return params,grads,costs
#预测 (权重,偏置,输入)
#返回的是一个(1,m)的矩阵
def predict(W , b, X):
m=X.shape[1] #得到X列数,即样本个数 X(n,m)
Y_prediction=np.zeros(1,m) #定义一个存放预测值的(1,m)矩阵
W=W.reshape(X.shape[0],1) #定义一个(n,1)的矩阵,用于存放w1-wn的权重
#预测
A=basic_sigmoid(np.dot(W.T,X)+b) #(1,m)的预测值
for i in range(A.shape[1]): #循环m次
if A[0,i]<=0.5:
Y_prediction[0,i]=0
if A[0,i]>=0.5:
Y_prediction[0,i]=1
assert(Y_prediction.shape==(1,m))
return Y_prediction
#汇总函数
def model(X_train,Y_train,X_test,Y_test,num_iterations=2000,learning_rate=0.5):
# 初始化W,b
W,b=initialize_weights(X_train)
#梯度下降优化
#应该是这个吧X_train,Y_train
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))
#数据汇总
d={
"costs":costs,
"Y_prediction_train":Y_prediction_train,
"Y_prediction_test":Y_prediction_test,
"W":W,
"b":b,
"learning_rate":learning_rate,
"num_iterations":num_iterations
}
return d
if __name__=="__main__":
# 最后运行模型
model_result = model(X_train, Y_train, X_test, Y_test)
print(model_result)
- 总体实现 单神经元
import numpy as np
# 训练集
X_train = np.array([
[0.1, 0.3, 0.5, 0.7, 0.9, 0.2, 0.4, 0.6], # 特征1
[1.2, 0.8, 1.0, 1.5, 0.5, 1.1, 0.9, 1.3] # 特征2
]) # (2, 8) 特征矩阵,2个特征,8个样本
Y_train = np.array([
[0, 0, 0, 1, 1, 0, 0, 1]
]) # (1, 8) 标签向量
# 测试集
X_test = np.array([
[0.3, 0.8, 0.2, 0.5], # 特征1
[1.0, 0.7, 1.1, 0.9] # 特征2
]) # (2, 4) 特征矩阵,2个特征,4个样本
Y_test = np.array([
[0, 1, 0, 1]
]) # (1, 4) 标签向量
# 激活函数sigmoid()
def basic_sigmoid(x):
s = 1 / (1 + np.exp(-x))
return s
# 初始化W,b
def initialize_weights(shape):
w = np.zeros((shape, 1))
b = 0
assert w.shape == (shape, 1)
assert isinstance(b, float) or isinstance(b, int)
return w, b
# 前向传播与反向传播
def propagate(W, b, X, Y):
m = X.shape[1] # 样本个数
# 前向传播 (添加了数值稳定性)
A = basic_sigmoid(np.dot(W.T, X) + b)
# 计算损失 (避免log(0))
epsilon = 1e-5
cost = -1 / m * np.sum(Y * np.log(A + epsilon) + (1 - Y) * np.log(1 - A + epsilon))
# 反向传播
dz = A - Y
dW = 1 / m * np.dot(X, dz.T)
db = 1 / m * np.sum(dz)
# 验证
assert dW.shape == W.shape
assert isinstance(db, float)
cost = np.squeeze(cost)
assert cost.shape == ()
grads = {"dW": dW, "db": db}
return grads, cost
# 优化
def optimize(W, b, X, Y, num_iterations, learning_rate):
costs = []
# 复制参数避免修改原始值
W = W.copy()
b = b
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
# 每100次记录一次损失
if i % 100 == 0:
costs.append(cost)
print(f"迭代 {i}, 损失: {cost:.6f}")
# 记录最终损失
_, final_cost = propagate(W, b, X, Y)
costs.append(final_cost)
print(f"最终迭代 {num_iterations}, 损失: {final_cost:.6f}")
params = {"W": W, "b": b}
grads = {"dW": dW, "db": db}
return params, grads, costs
# 预测
def predict(W, b, X):
m = X.shape[1]
Y_prediction = np.zeros((1, m)) # 修正这里:np.zeros((1, m))
# 确保W是列向量
if len(W.shape) == 1:
W = W.reshape(-1, 1)
# 计算预测值
A = basic_sigmoid(np.dot(W.T, X) + b)
# 二分预测
Y_prediction = (A > 0.5).astype(int)
assert Y_prediction.shape == (1, m)
return Y_prediction
# 模型汇总
def model(X_train, Y_train, X_test, Y_test, num_iterations=2000, learning_rate=0.5):
# 初始化
n_features = X_train.shape[0]
W, b = initialize_weights(n_features)
# 优化
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)
# 计算准确率
train_accuracy = 100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100
test_accuracy = 100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100
print("\n模型训练结果:")
print(f"训练集准确率: {train_accuracy:.2f}%")
print(f"测试集准确率: {test_accuracy:.2f}%")
# 结果汇总
result = {
"costs": costs,
"Y_prediction_train": Y_prediction_train,
"Y_prediction_test": Y_prediction_test,
"W": W,
"b": b,
"learning_rate": learning_rate,
"num_iterations": num_iterations,
"train_accuracy": train_accuracy,
"test_accuracy": test_accuracy
}
return result
if __name__ == "__main__":
# 运行模型
model_result = model(
X_train=X_train,
Y_train=Y_train,
X_test=X_test,
Y_test=Y_test,
num_iterations=2000,
learning_rate=0.05
)
print("\n最终参数:")
print(f"权重 W: \n{model_result['W']}")
print(f"偏置 b: {model_result['b']:.4f}")
总的来说
逻辑回归只会给出 0 1的判断,而我们优化的就是减小这个0 1判断的误差