一、Pytorch简介和安装
1. 1 Pytorch的发展
PyTorch是一个由Facebook的人工智能研究团队开发的开源深度学习框架。在2016年发布后,PyTorch很快就因其易用性、灵活性和强大的功能而在科研社区中广受欢迎。
PyTorch的发展历程是一部充满创新和挑战的历史,它从一个科研项目发展成为了全球最流行的深度学习框架之一。在未来,我们有理由相信,PyTorch将会在深度学习领域继续发挥重要的作用。
1. 2 Pytorch的优点
PyTorch不仅是最受欢迎的深度学习框架之一,而且也是最强大的深度学习框架之一。它有许多独特的优点,使其在学术界和工业界都受到广泛的关注和使用。接下来我们就来详细地探讨一下PyTorch的优点。
1. 动态计算图
PyTorch最突出的优点之一就是它使用了动态计算图(Dynamic Computation Graphs,DCGs),与TensorFlow和其他框架使用的静态计算图不同。动态计算图允许你在运行时更改图的行为。这使得PyTorch非常灵活,在处理不确定性或复杂性时具有优势,因此非常适合研究和原型设计。
2. 易用性
PyTorch被设计成易于理解和使用。其API设计的直观性使得学习和使用PyTorch成为一件非常愉快的事情。此外,由于PyTorch与Python的深度集成,它在Python程序员中非常流行。
3. 易于调试
由于PyTorch的动态性和Python性质,调试PyTorch程序变得相当直接。你可以使用Python的标准调试工具,如PDB或PyCharm,直接查看每个操作的结果和中间变量的状态。
4. 强大的社区支持
PyTorch的社区非常活跃和支持。官方论坛、GitHub、Stack Overflow等平台上有大量的PyTorch用户和开发者,你可以从中找到大量的资源和帮助。
5. 广泛的预训练模型
PyTorch提供了大量的预训练模型,包括但不限于ResNet,VGG,Inception,SqueezeNet,EfficientNet等等。这些预训练模型可以帮助你快速开始新的项目。
6. 高效的GPU利用
PyTorch可以非常高效地利用NVIDIA的CUDA库来进行GPU计算。同时,它还支持分布式计算,让你可以在多个GPU或服务器上训练模型。
综上所述,PyTorch因其易用性、灵活性、丰富的功能以及强大的社区支持,在深度学习领域中备受欢迎。
1.3 Pytorch的主要使用场景
PyTorch的强大功能和灵活性使其在许多深度学习应用场景中都能够发挥重要作用。以下是PyTorch在各种应用中的一些典型用例:
1. 计算机视觉
在计算机视觉方面,PyTorch提供了许多预训练模型(如ResNet,VGG,Inception等)和工具(如TorchVision),可以用于图像分类、物体检测、语义分割和图像生成等任务。这些预训练模型和工具大大简化了开发计算机视觉应用的过程。
2. 自然语言处理
在自然语言处理(NLP)领域,PyTorch的动态计算图特性使得其非常适合处理变长输入,这对于许多NLP任务来说是非常重要的。同时,PyTorch也提供了一系列的NLP工具和预训练模型(如Transformer,BERT等),可以帮助我们处理文本分类、情感分析、命名实体识别、机器翻译和问答系统等任务。
3. 生成对抗网络
生成对抗网络(GANs)是一种强大的深度学习模型,被广泛应用于图像生成、图像到图像的转换、样式迁移和数据增强等任务。PyTorch的灵活性使得其非常适合开发和训练GAN模型。
4. 强化学习
强化学习是一种学习方法,其中智能体通过与环境的交互来学习如何执行任务。PyTorch的动态计算图和易于使用的API使得其在实现强化学习算法时表现出极高的效率。
5. 时序数据分析
在处理时序数据的任务中,如语音识别、时间序列预测等,PyTorch的动态计算图为处理可变长度的序列数据提供了便利。同时,PyTorch提供了包括RNN、LSTM、GRU在内的各种循环神经网络模型。
总的来说,PyTorch凭借其强大的功能和极高的灵活性,在许多深度学习的应用场景中都能够发挥重要作用。无论你是在研究新的深度学习模型,还是在开发实际的深度学习应用,PyTorch都能够提供强大的支持。
1.4 Pytorch的安装
可以参考如下博客: Pytorch的安装
二、张量
2.1 张量简介
张量(Tensor),是Pytorch中最基础的概念。Pytorch中的Tensor类似于Numpy中的ndarrays结构,同时 Tensors 可以使用GPU进行计算。简单来说,Tensor实际上就是一个多维数组(multidimensional array)。而Tensor的目的是能够创造更高维度的矩阵、向量。
那么,便有一个问题:张量与矩阵、向量、标量的关系是怎么样的?
简而言之:
标量(scalar)是一个标量表示一个0维的数据,没有方向。
向量(vector)是一个一维的数组,有一个方向,数据沿一个方向排列存放。
矩阵(matrix)是一个二维数组,如灰度图像。有行和列两个维度,分别对应图像的高和宽。无法表示RGB图像。
张量(tensor)是一个多维数组,它是标量、向量、矩阵的高维扩展。如RGB图像,第一个维度为图像的高,第二个维度为图像的宽,第三个维度为色彩通道。张量为一个多维数组。
它们之间的关系可以这样描述:标量是0阶张量,向量是一维张量,矩阵维二维张量,如下图所示:

(2)Tensor与Variable
PyTorch的早期版本中,可以使用torch.autograd.Variable类进行创建支持梯度计算和跟踪的张量,torch.autograd.Variable包含以下5个属性:data、grad、grad_fn、requires_grad、is_leaf。但目前来看,在较新的PyTorch版本一般直接使用 torch.Tensor,其在torch.autograd.Variable的基础上,又增加了dtype、shape、device三个属性。
Variable是torch.autograd中的数据类型,主要用于封装Tensor,进行求导。
Variable的5个属性:
data:被包装的Tensor
grad:data的梯度
grad_fn:创建Tensor的Function,是自动求导的关键
requires_grad:指示是否需要梯度
is_leaf: 指示是否是叶子结点(张量)
其关系可以如下表示:

(3)Tensor
在 PyTorch 0.4.0 之后,Variable 并入了 Tensor。在之后版本的 Tensor 中,除了具有上面 Variable 的 5 个属性,还有另外 3 个属性。
dtype:张量的数据类型,如,torch.FloatTensor,torch.cuda.FloatTensor
shape:张量的形状,如(64,3,256,256)->(batch_size,channel,height,width),这里的3指的是RGB通道数。
device:张量所在的设备,GPU/CPU,是加速计算的关键。
关系可以如下图表示:

2.2 张量的创建
2.2.1 直接创建
(1)通过torch.tensor()创建

-
data:数据,可以使list,numpy
-
dtype:数据类型,默认与data的一致
-
device:所在设备,cuda/cpu。device=‘cuda’
-
requires_grad:是否需要梯度
-
pin_memory: 是否存于锁页内存,通常设为false
测试代码
import torch
import numpy as np
# 通过torch.tensor创建张量
arr = np.ones((3, 3))
print("ndarray的数据类型:", arr.dtype)
t = torch.tensor(arr)
# 测试结果如下
print(t)
ndarray的数据类型: float64
tensor([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]], device='cuda:0', dtype=torch.float64)
(2) 通过torch.from_numpy(ndarray)创建
注意事项:从torch.from_numpy创建的tensor与原ndarray共享内存,当修改其中一个的数据,另一个也将会被改动。

测试代码
arr = np.array([[1, 2, 3], [4, 5, 6]])
t = torch.from_numpy(arr)
print(arr, '\n',t)
arr[0, 0] = 0
print('*' * 10)
print(arr, '\n',t)
t[1, 1] = 100
print('*' * 10)
print(arr, '\n',t)
# 结果:
[[1 2 3]
[4 5 6]]
tensor([[1, 2, 3],
[4, 5, 6]], dtype=torch.int32)
**********
[[0 2 3]
[4 5 6]]
tensor([[0, 2, 3],
[4, 5, 6]], dtype=torch.int32)
**********
[[ 0 2 3]
[ 4 100 6]]
tensor([[ 0, 2, 3],
[ 4, 100, 6]], dtype=torch.int32)
2.2.2 根据数值创建
(1)torch.zeros()和torch.ones_like()
功能:依size创建全0张量;根据input的维度创建全0张量

-
size:张量的形状,如(3,3)、(3,224,224)
-
out:表示输出张量,就是再把这个张量赋值给别的一个张量,但是这两个张量时一样的,指的同一个内存地址
-
layout: 内存中布局形式,有strided(默认),sparse_coo(稀疏张量设置)等,一般采用默认
-
device:所在设备,gpu/cpu
-
requires_grad:是否需要梯度
(2) torch.full()
功能:自定义数值张量

-
size:张量的形状,如(3,3)、(3,224,224)
-
ill_value:张量的值 i
(4)torch.arange()
功能:创建等差的一维张量
注意事项:数值区间为[start,end),因为区间是作弊又开的,所以取不到最后的值。

-
start:数列起始值
-
end:数列“结束值”
-
step:步长,数列公差,默认为1
(5)torch.linspace()
功能:创建均分的1维张量
注意事项:数值区间为[start,end],左闭右闭,能取到最后的值,这与arrange()方法是不一样的

-
start:数列起始值
-
end:数列“结束值”
-
step:步长,数列公差,默认为1
[步长计算:(end-start)/(steps - 1)],如[0,10]且步长为1,会生成一个长为11的张量。
2.2.3 依据概率分布创建张量
(1)torch.normal()
功能:生成正态分布(高斯分布)
-
mean:均值
-
std:标准差

四种模式:
mean为标量,std为标量
mean为标量,std为张量
mean为张量,std为标量
mean为张量,std为张量
注意事项:当mean和std均为标量时, 应设定size来规定张量的长度,分别各有两种取值,所以这里会有四种模式。
测试代码
# 第一种模式 - 均值是标量, 方差是标量 - 此时产生的是一个分布, 从这一个分部种抽样相应的个数,所以这个必须指定size,也就是抽取多少个数
t_normal = torch.normal(0, 1, size=(4,))
print(t_normal) # 来自同一个分布
# 第二种模式 - 均值是标量, 方差是张量 - 此时会根据方差的形状大小,产生同样多个分布,每一个分布的均值都是那个标量
std = torch.arange(1, 5, dtype=torch.float)
print(std.dtype)
t_normal2 = torch.normal(1, std)
print(t_normal2) # 也产生来四个数,但是这四个数分别来自四个不同的正态分布,这些分布均值相等
# 第三种模式 - 均值是张量,方差是标量 - 此时也会根据均值的形状大小,产生同样多个方差相同的分布,从这几个分布中分别取一个值作为结果
mean = torch.arange(1, 5, dtype=torch.float)
t_normal3 = torch.normal(mean, 1)
print(t_normal3) # 来自不同的分布,但分布里面方差相等
# 第四种模式 - 均值是张量, 方差是张量 - 此时需要均值的个数和方差的个数一样多,分别产生这么多个正太分布,从这里面抽取一个值
mean = torch.arange(1, 5, dtype=torch.float)
std = torch.arange(1, 5, dtype=torch.float)
t_normal4 = torch.normal(mean, std)
print(t_normal4) # 来自不同的分布,各自有自己的均值和方差
(2)torch.randn()
功能:生成标准正态分布

(3)其他方法

torch.randperm():

torch.bernoulli():

三、张量的操作与线性回归
3.1张量的操作
3.1.1张量拼接与拆分
(1)torch.cat()
功能:将张量按维度dim进行拼接

-
tensors:张量序列
-
dim:要拼接的维度:dim=0时表示按行拼接,dim=1时表示按列拼接
注意事项:.cat是在原来的基础上根据行和列,进行拼接, 浮点数类型拼接才可以,long类型拼接会报错
测试代码
# 张量的拼接
t = torch.ones((2, 3))
print(t)
t_0 = torch.cat([t, t], dim=0) # 行拼接
t_1 = torch.cat([t, t], dim=1) # 列拼接
print(t_0, t_0.shape)
print(t_1, t_1.shape)
(2)torch.stack()
功能:在新创建的维度dim上进行拼接

-
tensors:张量序列
-
dim:要拼接的维度
注意事项:.stack()会拓展维度,但是.cat()不会。简单地说.stack()新加了一个维度Z轴。
测试代码
# 张量的拼接(stack)
t = torch.ones((2, 3))
t_stack = torch.stack([t,t,t], dim=0)
print(t_stack)
print(t_stack.shape)
t_stack1 = torch.stack([t, t, t], dim=1)
print(t_stack1)
print(t_stack1.shape)
(3)torch.chunk()
功能:将张量按维度dim进行平均切分
返回值:张量列表
注意事项:若不能整除,最后一份张量小于其他张量

-
input:要切分的张量
-
chunks:要切分的份数
-
dim:要切分的维度(在哪个维度进行切分)
测试代码
a = torch.ones((2, 7)) # 7
list_of_tensors = torch.chunk(a, dim=1, chunks=3) # 按照第一个维度(即列)切成三块, 那么应该是(2,3), (2,3), (2,1) 如果最后一份不能够乘除则要少于其他张量。
print(list_of_tensors)
for idx, t in enumerate(list_of_tensors):
print("第{}个张量:{}, shape is {}".format(idx+1, t, t.shape))
(4)torch.split()
功能:将张量按维度dim进行切分(可以指定维度)
返回值:张量列表
注意事项:list元素的和必须等于指定维度上张量的长度。

-
tensor:要切分的张量
-
split_size_or_sections:为int时,表示每一份的长度;为list时,按list元素切分(和为当前维度的长度,否则报错)
-
dim:要切分的维度
测试代码
# split
t = torch.ones((2, 5))
list_of_tensors = torch.split(t, [2, 1, 2], dim=1) # [2 , 1, 2], list元素的和必须等于指定维度上张量的长度,即2+1+1=5。
for idx, t in enumerate(list_of_tensors):
print("第{}个张量:{}, shape is {}".format(idx+1, t, t.shape))
3.1.2 张量索引
(1)torch.index_select()
功能:按照索引查找,需要先指定一个tensor的索引量,然后指定类型是long的
返回值:依index索引数据拼接的张量

-
input:要索引的张量
-
dim:要索引的维度
-
index:要索引数据的序号,index的数据类型是long类型
测试代码
t = torch.randint(0, 9, size=(3, 3)) # 从0-8随机产生数组成3*3的矩阵
print(t)
idx = torch.tensor([0, 2], dtype=torch.long) # index的数据类型是long类型
t_select = torch.index_select(t, dim=1, index=idx) #第0列和第2列拼接返回
print(t_select)
(2)torch.masked_select()
功能:按照值的条件进行查找,需要先指定条件作为mask,一般用来筛选数据
返回值:一维张量(因为不能确定张量中True的个数)

-
input:要索引的张量
-
mask:与input同形状的布尔类型张量
测试代码
t = torch.randint(0, 9, size=(3, 3)) # 从0-8随机产生数组成3*3的矩阵
mask = t.ge(5) # le表示<=5, ge表示>=5 gt >5 lt <5
print("mask: \n", mask)
t_select1 = torch.masked_select(t, mask) # 选出t中大于5的元素
print(t_select1)
3.1.3张量变换
(1)torch.reshape()
功能: 变换张量形状
注意事项:当张量在内存中是连续时,新张量与input共享数据内存

-
input:要变换的张量
-
shape:新张量的形状,若维度为-1,则表示根据其他维度的大小自动计算
测试代码
# torch.reshape
t = torch.randperm(8) # randperm是随机排列的一个函数,生成0到n-1之间n个数的随机排列
print(t)
t_reshape = torch.reshape(t, (-1, 2, 2)) # -1表示自动计算该维度
print("t:{}\nt_reshape:\n{}".format(t, t_reshape))
t[0] = 1024
print("t:{}\nt_reshape:\n{}".format(t, t_reshape))
print("t.data 内存地址:{}".format(id(t.data)))
print("t_reshape.data 内存地址:{}".format(id(t_reshape.data))) # 这个注意一下,两个是共内存的
(2)torch.transpose()和torch.t()

功能:交换张量的两个维度,矩阵的转置和图像的预处理中常用。
torch.transpose()
-
input:要变换的张量
-
dim0:要交换的维度
-
dim1:要交换的维度

功能:二维张量转置,对于矩阵而言,等价于张量torch.transpose(input, 0, 1)
测试代码
# torch.transpose
t = torch.rand((2, 3, 4)) # 产生0-1之间的随机数
print(t)
t_transpose = torch.transpose(t, dim0=0, dim1=2) # c*h*w h*w*c, 这表示第0维和第2维进行交换
print("t shape:{}\nt_transpose shape: {}".format(t.shape, t_transpose.shape))
(3)torch.squeeze()和torch.unsqueeze()

功能:压缩长度为1的维度(轴)
- dim:若为None,移除所有长度为1的轴;若指定维度,当且仅当该轴长度为1时,可以被移除;

功能:依据dim扩展维度
- dim:扩展的维度
测试代码
# torch.squeeze
t = torch.rand((1, 2, 3, 1))
t_sq = torch.squeeze(t)
t_0 = torch.squeeze(t, dim=0)
t_1 = torch.squeeze(t, dim=1)
print(t.shape) # torch.Size([1, 2, 3, 1])
print(t_sq.shape) # torch.Size([2, 3])
print(t_0.shape) # torch.Size([2, 3, 1])
print(t_1.shape) # torch.Size([1, 2, 3, 1])
3.1.4张量数学运算
Pytorch中中的数学运算大致可以分为下图的三大类: 加减乘除, 对数指数幂函数,三角函数

torch.add()
功能:逐元素计算input+alpha*other

-
input:第一个张量
-
alpha:乘项因子
-
other:第二个张量
测试代码
# torch.add()
t_0 = torch.randn((3, 3))
t_1 = torch.ones_like(t_0)
t_add = torch.add(t_0, 10, t_1)
print("t_0:\n{}\nt_1:\n{}\nt_add_10:\n{}".format(t_0, t_1, t_add))
3.2线性回归(Linear Regression)


测试代码
# Linear Regression
import torch
im
# 随机生成X,Y
x = torch.rand(20, 1) * 10
y = 2 * x + (5 + torch.randn(20, 1))
# 构建线性回归函数的参数
w = torch.randn((1), requires_grad=True)
b = torch.zeros((1), requires_grad=True) # 因为w,b都是参数,所以都需要计算梯度
for iteration in range(100):
# 前向传播
wx = torch.mul(w, x) # mul表示矩阵乘法
y_pred = torch.add(wx, b) # y = wx + 1 * b(alpha默认为1)
# 计算loss
loss = (0.5 * (y-y_pred)**2).mean() # 系数1/1是为了求导时计算的简洁
# 反向传播
loss.backward()
# 更新参数
b.data.sub_(lr * b.grad) # 相当于-=
w.data.sub_(lr * w.grad)
# 梯度清零
w.grad.data.zero_()
b.grad.data.zero_()
print(w.data, b.data)
四、autograd与回归算法
4.1 autograd——自动求导系统
PyTorch 中所有神经网络的核心是 autograd包。 autograd包为张量上的所有操作提供了自动求导。 它是一个在运行时定义的框架,这意味着反向传播是根据代码来确定如何运行,并且每次迭代可以是不同的。
4.1.1 torch.autograd.backward()
功能:自动求取梯度

-
tensors:用于求导的张量,如loss
-
retain_graph:保存计算图, 由于Pytorch采用了动态图机制,在每一次反向传播结束之后,计算图都会被释放掉。如果我们不想被释放,就要设置这个参数为True
-
create_graph:表示创建导数计算图,用于高阶求导。
-
grad_tensors:表示多梯度权重。如果有多个loss需要计算梯度的时候,就要设置这些loss的权重比例。
测试代码
grad_tensors = torch.tensor([1., 1.])
loss.backward(gradient=grad_tensors)
print(w.grad) # 这时候会是tensor([7.]) 5+2
grad_tensors = torch.tensor([1., 2.])
loss.backward(gradient=grad_tensors)
print(w.grad) # 这时候会是tensor([9.]) 5+2*2
4.1.2 torch.autograd.grad
功能:求取梯度

-
outputs:用于求导的张量,如loss
-
inputs:需要梯度的张量
-
create_graph:创建导数计算图,用于高阶求导
-
retain_graph:保存计算图
-
grad_outputs:多梯度权重
注意事项:
- 梯度不自动清零(梯度会叠加,需手动清零,w.grad.zero_(),zero_的下划线表示原位操作)
- 依赖于叶子结点的结点,requires_grad默认为True
- 叶子结点不可执行in-place,这是因为反向传播时还需要用到叶子结点的数据,故叶子结点不能改变
4.1.2 前向传播、反向传播和计算图
以简单的深度神经网络为例,为了完成loss的优化,需要不断以mini-batch的数据送入模型网络中进行迭代过程,最终优化网络达到收敛:
- 1.mini-batch送入网络进行前向传播后输出的预测值,同真实值(label)对比后用loss函数计算出此次迭代的loss
- 2.loss进行反向传播,送入神经网络模型中之前的每一层,以更新weight矩阵和bias
也就是说模型训练的重点过程可以总结为两点:前向传播和反向传播,而其中前向传播就是矩阵+激活函数组合运算;而反向传播可以理解为稍显复杂的矩阵计算。
在Pytorch中,反向传播的计算依赖于autograd自动微分机制(这里说的自动微分,即指求导/梯度)。而autograd实现的基础,有以下两个部分:
- 1.数学基础——链式求导法则和雅克比矩阵
- 2.底层结构基础——由Tensor张量为基础构成的计算图模型
在pytorch中,底层结构是由tensor组成的计算图,虽然框架代码在实际autograd自动求梯度的过程中,并没有显示地构造和展示出计算图,不过其计算路径确实是沿着计算图的路径来进行的。
** 计算图**,即用图的方式来表示计算过程。如下是使用numpy的示例:
numpy表示
# numpy
import numpy as np
np.random.seed(0)
N, D = 3, 4
x = np.random.randn(N, D)
y = np.random.randn(N, D)
z = np.random.randn(N, D)
a = x * y
b = a + z
c = np.sum(b)
上述过程的计算图可以如下表示:

如上,蓝绿色的一个个节点构成了一个计算图,节点里的内容是变量或者计算符,这就是一个简单的计算图。同样的计算过程,也可以用pytorch中的tensor来表示:
python表示
import torch
x = torch.randn(N, D, requires_grad=True)
y = torch.randn(N, D)
z = torch.randn(N, D)
a = x * y
b = a + z
c = torch.sum(b)
如果用numpy表示,为了求出所有元素的梯度,需要以下几步:
grad_c = 1.0
grad_b = grad_c * np.ones((N, D))
grad_a = grad_b.copy()
grad_z = grad_b.copy()
grad_x = grad_a * y
grad_y = grad_a * x
而这只是一个很简单的情形,当面对深层神经网络以及复杂度较大的计算需求时候,框架的好处便能体现。基于计算图的数据结构使得pytorch可以应对复杂的神经网络,能方便地利用autograd机制来自动求导,只需一个.backward()即可自动求出标量对所有变量的梯度,并将梯度值存在各个变量tensor节点中,只需.grad便可读取:
c.backward()
print(x.grad)
4.1.3.链式法则和雅克比矩阵
(1)神经网络中的链式法则
链式法则是微积分中的求导法则,用于求一个复合函数的导数,是在微积分的求导运算中一种常用的方法,类似地也能用于神经网络的计算中。
下面以一个简单的神经网络模型为例:
一个神经网络中有5个神经元 a , b , c , d , L a,b,c,d,L a,b,c,d,L;其中 w 1 w 4 w_{1}~w_{4} w1 w4为权重矩阵, L L L为输出。满足以下计算关系:
b = w 1 ∗ a c = w 2 ∗ a d = w 3 ∗ b + w 4 ∗ c L = 10 − d b=w_{1}∗a \\ c=w_{2}∗a \\ d=w_{3}∗b+w_{4}∗c\\ L=10−d b=w1∗ac=w2∗ad=w3∗b+w4∗cL=10−d
组成的前向计算图如下:图片来源

在pytorch的神经网络模型中,通过反向传播来更新weight和bias的梯度时,计算过程就类似如下的计算图:图片来源

通过雅克比矩阵,即可表示所有L对所有权重的偏导:
J = [ ∂ L ∂ w 1 , ∂ L ∂ w 2 , ∂ L ∂ w 3 , ∂ L ∂ w 4 ] J=[\frac{\partial L}{\partial w_1},\frac{\partial L}{\partial w_2},\frac{\partial L}{\partial w_3},\frac{\partial L}{\partial w_4}] J=[∂w1∂L,∂w2

本文介绍了Pytorch深度学习框架,包括其发展、优点、使用场景和安装方法。详细讲解了张量的概念、创建方式、操作,以及线性回归和softmax回归模型。还阐述了autograd自动求导系统,包括前向传播、反向传播和动态计算图原理,帮助读者掌握Pytorch基础应用。
最低0.47元/天 解锁文章
2万+






