CSDN博客-第3天-XOR与两层MLP

发布时间:2026/7/2 16:00:06
CSDN博客-第3天-XOR与两层MLP 【深度学习入门 Day 3】从线性分不开到两层 MLP用 NumPy 训练 XOR本文记录深度学习学习第 3 天的内容从 XOR 问题出发理解为什么单个神经元只能做线性分类为什么需要隐藏层以及如何用 NumPy 手写一个两层 MLP。最后补充激活函数的选择为什么这次用tanh比ReLU更容易收敛以及不同场景下该怎么选激活函数。文章目录一、今天的问题单个神经元为什么不够二、XOR一条直线分不开的数据三、从单层模型到两层 MLP四、前向传播从输入到预测五、损失函数二元交叉熵 BCE六、反向传播从输出层传回隐藏层七、为什么换成 tanh 后更容易学会 XOR八、激活函数怎么选九、完整 NumPy 训练代码十、今日总结十一、课后自测一、今天的问题单个神经元为什么不够前两天我们训练的是一个 sigmoid 神经元z X w b a sigmoid(z)如果这是一个二维二分类问题那么分类边界来自X w b 0展开就是w1*x1 w2*x2 b 0这在二维平面里是一条直线。所以单个 sigmoid 神经元本质上只能做一件事用一条直线把平面分成两边。如果数据刚好可以被一条直线分开单个神经元就够用。但如果数据本身不是线性可分的一条直线再怎么移动、旋转也分不开。二、XOR一条直线分不开的数据今天使用经典的 XOR 数据Xnp.array([[0.0,0.0],[0.0,1.0],[1.0,0.0],[1.0,1.0],])ynp.array([[0.0],[1.0],[1.0],[0.0]])表格形式x1x2y000011101110XOR 的规律是两个输入相同 - 0 两个输入不同 - 1在平面上看标签为 1 的两个点在对角线上(0, 1), (1, 0)标签为 0 的两个点也在另一条对角线上(0, 0), (1, 1)这就导致没有任何一条直线可以把两个 1 和两个 0 完全分开。这就是 XOR 经常被用来解释神经网络的原因它非常小但足够说明“线性模型不够用”。三、从单层模型到两层 MLP为了解决 XOR我们加一个隐藏层。今天的模型结构是X ↓ z1 X W1 b1 ↓ a1 tanh(z1) ↓ z2 a1 W2 b2 ↓ a2 sigmoid(z2)这就是一个最小版本的两层 MLP。如果隐藏层有 4 个神经元各个变量的形状是X.shape (4, 2) W1.shape (2, 4) b1.shape (1, 4) z1.shape (4, 4) a1.shape (4, 4) W2.shape (4, 1) b2.shape (1, 1) z2.shape (4, 1) a2.shape (4, 1)这里最容易错的是W1的形状。因为X W1 (4, 2) (?, ?) (4, 4)所以W1.shape (2, 4)口诀X: 样本数 x 输入特征数 W1: 输入特征数 x 隐藏神经元数 Z1: 样本数 x 隐藏神经元数隐藏层的意义不是“多算了一层”而是学习一组新的特征表示让原本线性分不开的数据在新的表示空间里变得更容易分开。四、前向传播从输入到预测先初始化参数np.random.seed(42)W1np.random.randn(2,4)*0.1b1np.zeros((1,4))W2np.random.randn(4,1)*0.1b2np.zeros((1,1))定义激活函数deftanh(z):returnnp.tanh(z)defsigmoid(z):return1/(1np.exp(-z))前向传播z1X W1b1 a1tanh(z1)z2a1 W2b2 a2sigmoid(z2)刚初始化时模型基本还不会判断输出通常都在 0.5 附近a2 ≈ [0.5, 0.5, 0.5, 0.5]这很正常。随机初始化的模型一开始就是在猜。五、损失函数二元交叉熵 BCE二分类任务常用 BCE也就是 Binary Cross Entropydefbce_loss(a2,y):eps1e-8return-np.mean(y*np.log(a2eps)(1-y)*np.log(1-a2eps))单样本公式是L -[y log(a) (1-y) log(1-a)]分两种情况看。如果真实标签是 1L -log(a)此时a越接近 1损失越小。如果真实标签是 0L -log(1-a)此时a越接近 0损失越小。所以 BCE 做的事情可以理解成如果真实是 1就惩罚模型没有把概率预测到 1如果真实是 0就惩罚模型没有把概率预测到 0。代码里的eps 1e-8是为了防止出现log(0)避免程序得到inf或nan。六、反向传播从输出层传回隐藏层1. 输出层梯度对于sigmoid BCE输出层梯度有一个非常常用的简化dZ2(a2-y)/N也就是说输出层误差信号 预测值 - 真实值。直觉上很好理解。如果真实标签是 1但模型预测 0.2a2 - y 0.2 - 1 -0.8梯度是负的参数更新会让z2变大从而让sigmoid(z2)也变大。如果真实标签是 0但模型预测 0.8a2 - y 0.8 - 0 0.8梯度是正的参数更新会让z2变小从而让sigmoid(z2)也变小。输出层参数梯度dW2a1.T dZ2 db2np.sum(dZ2,axis0,keepdimsTrue)形状关系a1.T dZ2 (4, 4) (4, 1) (4, 1)正好对应W2.shape (4, 1)2. 隐藏层梯度误差信号继续传回隐藏层dA1dZ2 W2.T形状是(4, 1) (1, 4) (4, 4)因为隐藏层用了tanha1tanh(z1)而tanh(z) 1 - tanh(z)^2所以dZ1dA1*(1-a1**2)第一层参数梯度dW1X.T dZ1 db1np.sum(dZ1,axis0,keepdimsTrue)形状闭环dW1.shape W1.shape (2, 4) db1.shape b1.shape (1, 4)这说明反向传播的矩阵形状是对的。七、为什么换成 tanh 后更容易学会 XOR一开始尝试用ReLUdefrelu(z):returnnp.maximum(0,z)可能会出现模型卡住的情况例如预测变成pred [1, 1, 1, 0]它学到了一个不完整的规则只有 [1, 1] 判成 0其他都判成 1但 XOR 真正应该是pred [0, 1, 1, 0]这不是代码一定写错了而是优化过程可能卡在了不好的位置。ReLU的特点是ReLU(z) max(0, z)当z 0时ReLU(z) 0也就是说对应神经元可能收不到梯度参数不容易继续调整。这种现象常被称为“ReLU 死亡”。而tanh的输出范围是(-1, 1)它有几个特点输出是零中心的既有正值也有负值。对 XOR 这种需要表达“相同/不同”“正反关系”的小问题比较友好。在输入不太大的区域梯度比较连续不会像 ReLU 那样一半区域直接为 0。所以这次 XOR 小实验中把隐藏层从ReLU换成tanh后模型更容易训练到pred [0, 1, 1, 0]要注意不是说 tanh 永远比 ReLU 好而是具体问题、网络深度、初始化、学习率都会影响激活函数的效果。八、激活函数怎么选1. Sigmoid公式sigmoid(z) 1 / (1 exp(-z))输出范围(0, 1)适合二分类输出层需要把结果解释为概率的地方不太适合深层网络的隐藏层原因容易梯度消失输出不是零中心本次实验中sigmoid用在输出层a2sigmoid(z2)因为我们要输出“属于 1 类的概率”。2. Tanh公式tanh(z) (exp(z) - exp(-z)) / (exp(z) exp(-z))输出范围(-1, 1)适合小型网络输入范围较小的问题RNN 等传统序列模型像 XOR 这种需要表达正负关系的小实验优点输出零中心比 sigmoid 更适合做隐藏层缺点输入绝对值很大时仍然会梯度饱和很深的网络中不如 ReLU 系列常用本次实验中tanh用在隐藏层a1tanh(z1)3. ReLU公式ReLU(z) max(0, z)适合大多数前馈神经网络的隐藏层CNN深层网络优点计算简单正区间梯度稳定训练深层网络通常更快缺点当神经元长期落在负区间时梯度为 0可能出现“死亡 ReLU”对学习率和初始化有一定敏感性经验如果没有特别理由隐藏层通常可以先试 ReLU。4. Leaky ReLU公式LeakyReLU(z) max(0.01z, z)适合想用 ReLU但担心神经元死亡小数据或训练不稳定时优点负区间也保留一点梯度比 ReLU 更不容易完全卡死5. GELU常见于 TransformerGELU(z) z * Φ(z)适合TransformerBERT / GPT 等大模型结构经验如果你在写 Transformer默认考虑 GELU 或其近似版本。6. Softmax适合多分类输出层它会把多个类别的分数转成概率分布所有类别概率之和 1例如数字识别 10 分类[0, 1, 2, ..., 9]输出层一般用softmax cross entropy简单选择表场景推荐激活函数二分类输出层Sigmoid多分类输出层Softmax普通隐藏层ReLUReLU 容易卡住Leaky ReLU小型实验 / XOR / 零中心隐藏表示TanhTransformer / 大模型GELU九、完整 NumPy 训练代码importnumpyasnp# XOR 数据Xnp.array([[0.0,0.0],[0.0,1.0],[1.0,0.0],[1.0,1.0],])ynp.array([[0.0],[1.0],[1.0],[0.0]])np.random.seed(42)# 输入层 2 个特征隐藏层 4 个神经元W1np.random.randn(2,4)*0.1b1np.zeros((1,4))# 输出层 1 个神经元W2np.random.randn(4,1)*0.1b2np.zeros((1,1))lr0.1deftanh(z):returnnp.tanh(z)defsigmoid(z):return1/(1np.exp(-z))defbce_loss(a2,y):eps1e-8return-np.mean(y*np.log(a2eps)(1-y)*np.log(1-a2eps))forstepinrange(10001):# forwardz1X W1b1 a1tanh(z1)z2a1 W2b2 a2sigmoid(z2)lossbce_loss(a2,y)# backwardNlen(y)dZ2(a2-y)/N dW2a1.T dZ2 db2np.sum(dZ2,axis0,keepdimsTrue)dA1dZ2 W2.T dZ1dA1*(1-a1**2)dW1X.T dZ1 db1np.sum(dZ1,axis0,keepdimsTrue)# updateW2W2-lr*dW2 b2b2-lr*db2 W1W1-lr*dW1 b1b1-lr*db1ifstep%10000:pred(a20.5).astype(int)print(fstep{step:05d}, floss{loss:.6f}, fa2{a2.ravel().round(3)}, fpred{pred.ravel()})如果训练成功最终预测会接近pred [0 1 1 0]这说明模型已经学会了 XOR。十、今日总结今天的核心内容可以压缩成 5 点单个 sigmoid 神经元的分类边界是一条直线因此无法解决 XOR。XOR 是线性不可分问题需要隐藏层引入非线性表示。两层 MLP 的前向传播是X - tanh hidden - sigmoid output。sigmoid BCE的输出层梯度可以简化为dZ2 (a2 - y) / N。激活函数会影响训练效果本次 XOR 小实验中tanh比ReLU更容易收敛。最终要记住这句话网络结构决定模型有没有能力表达某种规律激活函数、初始化和学习率会影响训练能不能顺利找到这组参数。十一、课后自测为什么单个 sigmoid 神经元只能得到一条线性分类边界XOR 为什么不能被一条直线分开如果X.shape (4, 2)隐藏层有 4 个神经元为什么W1.shape (2, 4)sigmoid BCE为什么可以得到dZ2 (a2 - y) / Ntanh(z)为什么可以写成1 - a1 ** 2ReLU 在什么情况下可能卡住二分类输出层为什么通常用 sigmoid而多分类输出层通常用 softmax