• PyTorch(四)数据转换与构建神经网络


    #c 总结 文档总结

    文档目录:

    1. 数据转换:主要讲解「transforms」,涉及到的知识点有「匿名函数」,「对象自调用」

    2. 创建神经模型:涉及的知识点有「加速训练」「神经网络定义」「调用神经网络」「模型层」「模型参数」

    1 数据转换(Transforms)

    #c 说明 转换的目的

    数据并不总是以「训练机器学习算法」所需的最终处理形式出现。使用转换(transforms)来执行数据的「一些操作」,使其适合训练。

    #e TorchVision中的转换 转换的目的

    所有TorchVision数据集都有两个参数:
    transform用于修改特征。
    target_transform用于修改标签,它们接受包含转换逻辑的可调用对象。

    #e FashionMNIST转换

    FashionMNIST的特征数据以PIL图像格式存在,标签是整数。为了训练,需要将「特征」转换为使用的的「张量形式」,并将标签转换为one-hot编码的张量。使用ToTensorLambda实现这些转换。

    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))    
    )
    '''
        target_transform参数用于对目标(标签)进行转换。
        lamdba:定义一个匿名函数,y是函数的参数
        torch.zeros(10, dtype=torch.float)创建一个长度为10,数据类型为float的零向量
        .scatter_(0, torch.tensor(y), value=1)就地操作,在零向量的y索引的位置上的值设置为1,设置一个one-hot编码
        '''
    

    #d 匿名函数

    匿名函数,通常在Python中通过lambda关键字定义,是一种没有名称的简单函数。它们通常用于执行简短的、一次性的任务,特别是在需要函数对象的地方,但又不想使用完整的函数定义语法,是一个非常有用的工具,它提供了一种快速定义简单函数的方式,使得代码更加简洁,增加了编程的灵活性。

    匿名函数的主要属性和特点包括:

    1. 没有名称:正如“匿名”所暗示的,这类函数没有名称。
    2. 简洁的定义:通常只有一行代码,适用于简单的逻辑。
    3. 自动返回值lambda表达式的结果自动成为返回值,无需显式使用return语句。
    4. 可接受任意数量的参数:但只能有一个表达式。

    创造「匿名函数」的目的

    • 简化代码:对于简单的函数,使用lambda可以避免定义标准的函数,从而减少代码量。
    • 功能性编程lambda表达式支持函数式编程范式,在处理高阶函数和操作(如mapfiltersorted等)时非常有用。

    没有这个概念「匿名函数」有什么影响

    • 代码冗长:对于需要传递简单函数作为参数的场合,如果没有匿名函数,就需要定义标准的函数。这会使得代码变得更加冗长,特别是当这个函数只需要在一个地方使用时。
    • 减少灵活性:在某些情况下,使用匿名函数可以使代码更加灵活和简洁。没有匿名函数,某些编程模式(如即时使用的小函数作为参数传递)会更加复杂。
    • 降低可读性:虽然过度使用lambda可能会降低代码的可读性,但在适当的场合使用它们可以使代码更加直观。没有lambda表达式,对于那些最适合使用匿名函数的场景,代码可能会变得更难理解。

    #e 快速决策 匿名函数

    想象你在一个快餐店,需要快速决定每个人的饮料。你可以为每个人设置一个简单的规则(匿名函数):如果某人喜欢甜的,就选择可乐;如果不喜欢甜的,就选择无糖的绿茶。

    在这个例子中,「决策规则」就像一个匿名函数,它根据一个「输入」(是否喜欢甜的)来快速决定「输出」(选择哪种饮料)。虽然这不是编程中的直接应用,但它展示了匿名函数概念的一个类比——根据输入快速产生输出,而不需要为这个决策过程命名或详细定义。

    #e 列表排顺 匿名函数

    在Python中,我们经常需要根据列表中元素的「某个属性」或「值」来对列表进行排序。使用lambda表达式,我们可以轻松地定义排序的关键字。
    在这个例子中,lambda x: x[1]是一个匿名函数,它接受一个参数x(在这里是列表中的一个元组)并返回「元组」的第二个元素作为排序的依据。

    # 根据元组的第二个元素进行排序
    tuples = [(1, 'banana'), (2, 'apple'), (3, 'cherry')]
    tuples.sort(key=lambda x: x[1])
    print(tuples)  # 输出: [(2, 'apple'), (1, 'banana'), (3, 'cherry')]
    

    #e filter()函数 匿名函数

    filter()函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器。使用lambda表达式,我们可以定义过滤的条件。
    这里,lambda x: x % 2 == 0是一个匿名函数,它接受一个参数x并检查x是否为偶数。filter()函数使用这个匿名函数来决定哪些元素应该被包含在结果列表中。

    # 过滤出列表中的偶数
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
    print(even_numbers)  # 输出: [2, 4, 6, 8]
    

    #d 对象自调用

    Python中的「对象自调用」概念指的是一个对象可以表现得像一个函数,即这个对象可以被直接调用。这是通过在类中定义__call__魔术方法(magic method)来实现的。对象自调用的能力使得对象在调用时可以执行特定的操作。

    属性:

    • __call__方法:这是判断一个对象是否支持自调用的主要依据。如果一个对象的类中定义了__call__方法,那么这个对象就可以被直接调用。

    「对象自调用」解决的问题:

    • 灵活性增强:对象自调用提供了一种使对象行为更加灵活的方式。它允许对象在保持状态的同时,还能够像函数一样被调用,从而在对象和函数之间提供了一种灵活的转换机制。
    • 接口统一:在某些设计模式中,如命令模式,可以通过对象自调用的方式来统一不同操作的接口。这样,无论是直接执行一个函数还是通过对象来执行,都可以有相同的调用方式。

    没有「对象自调用」的影响:

    • 减少灵活性:没有对象自调用的概念,对象和函数之间的界限会更加明确,这在某些情况下减少了编程的灵活性。对象将不能直接作为函数来调用,可能需要更多的代码来实现相同的功能。
    • 设计模式限制:某些设计模式的实现会受到限制。例如,在需要将对象作为可调用实体传递的场景中,如果没有对象自调用的概念,就需要额外的机制来模拟这一行为,增加了实现的复杂度。

    #e 智能音箱 对象自调用

    想象一下,你有一个智能音箱,比如一个小爱同学或者天猫精灵。这个智能音箱就像是一个具有「对象自调用」能力的对象。我们可以把它想象成一个Python对象,这个对象有一个__call__方法,允许你直接对它说话(调用它),然后它会根据你的命令「执行相应的操作」。当对智能音箱发出命令时,就像在调用对象的__call__方法,根据你的「命令(输入参数)」,它会「执行相应的操作」并给出反馈。

    #e 计数类器 对象自调用

    class Counter:
        def __init__(self):
            self.count = 0
    
        def __call__(self, increment=1):
            self.count += increment
            return self.count
    
    counter = Counter()
    print(counter())  # 输出: 1
    print(counter(10))  # 输出: 11
    

    2 创建神经网络

    2.1 获取训练的设备

    #d 加速训练

    可以使用硬件如GPU,MPS来加速模型的训练。

    #e 获取代码 加速训练

    可以使用torch.cuda 或者`torch.backends.mps来判断硬件设备是否可用。

    device = (
        "cuda"
        if torch.cuda.is_available()
        else "mps"
        if torch.backends.mps.is_available()
        else "cpu"
    )
    print(f"Using {device} device")
    # Using cuda device
    

    2.2 定义神经网络

    #d 神经网络定义

    通过继承nn.Module来定义神经网络,并在__init__方法中初始化神经网络层。每一个nn.Module的子类都在forward方法中实现了对「输入数据」的操作。

    #e 定义代码 神经网络定义

    class NueralNetwork(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#返回输出
    

    #d 调用神经网络

    在定义好神经网络过后,通过实例化神经网络,并传递需要的数据来获取预测值。

    #e 实例化 调用神经网络

    model = NueralNetwork().to(device)
    print(model)
    '''
    NueralNetwork(
      (flatten): Flatten(start_dim=1, end_dim=-1)
      (linear_relu_stack): Sequential(
        (0): Linear(in_features=784, out_features=512, bias=True)
        (1): ReLU()
        (2): Linear(in_features=512, out_features=512, bias=True)
        (3): ReLU()
        (4): Linear(in_features=512, out_features=10, bias=True)
      )
    )
    

    #e 传递数据 调用神经网络

    使用模型,将「输入数据」传递给它。将会执行模型的forward方法以及一些后台操作。但不要直接调用model.forward()。
    调用模型对输入数据执行操作,将返回一个二维张量,其中维度0(dim=0)对应于每个类别的10个原始预测值,维度1(dim=1)对应于每个输出的单独值。我们通过传递给nn.Softmax模块的一个实例来获得预测概率。

    X = torch.rand(1, 28, 28, device=device)#生成一个随机张量,参数分别为张量的形状和设备,形状为1*28*28的张量
    logits = model(X)#将张量传递给神经网络
    pred_probab = nn.Softmax(dim=1)(logits)#将预测值传递给Softmax函数,dim=1表示计算每行的softmax,(logits)是「对象自调用」
    y_pred = pred_probab.argmax(1)#返回每行中最大值的索引,若参数为0,则返回每列中最大值的索引
    print(f"Predicted class: {y_pred}")#打印预测的类别
    '''
    Predicted class: tensor([1], device='cuda:0')
    '''
    

    2.3 模型层

    #d 模型层

    模型层「layers」是指在定义神经网络时,编写在__init__()函数下的内容。

    #c 说明 模型层讲解 模型层

    接下里将分解FashionMNIST模型中的层。为了讲解「模型层」,将取一个包含3张28x28大小图像的样本小批量,并观察当将其通过网络传递时会发生什么。

    input_image = torch.rand(3, 28, 28)#随机生成一个3*28*28的张量
    print(input_image.size())#打印张量的形状
    print(input_image.shape)#打印张量的形状
    '''
    torch.Size([3, 28, 28])
    torch.Size([3, 28, 28])
    '''
    

    #e nn.Flatten 模型层

    初始化Flatten层后,可以通过调用它来展平3D张量。将32828图像转换成一个连续的784像素值的数组。

    flatten = nn.Flatten() #实例化Flatten层
    flat_image = flatten(input_image)#将张量传递给Flatten层
    print(flat_image.size())#打印张量的形状
    '''
    torch.Size([3, 784])
    '''
    

    #e nn.Linear 模型层

    Linear层使用一种称为「权重」的内部张量,以及一种称为「偏置」的内部张量,对输入张量进行「线性变换( linear transformation)」。

    layer1 = nn.Linear(in_features=28*28,out_features=20)#in_features表示输入特征的形状,out_features表示输出特征的形状
    hidden1 = layer1(flat_image)#将张量传递给Linear层
    print(hidden1.size())#打印张量的形状
    '''
    torch.Size([3, 20])
    '''
    

    #e nn.ReLU 模型层

    「非线性激活函数」对模型的「输人」和「输出」创建「复杂的映射」。在线性变换后,引入非线性激活函数,帮助神经网络学习各种规律。

    print(f"Before ReLU:{hidden1}\n\n")#打印隐藏层的输出
    hidden1 = nn.ReLU()(hidden1)#将隐藏层的输出传递给ReLU激活函数
    print(f"After ReLU: {hidden1}")#打印隐藏层的输出
    '''
    Before ReLU:tensor([[-0.6295, -0.0362, -0.1422,  0.1866, -0.0955,  0.1350, -0.0350, -0.0746,
             -0.3552,  0.2612, -0.1565, -0.1210, -0.1081,  0.0425,  0.3023,  0.0560,
              0.2418, -0.0035,  0.9525,  0.1108],
            [-0.6520, -0.3238, -0.0208,  0.0317,  0.0194,  0.5342, -0.2582, -0.3136,
             -0.3851,  0.2427, -0.0782, -0.3597, -0.2151, -0.1793, -0.0808, -0.1593,
              0.4785, -0.0835,  0.9555, -0.1394],
            [-0.9776, -0.3067, -0.1160, -0.0596,  0.1393,  0.2737,  0.1556,  0.0434,
             -0.6965,  0.4378, -0.2360, -0.1565,  0.3842, -0.2784,  0.3218, -0.0107,
              0.5351, -0.2072,  0.8570, -0.1982]], grad_fn=)
    
    
    After ReLU: tensor([[0.0000, 0.0000, 0.0000, 0.1866, 0.0000, 0.1350, 0.0000, 0.0000, 0.0000,
             0.2612, 0.0000, 0.0000, 0.0000, 0.0425, 0.3023, 0.0560, 0.2418, 0.0000,
             0.9525, 0.1108],
            [0.0000, 0.0000, 0.0000, 0.0317, 0.0194, 0.5342, 0.0000, 0.0000, 0.0000,
             0.2427, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.4785, 0.0000,
             0.9555, 0.0000],
            [0.0000, 0.0000, 0.0000, 0.0000, 0.1393, 0.2737, 0.1556, 0.0434, 0.0000,
             0.4378, 0.0000, 0.0000, 0.3842, 0.0000, 0.3218, 0.0000, 0.5351, 0.0000,
             0.8570, 0.0000]], grad_fn=)
    '''
    

    #e nn.Sequential 模型层

    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)#随机生成一个3*28*28的张量
    logits = seq_modules(input_image)#将张量传递给Sequential容器
    

    #e nn.Softmax 模型层

    神经网络的最后一个线性层返回「原始预测值」,这些值被称为「logits」,[-infty, infty]中的原始值传递给nn.Softmax模块,将其转换为[0, 1]范围内的值。dim参数表示沿着哪个轴计算softmax。

    softmax = nn.Softmax(dim=1)#实例化Softmax函数
    pred_probab = softmax(logits)#将logits传递给Softmax函数
    

    2.4 模型参数

    #d 模型参数

    「神经网络」内部的许多「层」是「参数化」的,在训练过程中能够优化相关「权重」和「偏置」。继承nn.Module的类会自动「模型对象」内部的定义的参数,可以使用模型的parameters()或named_parameters()方法使所有参数可访问。

    #e 打印参数例子 模型参数

    print("Model structure: ", model, "\n\n")#打印模型的结构
    for name, param in model.named_parameters():#遍历模型的参数
        print(f"Layer: {name} | Size: {param.size()} | Values: {param[:2]} \n")#打印参数的名称、形状和前两个值
    '''
    Model structure:  NueralNetwork(
      (flatten): Flatten(start_dim=1, end_dim=-1)
      (linear_relu_stack): Sequential(
        (0): Linear(in_features=784, out_features=512, bias=True)
        (1): ReLU()
        (2): Linear(in_features=512, out_features=512, bias=True)
        (3): ReLU()
        (4): Linear(in_features=512, out_features=10, bias=True)
      )
    )
    Layer: linear_relu_stack.0.weight | Size: torch.Size([512, 784]) | Values: tensor([[ 1.2033e-02, -3.3190e-02,  3.5117e-02,  ..., -3.1082e-04,
              3.1766e-02,  8.9217e-05],
            [-2.2151e-02,  8.2360e-03,  2.6249e-02,  ...,  1.1201e-02,
              1.0973e-02,  3.0528e-02]], device='cuda:0', grad_fn=)
    
    Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values: tensor([0.0314, 0.0233], device='cuda:0', grad_fn=)       
    
    Layer: linear_relu_stack.2.weight | Size: torch.Size([512, 512]) | Values: tensor([[-0.0299, -0.0194,  0.0357,  ..., -0.0063, -0.0406,  0.0399],
            [ 0.0007,  0.0034,  0.0072,  ...,  0.0176, -0.0431,  0.0424]],
           device='cuda:0', grad_fn=)
    
    Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values: tensor([0.0287, 0.0206], device='cuda:0', grad_fn=)       
    
    Layer: linear_relu_stack.4.weight | Size: torch.Size([10, 512]) | Values: tensor([[ 0.0293,  0.0368, -0.0042,  ..., -0.0112, -0.0114, -0.0138],
            [ 0.0157,  0.0046, -0.0023,  ..., -0.0414, -0.0390, -0.0082]],
           device='cuda:0', grad_fn=)
    
    Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values: tensor([0.0046, 0.0029], device='cuda:0', grad_fn=) 
    '''  
    
  • 相关阅读:
    【物理应用】基于Matlab模拟极化雷达回波
    【LeetCode-简单题】501. 二叉搜索树中的众数
    nodejs安装及环境配置
    ES可视化工具--elasticsearch-head--下载、安装、使用
    原生js实现简单的省市区联动效果
    Simple QML Tutorial[来自官档]
    软考 系统架构设计师系列知识点之特定领域软件体系结构DSSA(5)
    Tensorflow 2.1 MNIST 图像分类
    C++面试经典题目汇总
    MySQL中的存储过程(详细篇)
  • 原文地址:https://blog.csdn.net/qq_45031509/article/details/139975393