dl学习笔记:(5)深度神经网络的正向传播
这小节的任务不多,使用封装好的torch.nn模块来实现一个完整的多层神经网络的正向传播。假设我们有500条数据,20个特征,标签为3分类。我们现在要实现一个三层神经网络,这个神经网络的架构如下:第一层有13个神经元,第二层有8个神经元,第三层是输出层。其中,第一层的激活函数是relu,第二层是sigmoid。
(1)导库
import torch
import torch.nn as nn
from torch.nn import functional as F
(2)定义数据张量
torch.manual_seed(250)
x = torch.rand((500,20),dtype = torch.float32)
y = torch.randint(low = 0,high = 3,size = (500,1),dtype = torch.float32)
由于是500条数据,20个特征,所以x是(500,20)
由于是3分类任务,所以这里的y取到的是0,1,2,这里的函数是左闭右开,所以参数是0和3
(3)定义model类
class model(nn.Module):
def __init__(self,in_features = 20,out_features = 3):
super(model,self).__init__()
self.linear1 = nn.Linear(in_features,13,bias = True)
self.linear2 = nn.Linear(13,8,bias = True)
self.output = nn.Linear(8,out_features,bias = True)
def forward(self,x):
z1 = self.linear1(x)
sigma1 = torch.relu(z1)
z2 = self.linear2(sigma1)
sigma2 = torch.sigmoid(z2)
z3 = self.output(sigma2)
sigma3 = F.softmax(z3,dim = 1)
return sigma3
首先定义__init__,由于开始的传入和最后的输出和模型没什么关系,取决于你的数据和任务,所以这里作为参数传入in_features和out_features
super(model, self)
:这里的super()
表示调用父类nn.Module
的方法,第一个参数model
是当前子类的名称,第二个参数self
是当前实例对象。super(model, self)
返回父类nn.Module
的代理对象。super(model, self).__init__()
:调用父类nn.Module
的构造函数(__init__()
),这样就可以在子类中正确地初始化父类的部分。
nn.Module
中有许多有用的功能和初始化机制,比如管理模型的参数、注册模型的子模块、以及其他自动化的功能。如果你不调用父类的构造函数,就会失去这些功能。因此,必须调用 super().__init__()
来确保父类的构造方法被正确地执行,避免遗漏父类中的初始化代码。
后面定义linear的函数在上一节已经介绍过了,里面的参数按照具体的任务来就行。
当然nn.Module中的init函数还有很多其他的东西,如下图我们用到具体某一个的时候会提及:
同理下面的前向传播也是一样,在前面的小节中已经介绍过了,这里也只是简单调用
这里我们简单拓展一下python中的self:
Python 中的 self
确实与 C++ 中隐式的 this
指针有类似的作用。它们都是用于引用当前对象的机制。
-
设计哲学:Python 选择显式地使用
self
,符合 Python 的设计哲学 "Explicit is better than implicit"(明确优于隐式)。这种做法让代码的意图更加清晰,易于理解。当你看到方法定义时,明确知道self
是指向当前对象的引用,而不需要隐式的规则来推断。 -
避免歧义:通过显式地使用
self
,Python 避免了像 C++ 中的this
可能引起的歧义。例如,在 C++ 中,如果某个方法名称和类成员变量相同,this
指针可能会引发混淆,而在 Python 中,self
的明确存在避免了这种问题。
-
C++:
- 在实例化对象时,编译器首先为对象分配内存(根据类的大小计算),然后调用构造函数。
- 构造函数会初始化对象的状态(如成员变量)。
- 对象在创建时,
this
指针就指向该对象的内存地址,所以实例方法可以使用this
来访问成员变量和方法。
-
Python:
- 在实例化时,Python 会动态为对象分配内存,内存大小由 Python 的对象模型决定。
- 在 Python 中,
self
是显式传递的,代表当前对象实例的引用。每当你调用实例方法时,Python 会将self
(即当前对象)作为第一个参数传递给方法。 - 因为 Python 对象是动态管理的,所以内存的分配、垃圾回收等过程都由 Python 运行时来处理。
(4)实例化model
先定义输入和输出参数:
input_features = x.shape[1]
output_features = len(y.unique())
实例化:
torch.manual_seed(250)
net = model(in_features = input_features,out_features = output_features)
net.forward(x)
至此一个完整的前向传播就已经完成了,结果如下:
下一步我们可以查看一下层的各种属性:
可以看到输出是500条数据,对于三个类别的概率,这也和最后的softmax层输出结果一致
同样的我们也可以查看,对应层的权重和b:
下面我们将每一层的形状输出来:
我们会发现,这里面的每一层权重的形状都是下一层的神经元个数和上一层神经元个数,是反过来的。例如这里的第一层,输入是20个特征第一层有13个神经元,这里却是(13,20)。这是因为为了矩阵乘法的维度匹配,整个流程的形状变化过程如下:
1.先将输入的特征矩阵转置变成(20,500)
2.第一层:(13,20)*(20,500) = (13,500)
3.第二层:(8,13)*(13,500)= (8,500)
4.输出层:(3,8)*(8,500) = (3,500)
5.最后softmax函数输出的时候会再次转置调回来,得到输出(500,3)