近期在看李沐大神的动手学深度学习课程,在此,记录一些重要知识点,不要让知识到处飘,让知识汇总起来。
1、PyTorch 的 backward 必须是标量#
import torch
x = torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x,x)
y.backward()
# 默认情况下,PyTorch会累积梯度,需要清除之前的值
# 对非标量调用 'backward' 需要传入一个 'gradient' 参数,该参数指定微分函数
x.grad.zero_() # 清除之前x的梯度
y = x * x # 这里的y不是一个标量,这是一个向量
print(y)
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward() # y.sum()后就讲向量转为标量了,对标量求导
x.grad
🔹 关键点:PyTorch 的 backward 必须是标量
在 PyTorch 中,.backward()
的设计是基于 链式法则,主要针对标量(单个输出值)对输入的梯度。
如果你调用 y.backward()
,这里的 y 必须是一个标量,因为:
-
叫做 y 关于 x 的梯度(gradient)
-
是 y 对 x 的偏导数
但是到底它具体是什么,取决于 $y$ 和 $x$ 的维度。
🔸 情况 1:y 是标量,x 是向量
假设:
那么:
也就是:
一个长度为 的向量,表示 y 对每个 x 分量的偏导数。
这正是机器学习里最常用的情况,比如 ** loss 对权重的梯度 **。
🔸 情况 2:y 是向量,x 是向量
假设:
那么:
这个矩阵 叫做 Jacobian 矩阵,它的 元素是:
2、Jacobian 矩阵#
Jacobian 矩阵是:
一个向量函数对输入向量的偏导矩阵。
假设:
-
输入:
-
输出:
Jacobian:
直观上:
-
第 行: 对所有 的偏导。
-
描述:输入变量小变动对每个输出变量的线性影响。
3、Hessian 矩阵#
Hessian 矩阵是:
一个标量函数对输入向量的二阶偏导矩阵。
假设:
-
输入:
-
输出:
Hessian:
但是当 是一个 向量,梯度其实是 Jacobian 矩阵,不是单个梯度向量:
4、pytorch 中 dtype 和 type 的区别#
先放结论:
dtype
是张量内元素的具体数值类型,type
是张量对象本身的 Python 类型(包含设备信息)。
它们关注的层面完全不一样。
📍 1️⃣ dtype
是什么?
-
指:张量里每个元素的存储类型。
-
举例:
-
torch.float32
→ 单精度浮点数 -
torch.int64
→ 64 位整数 -
torch.bool
→ 布尔类型
-
看代码:
x = torch.tensor([1, 2, 3], dtype=torch.float32)
print(x.dtype) # 输出:torch.float32
它只告诉你「这个张量的元素用什么格式存储」。
📍 2️⃣ type
是什么?
-
指:张量对象在 PyTorch 中的全名,包括数据类型和设备。
-
举例:
-
torch.FloatTensor
→ float32 的 CPU 张量 -
torch.cuda.FloatTensor
→ float32 的 GPU 张量 -
torch.IntTensor
→ int32 的 CPU 张量
-
看代码:
x = torch.tensor([1, 2, 3])
print(x.type()) # 输出:torch.IntTensor(或你的系统默认类型)
它告诉你「这个张量对象在 PyTorch 系统里的完整类型名」。
📍 ⚠ 关键区别
对比项 | dtype | type |
---|---|---|
关注点 | 元素的数值类型 | 张量对象的完整 PyTorch 类型(包括设备信息) |
举例 | torch.float32, torch.int64 | torch.FloatTensor, torch.cuda.FloatTensor |
主要用途 | 精度、存储、计算相关 | 区分不同张量类别,调试 / 检查用 |
改变方式 | .to(dtype) ,.float() | .type() (更换整个类型对象) |
5、Soft Label vs Hard Label#
通常我们训练分类模型,用的标签是:
✅ hard label(硬标签)
比如,对于 3 分类任务,真实标签:
类别 A → [1, 0, 0]
类别 B → [0, 1, 0]
类别 C → [0, 0, 1]
这种标签完全是 独热编码 (one-hot),只认对与错。
soft label(软标签) 则是:
每个类别对应一个概率,而不是硬性的 0 或 1。
比如:
对于某张图片,soft label → [0.7, 0.2, 0.1]
这表示:
-
有 70% 概率是类别 A
-
20% 概率是类别 B
-
10% 概率是类别 C
换句话说,标签也 “承认模糊性”,不是全或无。
6、softmax 回归和 logistic 回归#
** 📦 共同点 **
✅ 本质都是分类模型
✅ 都用线性函数 + 激活(sigmoid 或 softmax)
✅ 都用交叉熵(cross entropy)作为损失函数
但它们用在不同的任务上。
🌟 主要区别
项目 | Logistic 回归 | Softmax 回归 |
---|---|---|
任务类型 | 二分类(binary classification) | 多分类(multi-class classification) |
输出层激活函数 | sigmoid(单值输出 0~1) | softmax(向量输出,每个类别概率) |
输出维度 | 1 维 | C 维(C = 类别数) |
目标标签 | 0 或 1 | one-hot 编码,比如 [0,0,1,0] |
决策方式 | 输出 > 0.5 判为正类 | 最大概率的类别作为预测结果 |
📊 Logistic 回归细节
-
假设你有:
-
输入特征
-
权重 和偏置
-
-
模型计算:
其中 是 sigmoid 函数。
输出 是一个概率值(0 到 1),代表正类的概率。
损失函数:
📊 Softmax 回归细节
-
假设你有:
-
输入特征
-
权重矩阵 (shape: num_features × num_classes)
-
偏置
-
-
模型计算:
其中
输出 $\hat {y}$ 是一个长度为 C 的向量,每个元素是对应类别的概率。
损失函数:
** 🧠 为什么 logistic 回归不能直接用在多分类?**
因为 sigmoid 只输出一个值,而多分类需要输出多个类别的概率,且这些概率要满足:
总和为 1,互相排斥。
这就是 softmax 的设计目的。
✅ 总结一句话
Logistic 回归 ≈ 2 类 softmax 特例
Softmax 回归 = 多分类推广版 logistic 回归
7、似然和概率#
🌟 什么是似然函数?
简单说:
似然函数(likelihood function)是给定模型参数下,观察到数据的概率。
你可以理解为:
模型假设有某个参数 → 在这个参数下,生成我们现在手上这批数据的 “可能性” 有多大。
📊 和概率的区别?
很多人容易混:
✅ 概率:已知参数,算事件的可能性。
✅ 似然:已知数据,换着看哪个参数下更可能生成这些数据。
虽然数学公式长得一样,但用途反过来了。
🏗️ 数学表达
假设:
-
数据:
-
参数:
概率:
似然函数:
区别在于:
-
概率: 固定,看 。
-
似然: 固定,看 。
🌟** 举个简单例子 **
假设你有一个硬币,抛了 10 次,结果有 7 次正面。
我们想估计硬币正面的概率 。
✅ 模型假设
抛硬币次数:10
正面概率:
事件:7 次正面
✅ 似然函数
其中:
-
是组合数(固定值,不影响最大化)。
-
核心是:
给定 ,生成这个数据(7 正 3 反)的概率。
🌟 最大似然估计(MLE)
通常我们用:
找到让似然函数最大的参数。
在这个例子里:
-
最大化
-
最优
这就是最大似然估计。
🔥 在机器学习中
机器学习里的很多训练,其实都是:
用最大似然,去拟合参数。
比如:
-
回归模型 → 高斯分布的最大似然
-
分类模型 → softmax 下的最大似然
-
神经网络 → 交叉熵损失,其实就是最大似然推导出来的
✅ 总结一句话
似然函数 = 在给定参数下,观察到当前数据的概率(把它看作参数的函数)。
最大化似然,就是找最可能生成数据的参数。
8、感知机#
感知机(Perceptron) 是一种非常基础的 二分类线性模型,可以看作是神经网络的最早形式(单层、无隐藏层)。
它的主要特点和要点是:
✅ 基本形式:
感知机就是用一个权重向量 w 和偏置 b,去对输入特征向量 x 做线性组合,再经过一个符号函数(sign function),决定输出是 +1 还是 -1。
公式:
f(x) = sign(w·x + b)
✅ 目标:
找到一组 w, b,让所有样本能被一个超平面分开(即线性可分)。
✅ 训练算法:
-
初始化权重和偏置(通常为零或小值)。
-
对每个误分类样本,更新权重:
w ← w + η * y * x
b ← b + η * y
这里 η 是学习率,y 是真实标签(+1 或 -1)。 -
持续迭代,直到所有样本都被正确分类(或达到最大迭代次数)。
✅ 局限性(硬伤):
-
只能处理线性可分的问题。非线性数据(比如 XOR 问题)它完全无能为力。
-
没有概率输出,只是硬分类。
-
容易受噪声和异常值影响。
✅ 意义和历史地位:
虽然现在深度学习早已超越感知机,但感知机是神经网络发展的起点。当年 Minsky 和 Papert 在 1969 年写的《Perceptrons》一书指出它不能解决 XOR 问题,这直接导致 AI 寒冬的到来。直到多层网络(MLP)和反向传播算法出现,才打破了这个局限。
9、多层感知机#
二分类问题公式
二分类问题公式与多分类问题区别在于 的形状为。(k 为类别个数)
每一层隐藏层都需要一个激活函数(非线性函数),如果没有激活函数,就相当于一个大的线性函数。
输出层可以不需要激活函数。
多层感知机多分类问题与 Softmax 回归的区别,在于多了隐藏层,其余均相同。
代码
import torch
from torch import nn
# 构建模型,隐藏层包含256个隐藏单元,并使用了ReLU激活函数
net = nn.Sequential(nn.Flatten(),nn.Linear(784,256),nn.ReLU(),nn.Linear(256,10))
代码解释
nn.Sequential()
用来按顺序堆叠一系列子模块(层、激活函数等),自动组织前向传播。换句话说:它就是一个有序容器,把你想要的网络模块按顺序包起来。
nn.Flatten()
把输入的多维张量展平成一维向量。
对 MNIST 来说,输入图像 shape 是 [batch_size, 1, 28, 28](灰度图),Flatten 后变成 [batch_size, 784],方便接入全连接层。
10、激活函数#
Sigmoid 函数#
✅ Sigmoid 激活函数 定义为:
✅ 输出范围:
(0, 1) —— 把任意实数映射到 0 和 1 之间。
✅ 图像特征:
-
S 形曲线(所以叫 sigmoid,sigmoid = S 型)。
-
中心对称于 (0, 0.5)。
-
当 很大或很小时,梯度接近 0 —— 梯度消失。
✅ 优点:
-
可以解释为概率(尤其适合二分类最后一层)。
-
光滑、连续、可微。
✅ 缺点(致命的):
-
梯度消失:当 很大或很小时,导数接近 0,反向传播几乎没法更新权重。
-
输出不以 0 为中心:这会让梯度更新时收敛变慢,因为正负梯度不对称。
-
容易饱和:输入太大或太小都会被压平。
✅ 现在的主流做法:
-
除了二分类最后一层,隐藏层几乎都不用 sigmoid,而是用 ReLU 或更先进的变体。
-
如果是二分类,最后一层用 sigmoid,损失函数用 binary cross entropy。
Tanh 函数#
✅ tanh(双曲正切)激活函数 定义:
✅ 输出范围:
(-1, 1) —— 把任意实数压缩到 -1 到 1 之间。
✅ 图像特征:
-
S 形曲线(跟 sigmoid 类似,但上下对称)。
-
中心对称于 (0, 0)(这比 sigmoid 好,输出均值更接近 0,有助于优化收敛)。
-
当 很大或很小时,也会出现梯度消失。
✅ 优点:
-
相比 sigmoid,输出以 0 为中心,对梯度更新更友好。
-
光滑、连续、可微。
✅ 缺点(核心问题):
- 和 sigmoid 一样,容易饱和 → 梯度消失。
✅ 现在的主流做法:
-
在某些需要对称输出的模型(比如 RNN)中,tanh 仍然有用。
-
但对于深层神经网络的隐藏层,现代主流还是用 ReLU 及其改进版。
ReLU 函数#
✅ ReLU(Rectified Linear Unit)激活函数 定义:
✅ 输出范围:
✅ 图像特征:
-
当 ,输出 0。
-
当 ,输出 。
简单、直接,一条折线。
✅ 优点:
-
计算简单,收敛快。相比于 Sigmoid 函数和 Tanh 函数,这俩均需要指数运行,指数运算偏贵。
-
不容易出现梯度消失(因为正区间梯度恒为 1)。
-
稀疏激活(很多神经元输出 0,这有助于简化模型)。
✅ 缺点:
-
死亡 ReLU 问题:如果某个神经元在训练中被卡进负区间(输出一直为 0),它可能再也更新不了(因为负区间梯度为 0)。
-
对输入值不对称(只保留正值)。
✅ 现代改进版:
-
Leaky ReLU:负区间给一个很小的斜率。
-
Parametric ReLU (PReLU):让负区间的斜率可学习。
-
ELU、GELU、Swish:进一步改进。
11、模型复杂度#
12、正则化#
在机器学习中,正则化方法就是一种用来防止模型过拟合、提高模型泛化能力的技术集合。 它通过在模型的学习过程中引入一些 “惩罚” 或 “限制”,来约束模型的复杂度,使得模型不会过于 “完美” 地拟合训练数据中的每一个细节(尤其是噪声),从而能够更好地适应新的、未见过的数据。
只在训练中使用,预测时不需要。
正则化是什么?
一句话:正则化通过在损失函数里加入 “约束 / 惩罚”,主动限制模型自由度,逼它学到简洁、泛化好的规律,而不是死记硬背训练集里的噪声。
数学上常写成
-
:原始经验损失(交叉熵、MSE …)
-
:正则项(越大表示模型越 “复杂”)
-
:正则强度; 退化成无正则, 则把模型压到极简
核心作用
目标 | 解释 |
---|---|
抑制过拟合 | 减小方差,提升对未见样本的鲁棒性 |
数值稳定 | 避免权重爆炸或矩阵奇异 |
可解释性 | 稀疏化或结构化约束让特征 / 子网络更易阅读 |
防共线 | 在高维共线特征下仍能给出唯一解 |
L1 正则化 (Lasso Regression)#
✅** 1. L1 正则化是什么?**
一句话定义:
L1 正则化就是在损失函数中加入 所有权重绝对值的和 作为惩罚项。
它的目标是:
让某些不重要的权重 自动变成 0,从而让模型 “更简单”。
✅ 2. 数学形式(简洁地看)
普通损失函数是:
加上 L1 正则化后,变成:
其中:
-
是每个权重参数
-
是控制惩罚强度的超参数(越大,越 “狠”)
✅ 3. L1 的最大特点:让部分权重变为 0(稀疏化)
这就是 L1 和 L2 的关键区别:
所以如果你希望模型自动挑出重要特征、丢掉垃圾特征,L1 正则是理想选择。
✅ 4. 举个例子(想象)
你在做一个房价预测任务,有 100 个输入特征,但实际上只有 5 个有用。
如果你用 L1 正则,训练后模型可能只保留这 5 个特征的权重,其余 95 个直接变成 0。
这相当于模型自动做了特征选择。
✅ 总结一句话
L1 正则化 = 惩罚权重绝对值 → 促使部分参数变 0 → 自动选择重要特征。
L2 正则化(权重衰退)(Ridge Regression)#
✅ 一句话定义
L2 正则化 是在损失函数中加入 模型参数平方的惩罚项,
用来抑制权重过大,避免过拟合。
✅ 数学形式
设模型的原始损失函数为:
加上 L2 正则后,变成:
其中:
-
:正则化强度(超参数)一般取
-
:所有权重的平方和(就是 L2 范数)
✅ 实际效果(直觉)
没有 L2 的情况:
- 模型可能会疯狂放大某个权重,导致对训练数据拟合得很好,但泛化很差。
有了 L2 正则:
-
模型 “更保守”,不轻易拉大参数
-
模型对输入的波动更稳定(泛化能力更强)
✅ 在 PyTorch 中怎么用?
L2 正则 = 权重衰退,所以 PyTorch 中你只要在优化器里加:
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, weight_decay=1e-4)
这个 weight_decay
参数其实就是 !默认就是 L2 正则。
✅ 在 sklearn 中怎么用?
from sklearn.linear_model import Ridge # Ridge 就是带 L2 的线性回归
model = Ridge(alpha=1.0)
✅ 总结一句话
L2 正则化 = 惩罚权重平方,压缩但保留全部参数,提升模型泛化能力。
Dropout (丢弃法)#
✅ 一句话定义
Dropout 是一种随机屏蔽神经元的正则化方法,
它让神经网络在训练时更 “健壮”,防止过拟合。
✅ 背后的动机
深度神经网络容易过拟合,尤其当:
-
层数深
-
训练数据小
-
参数多,模型复杂
原因是:
模型会学会 “依赖某些神经元组合” 来记住训练数据 → 泛化能力变差。
Dropout 就是打破这种依赖 ——
训练时,随机屏蔽(置 0)一部分神经元,强迫网络不能只靠一小撮神经元合作,而是必须具备冗余能力。
✅ 怎么操作的?
训练时:
对某一隐层输出向量
训练期应用:
— 保留概率 $p$ 由你设定;通常 0.5–0.9。
-
:随机掩码,决定是否 “保留” 第 个单元。
-
:逐元素乘。
-
:inverted dropout 常用的重标定因子,使 ,保证训练 / 推断期激活尺度一致。
所以从数学角度看,它是一种 乘性二元噪声注入,并非真的把神经元物理地删掉。
随机掩码(random mask)= 一张用 0/1 取值填充的张量,决定在本轮前向 / 反向传播里哪些单元 “暂时关灯”,哪些 “正常工作”。
在 Dropout(或其它噪声正则)中,它是把乘性噪声注入网络的最小 “开关矩阵”。
元素值
测试时:
不 Drop,全部激活,但输出不缩放。
✅ 使用位置:
在全连接层(Dense Layer / Linear Layer)和其后的激活函数(如 ReLU, Sigmoid, Tanh 等)之间或之后。
- 更常见的是放在激活函数之后: Dense -> Activation -> Dropout
- 这样做是因为 Dropout 的目的是随机 “关闭” 神经元的输出。激活函数的输出代表了神经元的最终输出信号,对其进行 Dropout 更直接地模拟了神经元的随机失活。
- 少数情况下也可能放在激活函数之前: Dense -> Dropout -> Activation
虽然不那么主流,但也有研究和实践表明这样做有时也能取得效果。其逻辑是先对权重计算的线性组合结果进行 Dropout,然后再通过激活函数。
通常应用于一个或多个隐藏层 (Hidden Layers)。
- 对于较深的网络,可以在多个隐藏层之后都使用 Dropout。
是否在所有隐藏层都使用,以及使用多大的 Dropout 率 (dropout rate/probability),通常是需要通过实验调整的超参数。
一般不建议在输出层 (Output Layer) 使用 Dropout。
- 输出层负责产生最终的预测结果。如果在输出层使用 Dropout,可能会干扰模型最终的预测输出,特别是对于分类任务,可能会随机丢弃掉某些类别的预测信号,这通常是不希望看到的。
✅ PyTorch 实现:
import torch.nn as nn
net = nn.Sequential(
nn.Linear(256, 128),
nn.ReLU(),
nn.Dropout(p=0.5), # Dropout 层:训练时屏蔽 50% 神经元
nn.Linear(128, 10)
)
✅ Dropout 的优势:
优点 | 描述 |
---|---|
抑制过拟合 | 强制网络不能依赖某些特征 |
增强泛化能力 | 每次训练像在训练不同 “子网络” |
易用 | 一行代码即可加入 |
⚠️ 注意事项:
-
Dropout 只在训练阶段起作用,测试阶段必须关闭 Dropout。
-
如果模型已经很小或者数据量足够,Dropout 有时反而会伤害性能(欠拟合)。
✅ 总结一句话:
Dropout 是一种 “训练时随机丢点,测试时全开” 的策略,增强模型鲁棒性,防止过拟合。
Early Stopping (早停法)#
✅ 一句话定义
Early Stopping 是通过监控验证集表现,在模型开始过拟合前停止训练的一种方法。
🧠 为什么需要 Early Stopping?
我们在训练一个模型时,经常会看到这样的现象:
轮数 | 训练集准确率 | 验证集准确率 |
---|---|---|
1 | 60% | 58% |
10 | 95% | 88% ✅ |
30 | 99% ✅ | 70% ❌ |
训练集越来越好,但验证集越来越差。这说明:
模型正在 “死记硬背” 训练数据 → 过拟合开始了。
这时候继续训练反而是浪费时间、甚至破坏模型。
✅** Early Stopping 的核心思路 **
很简单:
观察验证集的表现,一旦它开始下降,就立刻停止训练,保留效果最好的模型。
这样:
-
模型不会过拟合
-
训练速度更快
-
一般不需要复杂的正则项
👉 所以 Early Stopping 是一种训练级别的正则方法,而不是结构级别的。
✅ 总结一句话
Early Stopping 是最朴素但极有效的正则化策略:当验证集不再变好,立刻停。
13、权重初始化#
为了避免 “信号爆炸或消失”,我们希望
网络里每一层的激活(正向)和梯度(反向)的均值 = 0,方差 = 常数。
具体地它把每层的输出 和梯度 当作随机变量:
方向 | 均值 | 方差 |
---|---|---|
正向 | ||
反向 |
其中 是两个你自己定的常数(一般取 1 或 2),且 对所有层 和所有通道 都一样。
推导一层的 “方差守恒” 条件(全连接层为例)
设
常见假设
-
-
-
与 独立、元素之间近似独立
于是
要求 输出方差继续等于 ⇒
反向同理(梯度一路乘 传播),要求
两端都顾及 → 折中方案
这就是 Glorot/Xavier 初始化。随机初始化
Xavier 初始化(Glorot 初始化)#
一句话:它是一种设置网络权重初值的策略,通过让正向激活和反向梯度在每一层都保持方差相近,缓解深度网络里的梯度消失 / 爆炸问题。提出者是 Xavier Glorot 与 Yoshua Bengio(2010)。
具体公式
采样分布 | 建议方差 | 实际采样区间 / 标准差 |
---|---|---|
均匀 U (−r, r) | ||
正态 𝒩(0, σ²) | 同上 |
-
n_in
: 本层每个神经元接收的输入维度 -
n_out
: 本层神经元个数
对 卷积层:
与 He 初始化的区别
名称 | 建议方差 | 适用激活 |
---|---|---|
Xavier/Glorot | Sigmoid、tanh、soft-sign 等双侧激活 | |
He/Kaiming | ReLU、Leaky-ReLU、GELU(单侧激活会丢掉 1/2 能量,需更大方差) |