🏠 总目录📚 本教程 07 · 从线性到神经网络
📑 本页目录(点开跳转)

07 · 从线性到神经网络

28 分钟 | ⭐ 核心 | 🔨 有代码


🎯 一句话

神经网络 = 线性层 + 非线性激活,堆起来。 关键在「非线性激活」这四个字——没有它,堆一百层也还是一条直线。

层数(从输出往输入数)梯度大小(对数刻度)ReLU (f′=1)tanh 实际sigmoid (f′≤0.25)110⁻⁴10⁻⁸10⁻¹²每穿过一层,梯度就乘一次 W·f′ —— 这是【连乘】不是相加⭐ sigmoid 10 层后梯度只剩 10⁻⁶,前几层等于没在训练 —— 这就是 ReLU 是转折点的数学原因
纵轴是对数刻度。每穿过一层,梯度就乘一次 W·f′ —— 这是连乘不是相加。⭐ sigmoid 10 层后只剩 10⁻⁶,前几层等于没在训练 —— 这就是 ReLU 成为转折点的数学原因。

💥 一、先看线性模型的死穴:异或问题

   XOR(异或):两个输入不同时输出 1

     x2
     1 │ ●(0,1)=1      ○(1,1)=0
       │
     0 │ ○(0,0)=0      ●(1,0)=1
       └──────────────────────► x1
         0              1

   ⚠️ 你画不出一条直线,把 ● 和 ○ 分开。

🔨 实测(三行代码,结论惊人)

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.neural_network import MLPClassifier

X = np.array([[0,0],[0,1],[1,0],[1,1]], dtype=float)
y = np.array([0, 1, 1, 0], dtype=float)          # XOR

print("逻辑回归(线性)     ", LogisticRegression().fit(X,y).score(X,y))
print("MLP(隐层4,tanh)   ", MLPClassifier(hidden_layer_sizes=(4,), activation="tanh",
                                          max_iter=5000, random_state=1).fit(X,y).score(X,y))
print("MLP但激活=identity", MLPClassifier(hidden_layer_sizes=(4,), activation="identity",
                                          max_iter=5000, random_state=1).fit(X,y).score(X,y))
逻辑回归(线性)        50%    ← 永远做不对
MLP(隐层4,tanh)     100%    ← 加了隐层 + 非线性,解决了
MLP但激活=identity    50%    ← ⭐ 有隐层但没非线性 → 照样不行!

🔑 第三行是本章的灵魂有隐层不等于有能力。真正带来能力的是「非线性激活」。 因为线性套线性还是线性:W₂(W₁x) = (W₂W₁)x,等价于一个单层线性模型。


🧱 二、神经网络的结构

输入层x₁、x₂、x₃
隐藏层3 个神经元
隐藏层3 个神经元
输出层ŷ
每个圆圈(神经元)做两件事
层与层之间是全连接:前一层每个神经元的输出,都会送进后一层的每个神经元。

一层的完整计算(记住这个式子,整个深度学习都是它的堆叠):

$$\mathbf{a}^{(l)} = f\left(W^{(l)}\mathbf{a}^{(l-1)} + \mathbf{b}^{(l)}\right)$$

💡 人话上一层的输出 → 乘个矩阵加个偏置 → 过一个非线性函数 → 交给下一层。

🔗 连接:这就是全景导论第 2 章里 Transformer 那个 FFN 的结构。 Transformer 只是在这基础上加了注意力机制。

👁️ 神经网络到底在做什么(一个几何直觉)

   每一层做两件事:

   ① 线性变换 Wx+b  →  【旋转 + 拉伸 + 平移】空间
   ② 非线性激活     →  【折叠 / 弯曲】空间

   ⭐ 多层堆叠 = 反复"拉伸 + 折叠",
      把原本【纠缠在一起】的两类数据,掰成【一条直线就能分开】的形状

💡 换个说法

最后一层永远是个线性分类器(就是第 3 章的逻辑回归)。 前面所有层的工作,都是把数据变换成"线性可分"的样子

所以"深度学习"本质上是"自动的特征工程" —— 你在第 16 章手工造的那些交叉特征, 神经网络在用梯度下降自己造。

回头看异或

   原始空间:      变换后(隐层空间):

   ● ○            ●
   ○ ●      →         ●     ← 折叠之后,一条直线就能分开 ✅
                  ○ ○

⚡ 三、激活函数(选 ReLU 就对了)

函数 公式 特点
Sigmoid $\frac{1}{1+e^{-z}}$ 压到 (0,1)。⚠️ 两端梯度趋近 0 → 梯度消失。现在只用在输出层做二分类
Tanh $\frac{e^z-e^{-z}}{e^z+e^{-z}}$ 压到 (-1,1),零中心。比 Sigmoid 好,但仍会梯度消失
ReLU $\max(0, z)$ 默认首选。计算极快、正区间梯度恒为 1 不消失
Leaky ReLU $\max(0.01z, z)$ 解决 ReLU 的"神经元死亡"(负区间梯度为 0)
GELU / SwiGLU 平滑版 ReLU Transformer 时代的主流
   Sigmoid              ReLU
      1 ┤   ╭────         ┤      ╱
    0.5 ┤  ╱              ┤     ╱
      0 ┤─╯               ┤────╱
        └──────           └──────
   两端斜率≈0 💀        正区间斜率=1 ✅
   梯度消失             梯度不消失

🔑 ReLU 为什么是深度学习的转折点: Sigmoid 两端梯度接近 0,反向传播时层层相乘会指数级衰减 → 深层网络根本训不动。 ReLU 正区间梯度恒为 1,梯度能一路传到底。这一个改动让"深"变成了可能。

算一下差距有多大

   Sigmoid 的导数最大值 = 0.25(在 z=0 处,两端更小)

   10 层:0.25¹⁰ ≈ 0.00000095      ← 梯度只剩百万分之一 💀
   30 层:0.25³⁰ ≈ 10⁻¹⁸           ← 完全归零

   ReLU 正区间导数 = 1
   10 层:1¹⁰ = 1                   ← 原样传回 ✅

⚠️ ReLU 的代价:神经元死亡

   ReLU 负区间的导数 = 0

   → 如果某个神经元的输入【一直是负的】
   → 它的梯度永远是 0
   → 权重永远不更新
   → 这个神经元【彻底死了】💀

   常见诱因:学习率太大,一次大更新把权重推到"永远输出负数"的区域

三个解法

解法 说明
降学习率 最常见的根因就是 lr 太大
Leaky ReLU / GELU 负区间给一点点梯度,不会彻底死
合适的初始化 He 初始化(PyTorch 的 kaiming_normal_)就是为 ReLU 设计的

💡 自检方法:训练后统计 (activation == 0).float().mean()如果某层超过 50% 的神经元恒为 0,多半有大量死亡神经元。

🔧 初始化:一个容易被跳过但很关键的细节

   初始化太小 → 信号层层衰减 → 深层几乎收不到信息
   初始化太大 → 信号层层放大 → 激活值爆炸

   ⭐ 好的初始化让【每一层的激活值方差保持一致】

   Xavier/Glorot:为 tanh/sigmoid 设计,方差 ∝ 1/(fan_in + fan_out)
   He/Kaiming ⭐:为 ReLU 设计,方差 ∝ 2/fan_in
                  (因为 ReLU 砍掉了一半,所以要乘 2 补回来)

💡 PyTorch 的默认初始化对 nn.Linear 已经不错,但用 ReLU 时显式指定更稳:

nn.init.kaiming_normal_(layer.weight, nonlinearity="relu")

🌈 四、万能近似定理(以及它的误导性)

理论:只要隐藏层够宽,一个隐藏层的神经网络就能逼近任意连续函数。

⚠️ 但这个定理有很强的误导性

定理说的 定理没说的
存在这样的网络 怎么找到它(能否用梯度下降训出来)
一层就够 需要指数级宽才行
能拟合训练数据 能否泛化到新数据

实践结论深比宽更高效。 同样的参数量,深层网络能表达的函数复杂度远高于浅层—— 因为每一层都在前一层的特征上做组合,是层级抽象(第 12 章 CNN 会看得很清楚)。


🏗️ 五、一个完整 MLP 的样子

import torch.nn as nn

model = nn.Sequential(
    nn.Linear(30, 64),   # 输入 30 个特征 → 64 个神经元
    nn.ReLU(),           # ⭐ 非线性
    nn.Dropout(0.2),     # 正则化(第 10 章)
    nn.Linear(64, 32),
    nn.ReLU(),
    nn.Linear(32, 1),    # 输出 1 个数
    # 二分类不用加 Sigmoid —— 用 BCEWithLogitsLoss,它内部做了且数值更稳
)

结构怎么定(经验值)

决策 经验
几层 表格数据 2–4 层就够;再深收益很小
每层多宽 常见 64/128/256,逐层递减
激活 ReLU(不确定时的默认)
输出层 回归:不加激活;二分类:不加(用 BCEWithLogitsLoss);多分类:不加(用 CrossEntropyLoss)

⚠️ 表格数据的现实神经网络在中小规模表格数据上经常打不过 GBDT(第 4 章)。 别默认上神经网络——第 19 章挑战 B 会让你亲手验证这一点。


🔗 和站内其他章的关系

相关的地方 根因在这
推荐算法第 8 章的 DeepFM、DIN 它们的"Deep"部分就是本章的 MLP
全景导论第 2 章的 FFN 就是本章的线性层+激活
Kaggle 板块里 NN 常输给 LightGBM 表格数据上 GBDT 的归纳偏置更合适
第 3 章逻辑回归 就是"零隐层的神经网络"

✅ 检查点

  1. 异或实验里,activation="identity" 的 MLP 为什么也是 50%?
  2. 一层神经网络做哪两件事?写出那个式子。
  3. 用几何直觉说:神经网络到底在做什么?为什么说它是"自动的特征工程"?
  4. 为什么 ReLU 是深度学习的转折点?Sigmoid 10 层后梯度剩多少?
  5. ReLU 的代价是什么?三个解法?怎么自检?
  6. 初始化太小/太大分别会怎样?He 初始化为什么要乘 2?
  7. 万能近似定理有哪三个误导性?
  8. 为什么二分类的输出层不加 Sigmoid?
👀 答案
  1. identity 激活 = 没有非线性,线性套线性还是线性(W₂W₁x 等价于单层),所以退化成逻辑回归,画不出异或的分界。
  2. ①线性变换 z=Wx+b ②非线性激活 a=f(z)。式子:a⁽ˡ⁾ = f(W⁽ˡ⁾a⁽ˡ⁻¹⁾ + b⁽ˡ⁾)。
  3. 线性层旋转拉伸空间,激活函数折叠空间——反复拉伸折叠,把纠缠的两类掰成一条直线能分开的形状。最后一层永远是线性分类器,前面所有层都在把数据变换成"线性可分"的样子——所以它是自动的特征工程。
  4. Sigmoid 两端梯度趋近 0,反传层层相乘指数衰减,深层训不动。10 层后 0.25¹⁰ ≈ 百万分之一,30 层后 10⁻¹⁸。ReLU 正区间梯度恒为 1,原样传回。
  5. 神经元死亡——输入一直为负则梯度永远是 0,权重再也不更新。解法:①降学习率(最常见根因)②Leaky ReLU/GELU ③He 初始化。自检:统计 (activation == 0).float().mean()某层超 50% 恒为 0 就有问题
  6. 太小→信号层层衰减深层收不到信息;太大→激活值爆炸。好的初始化让每层激活值方差保持一致He 乘 2 是因为 ReLU 砍掉了一半(负区间归零),要补回来
  7. 只说存在不说怎么找到、一层需要指数级宽、能拟合不代表能泛化。实践上深比宽更高效
  8. BCEWithLogitsLoss,它内部合并了 Sigmoid 和交叉熵,数值上更稳定(避免 log(0))。

🛑 可以停在这里

走神救援

神经网络=线性层+非线性激活堆叠。异或实验三行结论:线性50%、MLP+tanh 100%、MLP但identity激活还是50%——证明带来能力的是非线性不是隐层(线性套线性还是线性)。一层=Wx+b后过激活。⭐几何直觉:线性层旋转拉伸空间,激活折叠空间;最后一层永远是线性分类器,前面所有层都在把数据掰成"线性可分"的形状——所以深度学习本质是自动的特征工程。激活选ReLU(正区间梯度恒为1;Sigmoid 10层后只剩百万分之一,30层后10⁻¹⁸,这就是ReLU成为转折点的原因)。⚠️ ReLU的代价是神经元死亡(输入恒负→梯度永远0→彻底死掉),根因多半是学习率太大;自检:某层超50%激活恒为0就有问题。初始化要让每层激活方差一致,He初始化乘2是因为ReLU砍掉了一半。万能近似定理有误导性:只说存在不说怎么找到,实践上深比宽高效。表格数据上NN常输给GBDT。

下一节 👉 08-反向传播.md

打卡记录保存在你的浏览器里,首页能看到总进度