深度学习笔记
Course 1:神经网络与深度学习 学习目标 :从零开始理解神经网络的核心原理,掌握前向传播与反向传播的数学推导,能够用纯 NumPy 搭建一个任意深度的神经网络。 目录 第一章:深度学习引言 第一章深度学习引言 第二章:神经网络编程基础——逻辑回归 第二章神经网络编程基础逻辑回归 第三章:浅层神经网络 第三章浅层神经网络 第四章:深层神经网络 第四章深层神经网络 本课小结与关键公式速查 本课小结与关键公式速查 第一章:深度学习引言…
At a glance
Reading effort and structure before you settle in.
Reader briefing
In this note
47 sections
Reading deck
Course 1:神经网络与深度学习
学习目标:从零开始理解神经网络的核心原理,掌握前向传播与反向传播的数学推导,能够用纯 NumPy 搭建一个任意深度的神经网络。
目录
第一章:深度学习引言
1.1 什么是神经网络?
直觉理解
想象你是一个房产中介,客户问你:"这套房子值多少钱?"你脑子里会自动考虑面积、地段、楼层等因素,然后给出一个预估价格。神经网络做的事情本质上是一样的——它是一个"从输入到输出的函数",只不过这个函数的参数是通过数据自动学习的。
最简单的神经网络就是一个单神经元:
输入 x (面积) --→ [神经元] --→ 输出 y (价格)
这个神经元内部做的事情:
- 计算 (线性变换)
- 通过激活函数得到 (ReLU 激活)
为什么用 而不是直接用 ?因为房价不能为负数!这个 函数叫做 ReLU(Rectified Linear Unit,修正线性单元),它是深度学习中最常用的激活函数。
从单神经元到神经网络
当输入特征变多(面积、卧室数、邮编、财富水平...),我们可以把多个神经元组合起来:
graph LR x1[面积] --> h1((家庭大小)) x2[卧室数] --> h1 x1 --> h2((步行适宜度)) x3[邮编] --> h2 x3 --> h3((学区质量)) x4[财富水平] --> h3 h1 --> y((预测价格)) h2 --> y h3 --> y
中间的 就是隐藏层神经元。有趣的是,我们不需要手动告诉网络"用面积和卧室数来判断家庭大小"——给它足够的数据,它会自己学到这些中间表示。这就是神经网络的魅力所在。
1.2 监督学习的类型
深度学习近年来最大的实际价值来自监督学习(Supervised Learning)——给定输入 和标签 ,学习映射 。
| 应用场景 | 输入 | 输出 | 网络类型 |
|---|---|---|---|
| 房价预测 | 房屋特征 | 价格 | 标准 NN |
| 广告点击预测 | 广告+用户信息 | 是否点击(0/1) | 标准 NN |
| 图片分类 | 图像像素 | 类别标签(1~1000) | CNN(卷积神经网络) |
| 语音识别 | 音频片段 | 文字转写 | RNN(循环神经网络) |
| 机器翻译 | 英文文本 | 中文文本 | RNN / Transformer |
| 自动驾驶 | 图像+雷达 | 其他车辆位置 | 混合架构 |
经验法则:
- 结构化数据(表格、数据库)→ 标准全连接网络
- 图像数据 → CNN
- 序列数据(文本、音频、时间序列)→ RNN / Transformer
1.3 为什么深度学习现在才火起来?
深度学习的核心想法(多层神经网络)在 1980 年代就存在了,为什么直到 2012 年才爆发?三个驱动力:
graph TD A[深度学习的崛起] --> B[📊 数据规模] A --> C[💻 计算能力] A --> D[🧮 算法创新] B --> B1[互联网 + 移动设备<br>产生海量标注数据] C --> C1[GPU / TPU 大规模并行计算] D --> D1[ReLU 替代 Sigmoid<br>加速梯度下降收敛]
关键洞察:在数据量较小的时候,传统机器学习(SVM、随机森林等)可能表现得和神经网络一样好甚至更好。但随着数据量增大,大型神经网络的性能会持续提升,而传统算法会趋于平坦。
这就是为什么在大数据时代,深度学习成为了王者。
第二章:神经网络编程基础——逻辑回归
逻辑回归(Logistic Regression)不只是一个简单的分类算法——它是理解整个神经网络的基石。把逻辑回归彻底搞懂,神经网络就只是"多个逻辑回归的堆叠"。
2.1 二分类问题与符号约定
问题定义:给定一张图片,判断是不是猫。输出 。
符号约定(贯穿整个课程,请牢记):
| 符号 | 含义 | 维度 |
|---|---|---|
| 训练样本数量 | 标量 | |
| 输入特征维度 | 标量 | |
| 第 个训练样本 | ||
| 输入矩阵(所有样本列拼接) | ||
| 第 个样本标签 | 标量 | |
| 标签向量 |
图像表示:一张 64×64 的 RGB 图像会被展平成一个 维的向量。
为什么 是 而不是 ? 这是深度学习的约定——每个样本是一列。这样做的好处是: 可以一次性计算所有样本的线性变换,代码更简洁。
2.2 逻辑回归模型
目标:学习参数 和 ,使得 。
第一步:线性变换
其中 ,。但是 的范围是 ,而我们需要概率值在 之间。
第二步:Sigmoid 挤压
Sigmoid 函数的关键性质:
- 当 时,
- 当 时,
- 当 时,
- 导数:(后面反向传播要用!)
import numpy as np
def sigmoid(z):
"""Sigmoid 激活函数"""
return 1 / (1 + np.exp(-z))
2.3 损失函数与代价函数
损失函数(Loss Function)——衡量单个样本的预测好坏:
直觉理解:
- 当 时,,要让 最小,就要让 尽可能大(接近 1)
- 当 时,,要让 最小,就要让 尽可能小(接近 0)
为什么不用均方误差 ? 因为当 时,MSE 是非凸的,梯度下降容易陷入局部最优。而交叉熵损失是凸函数,保证能找到全局最优。
代价函数(Cost Function)——衡量所有样本的平均损失:
2.4 梯度下降
直觉:想象你站在一座山上(代价函数的曲面),闭着眼睛想要走到最低点。策略很简单——每一步都沿着最陡峭的方向往下走。
更新规则:
其中 是学习率(Learning Rate),控制每一步走多远。
- 太大 → 步子迈太大,可能越过最低点,来回震荡
- 太小 → 步子太小,收敛极慢
- 通常从 0.01 开始尝试
2.5 计算图与反向传播
计算图是理解反向传播的关键工具。以逻辑回归为例(单个样本):
前向传播(从左到右):
x, w, b → z = w^Tx + b → a = σ(z) → L(a, y)
反向传播(从右到左,利用链式法则):
dL/da → dL/dz → dL/dw, dL/db
逐步推导:
Step 1:(输出对激活值的导数)
Step 2:(利用链式法则 + sigmoid 导数)
这个结果非常简洁优美! 就是"预测值减去真实值"。
Step 3: 和
整个训练集的梯度(取平均):
2.6 向量化:告别 for 循环
核心原则:在深度学习中,显式的 for 循环是性能杀手。NumPy 的向量化运算利用了 CPU/GPU 的 SIMD 指令,速度快几百倍。
for 循环版本(慢):
# 不要这样写!
z = 0
for i in range(n_x):
z += w[i] * x[i]
z += b
向量化版本(快):
# 要这样写!
z = np.dot(w.T, x) + b
Benchmark 对比:
import time
n = 1000000
a = np.random.rand(n)
b = np.random.rand(n)
# for 循环
tic = time.time()
c = 0
for i in range(n):
c += a[i] * b[i]
print(f"for 循环: {(time.time()-tic)*1000:.1f} ms")
# 向量化
tic = time.time()
c = np.dot(a, b)
print(f"向量化: {(time.time()-tic)*1000:.1f} ms")
# 典型结果:for 循环 ~500ms,向量化 ~1ms,快约 500 倍!
逻辑回归的完全向量化:
前向传播:
反向传播:
参数更新:
注意:整个过程只需要一个外层 for 循环——迭代次数的循环,而没有遍历样本的 for 循环。
2.7 Python 广播机制与常见陷阱
广播(Broadcasting) 是 NumPy 自动扩展维度以匹配运算的机制:
# (4,1) + (1,3) → (4,3):自动复制行和列
A = np.array([[1],[2],[3],[4]]) # shape (4,1)
B = np.array([[10, 20, 30]]) # shape (1,3)
C = A + B # shape (4,3)
# [[11, 21, 31],
# [12, 22, 32],
# [13, 23, 33],
# [14, 24, 34]]
广播规则:从右向左对齐维度,如果某个维度是 1 或不存在,就自动扩展。
常见陷阱:Rank-1 数组
# 危险!这不是列向量也不是行向量
a = np.random.randn(5)
print(a.shape) # (5,) —— rank-1 array
print(a.T.shape) # (5,) —— 转置后还是一样!
print(np.dot(a, a.T)) # 得到标量而不是矩阵!
# 安全做法:总是显式指定维度
a = np.random.randn(5, 1) # 列向量 (5,1)
print(a.shape) # (5, 1)
print(a.T.shape) # (1, 5)
print(np.dot(a, a.T)) # 得到 (5,5) 矩阵 ✓
最佳实践:
# 在关键位置加 assert 检查维度
assert w.shape == (n_x, 1), f"Expected ({n_x}, 1), got {w.shape}"
assert X.shape[0] == n_x, f"Expected first dim {n_x}, got {X.shape[0]}"
# 如果不确定,用 reshape 强制指定
a = np.random.randn(5)
a = a.reshape(5, 1) # 确保是列向量
第三章:浅层神经网络
从逻辑回归(0 个隐藏层)到 1 个隐藏层——这一小步,是从线性模型到非线性模型的一大步。
3.1 神经网络的表示
graph LR subgraph 输入层 Layer 0 x1[x₁] x2[x₂] x3[x₃] end subgraph 隐藏层 Layer 1 h1((a₁⁽¹⁾)) h2((a₂⁽¹⁾)) h3((a₃⁽¹⁾)) h4((a₄⁽¹⁾)) end subgraph 输出层 Layer 2 o1((a⁽²⁾ = ŷ)) end x1 --> h1 & h2 & h3 & h4 x2 --> h1 & h2 & h3 & h4 x3 --> h1 & h2 & h3 & h4 h1 --> o1 h2 --> o1 h3 --> o1 h4 --> o1
层的编号约定:
- 输入层 = 第 0 层(不计入网络层数)
- 隐藏层 = 第 1 层
- 输出层 = 第 2 层
- 这是一个 2 层神经网络(有 1 个隐藏层)
参数符号:
- :第 层的权重矩阵
- :第 层的偏置向量
- :第 层的激活值
3.2 前向传播公式
单个样本的前向传播:
向量化(所有样本):
矩阵维度速查(设输入 ,隐藏层 ,输出 ,样本数 ):
| 矩阵 | 维度 | 计算方式 |
|---|---|---|
维度记忆技巧: 的维度是"本层 × 前一层",即 。
3.3 激活函数大全
| 函数 | 公式 | 导数 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|---|
| Sigmoid | 二分类输出层 | 输出范围 (0,1) | 梯度消失;输出非零中心 | ||
| Tanh | 隐藏层 | 输出零中心化 | 仍有梯度消失问题 | ||
| ReLU | 隐藏层默认首选 | 计算快;缓解梯度消失 | "死神经元"问题 | ||
| Leaky ReLU | 隐藏层 | 避免死神经元 | 多一个超参数 |
代码实现:
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def tanh(z):
return np.tanh(z)
def relu(z):
return np.maximum(0, z)
def leaky_relu(z, alpha=0.01):
return np.maximum(alpha * z, z)
导数实现(反向传播要用):
def sigmoid_backward(dA, Z):
A = sigmoid(Z)
return dA * A * (1 - A)
def relu_backward(dA, Z):
dZ = np.array(dA, copy=True)
dZ[Z <= 0] = 0
return dZ
3.4 为什么需要非线性激活函数?
数学证明:如果所有层都用线性激活函数 ,那么:
无论堆叠多少层,最终都等价于一个线性变换!隐藏层完全没有意义。
结论:非线性激活函数是神经网络能够学习复杂函数的根本原因。没有非线性,深层网络退化为逻辑回归。
唯一的例外:输出层在做回归任务时可以用线性激活(预测任意实数值)。
3.5 反向传播完整推导
目标:计算 。
输出层(第 2 层):
隐藏层(第 1 层):
其中 表示逐元素乘法(element-wise), 是第 1 层激活函数的导数。
反向传播的通用模式(请牢记!):
这四个公式是一切反向传播的核心,从 2 层到 100 层,每一层都是重复这个模式。
3.6 随机初始化
问题:如果把所有权重初始化为 0 会怎样?
# 错误的初始化!
W = np.zeros((n_h, n_x))
答案:同一层的所有神经元会学到完全相同的东西!这叫对称性问题(Symmetry Problem)。
原因:如果 所有行相同,那么 的每个元素也相同 → 的每行也相同 → 更新后 仍然每行相同。无论训练多久,所有隐藏单元永远做同样的事。
正确做法:随机初始化 + 小权重
W1 = np.random.randn(n_h, n_x) * 0.01 # 乘 0.01 保持值较小
b1 = np.zeros((n_h, 1)) # b 可以初始化为 0
W2 = np.random.randn(n_y, n_h) * 0.01
b2 = np.zeros((n_y, 1))
为什么乘 0.01? 如果权重太大, 值会很大,sigmoid/tanh 的导数会趋近于 0(梯度消失),训练很慢。后续课程会学到更好的初始化方法(He 初始化、Xavier 初始化)。
第四章:深层神经网络
从 2 层到 层——深层神经网络的力量来自层级化的特征学习。
4.1 为什么深层更好?
直觉:层级化特征学习
以人脸识别为例,深层网络会自动学习这样的层级结构:
第 1 层 → 检测边缘(水平线、垂直线、对角线)
第 2 层 → 组合边缘 → 检测局部特征(眼睛、鼻子、嘴巴的部件)
第 3 层 → 组合部件 → 检测完整的人脸
graph LR A[像素] -->|第1层| B[边缘/纹理] B -->|第2层| C[局部特征<br>眼/鼻/嘴] C -->|第3层| D[人脸]
类似的层级在其他领域:
- 语音识别:音素 → 音节 → 词 → 句子
- 自然语言:字符 → 词 → 短语 → 句子 → 段落
电路理论的论据:存在一些函数,用深层网络( 个神经元)就能表示,但用浅层网络需要指数级()个神经元。例如,计算 个变量的异或(XOR),深层网络只需 层,每层常数个神经元。
4.2 深层网络的符号约定
| 符号 | 含义 |
|---|---|
| 网络总层数(不含输入层) | |
| 第 层的神经元数量 | |
| 第 层的激活值 | |
| 第 层的权重,shape: | |
| 第 层的偏置,shape: |
输入:,输出:
4.3 通用前向传播
对于第 层():
def L_model_forward(X, parameters):
"""L 层前向传播"""
caches = []
A = X
L = len(parameters) // 2 # 参数字典包含 W1,b1,...,WL,bL
# 前 L-1 层使用 ReLU
for l in range(1, L):
A_prev = A
W = parameters[f'W{l}']
b = parameters[f'b{l}']
Z = np.dot(W, A_prev) + b
A = np.maximum(0, Z) # ReLU
caches.append((A_prev, W, b, Z))
# 第 L 层使用 Sigmoid(二分类)
WL = parameters[f'W{L}']
bL = parameters[f'b{L}']
ZL = np.dot(WL, A) + bL
AL = 1 / (1 + np.exp(-ZL)) # Sigmoid
caches.append((A, WL, bL, ZL))
return AL, caches
4.4 矩阵维度速查表
这是调试深度学习代码最重要的工具——维度不对是最常见的 bug。
| 变量 | 维度 | 说明 |
|---|---|---|
| "本层 × 前一层" | ||
| 与本层神经元数匹配 | ||
| 本层神经元 × 样本数 | ||
| 与 相同 | 梯度维度总是与参数维度相同 | |
| 与 相同 | 同上 |
4.5 前向传播与反向传播的积木块
整个深度学习的训练过程,可以看作是在反复堆叠这样的"积木块":
┌─────────────────────────────────────────────────────────┐
│ 第 l 层的积木块 │
│ │
│ 前向:输入 A[l-1] → 计算 Z[l], A[l] → 输出 A[l] │
│ 缓存 (A[l-1], W[l], b[l], Z[l]) │
│ │
│ 反向:输入 dA[l] → 计算 dZ[l], dW[l], db[l] → 输出 dA[l-1] │
│ 使用缓存中的值 │
└─────────────────────────────────────────────────────────┘
graph LR subgraph 前向传播 A0[A⁰=X] -->|W¹,b¹| Z1[Z¹→A¹] Z1 -->|W²,b²| Z2[Z²→A²] Z2 -->|W³,b³| Z3[Z³→A³=ŷ] end Z3 --> L[Loss] L --> dA3[dA³] subgraph 反向传播 dA3 -->|cache³| dA2[dA²] dA2 -->|cache²| dA1[dA¹] dA1 -->|cache¹| dA0[dA⁰] end
为什么需要 cache? 反向传播需要用到前向传播中间计算的值()。如果不缓存,就得重新计算,浪费时间。这是空间换时间的经典策略。
4.6 参数 vs 超参数
| 参数(Parameters) | 超参数(Hyperparameters) |
|---|---|
| 学习率 | |
| 通过梯度下降学习 | 迭代次数 |
| 直接决定模型预测 | 隐藏层数 |
| 每层神经元数 | |
| 激活函数选择 | |
| Mini-batch 大小 |
超参数之所以叫"超"参数,是因为它们控制着参数的学习过程。选择好的超参数目前更多依赖经验和实验,Course 2 会详细讨论调优策略。
4.7 深度学习与大脑
经常有人说"神经网络模拟了人脑"——这个说法不太准确。
- 生物神经元的工作机制远比人工神经元复杂得多
- 人工神经网络只是从生物神经网络中获得了松散的灵感
- 我们对大脑的理解仍然非常有限
- 将深度学习类比为大脑可以作为入门直觉,但不要过度解读
本课小结与关键公式速查
核心概念回顾
- 单神经元 = 线性变换 + 激活函数:
- 神经网络 = 多层神经元的堆叠,通过非线性激活函数获得强大的表达能力
- 训练 = 前向传播计算损失 + 反向传播计算梯度 + 梯度下降更新参数
- 向量化 = 消除 for 循环,利用矩阵运算加速
关键公式速查
逻辑回归:
| 公式 | 表达式 |
|---|---|
| 模型 | |
| 损失 | |
| 梯度 | , |
L 层神经网络:
| 步骤 | 公式 |
|---|---|
| 前向传播 | , |
| 反向传播 | |
| 权重梯度 | |
| 偏置梯度 | |
| 传递梯度 | |
| 参数更新 |
维度速查:
Route control
After Reading
Choose the next trail: follow the same topic route, open the research shelf, or continue through nearby notes.