这是跟着李岩老师的课程做的笔记,自己学习交流为主
如果有错误,请忽视,反正我也懒得改
主打的就是一个随缘
有问题可以私信
作者神经衰弱的厉害,所以得等身体条件较好的情况下会统一回复
初版,后续如果有精力会精修
卷积神经网络应该会单开一个文章
等有时间了吧
全篇一万八千字,看的话还是需要有点耐心的
数据操作
在PyTorch中,数据操作是非常基础也非常重要的部分。主要涉及到PyTorch的张量(Tensor)操作,它类似于NumPy的多维数组,但还可以在GPU上运行以加速计算。
基础概念
张量(Tensor): 多维数组,可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)等等。
形状(Shape): 张量的维度,如4x3的矩阵,形状就是(4, 3)。
创建张量
在PyTorch中,你可以使用多种方法创建张量,比如直接从Python的列表创建,或者使用特定的函数。
import torch
# 从Python列表创建张量
x = torch.tensor([1, 2, 3])
print(x)
# 创建一个5x3的未初始化的张量
x = torch.empty(5, 3)
print(x)
# 创建一个5x3的随机初始化的张量
x = torch.rand(5, 3)
print(x)
# 创建一个5x3的零张量,数据类型为long
x = torch.zeros(5, 3, dtype=torch.long)
print(x)
张量操作
PyTorch提供了丰富的张量操作,比如索引、切片、数学运算等。
# 索引
y = x[1, :]
print(y)
# 改变形状,使用view
y = x.view(15)
z = x.view(-1, 5) # -1表示该维度由其他维度确定
print(y.shape, z.shape)
# 张量加法
y = torch.rand(5, 3)
print(x + y)
# 原地改变(In-place)
y.add_(x)
print(y)
使用GPU
为了在GPU上运行你的张量,你需要先将它们转移至GPU内存。
# 判断CUDA是否可用
if torch.cuda.is_available():
device = torch.device("cuda") # CUDA设备对象
y = torch.ones_like(x, device=device) # 直接在GPU上创建张量
x = x.to(device) # 或者使用.to("cuda")来移动张量
z = x + y
print(z)
print(z.to("cpu", torch.double)) # 移回CPU并转换为double类型
线性代数
线性代数是机器学习的重要基础,涉及向量、矩阵等概念和运算。在PyTorch中,你可以方便地执行这些线性代数运算。
向量和矩阵
在PyTorch中,向量和矩阵都可以用张量来表示。
向量: 一维张量
矩阵: 二维张量
矩阵运算
矩阵运算是线性代数中的核心,包括矩阵乘法、转置、逆等。
# 创建矩阵
x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float)
y = torch.tensor([[5, 6], [7, 8]], dtype=torch.float)
# 矩阵乘法
z = torch.mm(x, y)
print("矩阵乘法:\n", z)
# 转置
t = x.t()
print("转置:\n", t)
# 计算逆(仅对方阵有效)
inv_x = x.inverse()
print("逆矩阵:\n", inv_x)
特殊矩阵
特殊矩阵,如单位矩阵和对角矩阵,也是线性代数中的重要概念。
# 单位矩阵
I = torch.eye(3)
print("单位矩阵:\n", I)
# 对角矩阵
D = torch.diag(torch.tensor([1, 2, 3]))
print("对角矩阵:\n", D)
范数
范数是衡量向量大小的一种方法,在机器学习中经常用到。
# L2 范数
l2_norm = torch.norm(x)
print("L2 范数:", l2_norm)
# L1 范数
l1_norm = torch.norm(x, p=1)
print("L1 范数:", l1_norm)
广播机制
在进行矩阵运算时,PyTorch可以自动扩展一维数组以匹配更高维的数组,这称为广播。
# 广播示例
a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
print("a:\n", a)
print("b:\n", b)
print("广播运算 a + b:\n", a + b)
矩阵分解
矩阵分解是线性代数中的一个重要话题,如特征分解(Eigen decomposition)和奇异值分解(SVD)。
# 特征分解
eigenvalues, eigenvectors = torch.eig(x, eigenvectors=True)
print("特征值:\n", eigenvalues)
print("特征向量:\n", eigenvectors)
# 奇异值分解
U, S, V = torch.svd(x)
print("奇异值分解:\n", "U:\n", U, "\nS:\n", S, "\nV:\n", V)
解线性方程组
使用PyTorch解线性方程组是一种常见的操作,例如使用torch.solve。
# 解线性方程组 Ax = b
A = torch.tensor([[3, 1], [1, 2]], dtype=torch.float)
b = torch.tensor([9, 8], dtype=torch.float).unsqueeze(1) # 转为列向量
x, LU = torch.solve(b, A)
print("解 x:\n", x)
参数的用法
在PyTorch的线性代数运算中,很多函数都有额外的参数选项,用于调整运算的行为。
dtype: 指定张量的数据类型,例如torch.float、torch.int等。
device: 指定张量所在的设备,如torch.device("cuda")。
requires_grad: 指定是否需要自动计算该张量的梯度,对于后续的自动微分非常重要。
# 创建具有特定数据类型和梯度要求的张量
x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float, requires_grad=True)
print(x)
自动微分
在PyTorch中,自动微分是通过torch.autograd模块实现的。这个模块为张量上所有操作提供了自动微分的能力。它是一个运行时定义的框架,意味着反向传播是根据代码如何运行来确定的,每次迭代都可以不同。
关键概念
张量(Tensor): PyTorch中的主要数据结构,自动微分是通过对这些张量进行操作来实现的。
梯度(Gradient): 微分或导数的另一种说法,在优化问题中,我们通常需要计算函数的梯度。
计算图(Computation Graph): 是一个动态图,用于记录从输入张量到输出张量的操作序列。
启用梯度追踪
要在PyTorch中启用自动微分,你需要设置张量的requires_grad属性为True。
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
进行运算和梯度计算
在定义了梯度跟踪的张量之后,所有的操作都会被自动追踪。
y = x * x * 3
z = y.mean()
print(z)
现在,z是x的一个函数。我们可以通过调用.backward()来自动计算z关于x的梯度。
# 计算梯度
z.backward()
# 查看x的梯度
print(x.grad)
停止追踪梯度
在某些情况下,你可能不想追踪梯度。这可以通过.detach()方法或者使用with torch.no_grad():来实现。
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
y = x * x
z = y.detach() # z不再追踪梯度
# 或者
with torch.no_grad():
y = x * x
梯度累积
PyTorch默认会累积梯度,这在训练神经网络时非常有用。如果你需要多次反向传播,梯度会累加在.grad属性中。
注意事项
只有浮点数和复数类型的张量可以要求梯度。
使用.backward()时,如果张量是标量,则不需要为backward()指定任何参数;如果它有更多的元素,则需要指定一个gradient参数,它是与张量形状相同的张量。
自动微分的深入讲解
1. 创建需要梯度的张量
当你创建一个张量并设置requires_grad=True时,PyTorch会开始追踪在这个张量上的所有操作。
# 创建一个张量,设置requires_grad=True来跟踪它的梯度
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
requires_grad: 如果设置为True,PyTorch会记录所有在张量上的操作以便于后续的梯度计算。
2. 定义一个张量函数
接下来,你可以定义一个张量的函数。PyTorch会自动计算这个函数的梯度。
# 定义一个张量函数
y = x * x * 3
这里,y是关于x的函数。在这个例子中,y = 3x^2。
3. 计算梯度
利用.backward()方法,你可以让PyTorch计算梯度。如果y是标量(即单个数字),你可以直接调用y.backward()来计算梯度。
# 计算y关于x的梯度
y.backward(torch.tensor([1.0, 1.0, 1.0]))
.backward(): 这个方法会计算y的梯度。这里传入的torch.tensor([1.0, 1.0, 1.0])是梯度的权重,在大多数情况下,如果y是标量,你可以省略这个参数。
4. 查看梯度
计算完梯度后,你可以通过.grad属性来查看每个元素的梯度。
# 输出梯度
print(x.grad)
x.grad: 存储了x的梯度。在这个例子中,梯度dy/dx将会被存储在x.grad中。
5. 停止追踪梯度
在某些情况下,你不希望继续追踪计算的梯度,可以使用.detach()或with torch.no_grad():。
# 使用.detach()停止追踪梯度
y = x.detach()
# 使用with torch.no_grad():临时停止追踪梯度
with torch.no_grad():
y = x * x
.detach(): 创建一个新的张量,与原张量共享数据但不追踪梯度。
with torch.no_grad(): 这个代码块内的所有计算都不会追踪梯度。
注意事项
计算梯度是自动进行的,但是梯度是累加的。这意味着每次进行反向传播时,梯度都会加到.grad属性中。因此,在进行新的梯度计算前,通常需要将梯度清零。
当处理非标量输出时(例如输出是向量或矩阵),需要传递一个与输出相同形状的gradient参数给.backward()。
线性回归从零开始实现
线性回归的目标是找到一组权重和偏置,使得模型的输出尽可能接近目标值。在一个简单的线性回归模型中,输出是输入特征的加权和,加上一个偏置项(也称为截距)。
1. 数据准备
首先,我们需要准备数据。在这个例子中,我们将创建一个简单的合成数据集。
# 导入PyTorch
import torch
import numpy as np
# 真实的权重和偏置
true_w = torch.tensor([2, -3.4])
true_b = 4.2
# 创建随机数据集
features = torch.randn(1000, 2)
labels = torch.matmul(features, true_w) + true_b
labels += torch.randn(labels.shape) * 0.01 # 添加噪声
features: 输入特征,这里是一个1000行2列的张量。
labels: 标签,根据我们的线性模型和一些随机噪声生成。
2. 数据迭代
在训练模型时,我们需要批量获取数据。以下是一个简单的数据迭代器。
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
np.random.shuffle(indices) # 打乱顺序
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)]
)
yield features[batch_indices], labels[batch_indices]
batch_size = 10
batch_size: 批量大小。
yield: Python的生成器,用于每次返回一批数据。
3. 初始化模型参数
我们随机初始化模型的权重和偏置。
w = torch.randn(2, 1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)
w: 权重,需要梯度。
b: 偏置,需要梯度。
4. 定义模型
接下来,定义线性回归模型。
def linreg(X, w, b):
return torch.mm(X, w) + b
linreg: 线性回归模型函数。
5. 定义损失函数
我们将使用均方误差作为损失函数。
def squared_loss(y_hat, y):
return (y_hat - y.view(y_hat.shape)) ** 2 / 2
squared_loss: 均方误差损失函数。
6. 定义优化算法
这里我们使用小批量随机梯度下降(mini-batch stochastic gradient descent)。
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
sgd: 优化函数。
lr: 学习率。
param.data: 更新参数,不记录梯度操作。
7. 训练模型
在每个epoch中,我们将遍历所有的数据,并对每个小批量执行一次梯度下降。
# 训练参数
lr = 0.03 # 学习率
num_epochs = 3 # 迭代周期数
net = linreg # 我们的线性回归模型
loss = squared_loss # 损失函数
# 训练过程
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y).sum() # 计算小批量的损失
l.backward() # 计算梯度
sgd([w, b], lr, batch_size) # 使用小批量随机梯度下降进行优化
# 清零梯度
w.grad.data.zero_()
b.grad.data.zero_()
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
l.backward(): 计算梯度。
sgd([w, b], lr, batch_size): 对模型的参数进行优化。
w.grad.data.zero_(), b.grad.data.zero_(): 清零梯度,这是必须的步骤,因为PyTorch会累加梯度。
在这个训练过程中,模型的参数(w和b)通过梯度下降不断更新,以最小化训练数据集上的损失函数。每个epoch结束后,我们打印出当前的损失,观察训练过程中损失的变化。
线性回归的简洁实现
我们将使用PyTorch的nn模块来定义模型,torchvision和DataLoader来处理数据,以及optim模块来进行优化。
1. 生成数据集
这一步与从零开始的实现相同,我们生成相同的数据集。
import torch
from torch.utils.data import TensorDataset, DataLoader
# 真实的权重和偏置
true_w = torch.tensor([2, -3.4])
true_b = 4.2
# 创建数据集
features = torch.randn(1000, 2)
labels = torch.matmul(features, true_w) + true_b
labels += torch.randn(labels.shape) * 0.01
# 将特征和标签组合
dataset = TensorDataset(features, labels)
2. 读取数据
使用DataLoader来批量读取数据。
batch_size = 10
data_iter = DataLoader(dataset, batch_size, shuffle=True)
3. 定义模型
使用nn.Module来定义线性模型。
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
nn.Linear: 定义线性层,输入特征维数为2,输出特征维数为1。
4. 初始化模型参数
初始化权重和偏置。
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
5. 定义损失函数
使用PyTorch内置的均方误差损失函数。
loss = nn.MSELoss()
6. 定义优化算法
使用PyTorch内置的优化器,如SGD。
from torch.optim import SGD
optimizer = SGD(net.parameters(), lr=0.03)
SGD: 随机梯度下降优化器。
net.parameters(): 自动提取模型中的参数。
7. 训练模型
训练过程中,我们将使用优化器来更新模型的权重和偏置。
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
output = net(X)
l = loss(output, y.view(-1, 1))
optimizer.zero_grad() # 梯度清零
l.backward() # 计算梯度
optimizer.step() # 更新参数
print(f'epoch {epoch + 1}, loss: {l:f}')
optimizer.zero_grad(): 在每次迭代前清零梯度。
l.backward(): 计算梯度。
optimizer.step(): 更新权重和偏置。
Softmax回归从零实现
1. 获取和处理数据
首先,我们需要准备用于分类的数据集。这里我们仍然使用合成数据进行演示。
import torch
from torch.utils.data import TensorDataset, DataLoader
# 创建一个简单的合成数据集
features = torch.randn(1000, 2) # 特征维度为2
labels = torch.randint(0, 3, (1000,)) # 标签有3个类别
# 将特征和标签组合
dataset = TensorDataset(features, labels)
batch_size = 10
data_iter = DataLoader(dataset, batch_size, shuffle=True)
features和labels分别是特征和标签。
DataLoader用于批量获取数据。
2. 初始化模型参数
Softmax回归的模型输出是一个长度为类别数的向量,每个元素代表该类的预测概率。
num_inputs = 2 # 输入特征数量
num_outputs = 3 # 输出类别数量
W = torch.randn(num_inputs, num_outputs, requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
W和b是模型的权重和偏置。
3. 定义softmax操作
Softmax函数将输出转换为概率。
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(dim=1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
torch.exp:计算指数。
sum:沿特定维度求和。
4. 定义模型
定义softmax回归模型。
def net(X):
return softmax(torch.mm(X, W) + b)
torch.mm:矩阵乘法。
5. 定义损失函数
交叉熵损失函数适合用于分类问题。
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
torch.log:计算对数。
y_hat[range(len(y_hat)), y]:选择正确类别的预测概率。
6. 定义优化算法
使用小批量随机梯度下降进行优化。
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
param.data:参数更新。
7. 训练模型
进行模型的训练。
lr = 0.1
num_epochs = 5
for epoch in range(num_epochs):
for X, y in data_iter:
y_hat = net(X)
l = cross_entropy(y_hat, y).sum()
l.backward()
sgd([W, b], lr, batch_size)
W.grad.data.zero_()
b.grad.data.zero_()
print(f'epoch {epoch + 1}, loss: {l:f}')
每个epoch遍历一次数据集,使用cross_entropy计算损失,然后更新参数。
Softmax回归的关键算法和方法解释
1. Softmax函数
def softmax(X):
X_exp = torch.exp(X)
partition = X_exp.sum(dim=1, keepdim=True)
return X_exp / partition
作用:将模型的输出转换为概率分布。这是通过对每个输出应用指数函数,然后标准化确保概率之和为1来实现的。
参数:
X_exp: 对输入X的每个元素应用指数函数。
partition: 计算每行的总和,用于标准化。
dim=1: 指定在哪个维度上求和。
keepdim=True: 保持输出的维度与输入相同。
2. 模型定义
def net(X):
return softmax(torch.mm(X, W) + b)
作用:定义softmax回归模型。它首先计算输入X和权重W的矩阵乘法,然后加上偏置b,最后应用softmax函数。
参数:
torch.mm(X, W): 计算矩阵X和W的乘积。
b: 偏置向量。
3. 交叉熵损失函数
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
作用:计算预测概率分布y_hat和真实标签y之间的交叉熵损失。这在分类问题中是常用的损失函数。
参数:
y_hat: 模型的预测输出。
y: 真实的标签。
torch.log: 对y_hat中的元素应用对数函数,以计算交叉熵。
4. 优化算法(小批量随机梯度下降)
def sgd(params, lr, batch_size):
for param in params:
param.data -= lr * param.grad / batch_size
作用:对模型参数进行优化。通过梯度方向和学习率来更新每个参数。
参数:
params: 需要更新的模型参数(例如,W和b)。
lr: 学习率,控制更新的步长。
param.grad: 存储的梯度。
batch_size: 批量大小,用于梯度的标准化。
5. 训练循环
训练循环中,我们遍历数据集,计算每个小批量的损失,然后更新模型参数。
l.backward(): 计算损失l的梯度。
sgd([W, b], lr, batch_size): 使用小批量随机梯度下降来更新权重和偏置。
W.grad.data.zero_(), b.grad.data.zero_(): 在每次梯度计算后清零梯度,防止梯度累加。
Softmax回归的简洁实现
1. 读取数据
使用PyTorch提供的DataLoader来批量读取数据。
from torch.utils.data import DataLoader
batch_size = 10
data_iter = DataLoader(dataset, batch_size, shuffle=True)
DataLoader:方便的数据加载工具,可以自动进行批量处理和数据打乱。
dataset:包含特征和标签的数据集。
batch_size:每个小批量的大小。
shuffle:在每个epoch开始时,是否打乱数据。
2. 定义模型
使用PyTorch的nn.Module定义模型。
from torch import nn
net = nn.Sequential(nn.Linear(2, 3), nn.Softmax(dim=1))
nn.Linear:定义一个线性层,其参数包括输入和输出的特征维度。
nn.Softmax:应用softmax函数。
dim=1:指定在哪个维度上应用softmax。
3. 初始化模型参数
使用PyTorch的内置函数初始化模型参数。
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
normal_:用正态分布随机初始化权重。
fill_:将偏置初始化为0。
4. 定义损失函数
使用PyTorch的内置交叉熵损失函数。
loss = nn.CrossEntropyLoss()
CrossEntropyLoss:计算交叉熵损失,结合了softmax操作和交叉熵损失计算,适合多分类问题。
5. 定义优化算法
使用PyTorch的优化器,如SGD。
from torch.optim import SGD
optimizer = SGD(net.parameters(), lr=0.1)
SGD:随机梯度下降优化器。
net.parameters():自动获取模型的参数。
lr:学习率。
6. 训练模型
组织训练循环。
num_epochs = 5
for epoch in range(num_epochs):
for X, y in data_iter:
output = net(X)
l = loss(output, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
print(f'epoch {epoch + 1}, loss: {l:f}')
optimizer.zero_grad():在每次梯度计算前清零。
l.backward():计算损失梯度。
optimizer.step():进行一步参数更新。
多层感知机从零开始实现
1. 获取和处理数据
这一步与前面的类似,我们继续使用合成数据。
import torch
from torch.utils.data import DataLoader, TensorDataset
# 创建数据集
features = torch.randn(1000, 2) # 特征维度为2
labels = (features[:, 0] ** 2 + features[:, 1] ** 2 > 1).long() # 二分类问题
# 将特征和标签组合
dataset = TensorDataset(features, labels)
batch_size = 10
data_iter = DataLoader(dataset, batch_size, shuffle=True)
features和labels分别是特征和标签。
DataLoader用于批量读取数据。
2. 定义模型参数
为隐藏层和输出层定义权重和偏置。
num_inputs, num_outputs, num_hiddens = 2, 2, 256
W1 = torch.randn(num_inputs, num_hiddens, requires_grad=True) * 0.01
b1 = torch.zeros(num_hiddens, requires_grad=True)
W2 = torch.randn(num_hiddens, num_outputs, requires_grad=True) * 0.01
b2 = torch.zeros(num_outputs, requires_grad=True)
params = [W1, b1, W2, b2]
num_hiddens: 隐藏层的节点数。
W1, b1, W2, b2: 分别是两层的权重和偏置。
3. 定义激活函数
ReLU是一种常用的激活函数。
def relu(X):
return torch.max(X, torch.tensor(0.0))
torch.max: 计算两个张量的逐元素最大值。
4. 定义模型
定义多层感知机模型。
def net(X):
X = X.reshape((-1, num_inputs))
H = relu(torch.matmul(X, W1) + b1)
return torch.matmul(H, W2) + b2
torch.matmul: 矩阵乘法。
relu: 应用ReLU激活函数。
5. 定义损失函数
使用交叉熵损失函数。
loss = torch.nn.CrossEntropyLoss()
CrossEntropyLoss: 适合多分类问题的损失函数。
6. 定义优化算法
实现小批量随机梯度下降。
def sgd(params, lr, batch_size):
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
with torch.no_grad(): 不追踪梯度。
param.grad.zero_(): 清零梯度。
7. 训练模型
进行模型的训练。
lr, num_epochs = 0.1, 10
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X), y)
l.backward()
sgd(params, lr, batch_size)
print(f'epoch {epoch + 1}, loss: {l:f}')
l = loss(net(X), y): 计算损失。
l.backward(): 计算梯度。
sgd(params, lr, batch_size): 更新参数。
多层感知机的简洁实现
1. 读取数据
与之前相同,我们继续使用DataLoader来批量读取数据。
from torch.utils.data import DataLoader, TensorDataset
batch_size = 10
data_iter = DataLoader(dataset, batch_size, shuffle=True)
DataLoader:自动进行批量处理和数据打乱。
2. 定义模型
使用PyTorch的nn模块来定义模型。
from torch import nn
net = nn.Sequential(
nn.Linear(2, 256),
nn.ReLU(),
nn.Linear(256, 2)
)
nn.Linear:定义线性层,包括输入和输出的特征维度。
nn.ReLU:非线性激活函数,用于引入非线性。
Sequential:将多个模块按顺序组合成一个大的模块。
3. 初始化模型参数
PyTorch提供了方便的方法来初始化模型参数。
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)
init.normal_:用正态分布初始化权重。
net.apply:将init_weights函数应用于模型的每一个层。
4. 定义损失函数
使用PyTorch内置的交叉熵损失函数。
loss = nn.CrossEntropyLoss()
CrossEntropyLoss:自动进行softmax运算并计算交叉熵。
5. 定义优化算法
使用PyTorch的优化器,如SGD。
from torch.optim import SGD
optimizer = SGD(net.parameters(), lr=0.1)
SGD:随机梯度下降优化器。
net.parameters():自动提取模型中的参数。
lr:学习率。
6. 训练模型
组织训练循环。
num_epochs = 10
for epoch in range(num_epochs):
for X, y in data_iter:
output = net(X)
l = loss(output, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
print(f'epoch {epoch + 1}, loss: {l:f}')
optimizer.zero_grad():在每次梯度计算前清零。
l.backward():计算损失梯度。
optimizer.step():进行一步参数更新。
过拟合与权重衰减(L2正则化)
1. 过拟合的概念
过拟合:模型在训练数据上表现得很好,但在未见过的数据上表现不佳。
原因:模型可能过于复杂,学习了训练数据中的噪声和细节,而不是潜在的数据分布。
2. 权重衰减(L2正则化)
权重衰减:在损失函数中加入一个正则项,惩罚模型的权重大小。
L2正则化:正则项是权重的平方和的乘积。
3. 实现权重衰减
在PyTorch中,可以在优化器中设置权重衰减,来实现L2正则化。
optimizer = torch.optim.SGD(net.parameters(), lr=0.1, weight_decay=0.01)
weight_decay:权重衰减系数,对应于L2正则化中的λ。
SGD:随机梯度下降优化器。
4. 训练过程
在训练过程中,正则化项会自动加入到损失中。
for epoch in range(num_epochs):
for X, y in data_iter:
output = net(X)
l = loss(output, y) + weight_decay * l2_penalty(net.parameters())
optimizer.zero_grad()
l.backward()
optimizer.step()
l2_penalty:自定义的函数,计算权重的L2范数。
weight_decay * l2_penalty(net.parameters()):计算正则化项。
5. L2范数函数
计算模型参数的L2范数。
def l2_penalty(params):
l2_norm = sum(p.pow(2.0).sum() for p in params)
return l2_norm
p.pow(2.0).sum():计算每个参数的平方和。
注意点
权重衰减是防止过拟合的一种有效方法,特别是在数据量较少的情况下。
过大的权重衰减可能导致欠拟合,因此需要仔细选择衰减系数。
暂退法
1. 暂退法的概念
暂退法(Dropout):在训练过程中随机将一部分神经元的输出变为0,这样可以减少对特定特征的依赖,增强模型的泛化能力。
原理:每次前向传播时,随机选择一些节点忽略,这使得网络不能依赖于任何一个节点,从而防止过拟合。
2. 在PyTorch中使用Dropout
PyTorch提供了nn.Dropout模块,使得在神经网络中应用dropout变得简单。
dropout = nn.Dropout(p=0.2)
p:dropout的概率,即每个神经元被丢弃的概率。
3. 将Dropout添加到模型中
在模型的某些层后面添加dropout层。
net = nn.Sequential(
nn.Linear(2, 256),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(256, 2)
)
在ReLU激活函数之后添加了一个dropout层。
4. 训练与测试时的不同行为
Dropout在训练时和测试时的行为不同,在测试时应关闭dropout。
net.train() # 启用dropout
# 训练过程...
net.eval() # 关闭dropout
# 测试过程...
net.train():开启训练模式,启用dropout。
net.eval():开启评估模式,关闭dropout。
5. 训练过程
训练时使用dropout,测试时关闭。
for epoch in range(num_epochs):
net.train()
for X, y in data_iter:
output = net(X)
l = loss(output, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
net.eval()
# 测试模型性能...
训练过程中,net.train()确保dropout被激活。
测试时,net.eval()确保dropout被关闭。
注意点
Dropout通常在较复杂的网络中使用,特别是在有足够多数据的情况下。
Dropout的概率p是一个超参数,需要根据具体问题调整。
数值稳定性与参数初始化
1. 数值稳定性
数值稳定性:在神经网络的训练过程中,保证数值计算的稳定性,防止例如梯度消失或爆炸等问题。
常见做法:使用ReLU激活函数来避免梯度消失,梯度裁剪来避免梯度爆炸。
2. 参数初始化
正确的参数初始化可以帮助加速训练过程,增加收敛的可能性。
均匀或正态分布初始化:常用于简单网络结构。
Xavier/Glorot初始化:根据前一层的节点数自动调整权重的尺度,适用于深度网络。
He初始化:专为ReLU激活函数设计,考虑到ReLU的非线性特性。
3. 在PyTorch中进行参数初始化
PyTorch提供了多种初始化方法。
def init_weights(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
net.apply(init_weights)
init.xavier_uniform_:使用Xavier均匀初始化方法。
net.apply:将初始化函数应用于每个层。
4. 梯度裁剪
在训练过程中,梯度裁剪可以防止梯度爆炸。
torch.nn.utils.clip_grad_norm_(net.parameters(), max_norm=1)
clip_grad_norm_:裁剪梯度,确保梯度的范数不超过max_norm。
max_norm:梯度的最大范数。
5. 选择合适的激活函数
选择合适的激活函数也是保证数值稳定性的一种方式。
ReLU激活函数:避免梯度消失,特别是在深层网络中。
注意点
参数的初始化方式和激活函数的选择会显著影响模型的学习效果。
对于不同类型的网络和不同的问题,可能需要采用不同的初始化策略。
模型搭建
在PyTorch中,有两种主要的方式来搭建模型:使用Sequential类和定义自己的Module子类。
1. 使用Sequential类
Sequential类允许我们以顺序方式搭建模型。它适用于那些简单的前馈网络。
net = nn.Sequential(
nn.Linear(20, 256),
nn.ReLU(),
nn.Linear(256, 10)
)
优点:快速、直观。
限制:不够灵活,难以构建具有复杂拓扑结构的网络。
2. 定义自己的Module子类
更灵活的方法是创建nn.Module的子类,并定义自己的前向传播。
class CustomNet(nn.Module):
def __init__(self):
super(CustomNet, self).__init__()
self.layer1 = nn.Linear(20, 256)
self.layer2 = nn.Linear(256, 10)
def forward(self, x):
x = torch.relu(self.layer1(x))
return self.layer2(x)
net = CustomNet()
优点:非常灵活,可以自定义复杂的网络结构。
注意:需要手动定义每层以及如何在forward方法中连接它们。
参数管理
在PyTorch中,模型的参数存储在nn.Module的子类中,并可以通过parameters或named_parameters方法访问。
查看参数
可以查看模型的所有参数,或特定层的参数。
# 查看所有参数
for param in net.parameters():
print(param)
# 查看特定层的参数
for name, param in net.named_parameters():
if 'layer1' in name:
print(name, param.size())
parameters():返回模型所有参数的迭代器。
named_parameters():返回每个参数的名称和参数本身。
初始化参数
可以对模型的参数进行自定义初始化。
for name, param in net.named_parameters():
if 'weight' in name:
nn.init.xavier_uniform_(param)
使用init模块中的函数对特定的参数进行初始化。
共享参数
在多个层之间共享参数。
shared = nn.Linear(256, 10)
net = nn.Sequential(
nn.Linear(20, 256),
nn.ReLU(),
shared,
nn.ReLU(),
shared
)
shared层在网络中被多次使用,其参数在这些层之间共享。
自定义层
在PyTorch中,你可以通过扩展nn.Module来自定义层,从而创建符合特定需求的神经网络结构。
1. 定义一个自定义层
class CustomLayer(nn.Module):
def __init__(self, size, activation_fn=None):
super(CustomLayer, self).__init__()
self.weight = nn.Parameter(torch.randn(size))
self.activation_fn = activation_fn
def forward(self, x):
x = torch.matmul(x, self.weight)
if self.activation_fn is not None:
x = self.activation_fn(x)
return x
nn.Parameter:用于定义参数,这些参数会自动被识别为模型的可训练参数。
activation_fn:可以为自定义层指定一个激活函数。
2. 使用自定义层
custom_layer = CustomLayer(size=(20, 10), activation_fn=torch.relu)
output = custom_layer(input_data)
CustomLayer(size=(20, 10), activation_fn=torch.relu):创建一个具有指定大小和激活函数的自定义层。
文件读写操作
在PyTorch中,可以使用torch.save和torch.load来保存和加载模型。
1. 保存模型
torch.save(net.state_dict(), 'model.pth')
net.state_dict():获取模型的状态(参数)。
'model.pth':文件路径。
2. 加载模型
net.load_state_dict(torch.load('model.pth'))
torch.load('model.pth'):从文件中加载状态字典。
使用GPU计算
使用GPU进行计算可以显著提高神经网络训练的速度。
1. 检查GPU可用性
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
torch.device("cuda"):指定使用GPU。
torch.cuda.is_available():检查CUDA(GPU)是否可用。
2. 将模型和数据移动到GPU
net.to(device)
input_data = input_data.to(device)
net.to(device):将模型移动到指定设备(GPU或CPU)。
input_data.to(device):将数据移动到指定设备。
调整参数的技巧
学习率(Learning Rate):通常是调整的第一个参数。如果训练不稳定,尝试降低学习率;如果训练速度太慢,可以尝试增加学习率。
批量大小(Batch Size):较大的批量大小可以提高训练稳定性,但会增加内存使用。需要根据GPU的内存容量来调整。
优化器(Optimizer):不同的优化器(如Adam, SGD)可能会影响模型的训练速度和最终性能。根据具体问题选择合适的优化器。
正则化(Regularization):如L2正则化或Dropout可以帮助减少过拟合。