#c 总结 文档总结
文档目录:
-
数据转换:主要讲解「transforms」,涉及到的知识点有「匿名函数」,「对象自调用」
-
创建神经模型:涉及的知识点有「加速训练」「神经网络定义」「调用神经网络」「模型层」「模型参数」
1 数据转换(Transforms)
#c 说明 转换的目的
数据并不总是以「训练机器学习算法」所需的最终处理形式出现。使用转换(transforms)来执行数据的「一些操作」,使其适合训练。
#e TorchVision
中的转换 转换的目的
所有TorchVision数据集都有两个参数:
transform
用于修改特征。
target_transform
用于修改标签,它们接受包含转换逻辑的可调用对象。
#e FashionMNIST
转换
FashionMNIST的特征数据以PIL图像格式存在,标签是整数。为了训练,需要将「特征」转换为使用的的「张量形式」,并将标签转换为one-hot
编码的张量。使用ToTensor
和Lambda
实现这些转换。
python
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
关键字定义,是一种没有名称的简单函数。它们通常用于执行简短的、一次性的任务,特别是在需要函数对象的地方,但又不想使用完整的函数定义语法,是一个非常有用的工具,它提供了一种快速定义简单函数的方式,使得代码更加简洁,增加了编程的灵活性。
匿名函数的主要属性和特点包括:
- 没有名称:正如"匿名"所暗示的,这类函数没有名称。
- 简洁的定义:通常只有一行代码,适用于简单的逻辑。
- 自动返回值 :
lambda
表达式的结果自动成为返回值,无需显式使用return
语句。 - 可接受任意数量的参数:但只能有一个表达式。
创造「匿名函数」的目的:
- 简化代码 :对于简单的函数,使用
lambda
可以避免定义标准的函数,从而减少代码量。 - 功能性编程 :
lambda
表达式支持函数式编程范式,在处理高阶函数和操作(如map
、filter
、sorted
等)时非常有用。
没有这个概念「匿名函数」有什么影响:
- 代码冗长:对于需要传递简单函数作为参数的场合,如果没有匿名函数,就需要定义标准的函数。这会使得代码变得更加冗长,特别是当这个函数只需要在一个地方使用时。
- 减少灵活性:在某些情况下,使用匿名函数可以使代码更加灵活和简洁。没有匿名函数,某些编程模式(如即时使用的小函数作为参数传递)会更加复杂。
- 降低可读性 :虽然过度使用
lambda
可能会降低代码的可读性,但在适当的场合使用它们可以使代码更加直观。没有lambda
表达式,对于那些最适合使用匿名函数的场景,代码可能会变得更难理解。
#e 快速决策 匿名函数
想象你在一个快餐店,需要快速决定每个人的饮料。你可以为每个人设置一个简单的规则(匿名函数):如果某人喜欢甜的,就选择可乐;如果不喜欢甜的,就选择无糖的绿茶。
在这个例子中,「决策规则」就像一个匿名函数,它根据一个「输入」(是否喜欢甜的)来快速决定「输出」(选择哪种饮料)。虽然这不是编程中的直接应用,但它展示了匿名函数概念的一个类比------根据输入快速产生输出,而不需要为这个决策过程命名或详细定义。
#e 列表排顺 匿名函数
在Python中,我们经常需要根据列表中元素的「某个属性」或「值」来对列表进行排序。使用lambda
表达式,我们可以轻松地定义排序的关键字。
在这个例子中,lambda x: x[1]
是一个匿名函数,它接受一个参数x
(在这里是列表中的一个元组)并返回「元组」的第二个元素作为排序的依据。
python
# 根据元组的第二个元素进行排序
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()
函数使用这个匿名函数来决定哪些元素应该被包含在结果列表中。
python
# 过滤出列表中的偶数
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 计数类器 对象自调用
python
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来判断硬件设备是否可用。
python
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 定义代码 神经网络定义
python
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 实例化 调用神经网络
python
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模块的一个实例来获得预测概率。
python
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大小图像的样本小批量,并观察当将其通过网络传递时会发生什么。
python
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像素值的数组。
python
flatten = nn.Flatten() #实例化Flatten层
flat_image = flatten(input_image)#将张量传递给Flatten层
print(flat_image.size())#打印张量的形状
'''
torch.Size([3, 784])
'''
#e nn.Linear
模型层
Linear层使用一种称为「权重」的内部张量,以及一种称为「偏置」的内部张量,对输入张量进行「线性变换( linear transformation)」。
python
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
模型层
「非线性激活函数」对模型的「输人」和「输出」创建「复杂的映射」。在线性变换后,引入非线性激活函数,帮助神经网络学习各种规律。
python
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。
python
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。
python
softmax = nn.Softmax(dim=1)#实例化Softmax函数
pred_probab = softmax(logits)#将logits传递给Softmax函数
2.4 模型参数
#d 模型参数
「神经网络」内部的许多「层」是「参数化」的,在训练过程中能够优化相关「权重」和「偏置」。继承nn.Module的类会自动「模型对象」内部的定义的参数,可以使用模型的parameters()或named_parameters()方法使所有参数可访问。
#e 打印参数例子 模型参数
python
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>)
'''