Introduction to PyTorch 1. QuickStart Working Data PyTorch 提供了两个数据类型:torch.utils.data.DataLoader 和 torch.utils.data.Dataset . Dataset用来储存数据和标签(Label),DataLoader用来迭代数据。
1 2 3 4 5 import torchfrom torch import nnfrom torch.utils.data import DataLoaderfrom torchvision import datasetsfrom torchvision.transforms import ToTensor
PyTorch提供了数据库的接口:TorchText, TorchVision, TorchAudio. 我们将用TorchVision作为示例,它包含了真实的图片集,例如CIFAR,COCO以及其他 ,我们接下来使用FusionMNIST,每个数据集(Dataset)中都包含了两个参数 transform 和 target_transform 分别用来修改样本和样本标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 training_data = datasets.FashionMNIST( root="data" , train=True , download=True , transform=ToTensor(), ) test_data = datasets.FashionMNIST( root="data" , train=False , download=True , transform=ToTensor(), )
我们将Dataset作为参数传递给DataLoader,在我们的数据集上进行可迭代,并支持自动批处理、采样、洗牌和多进程数据加载。在这里,我们定义了batch_size=64 即数据加载器可迭代中的每个元素将返回一批64个特征和标签。
1 2 3 4 5 6 7 8 9 10 batch_size = 64 train_dataloader = DataLoader(training_data, batch_size=batch_size) test_dataloader = DataLoader(test_data, batch_size=batch_size) for X, y in test_dataloader: print (f"Shape of X [N, C, H, W]: {X.shape} " ) print (f"Shape of y: {y.shape} {y.dtype} " ) break
Creat Model 为了在PyTorch中定义神经网络,我们创建一个继承于nn.Module的类。我们在__init__函数中定义网络层,并在前向传播(forward function)中指定数据如何通过网络。为了加速神经网络的操作,我们将其移动到CUDA、MPS、MTIA或XPU等加速器,如果你的设备没有加速器,我们就使用CPU
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import os import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets, transforms device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu" print (f"Using {device} device" ) class NeuralNetwork (nn.Module): def __init__ (self ): super ().__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().to(device) print (model)
Optimizing model parameters 要训练一个模型,我们需要一个损失函数(loss function)和一个优化器(optimizer)
1 2 lose_fn = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=1e-3 )
在单个训练循环中,模型对训练数据集进行预测,并反向传播预测误差以调整模型的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 def train (dataloader, model, loss_fn, optimizer ): size = len (dataloader.dataset) model.train() for batch, (X, y) in enumerate (dataloader): X, y = X.to(device), y.to(device) pred = model(X) loss = loss_fn(pred, y) loss.backward() optimizer.step() optimizer.zero_grad() if batch % 100 == 0 : loss_val = loss.item() current = (batch + 1 ) * len (X) print (f"loss: {loss_val:>7f} [{current:>5d} /{size:>5d} ]" )
我们接下来在测试集上测试我们训练模型的性能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def test (dataloader, model, loss_fn ): size = len (dataloader.dataset) num_batches = len (dataloader) model.eval () test_loss, correct = 0 , 0 with torch.no_grad(): for X, y in dataloader: X, y = X.to(device), 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.1 f} %, Avg loss: {test_loss:>8f} \n" )
培训过程在几个epoches进行。在每个epoch中,模型都会学习参数,以做出更好的预测。我们在每个epoch打印模型的准确性和损失;我们希望看到每个epoch准确性的提高和损失的减少。
1 2 3 4 5 6 epochs = 5 for t in range (epochs): print (f"Epoch {t+1 } \n-------------------------------" ) train(train_dataloader, model, loss_fn, optimizer) test(test_dataloader, model, loss_fn) print ("Done!" )
Save Model 我们首先来保存模型
1 2 torch.save(model.state_dict(),"model.pth" ) print ("Saved PyTorch Model State to model.pth" )
再尝试加载模型:
1 2 model = NeuralNetwork().to(device) model.load_state_dict(torch.load("model.pth" , weights_only=True ))
用我们自己训练的模型来预测类别:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 classes = [ "T-shirt/top" , "Trouser" , "Pullover" , "Dress" , "Coat" , "Sandal" , "Shirt" , "Sneaker" , "Bag" , "Ankle boot" , ] model.eval () x, y = test_data[0 ][0 ], test_data[0 ][1 ] with torch.no_grad(): x = x.to(device) pred = model(x) predicted = classes[pred[0 ].argmax(0 )] actual = classes[y] print (f'Predicted: "{predicted} ", Actual: "{actual} "' )
2. Tensors 张量(Tensor)是一种专门的数据结构,与数组和矩阵非常相似。在PyTorch中,我们使用张量对模型的输入和输出以及模型的参数进行编码.张量与NumPy的ndarrays相似,只是张量可以在GPU或其他硬件加速器上运行。事实上,张量和NumPy数组通常可以共享相同的底层内存。
1 2 3 import torchimport numpy as np
Tensor的初始化方式 :
直接从数据创建,自动推导数据类型
1 2 data = [[1 , 2 ],[3 , 4 ]] x_data = torch.tensor(data)
从Numpy数据类型推导
1 2 np_array = np.array(data) x_np = torch.from_numpy(np_array)
从其他Tensor推导,直接保留原来Tensor的形状和类型
1 2 3 4 5 x_ones = torch.ones_like(x_data) print (f"Ones Tensor: \n {x_ones} \n" )x_rand = torch.rand_like(x_data, dtype=torch.float ) print (f"Random Tensor: \n {x_rand} \n" )
随机或者常量生成
shape是张量维度的数组。在以下函数中,它决定了输出张量的维度。
1 2 3 4 5 6 7 8 shape = (2 ,3 ,) rand_tensor = torch.rand(shape) ones_tensor = torch.ones(shape) zeros_tensor = torch.zeros(shape) print (f"Random Tensor: \n {rand_tensor} \n" )print (f"Ones Tensor: \n {ones_tensor} \n" )print (f"Zeros Tensor: \n {zeros_tensor} " )
Tensor的属性
Tensor 的属性包括形状、类型、运行设备
1 2 3 4 5 tensor = torch.rand(3 ,4 ) print (f"Shape of tensor: {tensor.shape} " )print (f"Datatype of tensor: {tensor.dtype} " )print (f"Device tensor is stored on: {tensor.device} " )
Tensor 的操作
这里 全面描述了1200多个张量运算,包括算术、线性代数、矩阵操作(转置、索引、切片)、采样等。这些操作中的每一个都可以在CPU和加速器上运行,如CUDA、MPS、MTIA或XPU。默认情况下,张量是在CPU上创建的。我们需要使用.to方法(在检查加速器可用性后)明确地将张量移动到加速器上。这种跨设备复制大型张量在时间和内存方面可能很昂贵!
1 2 3 if torch.accelerator.is_available(): tensor = tensor.to(torch.accelerator.current_accelerator())
索引和切片(indexing and slicing**)**
1 2 3 4 5 6 tensor = torch.ones(4 , 4 ) print (f"First row: {tensor[0 ]} " )print (f"First column: {tensor[:, 0 ]} " )print (f"Last column: {tensor[..., -1 ]} " )tensor[:,1 ] = 0 print (tensor)
连接张量(joining Tensor),可以使用torch.cat沿着给定维度串联一系列张量。另一个张量连接运算符torch.stack,与torch.cat有微妙的不同。
1 2 t1 = torch.cat([tensor, tensor, tensor], dim=1 ) print (t1)
dim=1 表示沿着第1维度(列方向,注意维度从0开始)进行拼接
假设原始 tensor 的形状是 (3, 4):
拼接前:三个 (3, 4) 的张量
拼接后:t1 的形状将是 (3, 12),因为第1维度的大小是 4+4+4=12
如果是 dim=0,则会在第0维度(行方向)进行拼接,结果形状会是 (9, 4)。
算术运算(Arithmetic operations**)**
1 2 3 4 5 6 7 8 9 10 11 12 y1 = tensor @ tensor.T print (f"y1: {y1} " )y2 = tensor.matmul(tensor.T) print (f"y2: {y2} " )y3 = torch.rand_like(y1) print (f"y3:{y3} " )torch.matmul(tensor, tensor.T,out=y3)
1 2 3 4 5 6 7 8 9 10 11 z1 = tensor*tensor print (f"z1: {z1} " )z2 = tensor.mul(tensor) print (f"z2: {z2} " )z3 = torch.rand_like(z1) print (f"z3: {z3} " )torch.mul(tensor,tensor,out=z3)
单元素张量(single-element tensors) 如果您有一个单元素张量,例如通过将张量的所有值汇总为一个值,您可以使用item()将其转换为Python数值:
1 2 3 agg = tensor.sum () agg_item = agg.item() print (agg_item,type (agg_item))
5.** In-place operations** 将结果存储到操作数中的操作称为就地操作。它们用后缀_表示。例如:x.copy_(y),x.t_()
1 2 3 print (f"{tensor} \n" )tensor.add_(5 ) print (tensor)
Tensors and Numpy
Tensor 和 Numpy 共享内存,它们指向同一个数据,改变一个的同时会改变另一个
Tensor to Numpy
1 2 3 4 t = torch.ones(5 ) print (f"t: {t} " )n = t.numpy() print (f"n: {n} " )
Numpy to Tensor
1 2 n = np.ones(5 ) t = torch.from_numpy(n)
改变Numpy也会改变Tensor
1 2 3 np.add(n, 1 , out=n) print (f"t: {t} " )print (f"n: {n} " )
Datasets and DataLoaders 前面我们已经简单介绍过这个例子,在这一部分,我们进一步学习。
1 2 3 4 5 6 import torchfrom torch.utils.data import Datasetfrom torchvision import datasetsfrom torchvision.transform import ToTensorimport matlibplot.pyplot as plt
root: 下载数据的存储路径 train: 选择训练数据还是测试数据 download:如果root下没有数据,则从网络中下载 transform and target_transform :特征和标签的转化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 training_data = datasets.FashionMNIST( root="data", train=True, download=True, transform=ToTensor() ) test_data = datasets.FashionMNIST( root="data", train=False, download=True, transform=ToTensor() )
利用matplotlib来可视化一些数据集中的样本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 labels_map = { 0 : "T-Shirt" , 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()
创建一个定义的数据集合
自定义数据集类必须实现三个函数:init 、len__和__getitem 。FashionMNIST图像存储在目录img_dir中,它们的标签单独存储在CSV文件notonations_file中,我们逐步解释每个函数的作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 import osimport pandas as pdfrom torchvision.io import decode_imageclass CustomImageDataset (Dataset ): """ 自定义图像数据集类,用于加载图像及其对应的标签 继承自PyTorch的Dataset类 """ def __init__ (self, annotations_file, img_dir, transform=None , target_transform=None ): """ 初始化数据集 参数: annotations_file (str): 包含图像文件名和标签的CSV文件路径 img_dir (str): 存储所有图像的目录路径 transform (callable, 可选): 应用于图像的可选变换函数 target_transform (callable, 可选): 应用于标签的可选变换函数 CSV格式: tshirt1.jpg, 0 tshirt2.jpg, 0 ...... ankleboot999.jpg, 9 """ self .img_labels = pd.read_csv(annotations_file) self .img_dir = img_dir self .transform = transform self .target_transform = target_transform def __len__ (self ): """ 返回数据集中的样本总数 返回: int: 数据集中的样本数量 """ return len (self .img_labels) def __getitem__ (self, idx ): """ 加载并返回给定索引处的样本(图像和标签) 参数: idx (int): 要获取的样本索引 返回: tuple: (图像, 标签) 图像是转换后的图像张量 标签是对应的标签(可能经过转换) """ img_path = os.path.join(self .img_dir, self .img_labels.iloc[idx, 0 ]) image = decode_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
Preparing data for training with DataLoaders
Dataset检索我们数据集的特征,并一次标记一个样本。在训练模型时,我们通常希望在”minibatches”中传递样本,在每个epoch重新打乱数据以减少模型过度拟合,并使用Python的multiprocessing来加快数据检索速度。
1 2 3 4 5 from torch.utils.data import DataLoadertrain_dataloader = DataLoader(training_data, batch_size=64 , shuffle=True ) test_dataloader = DataLoader(test_data, batch_size=64 , shuffle=True )
Iterate through the DataLoader
我们已经将该数据集加载到DataLoader中,并且可以根据需要迭代数据集。下面的每个迭代都会返回一批train_features和train_labels(分别包含batch_size=64个特征和标签)。因为我们指定了shuffle=True,在我们迭代所有批次后,数据被重新打乱。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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} " )
数据并不总是以训练机器学习算法所需的最终处理形式出现。我们使用Tranforms来对数据进行一些操作,并使其适合训练。所有TorchVision数据集都有两个参数——用于修改特征的transform和用于修改标签的target_transform。torchvision.transforms模块开箱即用提供了几种常用的变换。
FashionMNIST功能采用PIL图像格式,标签为整数。对于训练,我们需要将特征作为归一化张量,将标签作为one-hot 编码张量。为了进行这些转换,我们使用ToTensor和Lambda。
1 2 3 4 5 6 7 8 9 10 11 import torchfrom torchvision import datasetsfrom torchvision.transforms import ToTensor, Lambdads = 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 )) )
ToTensor将PIL图像或NumPy ndarray转换为FloatTensor。并将图像的像素强度值缩放在[0., 1.]范围内。
target_transform = Lambda( # 使用Lambda定义一个匿名函数作为transform lambda y: # 输入y是原始标签(假设是0-9的整数) torch.zeros(10, dtype=torch.float) # 先创建一个全0的张量,长度为10(对应10个类别) .scatter_( # 使用scatter_方法按索引填充1 0, # 沿第0维度操作(即对一维张量) torch.tensor(y), # 将y转换为张量作为索引位置 value=1 # 在索引位置填充值为1 ) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 **输入**:整数标签 `y`(例如 `y=2`)。 **输出**:长度为10的one-hot张量,在位置 `y` 处为1,其余为0。 例如,`y=2` → `[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]`。 ## Build the Neural Network 神经网络由对数据执行操作的层/模块组成。Torch.nn命名空间提供了构建自己的神经网络所需的所有构建块。PyTorch中的每个模块都是nn.Module的子类。神经网络是一个由其他模块(层)组成的模块本身。这种嵌套结构允许轻松构建和管理复杂的架构。在接下来的部分中,我们将构建一个神经网络,对FashionMNIST数据集中的图像进行分类。 ```python import os import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets, transforms
Get Device for Training
1 2 device = torch.accelerator.current_accelerator().type if torch.accelerator.is_available() else "cpu" print (f"Using {device} device" )
Define the class
我们通过子类nn.Module来定义我们的神经网络,并在__init__中初始化神经网络层。每个nn.Module子类都实现forward方法中的输入数据操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class NeuralNetwork (nn.Module): def __init__ (self ): super ().__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
创建一个对象,并且转移到device上运算:
1 2 model = NeuralNetwork().to(device) print (model)
输入数据,模型返回一个二维张量,其中dim=0对应于每个类的10个原始预测值的输出,dim=1对应于每个输出的单个值。我们通过nn.Softmax模块的实例获得预测概率。
1 2 3 4 5 6 7 8 9 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} " )
Model Layers
我们假设一个图片的大小:
1 2 input_image = torch.rand(3 ,28 ,28 ) print (input_image.size())
nn.Flatten
我们初始化nn.Flatten层,将每个2D 28x28图像转换为784像素值的连续数组。
1 2 3 flatten = nn.Flatten() flat_image = flatten(input_image) print (flat_image.size())
nn.Linear
线性层是一个使用其存储的权重和偏置对输入应用线性变换的模块。
1 2 3 layer1 = nn.Linear(in_features=28 *28 , out_features=20 ) hidden1 = layer1(flat_image) print (hidden1.size())
nn.ReLU
非线性激活是在模型的输入和输出之间创建复杂映射的原因。它们在线性变换后应用来引入非线性,帮助神经网络学习各种现象。
1 2 3 print (f"Before ReLU: {hidden1} \n\n" )hidden1 = nn.ReLU()(hidden1) print (f"After ReLU: {hidden1} " )
nn.Sequential
nn.Sequential是一个有序的模块容器。数据按照定义的相同顺序通过所有模块。您可以使用顺序容器来组装一个像seq_modules这样的快速网络。
1 2 3 4 5 6 7 8 9 seq_modules = nn.Sequential( flatten, layer1, nn.ReLU(), nn.Linear(20 , 10 ) ) input_image = torch.rand(3 ,28 ,28 ) logits = seq_modules(input_image) print (logits)
nn.Softmax
神经网络的最后一个线性层返回 logits,在[-infty,infty]的原始值——传递给nn.Softmax模块。对数被缩放为值[0, 1],表示模型对每个类的预测概率。dim参数表示值必须相加为1的维度。
1 2 3 softmax = nn.Softmax(dim=1 ) pred_probab = softmax(logits) print (pred_probab)
Model Parameters
神经网络中的许多层都是参数化的,即在训练期间优化了相关的权重和偏差。子类nn.Module自动跟踪nn.Module对象中定义的所有字段,并使用模型的parameters()或name_parameters()方法访问所有参数。
1 2 3 4 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" )
Automatic Differentiation with torch.autograd 在训练神经网络时,最常用的算法是反向传播(back propagation)。在这个算法中,参数(模型权重)根据损失函数相对于给定参数的梯度(gradient)进行调整。为了计算这些梯度,PyTorch有一个名为torch.autograd的内置微分引擎。它支持自动计算任何计算图的梯度。考虑最简单的单层神经网络,具有输入x、参数w和b以及一些损失函数。它可以在PyTorch中以以下方式定义:
1 2 3 4 5 6 7 8 import torchx = torch.ones(5 ) y = torch.zeros(3 ) 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)
在这个网络中,w和b是我们需要优化的参数。因此,我们需要能够根据这些变量计算损失函数的梯度。为了做到这一点,我们设置了这些张量的requires_grad属性。
我们应用于张量来构造计算图的函数实际上是类函数的对象。这个对象知道如何向前方向计算函数,以及如何在向后传播步骤中计算其导数。向后传播函数的引用存储在张量的grad_fn属性中。
1 2 print (f"Gradient function for z = {z.grad_fn} " )print (f"Gradient function for loss = {loss.grad_fn} " )
计算导数:
1 2 3 loss.backward() print (w.grad)print (b.grad)
默认情况下,所有requires_grad=True的张量都在跟踪其计算历史并支持梯度计算。然而,在某些情况下,我们不需要这样做,例如,当我们训练了模型,只想将其应用于一些输入数据时,即我们只想通过网络进行正向计算。我们可以通过使用torch.no_grad()块包围我们的计算代码来停止跟踪计算,另一种方法是使用detach()函数:
1 2 3 4 5 6 7 8 9 10 z = torch.matmul(x, w)+b print (z.requires_grad)with torch.no_grad(): z = torch.matmul(x, w)+b print (z.requires_grad)z = torch.matmul(x,w)+b z_det = z.detach() print (z_det.requires_grad)
Tensor Gradients and Jacobian Products
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 inp = torch.eye(4 , 5 , requires_grad=True ) out = (inp+1 ).pow (2 ).t() out.backward(torch.ones_like(out), retain_graph=True ) print (f"First call\n{inp.grad} " ) out.backward(torch.ones_like(out), retain_graph=True ) print (f"\nSecond call\n{inp.grad} " ) inp.grad.zero_() out.backward(torch.ones_like(out), retain_graph=True ) print (f"\nCall after zeroing gradients\n{inp.grad} " )
Optimizing Model Parameters 我们现在可以通过数据集来对我们自己构建模型的参数进行训练,我们前面构建的模型如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 import torchfrom torch import nnfrom torch.utils.data import DataLoaderfrom torchvision import datasetsfrom torchvision.transforms import ToTensortraining_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 ().__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()
超参数:
1 2 3 4 ``` Epoch-在数据集中迭代的次数 Batch size-在参数更新之前通过网络传播的数据样本数量 Learning Rate-每个batch/epoch更新多少模型参数。较小的值会导致学习速度变慢,而较大的值可能会导致训练期间的不可预测行为。
learning_rate = 1e-3 batch_size = 64 epochs = 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 **Optimization loop:** 一旦我们设置了超参数,我们就可以用循环来训练和优化我们的模型。循环的每一次迭代都被称为epoch。每个epoch由两个主要部分组成: - Train loop——迭代训练数据集,并尝试收敛到最佳参数。 - The validation/Test loop——迭代测试数据集,以检查模型性能是否在提高。 **Loss Function:** 当提供一些训练数据时,我们未经训练的网络可能不会给出正确的答案。损失函数衡量获得的结果与目标值的差异程度,这是我们在训练期间想要最小化的损失函数。为了计算损失,我们使用给定数据样本的输入进行预测,并将其与真实数据标签值进行比较。 常见的损失函数包括用于回归任务的nn.MSELoss(均方误差)和用于分类的nn.NLLLoss(负对数似然)。nn.CrossEntropyLoss结合了nn.LogSoftmax和nn.NLLLoss。 ```python # Initialize the loss function loss_fn = nn.CrossEntropyLoss()
Optimizer:
优化是调整模型参数的过程,以减少每个训练步骤中的模型错误。优化算法定义了该过程的执行方式(在本例中,我们使用随机梯度下降)。所有优化逻辑都封装在优化器对象中。在这里,我们使用SGD优化器;此外,PyTorch中有许多不同的优化器,如ADAM和RMSProp,它们更适合不同类型的模型和数据。
我们通过设置需要训练的模型参数并传递学习率超参数来初始化优化器。
1 optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
在训练循环中,优化分三个步骤进行:
调用optimizer.zero_grad()来重置模型参数的梯度。默认情况下,梯度加起来;为了防止重复计数,我们在每次迭代时都明确将其归零。
通过调用loss.backward()来反向传播预测损失。PyTorch将损失的梯度与每个参数一起计算。
一旦我们有了梯度,我们就会调用optimiter.step()来根据向后传递中收集的梯度来调整参数。
具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 def train_loop (dataloader, model, loss_fn, optimizer ): size = len (dataloader.dataset) model.train() for batch, (X, y) in enumerate (dataloader): pred = model(X) loss = loss_fn(pred, y) loss.backward() optimizer.step() optimizer.zero_grad() if batch % 100 == 0 : loss, current = loss.item(), batch * batch_size + len (X) print (f"loss: {loss:>7f} [{current:>5d} /{size:>5d} ]" ) def test_loop (dataloader, model, loss_fn ): model.eval () 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.1 f} %, Avg loss: {test_loss:>8f} \n" ) 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!" )
Save and Load the Model
model weigth的方式
model weights通过state_dict的方式save:
1 2 3 4 import torchimport torchvision.models as modelsmodel = models.vgg16(weights='IMAGENET1K_V1' ) torch.save(model.state_dict(), 'model_weights.pth' )
Load model weights你必须先创建一个相同结构的模型,再使用load_**state_dict()**的方式:
1 2 3 model = models.vgg16() model.load_state_dict(torch.load('model_weights.pth' , weights_only=True )) model.eval ()
model shapes的方式
保存模型
1 torch.save(model, 'model.pth' )
加载模型
1 model = torch.load('model.pth' , weights_only=False )