1 逻辑回归
逻辑回归的特点: 解决二进制问题, 输出标签y只有0或者1
1.1 逻辑回归的公式
逻辑回归的公式也叫 sigmoid 函数
\[g(z) = \frac{1}{1+e^{-z}}\]在 Logistic 回归的情况下,z(sigmoid 函数的输入)是线性回归模型的输出。
- 在单个示例的情况下, z是 scalar。
- 在多个示例的情况下, z可能是由m个值组成的向量,每个示例一个值。
python代码实现:
def sigmoid(z):
"""
Compute the sigmoid of z
Args:
z (ndarray): A scalar, numpy array of any size.
Returns:
g (ndarray): sigmoid(z), with the same shape as z
"""
g = 1/(1+np.exp(-z))
return g
1.2 决策边界(Decision boundary)
决策边界实质上是函数输入z等于0时的曲线

对于函数
\[g(z) = \frac{1}{1+e^{-z}}\]在这里 $z=\mathbf{w} \cdot \mathbf{x}$ is the vector dot product:
如果 $f_{\mathbf{w},b}(x) >= 0.5$, 预测 $y=1$
如果 $f_{\mathbf{w},b}(x) < 0.5$, 预测 $y=0$
1.3 逻辑回归的成本函数

如果与线性回归一样, 使用平方成本函数, 会产生很多局部最小值
因此, 为了避免产生局部最小值, 我们需要一个新的成本函数. 这个新的成本函数可以使得函数再次变为凸的.
逻辑回归的损失(loss)函数:
\[\begin{equation*} loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)}) = \begin{cases} - \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) & \text{if $y^{(i)}=1$}\\ \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) & \text{if $y^{(i)}=0$} \end{cases} \end{equation*}\]-
等式中 $f_{\mathbf{w},b}(\mathbf{x}^{(i)})$ 是模型的预测, $y^{(i)}$ 是模型的目标值
-
其中 $f_{\mathbf{w},b}(\mathbf{x}^{(i)}) = g(\mathbf{w} \cdot\mathbf{x}^{(i)}+b)$ ,在这里 $g$ 是逻辑回归函数(sigmoid function)
-
$f_{\mathbf{w},b}(\mathbf{x}^{(i)})$ 距离 $y^{(i)}$ 越远, 损失越大, 损失函数越大
或者,损失函数还可以写成另一个等式
\[loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)}) = (-y^{(i)} \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) - \left( 1 - y^{(i)}\right) \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right)\]注意在这个等式中 , $y^{(i)}$只能取到0或1
逻辑回归的成本(cost)函数
\[J(\mathbf{w},b) = \frac{1}{m} \sum_{i=0}^{m-1} \left[ loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)}) \right]\]其中:
-
$loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)})$ 是单个数据点的损失函数, 也就是:
\[loss(f_{\mathbf{w},b}(\mathbf{x}^{(i)}), y^{(i)}) = -y^{(i)} \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) - \left( 1 - y^{(i)}\right) \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right)\] -
其中
m是数据集中的训练样本数,并且: \(\begin{align*} f_{\mathbf{w},b}(\mathbf{x^{(i)}}) &= g(z^{(i)}) \\ z^{(i)} &= \mathbf{w} \cdot \mathbf{x}^{(i)}+ b \\ g(z^{(i)}) &= \frac{1}{1+e^{-z^{(i)}}} \end{align*}\)
python代码实现
def compute_cost_logistic(X, y, w, b):
"""
Computes cost
Args:
X (ndarray (m,n)): Data, m examples with n features
y (ndarray (m,)) : target values
w (ndarray (n,)) : model parameters
b (scalar) : model parameter
Returns:
cost (scalar): cost
"""
m = X.shape[0]
cost = 0.0
for i in range(m):
z_i = np.dot(X[i],w) + b
f_wb_i = sigmoid(z_i)
cost += -y[i]*np.log(f_wb_i) - (1-y[i])*np.log(1-f_wb_i)
cost = cost / m
return cost
1.4 逻辑回归的梯度下降
逻辑回归的梯度下降公式:
\[\begin{align*} &\text{重复以下公式, 直到收敛:} \; \lbrace \\ & \; \; \;w_j = w_j - \alpha \frac{\partial J(\mathbf{w},b)}{\partial w_j} \; & \text{这里 j= 0..n-1} \\ & \; \; \; \; \;b = b - \alpha \frac{\partial J(\mathbf{w},b)}{\partial b} \\ &\rbrace \end{align*}\]其中,每次迭代对所有不同$j$的$w_j$同时更新, 在这里
\[\begin{align*} \frac{\partial J(\mathbf{w},b)}{\partial w_j} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})x_{j}^{(i)} \\ \frac{\partial J(\mathbf{w},b)}{\partial b} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)}) \end{align*}\]m是数据集中的训练样本数- $f_{\mathbf{w},b}(x^{(i)})$ 是模型的预测, while $y^{(i)}$ 是目标
- 在 Logistic 回归模型中
$z = \mathbf{w} \cdot \mathbf{x} + b$
$f_{\mathbf{w},b}(x) = g(z)$
这里的 $g(z)$ 是 sigmoid 函数:
\(g(z) = \frac{1}{1+e^{-z}}\)
python代码实现
def compute_gradient_logistic(X, y, w, b):
"""
Computes the gradient for linear regression
Args:
X (ndarray (m,n): Data, m examples with n features
y (ndarray (m,)): target values
w (ndarray (n,)): model parameters
b (scalar) : model parameter
Returns
dj_dw (ndarray (n,)): The gradient of the cost w.r.t. the parameters w.
dj_db (scalar) : The gradient of the cost w.r.t. the parameter b.
"""
m,n = X.shape
dj_dw = np.zeros((n,)) #(n,)
dj_db = 0.
for i in range(m):
f_wb_i = sigmoid(np.dot(X[i],w) + b) #(n,)(n,)=scalar
err_i = f_wb_i - y[i] #scalar
for j in range(n):
dj_dw[j] = dj_dw[j] + err_i * X[i,j] #scalar
dj_db = dj_db + err_i
dj_dw = dj_dw/m #(n,)
dj_db = dj_db/m #scalar
return dj_db, dj_dw
1.5 使用scikit-learn完成逻辑函数回归
scikit-learn是一个基于 Python 的开源机器学习库,它提供了丰富且高效的工具,在实际项目的机器学习算法中有巨大应用
本节只做基础介绍, 未来打算开一个专题引入scikit-learn
# 引入数据集
import numpy as np
X = np.array([[0.5, 1.5], [1,1], [1.5, 0.5], [3, 0.5], [2, 2], [1, 2.5]])
y = np.array([0, 0, 0, 1, 1, 1])
# 训练模型
from sklearn.linear_model import LogisticRegression
lr_model = LogisticRegression()
lr_model.fit(X, y)
# 进行预测
y_pred = lr_model.predict(X)
print("Prediction on training set:", y_pred)
#检查精度
print("Accuracy on training set:", lr_model.score(X, y))
2 过拟合 与 正则化
2.1 过拟合
过拟合是说, 模型在训练数据上表现特别好,可在新数据上却表现不佳,这是因为模型把训练数据里的噪声和细节都学习了,从而失去了泛化能力。
在处理数据时, 我们通常会遇到如下几个情况:

- 未拟合(underfit):不能很好地适应训练集,有较大的偏差(high bias)
- 恰到好处(just right)
- 过拟合(overfit):有较大的方差(high variance)
现在, 我们关心的是为什么会出现过拟合这种情况
- 模型复杂度偏高:像深度神经网络、高阶多项式等复杂的模型,具有很强的拟合能力,它们能够精确地匹配训练数据中的每一个细节。
- 训练数据存在噪声或数量不足:如果训练数据中包含大量噪声,或者数据量较少,模型就可能会把噪声误认为是有用的特征。
- 训练时间过长:在训练过程中,如果模型在训练数据上不断优化,就可能会逐渐记住噪声的模式。

基本的解决思路有以下三个:
- 收集更多的数据.适合有大量数据的情况
- 挑选合适的特征.适合数据量较少,特征量较多的情况.缺点是只使用了训练集的一部分数据,丢掉了另一部分的数据.
- 正则化(Regularization),减小参数,防止某一个特征产生过大的影响
在工程中, 研究者一般采用下面的方法:
- 正则化(Regularization)
- L1/L2正则化:通过在损失函数中添加与模型参数相关的惩罚项,来限制模型参数的大小。接下来我们将详细阐述的L2正则化会惩罚参数的平方和,其表达式为 ( L = L_{loss} + \lambda \sum w_i^2 ),其中 ( \lambda ) 是正则化系数,( w_i ) 是模型参数。
- Dropout:是一种专门针对神经网络的正则化方法,它在训练过程中随机忽略一部分神经元,迫使网络学习更加鲁棒的特征。
- 数据增强(Data Augmentation):通过对现有数据进行变换,如旋转、缩放、裁剪等,生成更多的训练样本,从而减少模型对特定模式的依赖。
- 交叉验证(Cross - Validation):将训练数据分成多个子集,轮流使用其中一个子集作为验证集,其余子集作为训练集,这样可以更准确地评估模型的泛化能力。
- 早停(Early Stopping):在训练过程中,监控模型在验证集上的性能,当性能不再提升时,提前停止训练,避免模型过度拟合。
- 集成学习(Ensemble Learning):将多个不同的模型进行组合,如随机森林、梯度提升树等,通过模型之间的相互补充,降低过拟合的风险。
2.2 L2正则化(Regulation)
L2正则化方法,在回归模型中也叫Ridge回归(岭回归)

如上一节所述, L2正则化是对模型参数的惩罚(或者说缩小). 不过, 一般情况下我们不会缩小b, 因为缩小它对结果没什么变化.
\[J(\mathbf{w},b) = \frac{1}{2m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})^2 + \frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\]- 在成本函数加一个额外的正则化项,目的是缩小每一个参数的值
- 使用系数1/2主要是为了数学上的方便,这样在求导时会消去平方项前的2,得到简洁的结果。这样做尤其契合梯度下降算法.
- 一般不给$b$增加惩罚项,无论是否包括$b$实际上对结果影响都不大
- λ称为正则化参数,可以控制两个不同目标之间的取舍,进一步控制两项的平衡关系
- 第一个目标(与第一项有关):更好地拟合训练集数据
- 第二个目标(与正则化项有关):保持参数尽量的小
- 如果λ被设的太大的话,参数的惩罚程度过大,参数都会接近于0 →$h_w(x)$ = $w_0$ 这样会导致欠拟合; 当然, 太小的$\lambda$没有作用, 函数依然过拟合
- 因此,需要选择一个合适的正则化参数λ
首先确定$\lambda$的数量级, 从[1e-4, 1e-3, 1e-2, 0.1, 1, 10]中尝试
2.2.1 正则化的成本函数
正则化的成本函数是:
\(J(\mathbf{w},b) = \frac{1}{2m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})^2 + \frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\)
在这里:
\(f_{\mathbf{w},b}(\mathbf{x}^{(i)}) = \mathbf{w} \cdot \mathbf{x}^{(i)} + b\)
回顾一下,未正则化的是:
\[J(\mathbf{w},b) = \frac{1}{2m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})^2\]存在差异的项是, \(\frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\)
2.2.2 正则化的逻辑回归成本函数
正则化的逻辑回归成本函数 \(J(\mathbf{w},b) = \frac{1}{m} \sum_{i=0}^{m-1} \left[ -y^{(i)} \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) - \left( 1 - y^{(i)}\right) \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) \right] + \frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\) 在这里: \(f_{\mathbf{w},b}(\mathbf{x}^{(i)}) = sigmoid(\mathbf{w} \cdot \mathbf{x}^{(i)} + b)\)
回顾一下未正则化的表达式:
\[J(\mathbf{w},b) = \frac{1}{m}\sum_{i=0}^{m-1} \left[ (-y^{(i)} \log\left(f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right) - \left( 1 - y^{(i)}\right) \log \left( 1 - f_{\mathbf{w},b}\left( \mathbf{x}^{(i)} \right) \right)\right]\]差异项是 \(\frac{\lambda}{2m} \sum_{j=0}^{n-1} w_j^2\)
def compute_cost_logistic_reg(X, y, w, b, lambda_ = 1):
"""
Computes the cost over all examples
Args:
Args:
X (ndarray (m,n): Data, m examples with n features
y (ndarray (m,)): target values
w (ndarray (n,)): model parameters
b (scalar) : model parameter
lambda_ (scalar): Controls amount of regularization
Returns:
total_cost (scalar): cost
"""
m,n = X.shape
cost = 0.
for i in range(m):
z_i = np.dot(X[i], w) + b #(n,)(n,)=scalar, see np.dot
f_wb_i = sigmoid(z_i) #scalar
cost += -y[i]*np.log(f_wb_i) - (1-y[i])*np.log(1-f_wb_i) #scalar
cost = cost/m #scalar
reg_cost = 0
for j in range(n):
reg_cost += (w[j]**2) #scalar
reg_cost = (lambda_/(2*m)) * reg_cost #scalar
total_cost = cost + reg_cost #scalar
return total_cost #scalar
2.2.2 正则化的梯度下降(线性/逻辑)
重新定义函数$J_{(w,b)}$后, 照常操作梯度下降
线性和逻辑回归的梯度下降是一样的, 只是$f_{\mathbf{w},b}$的计算不同.
\[\begin{align*} \frac{\partial J(\mathbf{w},b)}{\partial w_j} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})x_{j}^{(i)} + \frac{\lambda}{m} w_j \tag{1} \\ \frac{\partial J(\mathbf{w},b)}{\partial b} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)}) \tag{2} \end{align*}\]- m 是数据集中的训练样本数
-
$f_{\mathbf{w},b}(x^{(i)})$ 是模型的预测, $y^{(i)}$ 是目标
- 在 线性 回归模型中 $f_{\mathbf{w},b}(x) = \mathbf{w} \cdot \mathbf{x} + b$
- 在 逻辑 回归模型中
$z = \mathbf{w} \cdot \mathbf{x} + b$
$f_{\mathbf{w},b}(x) = g(z)$
where $g(z)$ is the sigmoid function:
$g(z) = \frac{1}{1+e^{-z}}$
正则化后多出来的项是 \(\frac{\lambda}{m} w_j\).
下面我们尝试深入理解正则化后的 $w_j$ 式子
移项后我们发现 \(\begin{align*} w_j &= w_j (1-\alpha\frac{\lambda}{m} ) - \alpha\frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})x_{j}^{(i)} \end{align*}\)
其中第二项是通常的更新项 $\alpha$通常是0.01左右,$\lambda$通常是1-10,假设m为50,那么$\alpha\frac{\lambda}{m}$为0.0002.因此我们知道$\alpha\frac{\lambda}{m}$一般是大于0的小正数,因此$(1-\alpha\frac{\lambda}{m} )$通常是接近1但小于1的正数.每一次迭代$w_j$都会减小一点
def compute_gradient_linear_reg(X, y, w, b, lambda_):
"""
Computes the gradient for linear regression
Args:
X (ndarray (m,n): Data, m examples with n features
y (ndarray (m,)): target values
w (ndarray (n,)): model parameters
b (scalar) : model parameter
lambda_ (scalar): Controls amount of regularization
Returns:
dj_dw (ndarray (n,)): The gradient of the cost w.r.t. the parameters w.
dj_db (scalar): The gradient of the cost w.r.t. the parameter b.
"""
m,n = X.shape #(number of examples, number of features)
dj_dw = np.zeros((n,))
dj_db = 0.
for i in range(m):
err = (np.dot(X[i], w) + b) - y[i]
for j in range(n):
dj_dw[j] = dj_dw[j] + err * X[i, j]
dj_db = dj_db + err
dj_dw = dj_dw / m
dj_db = dj_db / m
for j in range(n):
dj_dw[j] = dj_dw[j] + (lambda_/m) * w[j]
return dj_db, dj_dw