1. 逻辑回归:从数学公式到Python实现
第一次接触吴恩达老师的《神经网络基础》课程时,我被逻辑回归的优雅设计深深吸引。这个看似简单的算法,却蕴含着神经网络最基础的思想。让我们从一个实际场景开始:假设你正在开发一个猫咪识别器,输入是一张64x64像素的图片,输出是0(非猫)或1(猫)。
逻辑回归的核心在于sigmoid函数,这个神奇的"S"形曲线能将任意实数映射到(0,1)区间。在Python中实现它只需要几行代码:
import numpy as np def sigmoid(z): return 1 / (1 + np.exp(-z))这个函数的导数有个美妙特性:σ'(z) = σ(z)(1-σ(z))。这个特性在反向传播时会大大简化计算。我曾在项目中忽略这个特性,结果梯度计算效率低了近40%。
完整的逻辑回归预测函数可以表示为:
def predict(w, b, X): z = np.dot(w.T, X) + b return sigmoid(z)这里w是权重向量,b是偏置项。初学者常犯的错误是维度不匹配,记得检查w.T的shape应该是(1, n_x),X是(n_x, m),其中n_x是特征数,m是样本数。
2. 损失函数:衡量预测与现实的差距
在训练模型时,我们需要量化预测值与真实值的差距。平方误差看起来直观,但在逻辑回归中会导致非凸优化问题。吴恩达老师推荐的交叉熵损失函数才是正解:
def compute_loss(y_hat, y): return - (y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat))这个函数有个巧妙的设计:当y=1时,-log(y_hat)促使y_hat趋近1;当y=0时,-log(1-y_hat)促使y_hat趋近0。我曾用Matplotlib可视化过这个函数,能清晰看到它对错误预测的"惩罚"力度。
将单个样本的损失扩展到整个训练集,我们得到成本函数:
def compute_cost(y_hat, y): m = y.shape[1] return np.sum(compute_loss(y_hat, y)) / m3. 梯度下降:寻找最优参数的登山指南
梯度下降是优化参数的核心算法。想象你在浓雾中下山,每次只能试探周围最陡的下降方向。数学上,参数的更新规则是:
def gradient_descent(w, b, X, y, learning_rate, iterations): m = y.shape[1] costs = [] for i in range(iterations): y_hat = predict(w, b, X) cost = compute_cost(y_hat, y) costs.append(cost) # 计算梯度 dz = y_hat - y dw = np.dot(X, dz.T) / m db = np.sum(dz) / m # 更新参数 w -= learning_rate * dw b -= learning_rate * db return w, b, costs学习率的选择很关键。太大可能导致震荡,太小收敛太慢。我的经验是从0.01开始尝试,每隔几次迭代观察成本变化,如果震荡就减小10倍,如果下降缓慢就增大10倍。
4. 向量化:告别低效的for循环
当处理大规模数据时,for循环会成为性能瓶颈。向量化利用CPU/GPU的并行计算能力,可以带来数百倍的加速。对比下面两种计算方式:
# 非向量化版本 z = np.zeros((1, m)) for i in range(m): z[0, i] = np.dot(w.T, X[:, i]) + b # 向量化版本 z = np.dot(w.T, X) + b在10万个样本的测试中,向量化版本仅需1.9毫秒,而非向量化版本需要531毫秒!这种差异在大规模神经网络中会被进一步放大。
完整的向量化逻辑回归实现如下:
def logistic_regression(X, y, learning_rate=0.01, iterations=1000): n_x, m = X.shape w = np.zeros((n_x, 1)) b = 0 costs = [] for i in range(iterations): # 正向传播 z = np.dot(w.T, X) + b y_hat = sigmoid(z) # 计算成本 cost = compute_cost(y_hat, y) costs.append(cost) # 反向传播 dz = y_hat - y dw = np.dot(X, dz.T) / m db = np.sum(dz) / m # 更新参数 w -= learning_rate * dw b -= learning_rate * db # 每100次迭代打印成本 if i % 100 == 0: print(f"迭代 {i}: 成本 {cost}") return w, b, costs5. 实战技巧与常见陷阱
在实际应用中,有几个关键点需要注意:
广播机制:NumPy的广播功能强大但也容易出错。比如计算百分比时:
# 正确做法 percentage = 100 * A / cal.reshape(1, 4) # 危险做法 percentage = 100 * A / cal # 可能引发不可预知的广播维度检查:使用assert语句确保矩阵维度正确:
assert(w.shape == (n_x, 1)) assert(X.shape == (n_x, m))特征缩放:虽然逻辑回归不像某些算法那样严格要求特征缩放,但适当的归一化可以加速收敛:
X = (X - np.mean(X, axis=1, keepdims=True)) / np.std(X, axis=1, keepdims=True)初始化:权重初始化为零在逻辑回归中可行,但在深层网络中会导致对称性问题。我习惯用:
w = np.random.randn(n_x, 1) * 0.016. 可视化:理解模型行为的窗口
可视化是理解模型的关键。我常用Matplotlib绘制:
- 学习曲线:观察成本随迭代次数的变化
plt.plot(costs) plt.ylabel('成本') plt.xlabel('迭代次数')- 决策边界:对于二维特征,可以绘制分类边界
x1 = np.linspace(X[0,:].min(), X[0,:].max(), 100) x2 = -(w[0]*x1 + b) / w[1] plt.plot(x1, x2, 'r')- Sigmoid曲线:直观理解预测概率
z = np.linspace(-10, 10, 100) plt.plot(z, sigmoid(z))7. 性能优化进阶技巧
当数据量极大时,还可以考虑:
Mini-batch梯度下降:每次迭代使用部分样本
batch_size = 64 for i in range(0, m, batch_size): X_batch = X[:, i:i+batch_size] y_batch = y[:, i:i+batch_size] # 在该batch上执行梯度下降动量法:加速收敛并减少震荡
v_dw = 0 v_db = 0 beta = 0.9 v_dw = beta * v_dw + (1 - beta) * dw v_db = beta * v_db + (1 - beta) * db w -= learning_rate * v_dw b -= learning_rate * v_db学习率衰减:随着迭代逐步减小学习率
learning_rate = 0.01 * (1 / (1 + decay_rate * epoch_num))8. 从逻辑回归到神经网络
逻辑回归可以看作单层神经网络。理解它的运作机制是学习更复杂网络的基础。当你掌握了:
- 前向传播计算预测值
- 损失函数衡量误差
- 反向传播计算梯度
- 梯度下降更新参数
这些核心概念后,扩展到深层神经网络就水到渠成了。在后续学习中,你会发现全连接层本质上就是多个逻辑回归单元的叠加,而softmax回归则是逻辑回归在多分类问题上的扩展。
在实际项目中,我建议先用逻辑回归建立baseline,确保数据管道和评估指标正确,然后再尝试更复杂的模型。这种循序渐进的方法能帮你快速定位问题所在。