pytorch安装
cmd
conda install pytorch torchvision torchaudio cpuonly -c pytorch
jupyter notebook 安装
conda install jupyter notebook
分类
分类问题的常见损失函数被称为交叉熵
回归
均方误差是回归问题中最常用、最直观的损失函数。
什么是回归
需要模型输出一个或多个连续数值的任务
联合概率
第一个被称为联合概率(joint probability)
P ( A = a , B = b ) P(A = a, B = b) P(A=a,B=b)
给定任意值a和b,联合概率可以回答:
A = a , B = b A =a,B = b A=a,B=b
同时满足的概率是多少?请注意,对于任何a和b的取值,
P ( A = a , B = b ) ≤ P ( A = a ) P(A = a, B = b) ≤ P(A = a) P(A=a,B=b)≤P(A=a)
这点是确定
的,因为要同时发生A = a和B = b, A = a就必须发生, B = b也必须发生(反之亦然)。因此, A = a和B = b同
时发生的可能性不大于A = a或是B = b单独发生的可能性
条件概率
联合概率的不等式带给我们一个有趣的比率:
0 ≤ P ( A = a , B = b ) P ( A = a ) ≤ 1 0\le \frac{ P(A=a,B=b)}{P(A=a)} \le 1 0≤P(A=a)P(A=a,B=b)≤1
我们称这个比率为条件概率(conditional
probability),并用P(B = b | A = a)表示它:它是B = b的概率,前提是A = a已发生。
贝叶斯定理
使用条件概率的定义,我们可以得出统计学中最有用的方程之一: Bayes定理(Bayes' theorem)。根据乘法法
则(multiplication rule )可得到P(A, B) = P(B | A)P(A)。根据对称性,可得到P(A, B) = P(A | B)P(B)。
假设P(B) > 0,求解其中一个条件变量,我们得到
P ( A ∣ B ) = P ( A ∣ B ) P ( A ) P ( B ) P(A|B)=\frac{ P(A|B)P(A)}{P(B)} P(A∣B)=P(B)P(A∣B)P(A)
期望和方差
为了概括概率分布的关键特征,我们需要一些测量方法。一个随机变量X的期望(expectation,或平均值
(average))表示为
E\[X\]=\\sum_{x}xP(X=x) 衡量随机变量 X 与其期望值的偏置。这可以通过方差来量化 衡量随机变量X与其期望值的偏置。这可以通过方差来量化 衡量随机变量X与其期望值的偏置。这可以通过方差来量化 Var\[X\]=E\[(X)-E\[x\])^2\]=E\[x^2\]-E\[x\]\^2
模型
在开始寻找最好的模型参数(model parameters) w和b之前,我们还需要两个东西:
(1)一种模型质量的度量方式;
(2)一种能够更新模型以提高模型预测质量的方法
模型质量的度量方式
损失函数
通常我们会选择非负数作为损失,且数值越小表示损失越小, 完美预测时的损失为0。
回归问题中最常用的损失函数是平方误差函数
l ( i ) ( w , b ) = 1 2 ( y ′ ( i ) − y ( i ) ) 2 l^{(i)}(w, b) = \frac{1}{2} ( y^{'}(i) − y(i))^2 l(i)(w,b)=21(y′(i)−y(i))2
由于平方误差函数中的二次方项,估计值 y ˆ ( i ) yˆ(i) yˆ(i)和观测值 y ( i ) y(i) y(i)之间较大的差异将导致更大的损失。为了度量模型
在整个数据集上的质量,我们需计算在训练集n个样本上的损失均值(也等价于求和)
L ( w , b ) = 1 n ∑ i = 1 n 1 2 ( y ′ ( i ) − y ( i ) ) 2 L(w, b) = \frac{1}{n} \sum_{i=1}^{n} \frac{1}{2} ( y^{'}(i) − y(i))^2 L(w,b)=n1i=1∑n21(y′(i)−y(i))2
python
import torch
def loss(W, b, X, y):
"""
函数1:计算损失值 + 反向传播求梯度(不更新参数)
功能:前向传播算预测值,计算损失,反向传播将梯度存入 W.grad 和 b.grad
参数说明:
W: 权重张量,shape=(n_features, 1),需设置 requires_grad=True
b: 偏置张量,shape=(1,),需设置 requires_grad=True
X: 输入特征矩阵,shape=(n_samples, n_features)
y: 真实标签,shape=(n_samples, 1)
返回:
loss_val: 损失值(Python标量)
"""
# 1. 前向传播:y_pred = X·W + b 计算预测值
y_pred = torch.matmul(X, W) + b
# 2. 计算损失
loss = torch.mean((y_pred - y) ** 2)
return loss.item()
提高模型预测质量的方法
随机梯度下降
算法的步骤如下:
(1)初始化模型参数的值,如随机初始化;
(2)从数据集中随机抽取小批量样 本且在负梯度的方向上更新参数,
python
import torch
def update_params(W, b, lr=0.01):
"""
函数2:仅更新参数(依赖已计算的梯度)
功能:使用 W.grad 和 b.grad,按梯度下降公式更新参数,更新后清零梯度
参数说明:
W: 权重张量(已通过函数1计算出 W.grad)
b: 偏置张量(已通过函数1计算出 b.grad)
lr: 学习率,默认0.01
返回:
updated_W: 更新后的权重 W
updated_b: 更新后的偏置 b
"""
# 1. 手动更新参数(关闭梯度追踪,不影响计算图)
with torch.no_grad():
W.data -= lr * W.grad # W = W - lr * 梯度
b.data -= lr * b.grad # b = b - lr * 梯度
# 2. 梯度清零(关键!避免下次梯度累积)
W.grad.zero_()
b.grad.zero_()
return W, b
正态分布
\\begin{flalign} \&方 差\\sigma\^2,标准差\\sigma,均值\\mu \\ \&\\sigma=\\sqrt\[\]{\\frac{1}{n} \\sum_{i=1}\^{n}{(X_i - \\mu )\^2} } \\ \&正态分布 = p(x) = \\frac{1}{\\sqrt{2\\pi\\sigma\^2 } } e\^{-\\frac{1}{2\\sigma ^2}(x-u)^2 }\& \\end{flalign}
全连接层
对于线性回归,每个输入都与每个输出相连,我们将这种变换称为全连接层或称为稠密层
定义模型步骤
定义模型
python
#%%
# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
初始化模型参数
python
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
计算均方误差方法
python
loss = nn.MSELoss()
定义优化算法
python
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
训练
python
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')
softmax
softmax回归的输出层也是全连接层
softmax输出的是属于哪一类的概率,属于激活函数
多层感知机
发挥多层架构的潜力,我们还需要一个额外的关键要素:在仿射变换之后对每个隐藏单元应用非线性的激活函数 激活函数的输出被称为活性值。一般来说,
有了激活函数,就不可能再将我们的多层感知机退化成线性模型:
激活函数
ReLU函数
sigmoid函数
tanh函数
解决过拟合
权重衰减
权重衰减是一种正则化技术,通过在损失函数中添加惩罚项来抑制模型过拟合,提高模型的泛化能力
我们在实例化优化器时直接通过weight_decay指定weight decay超参数。 默认情况下,PyTorch同时衰减权重和偏移
暂退法
暂退法在前向传播过程中,计算每一内部层的同时注入噪声,这已经成为训练神经网络的常用技术。 这种方法之所以被称为暂退法,因为我们从表面上看是在训练过程中丢弃(drop out)一些神经元。 在整个训练过程的每一次迭代中,标准暂退法包括在计算下一层之前将当前层中的一些节点置零。
对于深度学习框架的高级API,我们只需在每个全连接层之后添加一个Dropout层, 将暂退概率作为唯一的参数传递给它的构造函数。 在训练时,Dropout层将根据指定的暂退概率随机丢弃上一层的输出(相当于下一层的输入)。 在测试时,Dropout层仅传递数据。
python
net = nn.Sequential(nn.Flatten(),
nn.Linear(784, 256),
nn.ReLU(),
# 在第一个全连接层之后添加一个dropout层
nn.Dropout(dropout1),
nn.Linear(256, 256),
nn.ReLU(),
# 在第二个全连接层之后添加一个dropout层
nn.Dropout(dropout2),
nn.Linear(256, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights);
自定义块
python
class MLP(nn.Module):
# 用模型参数声明层。这里,我们声明两个全连接的层
def __init__(self):
# 调用MLP的父类Module的构造函数来执行必要的初始化。
# 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
super().__init__()
self.hidden = nn.Linear(20, 256) # 隐藏层
self.out = nn.Linear(256, 10) # 输出层
# 定义模型的前向传播,即如何根据输入X返回所需的模型输出
def forward(self, X):
# 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
return self.out(F.relu(self.hidden(X)))
保存和加载
保存模型
保存整个模型(含结构 + 参数)
python
# 保存整个模型
torch.save(model, "entire_model.pth")
# 加载整个模型
loaded_entire_model = torch.load("entire_model.pth")
loaded_entire_model.eval() # 若用于推理,建议开启eval模式(关闭Dropout等)
# 验证加载结果
x = torch.randn(1, 10) # 随机输入一个样本
output = loaded_entire_model(x)
print("\n加载整个模型的输出:\n", output)
print("加载后模型参数:\n", loaded_entire_model.linear.weight)
保存整个模型:加载即能用,无需关心结构定义,适合快速验证:
python
# 加载后直接调用,不用写模型类
loaded_model = torch.load("entire_model.pth")
loaded_model(x) # 直接推理
仅保存模型参数(权重)(推荐)
python
# 保存模型参数(state_dict)
torch.save(model.state_dict(), "model_params.pth")
# 加载模型参数
loaded_model = SimpleModel() # 先实例化模型结构
loaded_model.load_state_dict(torch.load("model_params.pth"))
loaded_model.eval()
# 验证加载结果
x = torch.randn(1, 10)
output = loaded_model(x)
print("\n加载参数后模型的输出:\n", output)
print("加载后模型参数:\n", loaded_model.linear.weight)
仅保存参数:必须先「还原模型结构」,再加载参数,步骤多一步但灵活:
python
# 第一步:先定义和训练时完全一致(或兼容)的模型结构
class SimpleModel(nn.Module): # 必须和保存参数时的模型结构一致(层名、维度匹配)
def __init__(self):
super().__init__()
self.linear = nn.Linear(10, 2) # 若这里改成 nn.Linear(10, 3),加载会失败
# 第二步:实例化模型 + 加载参数
model = SimpleModel()
model.load_state_dict(torch.load("model_params.pth")) # 关键:先有结构,再填参数
1.只加载参数的时候后续使用需要重新定义模型
2.若已经训练的参数不想改变可以进行冻结
python
# 冻结linear1层(复用层)的参数
for param in new_model.linear1.parameters():
param.requires_grad = False
卷积层
构造一个二维卷积层,它具有1个输出通道和形状为(1, 2)的卷积核
python
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)
# 定义卷积层:1 输入通道 → 16 输出通道,3×3 卷积核,padding=1(保持输出尺寸与输入一致)
conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, padding=1)
汇聚层((pooling))
通常当我们处理图像时,我们希望逐渐降低隐藏表示的空间分辨率、聚集信息,这样随着我们在神经网络中层叠的上升,每个神经元对其敏感的感受野(输入)就越大。
而我们的机器学习任务通常会跟全局图像的问题有关(例如,"图像是否包含一只猫呢?"),所以我们最后一层的神经元应该对整个输入的全局敏感。通过逐渐聚合信息,生成越来越粗糙的映射,最终实现学习全局表示的目标,同时将卷积图层的所有优势保留在中间层。
汇聚(pooling)层,它具有双重目的:降低卷积层对位置的敏感性,同时降低对空间降采样表示的敏感性。
python
pool2d = nn.MaxPool2d((2, 3), stride=(2, 3), padding=(0, 1)) #最大汇聚层
pool2d = nn.Conv2d(1, 6, kernel_size=5, padding=2) #平均汇聚层
LeNet
通过下面的LeNet代码,可以看出用深度学习框架实现此类模型非常简单。我们只需要实例化一个Sequential块并将需要的层连接在一起。
python
import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10))
RNN (循环神经网络)
统计次的出现频率
对时间序列有要求
python
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)
#len(vocab) 是词汇表大小,通常对应「one-hot 编码的词向量维度」(比如词汇表有 1000 个词,one-hot 编码后每个词是 1000 维向量,故 input_size=1000)。
#「隐藏层的神经元数量」(核心超参数)。决定 RNN 捕捉序列信息的能力:数值越大,模型容量越强(但易过拟合),通常取 50、128、256 等。输出的隐藏态维度 = hidden_size。
GRU(门控循环单元)
核心背景:为什么需要 GRU?
传统 RNN 是处理序列数据(如文本、时序信号)的基础模型,但它存在致命缺陷:当序列过长(如长句子、长时序数据)时,反向传播过程中梯度会逐渐衰减(梯度消失)或无限放大(梯度爆炸),导致模型无法学习到 "早期关键信息与后期信息的关联"(即 "长序列依赖问题")。
GRU其核心设计目标是:解决传统 RNN 处理长序列时的 "梯度消失 / 爆炸" 问题,
通过双门控机制,GRU 能自主判断 "哪些信息需要长期保留""哪些信息可以丢弃",彻底克服了传统 RNN 的梯度消失问题。
重置门 更新门
python
num_inputs = vocab_size
gru_layer = nn.GRU(num_inputs, num_hiddens)
model = d2l.RNNModel(gru_layer, len(vocab))
model = model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)
长短期记忆网络(LSTM)
核心背景:为什么需要 LSTM?
传统 RNN 的核心缺陷是 "长序列依赖问题":当处理长文本、长时序数据时,反向传播的梯度会随序列长度衰减或放大,导致模型无法学习到早期信息与后期信息的关联
LSTM 彻底解决了传统 RNN 的梯度消失问题,能精准捕捉长序列中 "早期关键信息与后期信息的关联":
输入门、忘记门和输出门
python
num_inputs = vocab_size
lstm_layer = nn.LSTM(num_inputs, num_hiddens)
model = d2l.RNNModel(lstm_layer, len(vocab))
model = model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)
深度循环神经网络
python
vocab_size, num_hiddens, num_layers = len(vocab), 256, 2
device = d2l.try_gpu()
lstm_layer = rnn.LSTM(num_hiddens, num_layers)
model = d2l.RNNModel(lstm_layer, len(vocab))
双向循环神经网络
双向循环神经网络使用了过去的和未来的数据
python
num_inputs = vocab_size
lstm_layer = nn.LSTM(num_inputs, num_hiddens, num_layers, bidirectional=True)
对比普通循环神经网络(Vanilla RNN)、深层循环神经网络(Deep RNN)、双向循环神经网络(Bi-RNN)
| 对比维度 | 普通RNN(Vanilla RNN) | 深层RNN(Deep RNN) | 双向RNN(Bi-RNN) |
|---|---|---|---|
| 核心定义 | 仅含「1个隐藏层」,且隐藏层仅按「单一方向」(通常是"从左到右",即t=1→t=T)处理序列。 | 含「2个及以上隐藏层」(堆叠),隐藏层仍按「单一方向」处理序列,同时存在「层间状态传递」(上一层的h_t作为下一层的输入)。 | 含「1个或多个隐藏层」,每个隐藏层包含「两个并行的循环单元」: - 正向单元(Forward RNN):从左到右处理(t=1→t=T),捕捉"过去→现在"的依赖; - 反向单元(Backward RNN):从右到左处理(t=T→t=1),捕捉"未来→现在"的依赖; 最终输出结合两个方向的隐藏状态。 |
| 隐藏层设计核心 | 「单层+单向」:无层间堆叠,仅时间维度循环。 类比:一条"单层单向流水线",原料(输入)按顺序加工一次。 | 「多层+单向」:层间堆叠(空间维度)+ 时间维度循环。 类比:多条"单向流水线串联",原料经多层精细化加工。 | 「单层/多层+双向」:同一层内双方向并行(信息维度扩展)+ 可选层间堆叠。 类比:一条"双向流水线",原料同时从左到右、从右到左加工,最终融合结果。 |
| 信息利用范围 | 仅能利用「当前时刻之前的历史信息」(单向时序),无法获取"未来信息"。 例:处理句子"他____去了巴黎"时,仅能参考"他"之前的词,无法利用"去了巴黎"的后文。 | 仅能利用「当前时刻之前的历史信息」(单向时序),但能通过多层堆叠抽象"深层历史依赖"。 例:处理"淋雨→感冒→吃药"时,能捕捉"淋雨"到"吃药"的多层因果链,但仍无法利用后文信息。 | 能同时利用「历史信息(过去→现在)+ 未来信息(现在→未来)」(双向时序)。 例:处理句子"小明在____买了面包"时,既能参考"小明在"的前文,也能利用"买了面包"的后文,推断出"超市""便利店"等答案。 |
| 特征提取能力 | 仅能捕捉「浅层局部特征」(如短序列的相邻依赖、时序数据的短期波动),无法抽象复杂层级特征。 | 能实现「分层特征提取」: - 底层隐藏层:捕捉基础特征(如词嵌入、局部波动); - 上层隐藏层:抽象高级特征(如短语语义、长期趋势); 核心是"深度增强"。 | 能捕捉「上下文完整特征」: - 单层双向:同时覆盖前后局部依赖(如文本的歧义消解); - 多层双向:结合"深度+双向",既抽象高级特征,又利用完整上下文; 核心是"范围增强"。 |
| 能力边界 | 1. 仅适配短序列(长序列梯度消失严重); 2. 无法建模复杂依赖(如跨句逻辑、多周期时序); 3. 表达能力弱,易欠拟合。 | 1. 结合LSTM/GRU后,可处理长序列(缓解梯度问题); 2. 能建模复杂深层依赖(如长文本逻辑、多周期趋势); 3. 表达能力强,适配高复杂度任务。 | 1. 结合LSTM/GRU后,可处理长序列+上下文依赖; 2. 擅长"需要完整上下文的任务"(如歧义消解、命名实体识别); 3. 表达能力取决于层数(单层弱于深层,多层接近深层双向)。 |
| 参数与训练难度 | 1. 参数最少(仅1个隐藏层的权重); 2. 训练速度最快,显存占用最低; 3. 无需复杂正则化,易实现。 | 1. 参数多(多层权重+层间传递权重); 2. 训练速度慢、显存占用高; 3. 梯度问题更突出(时间+层间双重衰减),需Dropout、Adam优化器等正则化,否则易过拟合。 | 1. 参数比同层数单向RNN多1倍(正向+反向单元权重); 2. 训练速度中等(慢于普通RNN,快于同层数深层RNN); 3. 需处理"双向隐藏状态融合"(如拼接、求和),但梯度问题仅比单向略严重。 |
| 适用场景 | 简单短序列任务(无复杂依赖、无需上下文): - 短文本分类(单句情感分析); - 简单时序趋势预测(3天内温度); - 入门级演示。 | 复杂长序列任务(需深层特征抽象): - 机器翻译(基础版)、长文本摘要; - 高精度时序预测(数月股价、设备故障预警); - 语音识别(基础特征提取)。 | 需完整上下文的任务(依赖前后信息): - 自然语言处理:命名实体识别(NER)、句法分析、歧义句翻译; - 语音识别:语音转文本(需前后音节关联); - 时序预测:需结合前后周期的场景(如电力负荷预测)。 |
束搜索
注意力机制 和Transformer
Transformer 之所以能跨领域普及,核心是它提供了一种 "通用的序列建模框架"------ 无论数据是文本、语音、图像、视频还是时序信号,都能通过 "序列化 + 注意力机制" 实现全局依赖建模和高效并行计算。
一文彻底搞懂 Transformer(图解+手撕)
手撕Transformer(一):注意力机制与位置编码
Transofrmer架构详解与PyTorch实现(附代码讲解)