pytorch学习笔记(一)-- pytorch深度学习框架基本知识了解

一、pytorch安装

           INSTALL PYTORCH的方式:pytorch的安装可以根据版本(stable、nightly),系统(Linux/Mac/Windows)、安装方式(Conda/Pip/Libtorch/Source)、编码语言(Python、C++/Java),计算平台(CUDA11.8、CUDA12.1、CUDA12.4、ROCm6.2、CPU)等进行区分。

下面对几种不同的安装方式做个说明:

conda安装:

conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia

conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidia

conda install pytorch torchvision torchaudio pytorch-cuda=12.4 -c pytorch -c nvidia

conda install pytorch torchvision torchaudio cpuonly -c pytorch

pip安装:

pip3 install torch torchvision torchaudio --index-url http://download.pytorch.org/whl/cu118

pip3 install torch torchvision torchaudio --index-url http://download.pytorch.org/whl/cu121

pip3 install torch torchvision torchaudio

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.2

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

libTorch安装:

cuda11.8

Download here (Pre-cxx11 ABI):https://download.pytorch.org/libtorch/cu118/libtorch-shared-with-deps-2.5.1%2Bcu118.zip

Download here (cxx11 ABI):https://download.pytorch.org/libtorch/cu118/libtorch-cxx11-abi-shared-with-deps-2.5.1%2Bcu118.zip

cuda12.1

Download here (Pre-cxx11 ABI):https://download.pytorch.org/libtorch/cu121/libtorch-shared-with-deps-2.5.1%2Bcu121.zip

Download here (cxx11 ABI):https://download.pytorch.org/libtorch/cu121/libtorch-cxx11-abi-shared-with-deps-2.5.1%2Bcu121.zip

cuda12.4

Download here (Pre-cxx11 ABI):https://download.pytorch.org/libtorch/cu124/libtorch-shared-with-deps-2.5.1%2Bcu124.zip

Download here (cxx11 ABI):https://download.pytorch.org/libtorch/cu124/libtorch-cxx11-abi-shared-with-deps-2.5.1%2Bcu124.zip

ROCm6.2

Download here (Pre-cxx11 ABI):https://download.pytorch.org/libtorch/rocm6.2/libtorch-shared-with-deps-2.5.1%2Brocm6.2.zip

Download here (cxx11 ABI):https://download.pytorch.org/libtorch/rocm6.2/libtorch-cxx11-abi-shared-with-deps-2.5.1%2Brocm6.2.zip

CPU:

Download here (Pre-cxx11 ABI):https://download.pytorch.org/libtorch/cpu/libtorch-shared-with-deps-2.5.1%2Bcpu.zip

Download here (cxx11 ABI):https://download.pytorch.org/libtorch/cpu/libtorch-cxx11-abi-shared-with-deps-2.5.1%2Bcpu.zip

source安装: 

https://github.com/pytorch/pytorch#from-source

二、Pytorch的基础知识

大多数深度学习框架主要涉及到的就是数据,模型创建、修改模型参数并且保存训练模型。这一章节就是学习如何通过Pytorch完成深度学习框架。我们先对pytorch学习框架的基础内容做一个整理,本来是做了一个脑图的,但是没法上传,截成图片也一直上传失败,CDSN导入不同格式的文件这方面还是有待提升。所以下面我会按照列表形式来做个说明:

  • 数据类型和数据集

        在pytorch框架里面,所有的数据, 他们的类型都通过torch.tensor来表示,至于tensor具体是什么样的概念,后面会详细解释。Dataset则是数据的一个集合,里面的内容包括源数据样本,以及标签(对应源数据的预期结果),Dataset作为深度学习的基础,实现了对数据的一个管理。按照使用场景来分类,可以分为训练数据集,测试数据集还有验证数据集。Dataset检索数据集中的特征值以及标签,一次只能检索一个样品。但是在训练中,我们一般想一次性小批量处理数据,在每个时期重新调整数据,以减少模型过度拟合,再使用Python的multiprocessing来加速检索。DataLoader 是一个可迭代对象,它通过一个简单的 API 为我们抽象了这种复杂性。

        下面我们开始详细了解一下tensor的概念:

        tensors是一种特殊的数据结构,和数组以及矩阵比较类似,在Pytorch中,我们使用tensors来对模型的输入和输出以及模型参数进行编码。
        Tensors和Numpy的ndarrays很相似,除了tensors可以运行GPU和其他一些硬件加速器。事实上,tensors以及Numpy arrays可以直接共享底层内存,不需要经过数据拷贝。Tensors也针对自动微分(autograde)进行了优化.

         1、tensors初始化

                1.1 直接通过数据进行初始化

                data = [[1, 2],[3, 4]]
                        x_data = torch.tensor(data) //cpu表达
                        y_data = torch.cuda.tensor(data) //gpu表达

                1.2 从Numpy array中初始化

                     np_array = np.array(data)
                     x_np = torch.from_numpy(np_array)

                1.3 从另一个tensors初始化,新的tensor默认会使用原tensors属性(shape,datatype),除非指定类型对它进行覆盖

                     x_ones = torch.ones_like(x_data) # retains the properties of x_data
                     x_ones = x_data
                     print(f"Ones Tensor: \n {x_ones} \n")
                     x_rand = torch.rand_like(x_data, dtype=torch.float) # overrides the datatype of x_data
                     print(f"Random Tensor: \n {x_rand} \n")

                1.4 使用random或者常量值进行初始化

                     shape是tensors的元组,它决定了tensors的维度.shape = (2,3,)
                     rand_tensor = torch.rand(shape);
                     one_tensor = torch.ones(shape);
                     zeros_tensor = torch.zeros(shape);  

Random Tensor:
 tensor([[0.3904, 0.6009, 0.2566],
        [0.7936, 0.9408, 0.1332]])

Ones Tensor:
 tensor([[1., 1., 1.],
        [1., 1., 1.]])

Zeros Tensor:
 tensor([[0., 0., 0.],
        [0., 0., 0.]])

                1.5 tensors的属性

                     tensors的属性包括shape、datatype以及在什么设备上存储
                     tensor = torch.rand(3,4)
                     print(f"Shape of tensor: {tensor.shape}")
                     print(f"datatype of tensor: {tensor.dtype}")
                     print(f"Device of tensor: {tensor.device}")
                     print(f"size of tensor:{tensor.size(0)}");                 

Shape of tensor:torch.Size([3, 4])
datatype of tensor:torch.float32
Device of tensor:cpu
size of tensor:3

   

         2、tensors的操作

                 大约有超过100个tensors的操作,包括算术、线性代数、矩阵操作(转置、索引、切片)、采样等,这里都有全面的描述.所有这些操作都可以在GPU上运行,我们需要使用.to方法来显性的修改tensors为GPU使用。

                if torch.cuda.is_available():   
                         tensor = tensor.to("cuda")       

                 2.1 索引和切片

                        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 //将第二列数据赋值为0
                        print(tensor)

First row: tensor([1., 1., 1., 1.])
First column: tensor([1., 1., 1., 1.])
Last column: tensor([1., 1., 1., 1.])
tensor([[1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.],
        [1., 0., 1., 1.]])

                 2.2 tensor连接

                         我们可以使用torch.cat和torch.stack来连接两个tensor
                        torch.cat是用于扩展维度大小的
                        tensor.shape ==> [3,4]
                        in: t = torch.cat([tensor,tensor], dim=1)
                        out: t.shape ==>[3,8]
                        in: t =  torch.cat([tensor,tensor], dim=0)
                        out: t.shape ==> [6,4]
                        torch.stack是用于增加维度的
                        tensor.shape ==> [1,3,4]
                        in: t = torch.stack([tensor,tensor],dim=0)
                        out: t.shape ==> [2,1,3,4]                      

                2.3 - tensor分割

                        torch.spilt是按照长度来分割的,也就是指定分割块的大小
                        tensor.shape ==> [3,4]
                        in: t = torch.split(tensor,1)
                        out : t[0].shape ==>[1,4]
                        torch.chunk是按照份数来分的,也就是指定要分成几块
                        in: t = torch.chunk(tensor,3)
                        out : t[0].shape ==>[1,4]       

                2.4 - tensor算术操作  

               - 矩阵相乘(以下三种方式,均表示两个矩阵相乘,结果一致。.T函数表示将tensor矩阵进行转置)  

                            y1 = tensor @ tensor.T
                            y2 = tensor.matmul(tensor.T)
                            y3 = torch.matmul(tensor, tensor.T)      

               - 元素基本运算

                     元素乘积(这个需要两个tensors变量shape一致,也就是矩阵维度一致)
                            z1 = tensor * tensor
                            z2 = tensor.mul(tensor)
                            z3 = torch.mul(tensor, tensor)
                      元素相加
                             z = torch.add(tensor,tensor)
                      元素相减
                             z = torch.sub(tensor, tensor)
                      元素相除
                             z = torch.div(tensor, tensor)     

                 - 单值tensors                           

                           agg = tensor.sum()
                           agg_item = agg.item()
                           print(agg_item, type(agg_item))                   

                 - 和Numpy进行桥接 

                        在CPU上的tensors和Numpy数组可以共享底层的内存

                        - Tensors to Numpy array
                                  t = torch.ones(5)
                                  print(f"t: {t}")
                                  n = t.numpy()
                                  print(f"n: {n}")
                        - Numpy array to tensors
                                  n = np.ones(5)
                                  t = torch.from_numpy(n)                                                

                2.5 - tensor维度变换,tensor维度范围(-a.dim - 1, a.dim)

                     - view/reshape(总体大小不变的情况下,进行shape更改)

                        a = torch.rand(4,1,28,28)
                        a.view(4, 28*28); //四维变二维
                        a.reshape(-1); //表示将tensor放平
                        a.reshape(-1,4); //表示将tensor放平后,按照四列来分配tensor,shape为[28*28, 4]                 

                    - squeeze/unsqueeze(维度挤压,删减维度或者增加维度)

                         unsqueeze范围 [-a.dim()-1, d.dim()]             

                        a.unsqueeze(0).shape //out: torch.size([1,4,1,28,28])
                        a.unsqueeze(-1).shape //out: torch.size([4,1,28,28,1])
                        a.unsqueeze(4).shape //out: torch.size([4,1,28,28,1])
                        a.unsqueeze(-4).shape //out: torch.size([4,1,1,28,28])
                        a.unsqueeze(-5).shape //out: torch.size([1, 4,1,28,28])
                        a.unsqueeze(5).shape //out: error dimension out of range         

                         squeeze: 如果没有给参数,就删除所有能删除的维(没有数值的维数都会被删除)                      

                        b = torch.rand(1,32,1,1)
                        b.squeeze().shape //out: torch.size([32])
                        b.squeeze(0).shape //out: torch.size([32,1,1])
                        b.squeeze(1).shape //out: torch.size([1,32,1,1])
                        b.squeeze(-1).shape //out: torch.size([1,32,1])
                        b.squeeze(-4).shape //out: torch.size([32,1,1])             

                2.5 - transpose/t/permute (维度转置)                         

                        .t方法转置只能用于2维矩阵
                        transpose可以指定两个维数之间进行交换
                        permute可以直接指定新的维数顺序           

                        a1 = a.transpose(1,3).view(4,3*32*32).view(4,3,32,32) //error
                        a1 = a.transpose(1,3).contiguous().view(4,3*32*32).view(4,3,32,32).transpose(1,3) //a1 不等于 a
                        a2 = a.transpose(1,3).contiguous().view(4,3*32*32).view(4,32,32,3).transpose(1,3) //a2 等于 a
                        //b.shape  //torch.size([4,3,32,1])
                        b1 = b.permute(0,2,3,1).shape //torch.size([4,32,1,3])                                                                                                                                              

                2.6 - expand/repeat (维度扩展)

                        expand: 扩展空间,但是不会复制数据
                        repeat: 扩展空间,并复制数据
                        b.expand(4,32,14,14).shape //out: torch.size([4,32,14,14])
                        b.expand(-1,32,-1,-1).shape //out: torch.size([1,32,1,1]) //-1表示保持该维不变
                        dib.repeat(4,32,1,1).shape //out: torch.size([4,1024,1,1]) 
                        b.repeat(4,1,1,1).shape //out.torch.size([4,32,1,1])
                        b.repeat(4,1,32,32).shape //out.torch.size([4,32,32,32])            

                 2.7 - 维度自动扩展

                        当两个计算的tensor的shape不同的时候,在满足以下几个条件的情况下,能够实现tensor的维度自动扩展,从而正常计算

                        (1) 当前维度为1,那么可以扩展到对面tensor对应维度的大小

                        (2)当前没有这个维度,那么可以先插入一个维度,然后再按照第(1)步,进行扩展

                2.8  - tensor的统计属性

                        - norm(范数)  

                              - 1-范数(L1范数):所有元素的绝对值的求和。例如,对于一个Tensor a,其1-范数可以通过a.norm(1)计算得到。
                              - 2-范数(L2范数):所有元素的绝对值的平方和的开方。这也是欧几里得距离的一种度量方式。例如,对于一个Tensor a,其2-范数可以通过a.norm(2)计算得到。
                         此外,还可以指定维度(dim)来计算范数。例如,对于一个二维Tensor b,b.norm(1, dim=1)将计算每一行的1-范数,结果是一个一维Tensor,其形状为[行数]。  

                        - mean(均值)

                                计算Tensor中所有元素的平均值。如果指定了维度(dim),则返回该维度上的平均值

                        - sum(总和)

                                计算Tensor中所有元素的和。如果指定了维度(dim),则返回该维度上元素的和。     

                        - prod(累乘)

                                计算Tensor中所有元素的累乘结果。如果指定了维度(dim),则返回该维度上元素的累乘结果。                             

                        - max min(最大值/最小值)

                                返回Tensor中的最大值。如果指定了维度(dim),则返回该维度上的最大值。同时,可以使用argmax函数返回最大值的索引。   

                        - argmax argmin(最大值和最小值所在索引)

                                解释如上条

                        - topk(前k大或者前k小的数及索引)
                        - kthvalue(第k小的数及索引)                  

                                        

                2.8 - 高阶运算

                        - where: 用于根据判断条件来对两个tensor进行判断赋值给第三方tensor
                        torch.where(condition, tensora, tensorb)
                          { tensorai  (if condition)
                        outi = {
                           { tensorbi } (else){
                       }
                        - gather:用于根据索引值收集input(常用于分类)
                        torch.gather(input, dim, index, out=None)
                        ==> out[i][j][k] = input[i][j][index[i][j][k]]                                                                                  

         3、再了解一下Datasets和DataLoaders                                                                                    

                处理样本的代码可能会逐渐变得混乱,难以管理。所以理想情况下,我们希望数据集代码和模型训练的代码是分离的,可以提高代码的可读性以及模块化。

                pytorch提供了两个数据接口,torch.utils.data.DataLoader和torch.utils.data.Dataset, 这两个接口允许我们使用预处理的数据集以及我们自己的数据。Dataset保存了样品以及相对应的标签,

                而DataLoader 包装了dataset可迭代的对象, 用于更方便的访问样本。

                pytorch的库里面提供了很多预加载的数据集(例如FashionMNIST),这些数据集是torch.utils.data.Dataset的子类,并实现了特定于特定数据的函数。他们可以被用来为为模型制作原型以及基准测试,

                我们可以在Image DatasetsText Datasets, and Audio Datasets这些地方找到他们。

                下面我们通过从TorchVision里面加载Fashion-MNIST数据集的示例,来熟悉一下Datasets和DataLoaders的使用

                3.1 加载数据集

import torch
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
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.FashionMNIST接口参数分析: 

                        root:train/test数据存储的目录

                        train:标识了是训练数据还是测试数据

                        download=True:如果root指定的路径没有数据,就在线下载网络数据

                        transform and target_transform:标识了物品和标签之间的映射关系,tansform用于对物品数据做预处理,target_transform用于对物品标签做预处理        

                ToTensor() 是一个常用的转换函数(来自torchvision.transforms模块),它的功能包括:                  

                        - 数据类型转换:将输入数据(如PIL图像、NumPy数组)转换为PyTorch张量(torch.Tensor)。
                        - 维度调整:自动调整数据维度,使其符合PyTorch模型的输入要求。
                        - 对图像数据:将原始的 (H, W, C)(高度、宽度、通道数,如PIL图像格式)转换为 (C, H, W)(PyTorch默认的通道优先格式)。
                        - 数值归一化:将像素值从整数范围 [0, 255] 自动缩放到浮点范围 [0.0, 1.0]。                                                             

                3.2 迭代以及可视化数据集
                   我们可以通过training_data[index]来索引数据集,也可以通过matplotlib来可视化一些样本 。对于matplotlib的使用不多加赘述,如果有兄弟感兴趣,可以在下面给我留个言,后续写个博客大家一起学习下。

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()

                3.3 使用dataloaders里面的数据开始进行训练

                        Dataset检索数据集中的特征值以及标签,一次只能检索一个样品。但是在训练中,我们一般想一次性小批量处理数据,在每个时期重新调整数据,以减少模型过度拟合,再使用Python的multiprocessing来加速书籍的检索。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)

                shuffle=True 的作用
                        - 训练阶段常用:在每个 epoch(训练轮次)开始时,随机打乱数据顺序。
                        - 核心优势:防止模型因数据顺序产生“记忆”(如始终先看到某一类样本),提升泛化能力。
                        - 实现机制:
                                - 每个 epoch 生成一个随机索引序列,按此索引加载数据。
                                - 不同 epoch 的数据顺序不同,但同一 epoch 内的 batch 顺序固定。
                                - 通过DataLoader进行迭代
                batch_size的作用:
                        - 指明将数据集分割成多大的数据块,方便迭代 。

          Dataloader的迭代方式:

单次迭代

# 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}")

循环迭代
for batch_idx, (data, labels) in enumerate(train_dataloader):
    print(f"Batch {batch_idx + 1}:")
    print(f"Data: {data}")
    print(f"Labels: {labels}")

                在 Python 中,enumerate() 是一个内置函数,用于在迭代可迭代对象(如列表、元组、字符串等)时,同时获取元素的索引和值。它返回一个枚举对象,该对象是一个迭代器,生成一系列的元组,每个元组包含两个元素:当前元素的索引和元素本身;                                                                                                                                                                                                

  • 模型构建

            算法模型的本质就是神经网络,里面可能包含多个层级或者说模块,这些层级或者模块提供对数据的操作。torch.nn提供了所有的用于构建神经网络的模块。神经网络本身是一个由其他模块(层)组成的模块。这种嵌套结构允许轻松构建和管理复杂的架构。在以下部分中,我们将构建一个神经网络来对 FashionMNIST 数据集中的图像进行分类。

        1、定义神经网络类

                我们通过子类nn.Module来定义我们的神经网络,并且在__init__函数里面初始化神经网络层。每一个nn.Module子类都会处理从forward函数里面输入进来的数据。

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()     
        self.flatten = nn.Flatten()   // nn.Flatten将连续范围的 dims 展平为张量
        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 = (
    "cuda"
    if torch.cuda.is_available()    
    else "mps"    
    if torch.backends.mps.is_available() 
    else "cpu"
)
model = NeuralNetwork().to(device)
print(model)

        我们将数据传入到model中,就会自动执行forward函数,而不需要手动执行forward函数。

        调用model后会返回一个二维的张量logits,dim=0表示每个对象对应的10个原始预测值的每个输出。dim=1表示每个输出的单个值,我们通过将这个logits张量传给nn.Softmax模块,可以获取到预测概率。

X = torch.rand(3, 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}")

        下面对NeuralNetwork神经网络的每个网络层进行逐步分析:

        1.1 torch.nn.Flatten(start_dim=1, end_dim=-1)默认将从二个维度开始到最后一个维度的数据铺平。

        我们初始化nn.Flatten层用于将每一张28*28的2D图片转换为一个784像素值的连续数组。

import torch
input_image = torch.rand(3,28,28)
flatten = nn.Flatten()
flat_image = flatten(input_image)
print(flat_image.size())

out:
torch.Size([3, 784])

        1.2 nn.linear是一个根据权重和偏差对输入数据执行线性转换的模块,线性转换是指一种保持向量加法和标量乘法的运算,在神经网络中,用于实现全连接层,允许网络在不同层之间传递线性关系。

layer1 = nn.Linear(in_features=28*28, out_features=20)
hidden1 = layer1(flat_image)
print(hidden1.size())

out:
torch.Size([3, 20])

       1.3 nn.Relu 一种非线性激活函数,创建了模型输入和输出之间的复杂映射,在线性转换到非线性时用到,帮助神经网络学习复杂多样的现象。在当前模型中,我们在Linear层之间使用nn.Relu,在模型中也有一些其他的方式来激活引入非线性。

print(f"Before ReLU: {hidden1}\n\n")
hidden1 = nn.ReLU()(hidden1)
print(f"After ReLU: {hidden1}")


out:
Before ReLU: tensor([[ 0.4158, -0.0130, -0.1144,  0.3960,  0.1476, -0.0690, -0.0269,  0.2690,
          0.1353,  0.1975,  0.4484,  0.0753,  0.4455,  0.5321, -0.1692,  0.4504,
          0.2476, -0.1787, -0.2754,  0.2462],
        [ 0.2326,  0.0623, -0.2984,  0.2878,  0.2767, -0.5434, -0.5051,  0.4339,
          0.0302,  0.1634,  0.5649, -0.0055,  0.2025,  0.4473, -0.2333,  0.6611,
          0.1883, -0.1250,  0.0820,  0.2778],
        [ 0.3325,  0.2654,  0.1091,  0.0651,  0.3425, -0.3880, -0.0152,  0.2298,
          0.3872,  0.0342,  0.8503,  0.0937,  0.1796,  0.5007, -0.1897,  0.4030,
          0.1189, -0.3237,  0.2048,  0.4343]], grad_fn=<AddmmBackward0>)


After ReLU: tensor([[0.4158, 0.0000, 0.0000, 0.3960, 0.1476, 0.0000, 0.0000, 0.2690, 0.1353,
         0.1975, 0.4484, 0.0753, 0.4455, 0.5321, 0.0000, 0.4504, 0.2476, 0.0000,
         0.0000, 0.2462],
        [0.2326, 0.0623, 0.0000, 0.2878, 0.2767, 0.0000, 0.0000, 0.4339, 0.0302,
         0.1634, 0.5649, 0.0000, 0.2025, 0.4473, 0.0000, 0.6611, 0.1883, 0.0000,
         0.0820, 0.2778],
        [0.3325, 0.2654, 0.1091, 0.0651, 0.3425, 0.0000, 0.0000, 0.2298, 0.3872,
         0.0342, 0.8503, 0.0937, 0.1796, 0.5007, 0.0000, 0.4030, 0.1189, 0.0000,
         0.2048, 0.4343]], grad_fn=<ReluBackward0>)

        知识扩展:

线性函数之后引入非线性特性函数(激活函数)的原因:

一、引入非线性特性

  1. 增强表达能力:线性函数是简单的线性变换,其输出是输入的线性组合。如果神经网络中仅使用线性函数,那么无论网络有多少层,其整体仍然只是一个线性变换,无法表示复杂的非线性关系。而非线性激活函数能够引入非线性特性,使得神经网络能够学习并表示复杂的函数和决策边界。
  2. 逼近任意函数:使用非线性激活函数,神经网络可以逼近任意的非线性函数。这对于处理复杂的数据和任务至关重要,如图像、视频、音频等数据的处理和分类。

二、优化和训练效率

  1. 梯度消失与梯度爆炸:某些非线性激活函数(如ReLU)在特定区间内具有恒定的梯度,这有助于缓解梯度消失或梯度爆炸的问题。这些问题在深度神经网络中尤为常见,并可能导致训练过程中的不稳定性和困难。
  2. 收敛速度:一些非线性激活函数(如Tanh和ReLU)具有更好的收敛特性,可以加快神经网络的训练速度。相比之下,线性激活函数可能无法提供这样的收敛优势。

三、实际应用需求

  1. 输出范围限制:某些非线性激活函数(如Sigmoid和Tanh)的输出范围受到限制,这使得它们在某些应用场景中特别有用。例如,Sigmoid函数的输出范围在(0,1)之间,适用于二分类问题的概率输出。
  2. 稀疏性:ReLU等非线性激活函数具有稀疏激活性,即它们可以使大部分神经元在给定输入下不激活(输出为零)。这种稀疏性有助于提高神经网络的泛化能力和计算效率。

        1.4 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)

        1.5 nn.Softmax

                神经网络的最后一个线性层返回logits, 里面包含[-infty, infty]中的原始值,传输给

        nn.Softmax模块,输出的结果的范围在[0,1]之间,并且和为1,表示模型对每个类别的预测概

        率。Softmax函数的特点在于它能够平滑地将任意实数值向量转换为概率分布,因此非常适合

        作为神经网络的最后一层激活函数,特别是在分类任务中。在训练过程中,Softmax函数与交

        叉熵损失函数一起使用,可以方便地计算预测概率与真实标签之间的差异,并据此调整网络

        的权重。

  • 自动求导

        自动求导的核心概念:自动求导是基于计算图(computation graph)来实现的。在计算图中,每个节点代表一个变量或操作的结果,边则表示数据流动的方向。深度学习框架通过构建这样的计算图来记录数据的前向传播过程,并在反向传播时利用链式法则计算梯度。pytorch有一个内置的微分引擎,称为torch.autograd. 它支持自动计算任何计算图的梯度。

        综上所述,自动求导是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)

在这个神经网络中,w和b是需要我们优化的参数。因此,我们需要能够计算关于这些变量的损失函数的梯度,为了实现这个,我们需要设置这些张量的 requires_grad 属性。

我们可以在创建一个tensor的时候设置requires_grad的值,或者在创建tensor后,使用x.requires_grad_(True)函数来设置。

我们应用于张量来构建计算图的函数实际上是 Function 类的对象。该对象知道如何在正向计算函数,以及如何在反向传播步骤中计算其导数。对反向传播函数的引用存储在张量的 grad_fn 属性中

        计算梯度值:

        为了优化神经网络中参数的权重,我们需要计算给定参数的损失函数的导数。也就是说,我们需要基于固定的X值和Y值,使用下面的公式来计算导数。我们调用loss.backward()来计算这些导数,并且从w.grad和b.grad里面取回值 

loss.backward()
print(w.grad)
print(b.grad)
out:
tensor([[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530],
[0.3313, 0.0626, 0.2530]])
tensor([0.3313, 0.0626, 0.2530])

        关闭梯度跟踪:

        默认情况下,所有具有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)

//另外一种方式来关闭梯度追踪
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
  • 模型参数优化

        上面我们已经通过反向求导,计算出权值和偏置值在每个点的一个梯度,现在我们需要根据这个梯度,对参数进行优化。

with torch.no_grad():            # 避免梯度跟踪
    w -= lr * w.grad  # 手动更新权重
    b -= lr * b.grad  # 手动更新权重

        但是这样挨个去更新参数,效率太低了,所以Pytorch提供了优化器,可以帮助我们来更新权值和偏置值等参数。优化是调整模型参数以减少每个训练步骤中的模型误差的过程。优化算法定义了如何执行此过程(在此示例中,我们使用随机梯度下降)。所有优化逻辑都封装在优化器对象中。在这里,我们使用 SGD 优化器;此外,PyTorch 中还有许多不同的优化器,例如 ADAM 和 RMSProp,它们更适合不同类型的模型和数据。

我们通过注册需要训练的模型参数并传入learning_rate超参数来初始化优化器。
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

在训练循环中,优化过程主要在下面三步实现:
(1)调用 optimizer.zero_grad() 重置模型参数的梯度。默认情况下,梯度会相加;为了防止重复计算,我们在每次迭代时都明确将其归零
(2)通过调用 loss.backward() 反向传播预测损失。PyTorch 存储相对于每个参数的wrt损失梯度。
(3) 一旦我们有了梯度,我们就调用 optimizer.step() 来通过在反向传递中收集的梯度来调整参数。

    model.train()   
    for batch, (X, y) in enumerate(dataloader):        
       # Compute prediction and loss        
       pred = model(X)        
       loss = loss_fn(pred, y)        
       # Backpropagation        
       loss.backward()        
       optimizer.step()        
       optimizer.zero_grad()       

注意:

设置优化器,主要考虑三个方面:
        一个是初始值,不同的初始值,可能会让优化器计算出不同的局部损失最小值,所以需要让初始值尽量靠近全局损失最小值的附近。
        一个是步长,步长设置较大,可能导致直接跳过了最小值的位置,所以刚开始尽量设置小一点,如果损失值能够正常收敛,再逐步扩大步长,来提高收敛效率
        一个是动量,用于加速收敛的参数,引入了先前梯度的权重,有助于克服局部最小值。SGDM(SGD with Momentum)在SGD的基础上引入了一阶动量,该动量可以看做是当前梯度与先前梯度方向的指数移动平均值。这样,当前的下降方向不仅由当前点的梯度方向决定,还由先前积累的下降方向决定,从而实现了惯性的效果。

          

  • 模型的保存以及加载

        模型的保存有两种方式,一种是保存整个模型,包括模型的结构和模型参数值,另一种是只保存模型的参数,下面对这两种方式通过实例加以说明:

        (1) 保存和加载模型的权重

#保存模型权重
model = models.vgg16(weights='IMAGENET1K_V1')
torch.save(model.state_dict(), 'model_weights.pth')

#加载模型权重
model = models.vgg16() # we do not specify ``weights``, i.e. create untrained model
model.load_state_dict(torch.load('model_weights.pth', weights_only=True))
model.eval()

        (2) 保存和加载模型的网络结构以及权重

#保存模型
torch.save(model, 'model.pth')

#加载模型
model = torch.load('model.pth', weights_only=False)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值