Python实现“经典梯度下降算法”训练逻辑回归模型
梯度下降实现要求
在这个任务中,我们将实现经典的梯度下降算法来训练逻辑回归模型。
完成这个任务的算法如下:
-
基于课程4. 线性模型上计算出的偏导数,我们将编写一个函数来计算模型参数的二元交叉熵梯度
-
让我们编写一个函数来根据计算出的梯度更新权重
-
让我们编写一个函数来训练模型
其中:
该模型经过多个周期的训练,在每个周期中,我们根据数据集中每个对象的预测来更新模型的权重。这样的周期被称为“时代”。也就是说,一个 epoch 是根据从数据集中为每个对象计算的模型误差实现的一组权重更新。
您需要分几个时期对模型进行训练。它们的数量由函数参数指定。在每个时期内,需要循环遍历训练样本中的所有对象并更新模型权重。
注意:
-
如果您在完成第一项任务时遇到困难,并因此无法完成,建议您详细熟悉课程4. 线性模型的章节 ∂ H ∂ ω i \frac{\partial H}{\partial \omega_i} ∂ωi∂H 的导数。
-
请注意,函数参数描述中的对象-特征矩阵表示numpy.array()类型的变量,其每个元素都是numpy.array()类型的对象——相应对象的特征向量。
-
假设自由系数 a0 位于列表 alpha 的末尾。
代码
import numpy as np
np.random.seed(42)
# 计算梯度的函数
def gradient(y_true: int, y_pred: float, x: np.array) -> np.array:
"""
y_true - 对于对象 x 的真实响应值
y_pred - 我们的模型预测的对象 x 属于类别 1 的概率值
x - 该对象的特征描述向量
期望在输出中得到关于预测 y_pred 值的模型参数的偏导数向量 H。
请注意,由于自由系数 a0 的存在,这个梯度的维度应该比 x 的维度大 1。
"""
# 在特征向量x前添加1,用于计算偏置项的梯度
x_with_bias = np.concatenate([x, np.array([1])])
grad = (y_pred - y_true) * x_with_bias
return grad
# 更新权重的函数
def update(alpha: np.array, gradient: np.array, lr: float):
"""
alpha: 当前模型参数向量的近似值
gradient: 计算出的关于模型参数的梯度
lr: 学习率,在参数更新公式中梯度前的乘数
"""
alpha_new = alpha - lr * gradient
return alpha_new
# 训练模型的函数
def train(
alpha0: np.array, x_train: np.array, y_train: np.array, lr: float, num_epoch: int
):
"""
alpha0 - 模型参数的初始近似值
x_train - 训练样本的对象-特征矩阵
y_train - 训练样本的正确答案
lr - 学习率,在参数更新公式中梯度前的乘数
num_epoch - 训练的轮数,即完整“遍历”整个数据集的次数
"""
alpha = alpha0.copy()
for epo in range(num_epoch):
for i, x in enumerate(x_train):
y_true = y_train[i]
# 计算预测值,这里使用sigmoid函数
def sigmoid(z):
return 1 / (1 + np.exp(-z))
# 预测值计算
y_pred = sigmoid(np.dot(alpha, np.concatenate([x, np.array([1])])))
grad = gradient(y_true, y_pred, x)
alpha = update(alpha, grad, lr)
return alpha
解析
1.导入必要的库
import numpy as np
np.random.seed(42)
import numpy as np
:导入numpy
库,它是 Python 中用于科学计算的基础库,提供了高效的多维数组对象和处理这些数组的工具。np.random.seed(42)
:设置随机数种子为 42,这样每次运行代码时生成的随机数序列都是相同的,保证结果的可重复性。
2.定义梯度计算函数
def gradient(y_true: int, y_pred: float, x: np.array) -> np.array:
"""
y_true - 对于对象 x 的真实响应值
y_pred - 我们的模型预测的对象 x 属于类别 1 的概率值
x - 该对象的特征描述向量
期望在输出中得到关于预测 y_pred 值的模型参数的偏导数向量 H。
请注意,由于自由系数 a0 的存在,这个梯度的维度应该比 x 的维度大 1。
"""
# 在特征向量x前添加1,用于计算偏置项的梯度
x_with_bias = np.concatenate([x, np.array([1])])
grad = (y_pred - y_true) * x_with_bias
return grad
gradient
函数用于计算逻辑回归损失函数关于模型参数的梯度。y_true
是样本的真实标签(0 或 1),y_pred
是模型预测样本属于类别 1 的概率,x
是样本的特征向量。x_with_bias
是在特征向量x
后面添加一个 1,用于计算偏置项的梯度。grad
是根据逻辑回归的梯度计算公式(y_pred - y_true) * x_with_bias
计算得到的梯度向量。
代码详细解释
- 添加偏置项
x_with_bias = np.concatenate([x, np.array([1])])
在逻辑回归模型中,除了特征的权重参数,还有一个偏置项(也称为截距)。为了在计算梯度时能统一处理权重和偏置,我们在特征向量 x
的末尾添加一个值为 1 的元素,生成新的向量 x_with_bias
。这样,偏置项就可以当作一个特殊的特征,其对应的权重就是偏置项的值。
- 计算梯度
grad = (y_pred - y_true) * x_with_bias
逻辑回归通常使用对数损失函数(也叫交叉熵损失函数)。对于单个样本,对数损失函数关于模型参数的梯度可以简化为 (y_pred - y_true) * x_with_bias
。具体推导过程如下:
逻辑回归模型的预测概率公式为:
y
^
=
σ
(
w
T
x
+
b
)
\ \hat{y} = \sigma(\mathbf{w}^T\mathbf{x} + b)
y^=σ(wTx+b)
其中,
σ
(
z
)
=
1
1
+
e
−
z
\sigma(z)=\frac{1}{1 + e^{-z}}
σ(z)=1+e−z1 是 sigmoid 函数,
w
\mathbf{w}
w是权重向量,
b
b
b 是偏置项,
x
\mathbf{x}
x 是特征向量。
对数损失函数为:
L
(
y
,
y
^
)
=
−
[
y
log
(
y
^
)
+
(
1
−
y
)
log
(
1
−
y
^
)
]
L(y, \hat{y}) = -[y \log(\hat{y}) + (1 - y) \log(1 - \hat{y})]
L(y,y^)=−[ylog(y^)+(1−y)log(1−y^)]
对损失函数关于权重
w
\mathbf{w}
w求偏导数可得:
∂
L
∂
w
=
(
y
^
−
y
)
x
\frac{\partial L}{\partial \mathbf{w}} = (\hat{y} - y) \mathbf{x}
∂w∂L=(y^−y)x
对损失函数关于偏置
b
b
b 求偏导数可得:
∂
L
∂
b
=
(
y
^
−
y
)
\frac{\partial L}{\partial b} = (\hat{y} - y)
∂b∂L=(y^−y)
将偏置项当作一个特殊的特征(对应特征值为 1),就可以将上述两个偏导数合并为一个向量:
∇
L
=
(
y
^
−
y
)
[
x
1
]
\nabla L = (\hat{y} - y) \begin{bmatrix} \mathbf{x} \\ 1 \end{bmatrix}
∇L=(y^−y)[x1]
这里的 grad
就是损失函数关于模型参数(权重和偏置)的梯度向量。
3.定义参数更新函数
def update(alpha: np.array, gradient: np.array, lr: float):
"""
alpha: 当前模型参数向量的近似值
gradient: 计算出的关于模型参数的梯度
lr: 学习率,在参数更新公式中梯度前的乘数
"""
alpha_new = alpha - lr * gradient
return alpha_new
update
函数用于根据计算得到的梯度更新模型的参数。alpha
是当前模型的参数向量,gradient
是计算得到的梯度向量,lr
是学习率。alpha_new
是更新后的模型参数向量,使用梯度下降法的更新公式alpha_new = alpha - lr * gradient
进行更新。
4.定义模型训练函数
def train(
alpha0: np.array, x_train: np.array, y_train: np.array, lr: float, num_epoch: int
):
"""
alpha0 - 模型参数的初始近似值
x_train - 训练样本的对象-特征矩阵
y_train - 训练样本的正确答案
lr - 学习率,在参数更新公式中梯度前的乘数
num_epoch - 训练的轮数,即完整“遍历”整个数据集的次数
"""
alpha = alpha0.copy()
for epo in range(num_epoch):
for i, x in enumerate(x_train):
y_true = y_train[i]
# 计算预测值,这里使用sigmoid函数
def sigmoid(z):
return 1 / (1 + np.exp(-z))
# 预测值计算
y_pred = sigmoid(np.dot(alpha, np.concatenate([x, np.array([1])])))
grad = gradient(y_true, y_pred, x)
alpha = update(alpha, grad, lr)
return alpha
train
函数用于训练逻辑回归模型。alpha0
是模型参数的初始值,x_train
是训练数据的特征矩阵,y_train
是训练数据的真实标签,lr
是学习率,num_epoch
是训练的轮数。alpha = alpha0.copy()
:复制初始参数,避免修改原始的初始参数。- 外层循环
for epo in range(num_epoch)
控制训练的轮数。 - 内层循环
for i, x in enumerate(x_train)
遍历训练数据集中的每个样本。 sigmoid
函数用于将线性组合的结果转换为概率值,范围在 0 到 1 之间。y_pred = sigmoid(np.dot(alpha, np.concatenate([x, np.array([1])])))
:计算样本属于类别 1 的概率。grad = gradient(y_true, y_pred, x)
:计算当前样本的梯度。alpha = update(alpha, grad, lr)
:根据梯度更新模型的参数。- 最后返回训练好的模型参数。
综上所述,这段代码通过梯度下降法迭代更新模型的参数,使得模型的预测结果逐渐接近真实标签,从而实现逻辑回归模型的训练。