从零到一:手把手构建你的第一个浅层神经网络
1. 神经网络入门从逻辑回归到浅层网络刚学完逻辑回归的你可能会好奇为什么还需要神经网络想象一下逻辑回归就像一把瑞士军刀能解决简单问题但遇到复杂任务时就力不从心了。神经网络则像一整个工具箱通过组合多个逻辑回归单元能够处理更复杂的模式识别。我在第一次接触神经网络时发现它最神奇的地方在于隐藏层的作用。这个夹在输入和输出之间的中间层就像个黑盒子能够自动学习数据的抽象特征。比如识别猫的图片时第一层可能学习边缘特征第二层组合成形状第三层就能识别出猫耳朵这样的高级特征。让我们用Python和NumPy搭建一个最简单的浅层神经网络单隐藏层。这个结构包含输入层特征向量隐藏层4个神经元输出层1个神经元import numpy as np # 网络结构参数 input_size 3 # 输入特征数 hidden_size 4 # 隐藏层神经元数 output_size 1 # 输出层2. 搭建神经网络的核心组件2.1 参数初始化打破对称性的艺术新手最容易犯的错误就是把所有参数初始化为零。我当初也这么干过结果网络完全学不到任何东西。这是因为对称初始化会导致所有神经元学习相同的特征。正确的做法是用小随机数初始化权重def initialize_parameters(): W1 np.random.randn(hidden_size, input_size) * 0.01 b1 np.zeros((hidden_size, 1)) W2 np.random.randn(output_size, hidden_size) * 0.01 b2 np.zeros((output_size, 1)) return {W1: W1, b1: b1, W2: W2, b2: b2}这里乘以0.01是为了避免初始值过大导致梯度消失特别是使用sigmoid/tanh时。我在实际项目中发现对于ReLU激活函数可以使用He初始化乘以√(2/n)效果更好。2.2 激活函数神经网络的非线性灵魂试过各种激活函数后我的经验是Sigmoid只用在二分类输出层其他情况基本淘汰Tanh效果通常比sigmoid好但面临梯度消失问题ReLU最常用的默认选择计算简单且梯度不会饱和Leaky ReLU解决神经元死亡问题适合深层网络def relu(Z): return np.maximum(0, Z) def sigmoid(Z): return 1/(1np.exp(-Z))3. 前向传播数据如何流过网络3.1 单样本计算流程我们先看单个样本如何通过网络输入层 → 隐藏层Z1 W1·X b1 → A1 g(Z1)隐藏层 → 输出层Z2 W2·A1 b2 → A2 σ(Z2)def forward_propagation(X, parameters): W1, b1, W2, b2 parameters.values() Z1 np.dot(W1, X) b1 A1 relu(Z1) Z2 np.dot(W2, A1) b2 A2 sigmoid(Z2) return {Z1: Z1, A1: A1, Z2: Z2, A2: A2}3.2 多样本向量化实现实际训练时需要同时处理多个样本。通过矩阵运算可以大幅提升效率输入X从向量变为矩阵每列是一个样本所有中间结果也变为矩阵# X.shape (输入特征数, 样本数) # 前向传播代码不变自动支持矩阵运算我曾在没有向量化的版本上训练网络速度慢了近100倍。向量化后1000个样本的批量处理时间与单个样本几乎相同4. 反向传播误差如何指导学习4.1 损失函数计算对于二分类问题使用交叉熵损失def compute_cost(A2, Y): m Y.shape[1] logprobs np.multiply(np.log(A2), Y) np.multiply(np.log(1-A2), (1-Y)) return -np.sum(logprobs)/m4.2 梯度计算与参数更新反向传播就像逆向参观工厂记录每个操作对最终结果的影响。我们需要计算输出层梯度隐藏层梯度参数更新量def backward_propagation(parameters, cache, X, Y): m X.shape[1] W2 parameters[W2] A1 cache[A1] A2 cache[A2] dZ2 A2 - Y dW2 np.dot(dZ2, A1.T)/m db2 np.sum(dZ2, axis1, keepdimsTrue)/m dZ1 np.dot(W2.T, dZ2) * (A1 0) # ReLU导数 dW1 np.dot(dZ1, X.T)/m db1 np.sum(dZ1, axis1, keepdimsTrue)/m return {dW1: dW1, db1: db1, dW2: dW2, db2: db2}5. 实战演练训练你的第一个神经网络5.1 合成数据集准备让我们创建一个简单的二分类数据集def load_dataset(): np.random.seed(1) X np.random.randn(2, 100) Y (X[0,:] X[1,:]).astype(int).reshape(1,100) return X, Y5.2 完整训练流程将前面所有组件组合起来def model(X, Y, learning_rate0.01, iterations1000): parameters initialize_parameters() for i in range(iterations): # 前向传播 cache forward_propagation(X, parameters) # 计算损失 cost compute_cost(cache[A2], Y) # 反向传播 grads backward_propagation(parameters, cache, X, Y) # 参数更新 parameters[W1] - learning_rate * grads[dW1] parameters[b1] - learning_rate * grads[db1] parameters[W2] - learning_rate * grads[dW2] parameters[b2] - learning_rate * grads[db2] if i % 100 0: print(f迭代 {i} 次后的损失: {cost}) return parameters5.3 模型评估与调试训练完成后我们需要评估模型表现def predict(parameters, X): cache forward_propagation(X, parameters) predictions (cache[A2] 0.5).astype(int) return predictions # 计算准确率 accuracy np.mean(predictions Y) * 100调试神经网络时我习惯先在小数据集上过拟合准确率达到100%确保实现正确。然后再扩展到完整数据集调整超参数。