赞
踩
"""
PyTorch 提供了两个数据加载:
torch.utils.data.DataLoader和
torch.utils.data.Dataset
可以使用预加载的数据集以及您、自己的数据
Dataset存储样本及标签
DataLoader将Dataset包裹成一个可迭代对象,以便轻松访问样本。
下面是如何从 TorchVision 加载Fashion-MNIST数据集的示例。
Fashion-MNIST是Zalando文章图像的数据集,由60,000个训练示例和10,000个测试示例组成。
每个示例都包含 28×28 灰度图像和来自 10 个类别之一的相关标签。
"""
"""
使用以下参数加载FashionMNIST 数据集:
root是存储训练/测试数据的路径,
train指定训练或测试数据集,
download=True如果数据不可用,则从 Internet 下载数据root。
transform和target_transform指定特征和标签转换
"""
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root='data',
train=False,
download=True,
transform=ToTensor()
)
"""
迭代和可视化数据集
我们将Datasets像列表一样手动索引:
training_data[index].
matplotlib用来可视化训练数据中的一些样本。
"""
labels_map={
0:'T-shirt/top',
1:'Trouser',
2:'Pullover',
3:'Dress',
4:'Coat',
5:'Sandal',
6:'Shirt',
7:'Sneaker',
8:'Bag',
9:'Ankle boot',
}
figure = plt.figure(figsize=(8,8))
cols,rows = 3,3
for i in range(1,cols*rows+1):
sample_idx = torch.randint(len(training_data),size=(1,)).item()
img,label = training_data[sample_idx]
figure.add_subplot(rows,cols,i)
plt.title(labels_map[label])
plt.axis("off")
plt.imshow(img.squeeze(),cmap="gray")
plt.show()
"""
创建自定义数据集
自定义Dataset类必须实现三个函数:__init__、__len__和__getitem__。
FashionMNIST图像存储在一个目录img_dir中,它们的标签分别存储在一个CSV文件annotations_file中
"""
import os
import pandas as pd
from torchvision.io import read_image
class CustomImageDataset(Dataset):
def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
self.img_labels = pd.read_csv(annotations_file)
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform
def __len__(self):
return len(self.img_labels)
def __getitem__(self, idx):
img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
image = read_image(img_path)
label = self.img_labels.iloc[idx, 1]
if self.transform:
image = self.transform(image)
if self.target_transform:
label = self.target_transform(label)
return image, label
"""
使用 DataLoaders 为训练准备数据
检索我们数据集的Dataset特征并一次标记一个样本。
在训练模型时,我们通常希望以“小批量”的形式传递样本,在每个 epoch 重新洗牌以减少模型过拟合,并使用 Pythonmultiprocessing加速数据检索。
DataLoader是一个可迭代的,它在一个简单的 API 中为我们抽象了这种复杂性。
"""
from torch.utils.data import DataLoader
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
"""
遍历 DataLoader
我们已将该数据集加载到DataLoader并且可以根据需要遍历数据集。
下面的每次迭代都会返回一批train_features和train_labels(分别包含batch_size=64特征和标签)。
因为我们指定shuffle=True了 ,所以在我们遍历所有批次之后,数据被打乱。
"""
# Display image and label.
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")
"""
数据并不总是以训练机器学习算法所需的最终处理形式出现。我们使用transform来对数据进行一些操作并使其适合训练。
所有TorchVision数据集都有两个参数 -transform修改特征和 target_transform修改标签 - 接受包含转换逻辑的可调用对象。torchvision.transforms模块提供了几个开箱即用的常用转换。
FashionMNIST 特征是 PIL 图像格式,标签是整数。对于训练,我们需要将特征作为归一化张量,并将标签作为 one-hot 编码张量。为了进行这些转换,我们使用ToTensor和Lambda。
"""
"""
ToTensor 将 PIL 图像或 NumPyndarray转换为FloatTensor. 并在 [0., 1.] 范围内缩放图像的像素强度值
Lambda 转换应用任何用户定义的 lambda 函数。在这里,我们定义了一个函数来将整数转换为 one-hot 编码张量。
它首先创建一个大小为 10 的零张量(我们数据集中的标签数量)并调用 scatter_,它在标签上分配 a value=1给定的索引y。
"""
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor,Lambda
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
target_transform=Lambda(lambda y:torch.zeros(10,
dtype=torch.float).scatter(0,torch.tensor(y),value=1))
)
"""
神经网络由对数据执行操作的层/模块组成。torch.nn命名空间提供了构建自己的神经网络所需的所有构建块。
PyTorch中的每个模块都是 nn.Module 的子类。神经网络是一个模块本身,它由其他模块(层)组成。
这种嵌套结构允许轻松构建和管理复杂的架构。
在接下来的部分中,将构建一个神经网络来对 FashionMNIST 数据集中的图像进行分类。
"""
import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
"""
我们希望能够在 GPU 等硬件加速器(如果可用)上训练我们的模型。让我们检查一下 torch.cuda是否可用,否则我们继续使用CPU。
"""
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")
"""
定义类
我们通过子类化定义我们的神经网络nn.Module,并在 中初始化神经网络层__init__。
每个nn.Module子类都在方法中实现对输入数据的操作forward。
"""
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
"""
我们创建 的实例NeuralNetwork,并将其移动到device,并打印其结构。
"""
model = NeuralNetwork().to(device)
print(model)
"""
在输入上调用模型会返回一个 10 维张量,其中包含每个类的原始预测值。
我们通过nn.Softmax模块的一个实例来获得预测概率。
"""
X = torch.rand(1, 28, 28, device=device)
logits = model(X)
pred_probab = nn.Softmax(dim=1)(logits)
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")
"""
模型层
让我们分解 FashionMNIST 模型中的层。
为了说明这一点,我们将抽取 3 张大小为 28x28 的图像的小批量样本。
"""
input_image = torch.rand(3,28,28)
print(input_image.size())
"""
nn.展平
我们初始化nn.Flatten 层以将每个 2D 28x28 图像转换为 784 个像素值的连续数组(保持小批量维度(dim=0))
"""
flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())
"""
nn.线性
线性层是一个模块,它 使用其存储的权重和偏差对输入应用线性变换。
"""
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
"""
nn.ReLu
非线性激活是在模型的输入和输出之间创建复杂映射的原因。
它们在线性变换后应用以引入非线性,帮助神经网络学习各种现象。
在这个模型中,我们在线性层之间使用nn.ReLU,但是还有其他激活可以在模型中引入非线性。
"""
print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")
"""
nn.Sequential
nn.Sequential是一个有序的模块容器。
数据按照定义的顺序通过所有模块。
可以使用顺序容器来组合一个快速网络,例如seq_modules.
"""
seq_modules = nn.Sequential(
flatten,
layer1,
nn.ReLU(),
nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)
"""
nn.Softmax
神经网络的最后一个线性层返回logits - [-infty, infty] 中的原始值 - 被传递给 nn.Softmax模块。
logits 被缩放为值 [0, 1],表示模型对每个类别的预测概率。
dim参数指示值必须总和为 1 的维度。
"""
softmax = nn.Softmax(dim=1)
pred_probab = softmax(logits)
"""
模型参数
神经网络内的许多层都是参数化的,即具有在训练期间优化的相关权重和偏差。
子类nn.Module化会自动跟踪模型对象中定义的所有字段,并使用模型parameters()或named_parameters()方法使所有参数都可以访问。
在此示例中,我们遍历每个参数,并打印其大小和其值的预览
"""
print(f"Model structure: {model}\n\n")
for name, param in model.named_parameters():
print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")
"""
自动微分TORCH.AUTOGRAD
在训练神经网络时,最常用的算法是 反向传播。在该算法中,参数(模型权重)根据损失函数相对于给定参数的梯度进行调整。
为了计算这些梯度,PyTorch 有一个内置的微分引擎,称为torch.autograd. 它支持任何计算图的梯度自动计算。
考虑最简单的一层神经网络,具有输入x、参数w和b以及一些损失函数。它可以通过以下方式在 PyTorch 中定义
"""
import torch
x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)
"""
计算梯度
为了优化神经网络中参数的权重,我们需要计算我们的损失函数对参数的导数,
即我们需要计算$\frac{\partial loss}{\partial w}$ 和$\frac{\partial loss}{\partial b}$
在一些固定值x和y下。
为了计算这些导数,我们调用 loss.backward(),然后用w.grad和b.grad检索值:
"""
"""
我们只能获取grad计算图的叶节点的属性,这些叶节点的requires_grad属性设置为True。
对于我们图中的所有其他节点,梯度将不可用。
出于性能原因,我们只能 backward在给定图上使用一次执行梯度计算。
如果我们需要backward在同一个图上进行多次调用,
我们需要传递 retain_graph=True给backward调用。
"""
loss.backward()
print(w.grad)
print(b.grad)
"""
禁用梯度跟踪
默认情况下,所有张量requires_grad=True都在跟踪它们的计算历史并支持梯度计算。
但是,在某些情况下我们不需要这样做,例如,当我们训练了模型并且只想将其应用于某些输入数据时,
即我们只想通过网络进行前向计算。
我们可以通过用块包围我们的计算代码来停止跟踪计算 torch.no_grad():
"""
z = torch.matmul(x, w)+b
print(z.requires_grad)
with torch.no_grad():
z = torch.matmul(x, w)+b
print(z.requires_grad)
"""
实现相同结果的另一种方法是使用detach()张量上的方法:
"""
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
"""
希望禁用梯度跟踪的原因如下:
将神经网络中的一些参数标记为冻结参数。这是微调预训练网络的一个非常常见的场景
当您只进行前向传递时加快计算速度,因为在不跟踪梯度的张量上进行计算会更有效。
"""
"""
现在我们有了模型和数据,是时候通过优化数据上的参数来训练、验证和测试我们的模型了。
训练模型是一个迭代过程;在每次迭代(称为epoch)中,模型对输出进行猜测,
计算猜测中的误差(损失),收集误差相对于其参数的导数,并优化这些参数使用梯度下降
"""
"""
先决条件代码
我们从前面的Datasets & DataLoaders 和Build Model部分加载代码。
"""
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
training_data = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor()
)
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
)
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(28*28, 512),
nn.ReLU(),
nn.Linear(512, 512),
nn.ReLU(),
nn.Linear(512, 10),
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork()
"""
超参数
超参数是可调整的参数,可让您控制模型优化过程。
不同的超参数值会影响模型训练和收敛速度(阅读有关超参数调整的更多信息)
我们为训练定义了以下超参数:
Number of Epochs - 迭代数据集的次数
Batch Size - 参数更新前通过网络传播的数据样本数
学习率- 在每个批次/时期更新模型参数的程度。
较小的值会产生较慢的学习速度,而较大的值可能会导致训练期间出现不可预测的行为。
"""
learning_rate = 1e-3
batch_size = 64
epochs = 5
"""
优化循环
一旦我们设置了超参数,我们就可以使用优化循环来训练和优化我们的模型。
优化循环的每次迭代称为epoch。
每个时期包括两个主要部分:
训练循环- 迭代训练数据集并尝试收敛到最佳参数。
验证/测试循环- 迭代测试数据集以检查模型性能是否正在改善。
"""
"""
损失函数
当呈现一些训练数据时,我们未经训练的网络可能不会给出正确的答案。
损失函数衡量得到的结果与目标值的相异程度,是我们在训练时要最小化的损失函数。
为了计算损失,我们使用给定数据样本的输入进行预测,并将其与真实数据标签值进行比较。
常见的损失函数包括用于回归任务的nn.MSELoss(均方误差)和 用于分类的nn.NLLLoss(负对数似然)。
nn.CrossEntropyLoss结合nn.LogSoftmax和nn.NLLLoss。
我们将模型的输出 logits 传递给nn.CrossEntropyLoss,这将对 logits 进行归一化并计算预测误差。
"""
# Initialize the loss function
loss_fn = nn.CrossEntropyLoss()
"""
优化器
优化是在每个训练步骤中调整模型参数以减少模型误差的过程。
优化算法定义了如何执行这个过程(在这个例子中,我们使用随机梯度下降)。
所有优化逻辑都封装在optimizer对象中。在这里,我们使用 SGD 优化器;
此外,PyTorch 中有许多不同的优化器 可用,例如 ADAM 和 RMSProp,
它们可以更好地用于不同类型的模型和数据。
我们通过注册模型需要训练的参数并传入学习率超参数来初始化优化器。
"""
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
"""
在训练循环中,优化分三个步骤进行:
调用optimizer.zero_grad()以重置模型参数的梯度。默认情况下渐变加起来;
为了防止重复计算,我们在每次迭代时明确地将它们归零。
通过调用来反向传播预测损失loss.backward()。PyTorch 存储每个参数的损失梯度。
一旦我们有了我们的梯度,我们调用optimizer.step()通过在反向传递中收集的梯度来调整参数。
"""
"""
全面实施
我们定义train_loop循环优化代码,并test_loop根据我们的测试数据评估模型的性能。
"""
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
for batch, (X, y) in enumerate(dataloader):
# Compute prediction and loss
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
def test_loop(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in dataloader:
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
"""
我们初始化损失函数和优化器,并将其传递给train_loop和test_loop。
随意增加 epoch 的数量来跟踪模型的改进性能
"""
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
epochs = 10
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
train_loop(train_dataloader, model, loss_fn, optimizer)
test_loop(test_dataloader, model, loss_fn)
print("Done!")
"""
如何通过保存、加载和运行模型预测来保持模型状态。
"""
import torch
import torchvision.models as models
"""
保存和加载模型权重
PyTorch 模型将学习到的参数存储在内部状态字典中,称为state_dict.
这些可以通过以下torch.save 方法持久化:
"""
model = models.vgg16(pretrained=True)
torch.save(model.state_dict(), 'model_weights.pth')
"""
要加载模型权重,首先需要创建一个相同模型的实例,然后使用load_state_dict()方法加载参数。
"""
model = models.vgg16() # we do not specify pretrained=True, i.e. do not load default weights
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()
# 一定要model.eval()在推理之前调用方法,将 dropout 和 batch normalization 层设置为评估模式。不这样做会产生不一致的推理结果。
"""
使用形状保存和加载模型
加载模型权重时,我们需要先实例化模型类,因为该类定义了网络的结构。
我们可能希望将此类的结构与模型一起保存,在这种情况下,我们可以将model(而不是model.state_dict())传递给保存函数:
"""
torch.save(model, 'model.pth')
"""
然后我们可以像这样加载模型:
"""
model = torch.load('model.pth')
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。