数据集
PyTorch中,torch.utils.data.DataLoader和torch.utils.data.Dataset 可以让我们方便使用预加载的数据集或者自己的数据集。Dataset存储数据样本及其对应的标签,而DataLoader将Dataset包裹起来,生成一个可迭代对象,以便轻松访问数据样本。
1. 加载数据集
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, # True为训练集,False为测试集
download=True, # 是否要下载
transform=ToTensor() # 对样本数据进行处理,转换为张量数据
)
# 测试数据集
test_data = datasets.FashionMNIST(
root="data",
train=False,
download=True,
transform=ToTensor()
2. 可视化数据集
# 标签字典,一个key键对应一个label
labels_map = {
0: "数字1",
1: "……",
……
}
# 设置画布大小
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")#img.squeeze()被用来去除图像数组中的单通道维度,以便能够正确地显示灰度图像。
3.自定义数据集
在定义自己的数据集时,需继承Dataset类
init:实例化Dataset对象时运行,完成初始化工作。
len:返回数据集的大小。
getitem:根据索引返回一个样本(数据和标签)。
import os
import pandas as pd
from torchvision.io import read_image
class ImageDataset(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
数据加载器
1.根据数据集生成一个可迭代的对象,用于模型训练
两个工具包,可配合DataLoader使用:
-
enumerate(iterable, start=0):输入是一个可迭代的对象和下标索引开始值;返回可迭代对象的下标索引和数据本身。
-
tqdm(iterable):进度条可视化工具包
from torch.utils.data import DataLoader data_loader = DataLoader( dataset=MyDataset,#定义好的数据集 batch_size=16,#每次放入网络训练的批次大小,默认为1. shuffle=True, #是否打乱数据的顺序,默认为False。一般训练集设置为True,测试集设置为False num_workers=0,#线程数,默认为0。在Windows下设置大于0的数可能会报错 drop_last=False,#是否丢弃最后一个批次的数据,默认为False。 )
2.加载数据
from torch.utils.data import DataLoader train_dataloader = DataLoader( dataset=training_data, batch_size=64, shuffle=True) test_dataloader = DataLoader( dataset=test_data, batch_size=64, shuffle=True)
3 遍历DataLoader
# 展示图片和标签
train_features, train_labels = next(iter(train_dataloader))
# (B,N,H,W)
print(f"Feature batch shape: {train_features.size()}")# Feature batch shape:torch.Size([64, 1, 28, 28])
print(f"Labels batch shape: {train_labels.size()}")# Labels batch shape: torch.Size([64])
# 获取第一张图片,去除第一个批量维度
img = train_features[0].squeeze()
label = train_labels[0]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")# Label: 8
torchvision.transforms图片处理
import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
ds = datasets.FashionMNIST(
root="data",
train=True,
download=True,
transform=ToTensor(),
# Lambda变换,定义了一个函数来将整数转换为one-hot编码张量
# 它首先创建一个大小为10的零张量(数据集中的标签数量)并调用scatter_,根据索引y将值更改为1
target_transform = Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(dim=0, index=torch.tensor(y), value=1))
)
transfroms.ToTensor()
将PIL Image或者numpy.ndarray格式的数据转换为tensor格式,像素值大小缩放至区间[0., 1.]。
transforms.Normalize()
对输入进行标准化,传入均值(mean[1],…,mean[n])和标准差(std[1],…,std[n]),n与输入的维度相同。
output[channel] = (input[channel] - mean[channel]) / std[channel]
transforms.ToPILImage()
将tensor或者numpy.ndarray格式的数据转换为PIL Image图片格式。
transforms.Resize()
修改图片尺寸。参数size可是序列或整数,传序列,修改后图片尺寸和序列一致;传入整数,则等比例缩放图片。
transforms.CenterCrop()
中心裁剪图片。参数size可序列或整数,传序列,则裁剪后的图片尺寸和序列一致;传整数,则裁剪尺寸长宽都为size的正方形
transforms.RandomCrop()
随机裁剪。参数size可以是序列也可以是整数
transforms.RandomResizedCrop()
将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小。
transforms.RandomHorizontalFlip()
有一定概率将图片水平翻转,默认概率为0.5。
transforms.RandomVerticalFlip()
有一定概率将图片垂直翻转,默认概率为0.5。
transforms.RandomRotation()
将图片旋转。参数degrees可以为序列或者数值,如果为序列,则旋转角度为(min_degree, max_degree);如果为数值,则旋转角度为(-degrees, +degrees)。
模型定义
torch.nn提供了构建神经网络所需的全部模块。
1.训练设备
device = "cuda" if torch.cuda.is_available() else "cpu"
2 定义模型
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__init__()
self.flatten = nn.Flatten()
self.linear_relu_stack = nn.Sequential(
nn.Linear(in_features=28 * 28, out_features=512),
nn.ReLU(),
nn.Linear(in_features=512, out_features=512),
nn.ReLU(),
nn.Linear(in_features=512, out_features=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)
3 网络模型中的各种层
我们随机生成3张大小为 28x28 的图像的小批量样本,观察每一层对输入数据处理的结果
input_image = torch.rand(3,28,28)
# torch.Size([3, 28, 28])
1 nn.Flatten
flatten = nn.Flatten()
flat_image = flatten(input_image)
# torch.Size([3, 784])
2 nn.Linear
layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())
# torch.Size([3, 20])
3 nn.ReLU
hidden1 = nn.ReLU()(hidden1)
4 nn.Sequential
nn.Sequential
可以理解为网络层的容器,在其中我们定义各种网络层,数据会按照我们设置的顺序经过所有网络层。
seq_modules = nn.Sequential(
flatten,
layer1,
nn.ReLU(),
nn.Linear(20, 10)
)
input_image = torch.rand(3,28,28)
logits = seq_modules(input_image)
优化模型参数
训练模型是一个迭代过程;在每次迭代(epoch)中,模型对输出进行预测,首先计算猜测值与真实值的误差(损失),然后计算误差关于其参数的导数,最后使用梯度下降法优化这些参数
1 超参数
- 训练次数epochs:迭代数据集的次数。
- 批处理大小batch_size:每次传入网络中的样本数量。
- 学习率learning_rate:在每个批次更新模型参数的程度。较小的值会产生较慢的学习速度,而较大的值可能会导致训练期间出现不可预测的行为。
2 优化循环
- 训练循环:迭代训练数据集并尝试收敛到最佳参数。
- 验证/测试循环:迭代测试数据集以检查模型性能是否正在改善。
损失函数
# 初始化损失函数
loss_fn = nn.CrossEntropyLoss()
优化器
- 调用
optimizer.zero_grad()
将模型参数的梯度归零。默认情况下梯度会累加。 - 调用
loss.backward()
来反向传播预测损失。PyTorch存储每个参数的损失梯度。 - 计算梯度完成后,调用
optimizer.step()
来调整参数。
# 优化模型参数
def train_loop(dataloader, model, loss_fn, optimizer, device):
size = len(dataloader.dataset)
for batch, (X, y) in enumerate(dataloader):
X = X.to(device)
y = y.to(device)
pred = model(X)
# 计算损失
loss = loss_fn(pred, y)
# 反向传播,优化参数
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, device):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
with torch.no_grad():
for X, y in dataloader:
X = X.to(device)
y = y.to(device)
# 前向传播,计算预测值
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
。
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(params=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, device)
test_loop(test_dataloader, model, loss_fn, device)
print("Done!")
# ...
# Epoch 5
# -------------------------------
# loss: 1.214354 [ 0/60000]
# loss: 1.228768 [ 6400/60000]
# loss: 1.314466 [12800/60000]
# loss: 1.234377 [19200/60000]
# loss: 1.242174 [25600/60000]
# loss: 1.027974 [32000/60000]
# loss: 1.062843 [38400/60000]
# loss: 1.157571 [44800/60000]
# loss: 1.091189 [51200/60000]
# loss: 1.143303 [57600/60000]
# Test Error:
# Accuracy: 64.6%, Avg loss: 1.092479
#
# Done!
保存和加载模型
保存和加载模型权重
# 样例代码如下
model = models.vgg16(pretrained=True) # pretrained=True加载预训练好的参数
torch.save(model.state_dict(), 'model_weights.pth')
# 要加载模型权重,首先需要创建一个相同模型的实例,然后使用load_state_dict()方法加载参数。
model = models.vgg16() # 不加载预训练好的参数
model.load_state_dict(torch.load('model_weights.pth'))
model.eval() # 将模型设置为测试模式,避免dropout和batch normalization对预测结果造成的影响
保存和加载整个模型
保存模型的结构和参数:
torch.save(model, 'model.pth')
加载模型:
model = torch.load('model.pth')
标签:loss,nn,self,torch,学习,pytorch,model,size
From: https://www.cnblogs.com/1019-Yan/p/18148058