pytorch张量运算
pytorch张量运算
使用cpu gpu训练数据
torch.cuda.is_available() # 判断机器支不支持cpu
一般用如下命令:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
fm = fm.to(device) # fm是定义的module
batch_x = batch_x.to(device) # 将数据转化为device
2.1 数据操作
- 深度学习落实到计算表现为矩阵计算
- pytorch、tensorflow中,计算的基本组件是Tensor。张量即多维数组,是矩阵计算的基本单元。
- Tensor:张量,一维张量即向量vector,二维张量即 二维数组。张量是n维数组的统称
- python中有专门进行矩阵计算的库:numpy。pytorch和tensorflow等 和 numpy除了矩阵计算的相似点之外,多了如下额外的功能:
- 张量支持GPU加速;numpy仅仅支持CPU
- 张量支持自动微分,可以自动进行求导
2.1.1 创建张量
创建向量
x = torch.arrange(12, dtype=torch.float) # 创建vector,除非额外指定,否则其存储于GPU中
x = x.reshape(3,4) # 改变tensor的形状
x = torch.zeros((2,3,4)) # 创建全0向量
x = torch.ones((2,3,4)) # 创建全1向量
x = torch.randn(3,4) # 创建 均值为0,方差为1的标准高斯分布
x = torch.tensor([[2,1,4,3],[1,2,3,4],[4,3,2,1]]) # 将list转化为tensor
x = torch.full((2,2),3) # 创建 向量维度大小为(2,2),所有数值为3的向量
从列表、numpy、数组创建tensor
x=torch.tensor([1,2,3],dtype=torch.float) # 从数组创建
x=torch.as_tensor((1,2,3),dtype=torch.float) # 从tuple创建
x = torch.from_numpy(np.random.randn(3)) # 从numpy数组
pytorch中的tensor有如下四种数据类型
- 浮点型:torch.float32(torch.float) , torch.float64(或者torch.double)
- 整型: yotch.int8,torch.int16, torch.int32(torch.int),torch.int64(torch.long)
- 布尔型:torch.bool
- 其他: torch.complex64, torch.complex128
通过tensor.dtype查看tensor的数据类型,创建tensor的时候指定
查看tensor的基本属性的操作
x.shape # 矩阵维度
x.size() # 张量维度
x.dim() # 张量维度数目
x.type() # 张量的数据类型
x.dtype # 查看张量的数据类型
x.T # 矩阵转置
查看张量位置
id(x) #查看tensor x引用地址
x.device #查看tensor x位于cpu还是gpu上
2.1.2 运算符
- 按元素计算,下列常见运算均支持:
- 常用的+、-、x,/ 均支持按照元素计算
- 求幂运算\(x**y\)
- 一元运算符 比如 \(torch.exp(x)\)
- x == y
- 当x和y相等的时候,逐元素判断是否相等
- 聚合操作:sum(), mean(), max(), min()
- 向量叠加或者连结(concatenate)
- 多个张量concatenate
- dim = 0 按照行连接
- dim = 1 按照列连接
torch.cat((x,y), dim=0) # 按行叠加
torch.cat((x,y), dim=1) # 按列叠加
2.1.3 广播机制
- 广播机制指的是:当两个向量之间进行操作的时候,由于不满足传统条件,numpy 或者 pytorch会自动进行补全
a = torch.arange(3).reshape((3,1))
b = torch.arange(2).reshape((1,2))
a+b
换算成numpy
a=np.arange(3).reshape((3,1))
b=np.arange(2).reshape((1,2))
>>> a
array([[0],
[1],
[2]])
>>> b
array([[0, 1]])
a进行复制列,b进行复制行。将a和b分别变成长度为 3*2的矩阵后,进行想加
>>> a+b
array([[0, 1],
[1, 2],
[2, 3]])
2.1.4 索引和切片
张量中的元素可以进行索引操作,和python的数组操作一致
- 0表示第一个元素;-1表示最后一个元素,可以通过指定范围选择某个范围中的数据 ;
- 特殊符号":" 表示 沿某一个轴的所有元素
- 可以利用切片操作对选择范围中的数据进行赋值
X[1,2]=9
>>> x=np.arange(9).reshape(3,3)
>>> x
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
>>> x[0,:]
array([0, 1, 2])
2.1.5 原位计算
矩阵操作某些写法会导致变量重新分配内存,即变量对应的内存地址会发生变化。ex: X = X + Y。但是有时候为了节省内存,我们更希望原位操作。针对矩阵,如下是表示原位操作:
- 更新矩阵:
X[:] = X + Y或者X += Y *=
函数id(变量) 用来表示变量引用指向的内存空间的地址,可以用来判断两个变量是否是内存上相同。
>>> X=np.zeros(4).reshape(2,2)
>>> X
array([[0., 0.],
[0., 0.]])
>>> Y = np.ones(4).reshape(2,2)
>>> id(X)
140502555798464
>>> X += Y
>>> id(X)
140502555798464
注意:Pytorch代码中很多操作都是原位操作,远不止这种写法
函数中,以_结尾的函数,都是原位操作,会改变tensor本身。
比如:y.add_(x),x.copy_(y),x_t(), x.squeeze_(),x.unsqueeze_(),mul_,reshape_()
In [69]: id(a)
Out[69]: 139970692801520
In [70]: a.add_(a)
Out[70]:
tensor([[ 0., 4., 8.],
[12., 16., 20.]])
In [71]: id(a)
Out[71]: 139970692801520
2.1.6 转换为其他python对象
将pytorch定义的tensor 转换为numpy(底层都一样)
A = X.numpy() # 将tensor转化为numpy
B = torch.tensor(A) # 进行转换
type(A), type(B)
(numpy.ndarray, torch.Tensor)
- 将常规张量转化为numpy:
a=torch.tensor([2,3])
b=a.numpy()
type(a),type(b)
(torch.Tensor, numpy.ndarray)
- 将大小为1的张量转换为python标量,使用item 或者 python的内置函数
a = torch.tensor([3.5])
a.item(), float(a), int(a) # 将张量转化为标量
适用于numpy
>>> a=np.array([1.0])
>>> type(a.item())
<type 'float'>
>>> type(int(a))
<type 'int'>
将numpy转化为张量
torch.from_numpy(np.array([1,2,3]))
2.1.7 张量叠加与切割
张量的拼接:几个小张量拼接成一个大的张量。主要的函数有torch.cat,torch.stack
张量的切割:将一个张量切割成几个小的。主要的函数有torch.chunk,torch.split
下面注意介绍:
torch.cat
功能:在给定维度上对张量序列进行叠加操作,所有的张量必须有相同的形状(连接维度除外)或者为空
In [87]: a
Out[87]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
In [88]: torch.cat((a,a),dim=0) # 沿行叠加
Out[88]:
tensor([[0., 1., 2.],
[3., 4., 5.],
[0., 1., 2.],
[3., 4., 5.]])
In [89]: torch.cat((a,a),dim=1) # 沿列叠加
Out[89]:
tensor([[0., 1., 2., 0., 1., 2.],
[3., 4., 5., 3., 4., 5.]])
torch.stack
功能:在一个新的维度上,叠加张量,所有的张量必须是相同的大小
In [92]: a # shape: 3*2
Out[92]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
In [93]: torch.stack((a,a),dim=0) # shape 2 * 2 * 3
Out[93]:
tensor([[[0., 1., 2.],
[3., 4., 5.]],
[[0., 1., 2.],
[3., 4., 5.]]])
In [97]: torch.stack((a,a),dim=1) # shape torch.Size([2, 2, 3])
Out[97]:
tensor([[[0., 1., 2.],
[0., 1., 2.]],
[[3., 4., 5.],
[3., 4., 5.]]])
In [100]: torch.stack((a,a),dim=2) # shape torch.Size([2, 3, 2])
Out[100]:
tensor([[[0., 0.],
[1., 1.],
[2., 2.]],
[[3., 3.],
[4., 4.],
[5., 5.]]])
torch.chunk
用法:torch.chunk(*input*, *chunks*, *dim=0*) → List of Tensors
功能:将一个大的tensor按照不同的维度分割成k个小的tensor。每个数据块都是输入张量的一个视图。
In [4]: a=torch.arange(6).reshape(2,3)
In [5]: a.chunk(3)
Out[5]: (tensor([[0, 1, 2]]), tensor([[3, 4, 5]]))
In [6]: a.chunk(3,dim=0) # 按照维度为0进行分割
Out[6]: (tensor([[0, 1, 2]]), tensor([[3, 4, 5]]))
In [7]: a.chunk(3,dim=1) # 按照维度为1进行分割
Out[7]:
(tensor([[0],
[3]]),
tensor([[1],
[4]]),
tensor([[2],
[5]]))
torch.split
用法:torch.split(*tensor*, *split_size_or_sections*, *dim=0*)
功能:将张量分成数据块,每个数据块都是原始张量的视图。和 torch.chunk功能相同
- 当split_size_or_sections 为整数的时候,函数会沿着维度方向尽量切割成长度为plit_size_or_sections的tensor
- 当split_size_or_sections 是list的时候,函数根据list的设置,沿着设定的维度方向,切割成长度不同的tensor
In [13]: a
Out[13]:
tensor([[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]])
In [16]: torch.split(a,2,dim=0) # 按照行分
Out[16]:
(tensor([[0, 1],
[2, 3]]),
tensor([[4, 5],
[6, 7]]),
tensor([[8, 9]]))
In [17]: torch.split(a,2,dim=1) # 按照列分
Out[17]:
(tensor([[0, 1],
[2, 3],
[4, 5],
[6, 7],
[8, 9]]),)
2.1.8 维度变化
维度变化函数可以直接对tensor的维度进行更改,比如 torch.flatten, torch.squeeze,torch.unsqueeze 。严格意义上来讲,torch.split,torch.cat 还是传统的 聚合函数如torch.sum 也是维度变化。
torch.flatten:扁平化
用法:torch.flatten(*input*, *start_dim=0*, *end_dim=-1*) → [Tensor]
功能:将张量进行进行压扁操作(扁平化操作)。 如果张量是1维,不需要压扁;如果张量是2维,压扁到1维;如果张量是3维,那么可以根据实际的需求进行全部压扁或者部分压扁操作。
输入:
- start_dim:从那一维度开始flatten
- end_dim:从那一维度结束flatten的操作
注意:维度dim从0开始,-1表示最后一维。
案例: 二维压一维
In [115]: a=torch.arange(4).reshape(2,2)
In [116]: a
Out[116]:
tensor([[0, 1],
[2, 3]])
In [117]: torch.flatten(a)
Out[117]: tensor([0, 1, 2, 3])
三维压二维
In [126]: a=torch.arange(12).reshape(2,2,3)
In [127]: a.flatten(0,1) # 将第0维消除 变成 4*3
Out[127]:
tensor([[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
In [128]: a.flatten(0,2) # 等价于a.flatten, a.flatten(0,-1)直接压到以维
Out[128]: tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
In [129]: a.flatten(1,2) # 将第1维压了,变成 2 * 6 按照什么方向压的
Out[129]:
tensor([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11]])
torch.squeeze
用法:torch.squeeze(*input*, *dim=None*) → [Tensor]
功能:squeeze 是挤压,消除,去掉的意思
- 对维度进行压缩,不设置参数,就是去掉矩阵中所有维数为1的维度。比如矩阵(1*3) 去掉维数为1的,大小变为长度为(3) 的tensor
- 如果设置dim,那么就是去掉指定维为1的操作 如 矩阵(
2*1*2), dim=1,那么输出矩阵就是2*2 - squeeze 和 unsqueeze对矩阵的数据个数都没有损失
案例
In [138]: a=torch.arange(3).reshape(1,3)
In [139]: b=torch.squeeze(a)
In [140]: b,b.shape
Out[140]: (tensor([0, 1, 2]), torch.Size([3]))
去掉指定维度
In [147]: a=torch.arange(6).reshape(2,1,3)
In [148]: b=torch.squeeze(a,dim=1)
In [149]: b.shape
Out[149]: torch.Size([2, 3])
torch.unsequeeze
用法:torch.unsqueeze(*input*, *dim*) → [Tensor]
功能:在指定位置增加维数为1的维度,比如一个长度为3的向量,在dim=0位置增加一个维度,就是(1*3),在dim=1的位置增加一个维度,张量维度就变为(3*1)
In [151]: a=torch.tensor([1,2,3])
In [152]: b=a.unsqueeze(dim=0)
In [153]: c=a.unsqueeze(dim=1)
In [154]: b.shape,c.shape
Out[154]: (torch.Size([1, 3]), torch.Size([3, 1]))
2.2 矩阵乘法介绍
torch中涉及矩阵乘法函数较多:如
- torch.mul, torch.multiply,*
- torch.dot, torch.mv,torch.mm,torch.bmm,torch.matmul
矩阵乘法最基本有两种方式:
- 点乘:按元素相乘。即两个矩阵中相同位置元素的乘积构成了新矩阵相应位置的元素
- 叉乘:传统矩阵乘法
2.2.1 点乘
*、torch.mul,torch.multiply 三者含义相同。
两个张量的点乘注意:
- 两个张量维度相同 ,按相同位置相同元素相乘
- 两个张量维度不同的时候,先通过广播转化为相同
向量和向量
In [29]: a=torch.ones(3,dtype=torch.float)
In [30]: b=torch.arange(3,dtype=torch.float)
In [31]: a*b,torch.mul(a,b)
Out[31]: (tensor([0., 1., 2.]), tensor([0., 1., 2.]))
In [32]: a*b == torch.mul(a,b)
Out[32]: tensor([True, True, True])
向量和矩阵
In [33]: a=torch.arange(6,dtype=torch.float).reshape(2,3)
In [34]: b=torch.ones(3,dtype=torch.float)
In [35]: a
Out[35]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
In [36]: b
Out[36]: tensor([1., 1., 1.])
In [37]: torch.mul(a,b)
Out[37]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
矩阵和矩阵
In [41]: a=torch.arange(6,dtype=torch.float).reshape(2,3)
In [42]: a
Out[42]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
In [43]: b=a
In [45]: torch.mul(a,b)
Out[45]:
tensor([[ 0., 1., 4.],
[ 9., 16., 25.]])
2.2.2 叉乘
叉乘就是传统的矩阵方法,两个向量的叉乘即两个向量的内积。
向量和向量 torch.dot
In [3]: a=torch.ones(3,dtype=torch.float)
In [4]: b=torch.arange(3,dtype=torch.float)
In [5]: a
Out[5]: tensor([1., 1., 1.])
In [7]: b
Out[7]: tensor([0., 1., 2.])
In [8]: torch.dot(a,b) # 矩阵点乘
Out[8]: tensor(3.)
向量和矩阵 torch.mv
In [11]: a=torch.arange(6,dtype=torch.float).reshape(2,3)
In [12]: b=torch.ones(3,dtype=torch.float)
In [13]: a
Out[13]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
In [14]: b
Out[14]: tensor([1., 1., 1.])
In [15]: torch.mv(a,b)
Out[15]: tensor([ 3., 12.])
矩阵和矩阵 torch.mm
In [16]: a=torch.arange(6,dtype=torch.float).reshape(2,3)
In [17]: b=torch.ones(3,2,dtype=torch.float)
In [18]: a
Out[18]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
In [19]: b
Out[19]:
tensor([[1., 1.],
[1., 1.],
[1., 1.]])
In [20]: torch.mm(a,b)
Out[20]:
tensor([[ 3., 3.],
[12., 12.]])
批量矩阵乘法 torch.bmm
bmm 表示批量矩阵乘法。能够进行多个矩阵的点乘。bmm分别表示batch(批量)、matrix(矩阵)、matrix(矩阵)的首个字母
In [22]: a=torch.tensor([[[1,2],[3,4]],[[1,2],[3,4]],[[1,2],[3,4]]],dtype=torch.float)
In [23]: b=torch.tensor([[[3,4],[1,2]],[[3,4],[1,2]],[[3,4],[1,2]]],dtype=torch.float)
In [26]: a.shape,b.shape
Out[26]: (torch.Size([3, 2, 2]), torch.Size([3, 2, 2]))
In [27]: torch.bmm(a,b)
Out[27]:
tensor([[[ 5., 8.],
[13., 20.]],
[[ 5., 8.],
[13., 20.]],
[[ 5., 8.],
[13., 20.]]])
**矩阵乘法统一接口 **torch.matmul
torch.matmul 可以用来计算向量与向量,向量与矩阵,矩阵与矩阵,批量矩阵之间的乘法
2.2.3 torch.einsum
可以包含上述所有操作。
torch.einsum('ij->ji',a) # 矩阵转置
In [48]: torch.einsum('ij->',a) # 矩阵元素整体求和
Out[48]: tensor(15.)
In [49]: torch.einsum('ij->i',a) # 矩阵元素列求和
Out[49]: tensor([ 3., 12.])
In [50]: torch.einsum('ij->j',a) # 行求和
Out[50]: tensor([3., 5., 7.])
矩阵相乘
In [51]: a
Out[51]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
In [52]: b = a
In [53]: torch.einsum('ij,jk->ik',a,b.T) # 矩阵相乘
Out[53]:
tensor([[ 5., 14.],
[14., 50.]])
向量矩阵相乘
In [60]: a
Out[60]:
tensor([[0., 1., 2.],
[3., 4., 5.]])
In [61]: c
Out[61]: tensor([1., 1., 1.])
In [62]: torch.einsum('ij,j->i',a,c)
Out[62]: tensor([ 3., 12.])
向量的点乘和叉乘
注意:向量点乘和叉乘与矩阵点乘和叉乘的区别。
向量的点乘:逐元素想乘,再求和。就是两个向量的内积。
- 物理含义:两个向量的夹角
- \(ab\) = |a||b|\(cos\theta\)
x=torch.randn(2)
torch.dot(x,x) # 点乘、内积
向量的叉乘:结果是一个向量
- 方向满足右手定律。垂直于向量a向量b构成平面的法向量
- 大小是 两个向量组成的平行四边形的面积 \(a*b\) = |a||b|\(cos\theta\)
2.3 数据预处理
2.4 自动微分
深度学习需要求导,通过导数明确优化方向。深度学习的工具都支持自动求导,即自动微分。自动微分的实现依赖于 计算图 和 链式求导规则。通过链式求导法则,可以将求导计算分解为一系列有限的可微分算子,而后通过计算图将整个运算过程描述出来
计算图
计算图是用来描述运算过程的有向无环图。计算图主要有两个元素:节点(node) 和边(edge)。
- 节点表示数据变量,如向量、矩阵,张量
- 边表示运算,比如加减乘除
torch.mul(),torch.mm(),torch.div等
叶子节点
叶子借点即leaf_node。叶子节点是用户自己构建的变量,反向传播过程即终点对叶子阶段进行求导。
链式求导法则
2.2 pytorch求导
- 创建可以计算梯度的变量
x=torch.tensor([1,2,3],dtype=torch.float32,requires_grad=True)
或者分两步
x=torch.arange(4.0) # 创建tensor
x.requires_grad_(True) #默认情况下 x.grad == None
梯度的默认值是None
- 创建函数表达式 & 计算梯度
y = torch.dot(x,x) # 定义表达式
y.backward() # 计算梯度 对y求x的梯度
print(x.grad) # 输出:tensor([0., 2., 4., 6.])
- 变量梯度清零
默认情况下,情况下pytorch会累计梯度。如果你要计算以x为自变量的另外一个函数,此时需要先对变量上已经累计的梯度进行清零。
x.grad.zero_() # 梯度清零
y=x.sum() # 以x为自变量构建的另外一个函数
y.backward() # 计算梯度
print(x.grad) # 打印变量x的梯度
3.1 tensor.grad.zero_() 梯度清零的使用场景
- 构建tensor x。其梯度打来
- 函数
y=f(x)对y关于\(x\) 在某一点求梯度x.grad - 使用\(x\) 构建函数\(g=g(x)\), 如果x不进行梯度清零,那么对g关于x求导,其结果为
x.grad = g关于x的导数+y关于x的导数
在深度学习里面,神经网络的参数是在动态变化中,所以其对应的映射关系也是在动态变化中。所以一次反向求导之后,进行第二次反向求导,那么需要将导数清零。
x=torch.tensor([1.0,1.0],requires_grad=True)
y=torch.dot(x,x) * 0.5
g=2*x
y.backward()
g.sum().backward()
x.grad # tensor([3., 3.]) = tensor([1., 1.]) + 2
- 分离计算 detach
detach方法:创建一个变量f是变量x的函数,但是 在进行梯度计算的时候,并不对f求x的梯度,将x视作常量。
场景:
y = f(x) * x 对 y求x的导数,此时希望f(x) 当常数,即y对x求导,结果是f(x)
f = x * x
f = f.detach()
y = f * x
y.sum().backward()
print(x.grad)
x.grad == f # 输出: tensor([True, True, True, True])
torch.no_grad()
-
简介:有一个与detach类似的方法,叫做
torch.no_grad(),上下文管理器。在该管理器中,所有的操作都不会计算梯度,但是也只是简单的禁用梯度计算。 -
机制:在上下文环境中,将所有的tensor的 ``requires_grad
暂时性的设置为False即可 -
优点
- 速度提升:因为不需要计算梯度
- 省内存:不需要存储梯度
注意:
torch.no_grad()只对当前的执行线程有效,不会影响到其他线程的计算- 一般用于模型的评估(在模型评估阶段不需要计算梯度)
model = ... # Your trained PyTorch model
with torch.no_grad():
inputs = ... # Your evaluation data
outputs = model(inputs)
# Calculate evaluation metrics (accuracy, loss, etc.)
2.3 定义网络
线索条
1、pandas 操作
2、数据类型
3、原位操作
- https://blog.csdn.net/qq_34243930/article/details/106886993
- https://blog.csdn.net/m0_38129460/article/details/90405086
4、构建layer
5、自动微分
- 自动微分:通过计算机求微分
- 计算图
- 动态图
- 静态图
- 叶子节点
6、detach、eval 操作是干什么?
- https://blog.csdn.net/qq_41813454/article/details/135129279
- https://blog.csdn.net/weixin_44584198/article/details/135146573
- https://python-code.dev/articles/324150004
番外篇
python可调用对象
python中一切皆对象。函数也是对象,但是不是所有的对象都是函数。即不是所有的对象都可以像函数一样使用。
类的实例对象可以转化为可调用对象。其关键在于类内定义__call__函数。
__call__函数
在类中定义了_call__函数,可以使得类的对象像函数那样被调用。类似于C++类重载() 运算符。
class A:
def __init__(self):
print('This is __init__')
def __call__(self, name):
print('This is call, %s' % name)
a= A() # 输出 This is __init__
a('name') # 输出 This is call, name
判断可调用对象
至少有三种方法可以判断一个对象是否可以调用
- 使用
callable函数,输出true表示该对象可以调用 - 使用
asattr(对象,"__call__") - 使用
collections模块中的abc中的abc.Callable+isinstance
callable(A) # 输出 True
hasattr(int,"__call__") # 输出 True
isinstance(int,abc.Callable) # s输出 True
常见的可调用对象
- 函数(内置、自定义)、数据类型(int)、类中的函数
- 实现了
__call__方法的类
应用
pytorch基于nn.Module定义的网络实例为什么可以自动调用forward函数?
如上问题案例解析:
class MLP(nn.Module):
def __init__(self):
super(MLP,self).__init__()
self.hidden = nn.Linear(20, 256)
self.out = nn.Linear(256, 10)
def forward(self, x):
hidden_out = F.relu(self.hidden(x))
return self.out(hidden_out)
net = MLP()
x=torch.rand(2, 20)
print(net(x)) # 其会自动调用forward函数
原因:
-
所有的神经网络都是以
nn.Module为基类派生的 -
nn.Module类中定义了__call__方法,该方法调用了forward函数 -
定义神经网络必须要定义
forwad函数综上,实质上利用了python的语言特性。
nn.Module是可调用对象 & forward函数会被__init__调用。所以,当执行对象的实例net(x)的时候,底层会自动调用forward方法计算结果
参考

浙公网安备 33010602011771号