FA:Formula and Algorithm,CNN:Convolution Netural Network
1. 引言
在 PyTorch 中,深度学习模型本质上是由一个个「层(Layer)」和「块(Block)」组合而成的。理解层与块的组织方式、参数的管理机制,以及如何自定义层和保存模型,是掌握 PyTorch 建模能力的关键一步。
本文将从最基础的nn.Sequential出发,逐步深入到自定义块、参数管理、参数绑定、自定义层以及模型读写,帮助你建立对 PyTorch 模型结构的完整认知。
2. 层和块
nn.Sequential定义了一种特殊的Module,它把多个层按顺序串联起来,前一个层的输出直接作为后一个层的输入。
2.1 回顾多层感知机
importtorchfromtorchimportnnfromtorch.nnimportfunctionalasF net=nn.Sequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))X=torch.rand(2,20)net(X)输出结果:
tensor([[-0.0214, -0.1789, -0.0700, -0.0238, -0.2697, 0.0381, 0.3078, -0.2082, -0.1502, 0.0433], [ 0.0200, -0.1466, -0.0633, 0.0031, -0.2042, 0.0993, 0.3137, -0.1206, -0.1057, 0.0434]], grad_fn=<AddmmBackward0>)3. 自定义块
除了使用内置的Sequential,我们还可以通过继承nn.Module来定义自己的块。自定义块需要实现__init__和forward两个方法。
classMLP(nn.Module):def__init__(self):super().__init__()# 调用父类的 __init__ 函数self.hidden=nn.Linear(20,256)self.out=nn.Linear(256,10)defforward(self,X):returnself.out(F.relu(self.hidden(X)))实例化多层感知机的层,然后在每次调用正向传播函数时调用这些层:
net=MLP()X=torch.rand(2,20)net(X)输出结果:
tensor([[-0.1600, 0.0363, 0.0851, 0.0364, 0.0189, 0.1590, 0.1519, 0.1299, -0.1382, -0.2075], [-0.1956, 0.0779, -0.0385, -0.0741, 0.0229, 0.0116, 0.1271, 0.0273, -0.0867, -0.0511]], grad_fn=<AddmmBackward0>)4. 顺序块
我们可以自己实现一个类似nn.Sequential的顺序块,把每个子块以字典的形式存入_modules中。
classMySequential(nn.Module):def__init__(self,*args):super().__init__()forblockinargs:# block 本身作为它的 key,存在 _modules 里面的为层,以字典的形式self._modules[block]=blockdefforward(self,X):forblockinself._modules.values():print(block)X=block(X)returnXnet=MySequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))X=torch.rand(2,20)net(X)输出结果:
Linear(in_features=20, out_features=256, bias=True) ReLU() Linear(in_features=256, out_features=10, bias=True) tensor([[-0.0651, 0.0377, -0.0348, -0.0377, 0.1602, 0.0022, -0.0904, 0.1742, -0.0520, 0.0189], [-0.0192, 0.1056, -0.0497, 0.0301, 0.2464, 0.0126, -0.1700, 0.4147, 0.0703, -0.0013]], grad_fn=<AddmmBackward0>)5. 正向传播
在forward函数中,我们不仅可以调用层,还可以执行任意 Python 代码,例如控制流、张量运算等。
classFixedHiddenMLP(nn.Module):def__init__(self):super().__init__()self.rand_weight=torch.rand((20,20),requires_grad=False)self.linear=nn.Linear(20,20)defforward(self,X):X=self.linear(X)X=F.relu(torch.mm(X,self.rand_weight+1))X=self.linear(X)whileX.abs().sum()>1:X/=2returnX.sum()net=FixedHiddenMLP()X=torch.rand(2,20)net(X)输出结果:
tensor(0.3770, grad_fn=<SumBackward0>)6. 混合组合块
各种块可以自由混合组合,构建出更复杂的模型结构。
classNestMLP(nn.Module):def__init__(self):super().__init__()self.net=nn.Sequential(nn.Linear(20,64),nn.ReLU(),nn.Linear(64,32),nn.ReLU())self.linear=nn.Linear(32,16)defforward(self,X):returnself.linear(self.net(X))chimera=nn.Sequential(NestMLP(),nn.Linear(16,20),FixedHiddenMLP())X=torch.rand(2,20)chimera(X)输出结果:
tensor(-0.1488, grad_fn=<SumBackward0>)7. 参数管理
7.1 访问参数
首先关注具有单隐藏层的多层感知机:
importtorchfromtorchimportnn net=nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))X=torch.rand(size=(2,4))print(net(X))tensor([[0.3941], [0.4224]], grad_fn=<AddmmBackward0>)访问参数,net[2]就是最后一个输出层:
print(net[2].state_dict())OrderedDict([('weight', tensor([[ 4.7564e-02, -5.3226e-02, 1.4919e-04, -2.8679e-01, 1.7408e-01, 3.0859e-01, -1.2281e-01, 5.6171e-02]])), ('bias', tensor([0.3129]))])查看目标参数的类型与数值:
print(type(net[2].bias))# 目标参数print(net[2].bias)print(net[2].bias.data)print(net[2].weight.grad==None)# 还没进行反向计算,所以 grad 为 None<class 'torch.nn.parameter.Parameter'> Parameter containing: tensor([0.3129], requires_grad=True) tensor([0.3129]) True7.2 一次性访问所有参数
print(*[(name,param.shape)forname,paraminnet[0].named_parameters()])print(*[(name,param.shape)forname,paraminnet.named_parameters()])('weight', torch.Size([8, 4])) ('bias', torch.Size([8])) ('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1]))说明:
0是第一层名字,1是 ReLU,它没有参数。
通过名字获取参数:
print(net.state_dict()['2.bias'].data)tensor([0.3129])7.3 从嵌套块收集参数
defblock1():returnnn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,4),nn.ReLU())defblock2():net=nn.Sequential()foriinrange(4):# f'block{i}' 可以传一个字符串名字过来,block2 可以嵌套四个 block1net.add_module(f'block{i}',block1())returnnet rgnet=nn.Sequential(block2(),nn.Linear(4,1))print(rgnet(X))print(rgnet)输出结果:
tensor([[-0.1750], [-0.1750]], grad_fn=<AddmmBackward0>) Sequential( (0): Sequential( (block0): Sequential( (0): Linear(in_features=4, out_features=8, bias=True) (1): ReLU() (2): Linear(in_features=8, out_features=4, bias=True) (3): ReLU() ) (block1): Sequential( (0): Linear(in_features=4, out_features=8, bias=True) (1): ReLU() (2): Linear(in_features=8, out_features=4, bias=True) (3): ReLU() ) (block2): Sequential( (0): Linear(in_features=4, out_features=8, bias=True) (1): ReLU() (2): Linear(in_features=8, out_features=4, bias=True) (3): ReLU() ) (block3): Sequential( (0): Linear(in_features=4, out_features=8, bias=True) (1): ReLU() (2): Linear(in_features=8, out_features=4, bias=True) (3): ReLU() ) ) (1): Linear(in_features=4, out_features=1, bias=True) )8. 内置初始化
8.1 正态分布初始化
net=nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))definit_normal(m):iftype(m)==nn.Linear:nn.init.normal_(m.weight,mean=0,std=0.01)# 下划线表示把 m.weight 的值替换掉nn.init.zeros_(m.bias)net.apply(init_normal)# 会递归调用,直到所有层都初始化print(net[0].weight.data[0])print(net[0].bias.data[0])tensor([ 0.0012, -0.0112, -0.0153, 0.0218]) tensor(0.)8.2 常数初始化
net=nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))definit_constant(m):iftype(m)==nn.Linear:nn.init.constant_(m.weight,1)nn.init.zeros_(m.bias)net.apply(init_constant)print(net[0].weight.data[0])print(net[0].bias.data[0])tensor([1., 1., 1., 1.]) tensor(0.)8.3 对某些块应用不同的初始化
defxavier(m):iftype(m)==nn.Linear:nn.init.xavier_uniform_(m.weight)definit_42(m):iftype(m)==nn.Linear:nn.init.constant_(m.weight,42)net[0].apply(xavier)net[2].apply(init_42)print(net[0].weight.data[0])print(net[2].weight.data)tensor([ 0.0479, -0.1771, 0.5267, -0.0020]) tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])9. 参数替换
自定义初始化,只保留绝对值大于 5 的权重,其余置零:
defmy_init(m):iftype(m)==nn.Linear:print("Init",*[(name,param.shape)forname,paraminm.named_parameters()][0])nn.init.uniform_(m.weight,-10,10)# 先计算布尔矩阵(判断 >= 5),再用布尔矩阵对应元素乘以原始矩阵# 保留绝对值大于 5 的权重,否则设为 0m.weight.data*=m.weight.data.abs()>=5net.apply(my_init)print(net[0].weight[:2])Init weight torch.Size([8, 4]) Init weight torch.Size([1, 8]) tensor([[ 0.0000, 7.1240, 0.0000, 5.1135], [-8.6745, -7.3974, 0.0000, -0.0000]], grad_fn=<SliceBackward0>)直接替换参数:
net[0].weight.data[:]+=1net[0].weight.data[0,0]=42print(net[0].weight.data[0])tensor([42.0000, 8.1240, 1.0000, 6.1135])10. 参数绑定
多个层可以共享同一组参数。下面的例子中,第 2 个隐藏层和第 3 个隐藏层共享权重。
shared=nn.Linear(8,8)net=nn.Sequential(nn.Linear(4,8),nn.ReLU(),shared,nn.ReLU(),shared,nn.ReLU(),nn.Linear(8,1))net(X)print(net[2].weight.data[0]==net[4].weight.data[0])net[2].weight.data[0,0]=100print(net[2].weight.data[0]==net[4].weight.data[0])tensor([True, True, True, True, True, True, True, True]) tensor([True, True, True, True, True, True, True, True])可以看到,修改其中一个层的权重,另一个共享层的权重也会同步变化。
11. 自定义层
11.1 无参数的自定义层
importtorchimporttorch.nn.functionalasFfromtorchimportnnclassCenteredLayer(nn.Module):def__init__(self):super().__init__()defforward(self,X):returnX-X.mean()layer=CenteredLayer()print(layer(torch.FloatTensor([1,2,3,4,5])))tensor([-2., -1., 0., 1., 2.])将层作为组件合并到更复杂的模型中:
net=nn.Sequential(nn.Linear(8,128),CenteredLayer())Y=net(torch.rand(4,8))print(Y.mean())tensor(-6.2864e-09, grad_fn=<MeanBackward0>)11.2 带参数的自定义层
classMyLinear(nn.Module):def__init__(self,in_units,units):super().__init__()# nn.Parameter 使得这些参数加上了梯度self.weight=nn.Parameter(torch.randn(in_units,units))self.bias=nn.Parameter(torch.randn(units,))defforward(self,X):linear=torch.matmul(X,self.weight.data)+self.bias.datareturnF.relu(linear)dense=MyLinear(5,3)print(dense.weight)Parameter containing: tensor([[-2.8449, 0.1887, 0.7945], [ 0.4226, 1.6180, -0.5880], [-0.4794, -0.0817, -0.3648], [-0.1979, 0.8702, -0.3515], [-1.4943, 0.3618, 0.2969]], requires_grad=True)使用自定义层直接执行正向传播计算:
print(dense(torch.rand(2,5)))tensor([[0.0000, 0.0000, 1.3957], [0.8225, 0.0000, 0.9089]])使用自定义层构建模型:
net=nn.Sequential(MyLinear(64,8),MyLinear(8,1))print(net(torch.rand(2,64)))tensor([[0.], [0.]])12. 读写文件
12.1 加载和保存张量
importtorchfromtorchimportnnfromtorch.nnimportfunctionalasF x=torch.arange(4)torch.save(x,'x-file')x2=torch.load("x-file")print(x2)tensor([0, 1, 2, 3])存储一个张量列表,然后把它们读回内存:
y=torch.zeros(4)torch.save([x,y],'x-files')x2,y2=torch.load('x-files')print(x2)print(y2)tensor([0, 1, 2, 3]) tensor([0., 0., 0., 0.])写入或读取从字符串映射到张量的字典:
mydict={'x':x,'y':y}torch.save(mydict,'mydict')mydict2=torch.load('mydict')print(mydict2){'x': tensor([0, 1, 2, 3]), 'y': tensor([0., 0., 0., 0.])}12.2 加载和保存模型参数
classMLP(nn.Module):def__init__(self):super().__init__()self.hidden=nn.Linear(20,256)self.output=nn.Linear(256,10)defforward(self,x):returnself.output(F.relu(self.hidden(x)))net=MLP()X=torch.randn(size=(2,20))Y=net(X)# 将模型的参数存储为一个叫做 "mlp.params" 的文件torch.save(net.state_dict(),'mlp.params')# 实例化了原始多层感知机模型的一个备份,直接读取文件中存储的参数clone=MLP()# 必须要先声明一下,才能导入参数clone.load_state_dict(torch.load("mlp.params"))print(clone.eval())# eval() 是进入测试模式Y_clone=clone(X)print(Y_clone==Y)MLP( (hidden): Linear(in_features=20, out_features=256, bias=True) (output): Linear(in_features=256, out_features=10, bias=True) ) tensor([[True, True, True, True, True, True, True, True, True, True], [True, True, True, True, True, True, True, True, True, True]])13. 总结
本文系统梳理了 PyTorch 中模型构建的核心机制:
- 层与块:
nn.Sequential是最简单的块,自定义块需继承nn.Module并实现forward。 - 正向传播:
forward中可以执行任意 Python 代码,实现灵活的控制流。 - 参数管理:通过
state_dict()、named_parameters()访问参数,通过apply()递归初始化。 - 参数绑定:多个层共享同一组参数,修改一处即同步生效。
- 自定义层:无参数层与带参数层(使用
nn.Parameter)均可灵活构建。 - 读写文件:
torch.save/torch.load支持张量、字典与模型参数的持久化。
掌握这些基础能力后,就可以更从容地搭建和调试复杂的深度学习模型了。