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=<AddmmBackward0>)


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=<ReluBackward0>)
'''

#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=<SliceBackward0>)

Layer: linear_relu_stack.0.bias | Size: torch.Size([512]) | Values: tensor([0.0314, 0.0233], device='cuda:0', grad_fn=<SliceBackward0>)       

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=<SliceBackward0>)

Layer: linear_relu_stack.2.bias | Size: torch.Size([512]) | Values: tensor([0.0287, 0.0206], device='cuda:0', grad_fn=<SliceBackward0>)       

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=<SliceBackward0>)

Layer: linear_relu_stack.4.bias | Size: torch.Size([10]) | Values: tensor([0.0046, 0.0029], device='cuda:0', grad_fn=<SliceBackward0>) 
'''  
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

remandancy.h

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值