深度学习——基础(基于pytorch代码)(代码片段)

颜妮儿 颜妮儿     2023-04-07     433

关键词:

写在前面:该系列文章大都摘自于李沐的《动手学深度学习》,一边学习一边记录和实践,希望自己可以有收获🧐

文章目录

预备知识

数据操作

Pytorch中对数据(tensor 张量)的操作类似于Numpy(ndarray),但由于深度学习大都是用GPU进行计算,而Numpy仅支持CPU计算,且Pytorch的张量类支持自动微分,所以我们有必要去了解如何使用Pytorch的张量。
生成张量

import torch
# 生成一个包含0~11的向量
x = torch.arange(12) #tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
# 获取每个维度的大小
x.shape # torch.Size([12])
# 获取张量中的元素总个数
x.numel() # 12

# 修改张量的形状,当确定行数或列数时,另一个值可以设为-1.会自动调整大小
X = x.reshape(3, 4)
# tensor([[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11]])

# 定义一个形状为(2,3,4),元素全为0的张量
torch.zeros((2, 3, 4))
# tensor([[[0., 0., 0., 0.],
#          [0., 0., 0., 0.],
#          [0., 0., 0., 0.]],
#
#         [[0., 0., 0., 0.],
#          [0., 0., 0., 0.],
#          [0., 0., 0., 0.]]])

# 定义一个形状为(2,3,4),元素全为1的张量
torch.ones((2, 3, 4))
# tensor([[[1., 1., 1., 1.],
#          [1., 1., 1., 1.],
#          [1., 1., 1., 1.]],
#
#         [[1., 1., 1., 1.],
#          [1., 1., 1., 1.],
#          [1., 1., 1., 1.]]])

# 定义一个形状为(3,4),元素是随机值,但服从标准正态分布
torch.randn(3, 4)
# tensor([[-1.0778, -0.7014, -0.1674, -0.8677],
#         [-0.4078, -1.1577,  1.0313,  0.7065],
#         [ 0.2872, -1.0250,  0.0995,  0.9716]])

张量的基本运算

# 对应位置的元素进行运算操作
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y  
# (tensor([ 3.,  4.,  6., 10.]),
#  tensor([-1.,  0.,  2.,  6.]),
#  tensor([ 2.,  4.,  8., 16.]),
#  tensor([0.5000, 1.0000, 2.0000, 4.0000]),
#  tensor([ 1.,  4., 16., 64.]))

# 求幂运算
torch.exp(x)
# tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])

# 判断两个张量对应位置的元素是否相等
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
X == Y
# tensor([[False,  True, False,  True],
#         [False, False, False, False],
#         [False, False, False, False]])

# 对张量中的元素值求和
X = torch.arange(12, dtype=torch.float32).reshape((3,4))
X.sum()
# tensor(66.)

张量的拼接
Python中,0轴表示行,1轴表示列。

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
#  dim=0 表示对行进行操作(行数改变);dim=1 表示对列进行操作(列数改变)
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
# (tensor([[ 0.,  1.,  2.,  3.],
#          [ 4.,  5.,  6.,  7.],
#          [ 8.,  9., 10., 11.],
#          [ 2.,  1.,  4.,  3.],
#          [ 1.,  2.,  3.,  4.],
#          [ 4.,  3.,  2.,  1.]]),
#  tensor([[ 0.,  1.,  2.,  3.,  2.,  1.,  4.,  3.],
#          [ 4.,  5.,  6.,  7.,  1.,  2.,  3.,  4.],
#          [ 8.,  9., 10., 11.,  4.,  3.,  2.,  1.]]))

广播机制
两个形状不同的张量通过广播机制按元素执行操作,该机制的工作方式为:

  1. 通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
  2. 对生成的数组执行按元素操作。
a = torch.arange(3).reshape((3, 1))
# tensor([[0],
#          [1],
#          [2]])
b = torch.arange(2).reshape((1, 2))
# tensor([[0, 1]])
a+b
# tensor([[0, 1],
#         [1, 2],
#         [2, 3]])

# a和b分别扩展为:
# a: tensor([[0, 0],
#           [1, 1],
#           [2, 2]])
# b: tensor([[0, 1],
#           [0, 1],
#           [0, 1]])

索引和切片

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
# 最后一行
X[-1]
# tensor([ 8.,  9., 10., 11.])
# 第2和第三行
X[1:3]
# tensor([[ 4.,  5.,  6.,  7.],
#          [ 8.,  9., 10., 11.]])

# 指定索引修改元素
X[1, 2] = 9
# tensor([[ 0.,  1.,  2.,  3.],
#         [ 4.,  5.,  9.,  7.],
#         [ 8.,  9., 10., 11.]])

# 修改第一行和第二行的元素为12
X[0:2, :] = 12
# tensor([[12., 12., 12., 12.],
#         [12., 12., 12., 12.],
#         [ 8.,  9., 10., 11.]])

节省内存
当对张量进行运算时,可能会为新结果分配内存,例如:

before = id(Y)
Y = Y + X
id(Y) == before # False

我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如:

Z = torch.zeros_like(Y)
print('id(Z):', id(Z)) # id(Z): 2675870772256
Z[:] = X + Y
print('id(Z):', id(Z)) # id(Z): 2675870772256

如果在后续计算中没有重复使用X, 我们也可以使用X[:] = X + Y或X += Y来减少操作的内存开销,例如:

before = id(X)
X += Y
id(X) == before # True

线性代数
矩阵及其转置

A = torch.arange(20).reshape(5, 4)
# tensor([[ 0,  1,  2,  3],
#         [ 4,  5,  6,  7],
#         [ 8,  9, 10, 11],
#         [12, 13, 14, 15],
#         [16, 17, 18, 19]])

# 转置
A.T
# tensor([[ 0,  4,  8, 12, 16],
#         [ 1,  5,  9, 13, 17],
#         [ 2,  6, 10, 14, 18],
#         [ 3,  7, 11, 15, 19]])

张量算法的基本性质

A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
# tensor([[ 0.,  1.,  2.,  3.],
#          [ 4.,  5.,  6.,  7.],
#          [ 8.,  9., 10., 11.],
#          [12., 13., 14., 15.],
#          [16., 17., 18., 19.]])
B = A.clone()  # 通过分配新内存,将A的一个副本分配给B

A + B
# tensor([[ 0.,  2.,  4.,  6.],
#          [ 8., 10., 12., 14.],
#          [16., 18., 20., 22.],
#          [24., 26., 28., 30.],
#          [32., 34., 36., 38.]])

# 两个矩阵的对应元素相乘,Hadamard积
A * B
# tensor([[  0.,   1.,   4.,   9.],
#         [ 16.,  25.,  36.,  49.],
#         [ 64.,  81., 100., 121.],
#         [144., 169., 196., 225.],
#         [256., 289., 324., 361.]])

# 矩阵乘法
B = torch.ones(4, 3)
torch.mm(A, B)
# tensor([[ 6.,  6.,  6.],
#         [22., 22., 22.],
#         [38., 38., 38.],
#         [54., 54., 54.],
#         [70., 70., 70.]])

# 将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘。
X = torch.arange(24).reshape(2, 3, 4)
# tensor([[[ 0,  1,  2,  3],
#          [ 4,  5,  6,  7],
#          [ 8,  9, 10, 11]],
#
#         [[12, 13, 14, 15],
#          [16, 17, 18, 19],
#          [20, 21, 22, 23]]])

a = 2
a + X
# tensor([[[ 2,  3,  4,  5],
#           [ 6,  7,  8,  9],
#           [10, 11, 12, 13]],
#
#          [[14, 15, 16, 17],
#           [18, 19, 20, 21],
#           [22, 23, 24, 25]]])

(a * X).shape
# torch.Size([2, 3, 4]))

降维

# 求和
x = torch.arange(4, dtype=torch.float32)
x.sum()
# tensor(6.)

A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
A.sum()
# tensor(190.)

# axis=0对行进行操作,及求每列的和
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
A_sum_axis0=A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
# (tensor([40., 45., 50., 55.]), torch.Size([4]))  一维结果

# axis=1对列进行操作,及求每行的和
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
A_sum_axis1=A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape
# (tensor([ 6., 22., 38., 54., 70.]), torch.Size([5])) 一维结果


# 求均值
# 获取每行的均值
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
# (tensor([ 8.,  9., 10., 11.]), tensor([ 8.,  9., 10., 11.]))  一维结果

非降维求和

A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
sum_A = A.sum(axis=1, keepdims=True)
# 仍是二维
# tensor([[ 6.],
#         [22.],
#         [38.],
#         [54.],
#         [70.]]) 

# 求每行元素除以各行均值:
A / sum_A
# tensor([[0.0000, 0.1667, 0.3333, 0.5000],
#         [0.1818, 0.2273, 0.2727, 0.3182],
#         [0.2105, 0.2368, 0.2632, 0.2895],
#         [0.2222, 0.2407, 0.2593, 0.2778],
#         [0.2286, 0.2429, 0.2571, 0.2714]])

如果我们想沿某个轴计算A元素的累积总和, 比如axis=0(按行计算),可以调用cumsum函数。 此函数不会沿任何轴降低输入张量的维度。

A.cumsum(axis=0)
# tensor([[ 0.,  1.,  2.,  3.],
#         [ 4.,  6.,  8., 10.],
#         [12., 15., 18., 21.],
#         [24., 28., 32., 36.],
#         [40., 45., 50., 55.]]) 

在原始数据之后添加一行计算结果。相应地,如果是按列进行计算,则在添加为列。

自动微分

深度学习框架通过自动计算导数,即自动微分(automatic differentiation)来加快求导。实际中,根据设计好的模型,系统会构建一个计算图(computational graph), 来跟踪计算是哪些数据通过哪些操作组合起来产生输出。 自动微分使系统能够随后反向传播梯度。 这里,反向传播(backpropagate)意味着跟踪整个计算图,填充关于每个参数的偏导数。
例如,求函数 y = 2 x T x y = 2\\mathbfx^\\mathrmT\\mathbfx y=2xTx关于 x \\mathbfx x求导,其中 x \\mathbfx x的初始值为 [ 0 , 1 , 2 , 3 ] [0, 1, 2, 3] [0,1,2,3]

import torch
x = torch.arange(4.0)
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
print(x.grad) # 输出默认值为None

现计算 y y y

y = 2 * torch.dot(x, x)
# tensor(28., grad_fn=<MulBackward0>)

接下来,通过调用反向传播函数来自动计算y关于x每个分量的梯度,并打印这些梯度:

y.backward()
x.grad # tensor([ 0.,  4.,  8., 12.])

函数 y = 2 x T x y = 2\\mathbfx^\\mathrmT\\mathbfx y=2xTx的梯度应为 4 x 4\\mathbfx 4x,验证梯度是否正确:

x.grad == 4 * x
# tensor([True, True, True, True])

非标量变量的反向传播
(我对这里不太理解)
当y不是标量时,向量y关于向量x的导数的最自然解释是一个矩阵。 对于高阶和高维的y和x,求导的结果可以是一个高阶张量。
然而,虽然这些更奇特的对象确实出现在高级机器学习中(包括深度学习中), 但当调用向量的反向计算时,我们通常会试图计算一批训练样本中每个组成部分的损失函数的导数。这里,我们的目的不是计算微分矩阵,而是单独计算批量中每个样本的偏导数之和。

# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad # tensor([0., 2., 4., 6.])

Python控制流的梯度计算
使用自动微分的一个好处是: 即使构建函数的计算图需要通过Python控制流(例如,条件、循环或任意函数调用),我们仍然可以计算得到的变量的梯度。在下面的代码中,while循环的迭代次数和if语句的结果都取决于输入a的值。

# c=k*a.k的值取决于a的输入
def f(a):
    b = a * 2
    while b.norm() < 1000:
        b = b * 2
    if b.sum() > 0:
        c = b
    else:
        c = 100 * b
    return c
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
a.grad == d / a # tensor(True)

深度学习计算

层和块

自定义块
块(block)可以描述单个层、由多个层组成的组件或整个模型本身。 使用块进行抽象的一个好处是可以将一些块组合成更大的组件。通过定义代码来按需生成任意复杂度的块, 我们可以通过简洁的代码实现复杂的神经网络。如图,右边的网络由4个块组成:

从编程的角度来看,块由类(class)表示。 它的任何子类都必须定义一个将其输入转换为输出的前向传播函数, 并且必须存储任何必需的参数。 注意,有些块不需要任何参数。 最后,为了计算梯度,块必须具有反向传播函数。在定义我们自己的块时,由于自动微分提供了一些后端实现,我们只需要考虑前向传播函数和必需的参数。
每个块的基本功能:

  1. 将输入数据作为其前向传播函数的参数。
  2. 通过前向传播函数来生成输出。请注意,输出的形状可能与输入的形状不同。
  3. 计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的。
  4. 存储和访问前向传播计算所需的参数。
  5. 根据需要初始化模型参数。
    构造一个块,其有256个隐藏单元的隐藏层和一个10个输出节点的输出层:
import torch
from torch import nn
from torch.nn import functional as F

class MLP(nn.Module):
    # 用模型参数声明层。这里,我们声明两个全连接的层
    def __init__(self):
        # 调用MLP的父类Module的构造函数来执行必要的初始化。
        # 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
        super().__init__()
        self.hidden = nn.Linear(20, 256)  # 隐藏层
        self.out = nn.Linear(256, 10)  # 输出层

    # 定义模型的前向传播,即如何根据输入X返回所需的模型输出
    def forward(self, X):
        # 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
        # 运算顺序为 A = W_1 * X, B = relu(B), C = w_2 * B
        # W_1为20*256的参数,w_2为 256*10的参数
        return self.out(F.relu(self.hidden(X)))
X = torch.rand(2, 20)
net = MLP()
net(X)
# tensor([[ 0.0718, -0.0758, -0.1922,  0.0293,  0.2414,  0.0770, -0.1361, -0.0221,
#           0.0587,  0.0078],
#         [ 0.0478, -0.0155, -0.0269,  0.0704,  0.1355,  0.2137, -0.1533,  0.0172,
#           0.2437,  0.0926]], grad_fn=<AddmmBackward0>)

顺序快
顺序块的作用:将多个模块串起来。
实现的两种方式:

  1. 一种将块逐个追加到列表中的函数;
  2. 一种前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。
    定义顺序块:
class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for idx, module in enumerate(args):
            # 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员
            # 变量_modules中。_module的类型是OrderedDict
            self._modules[str(idx)] = module

    def forward(self, X):
        # OrderedDict保证了按照成员添加的顺序遍历它们
        for block in self._modules.values():
            X = block(X)
        return X

__init__函数将每个模块逐个添加到有序字典_modules中,通过这样,在模块的参数初始化过程中, 系统知道在_modules字典中查找需要初始化参数的子块。
调用:

X = torch.rand(2, 20)
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)
# tensor([[-0.0622, -0.2874, -0.0551,  0.0102,  0.0808, -0.0382, -0.1675, -0.2091,
#          -0.1044,  0.2538],
#         [-0.0761, -0.3218, -0.1482, -0.0384, -0.0085,  0.1346, -0.1855, -0.1458,
#          -0.1240,  0.2864]], grad_fn=<AddmmBackward0>)

在前向传播函数中执行代码
Sequential类使模型构造变得简单, 允许我们组合新的架构,而不必定义自己的类。 然而,并不是所有的架构都是简单的顺序架构。 当需要更强的灵活性时,我们需要定义自己的块。另外,我们可能希望执行任意的数学运算, 而不是简单地依赖预定义的神经网络层。
到目前为止, 我们网络中的所有操作都对网络的激活值及网络的参数起作用。 然而,有时我们可能希望合并既不是上一层的结果也不是可更新参数的项, 我们称之为常数参数(constant parameter)。 例如,我们需要一个计算函数 f ( x , w ) = c ⋅ w T x f(\\mathbfx,\\mathbfw) = c \\cdot \\mathbfw^\\mathrmT\\mathbfx f(x,w)=cwTx的层, 其中是 x \\mathbfx x输入, w \\mathbfw w是参数, c c c是某个在优化过程中没有更新的指定常量。 则有:

class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X查看详情  

深度学习——pytorch基础(代码片段)

深度学习(1)——Pytorch基础作者:夏风喃喃参考:《动手学深度学习第二版》李沐文章目录深度学习(1)——Pytorch基础一.数据操作二.数据预处理三.绘图四.自动求导五.概率论导入相关包:importtorch ... 查看详情

「深度学习一遍过」必修18:基于pytorch的语义分割模型实现(代码片段)

...型基础上实现膨胀卷积 1自定义5层普通卷积 模型结构 pytorch代码 fromtor 查看详情

pytorch学习笔记3.深度学习基础(代码片段)

...多分类22.全连接层23.激活函数与GPU加速24.测试根据龙良曲Pytorch学习视频整理,视频链接:【计算机-AI】PyTorch学这个就够了!(好课推荐)深度学习与PyTorch入门实战——主讲人龙 查看详情

基于深度学习的语义分割初探fcn以及pytorch代码实现(代码片段)

基于深度学习的语义分割初探FCN以及pytorch代码实现FCN论文论文地址:https://arxiv.org/abs/1411.4038FCN是基于深度学习方法的第一篇关于语义分割的开山之作,虽然这篇文章的分割结果现在看起来并不是目前最好的,但其意... 查看详情

学习笔记:深度学习——基于pytorch的bert应用实践(代码片段)

学习时间:2022.04.26~2022.04.30文章目录7.基于PyTorch的BERT应用实践7.1工具选取7.2文本预处理7.3使用BERT模型7.3.1数据输入及应用预处理7.3.2提取词向量7.3.3网络建模7.3.4参数准备7.3.5模型训练7.基于PyTorch的BERT应用实践本节着重于将BER... 查看详情

深度学习---从入门到放弃pytorch基础(代码片段)

深度学习—从入门到放弃(一)pytorchTensor类似于numpy的array,pandas的dataframe;在pytorch里的数据结构是tensor,即张量tensor简单操作1.Flattenandreshape###Originalz:tensor([[0,1],[2,3],[4,5],[6,7],[8,9],[10,11]])Flatte 查看详情

深度学习及pytorch基础

【任务一】视频学习心得及问题总结根据下面三个视频的学习内容,写一个总结,最后列出没有学明白的问题。【任务二】代码练习在谷歌Colab上完成代码练习中的2.1、2.2、2.3、2.4节,关键步骤截图,并附一些自己的想法和解读... 查看详情

深度学习基础:4.pytorch搭建基础网络模型(代码片段)

...始,巩固基础才能看懂复杂代码。Torch基本架构使用Pytorch之前,首先要理清楚Pytorch基本架构。Pytorch的核心库是torch,根据不同领域细分可以分成计算机视觉、自然语言处理和语音处理,这三个领域分别有自己对应... 查看详情

pytorch深度学习实践入门01(代码片段)

文章目录基于PyTorch的两层神经网络一、基于numpy的两层神经网络实现:二、基于PyTorch的两层神经网络实现:三、使用nn库实现两层神经网络四、自定义nnModules实现两层神经网络总结基于PyTorch的两层神经网络提示:在... 查看详情

基于pytorch搭建gru模型实现风速时间序列预测(代码片段)

前言👑最近很多订阅了🔥《深度学习100例》🔥的用户私信咨询基于深度学习实现时间序列的相关问题,为了能更清晰的说明,所以建立了本专栏专门记录基于深度学习的时间序列预测方法,帮助广大零基... 查看详情

「深度学习一遍过」必修17:基于pytorch细粒度分类实战(代码片段)

本专栏用于记录关于深度学习的笔记,不光方便自己复习与查阅,同时也希望能给您解决一些关于深度学习的相关问题,并提供一些微不足道的人工神经网络模型设计思路。专栏地址:「深度学习一遍过」必修篇... 查看详情

「深度学习一遍过」必修14:基于pytorch研究深度可分离卷积与正常卷积的性能差异(代码片段)

本专栏用于记录关于深度学习的笔记,不光方便自己复习与查阅,同时也希望能给您解决一些关于深度学习的相关问题,并提供一些微不足道的人工神经网络模型设计思路。专栏地址:「深度学习一遍过」必修篇... 查看详情

(翻译)60分钟入门深度学习工具-pytorch(代码片段)

60分钟入门深度学习工具-PyTorch作者:SoumithChintala原文翻译自:一、Pytorch是什么?二、AUTOGRAD三、神经网络四、训练一个分类器五、数据并行他是一个基于Python的科学计算包,目标用户有两类为了使用GPU来替代numpy一个深度学习研... 查看详情

深度学习基础--多层感知机(mlp)(代码片段)

...层感知机(MLP)最近在阅读一本书籍–Dive-into-DL-Pytorch(动手学深度学习),链接:https://github.com/newmonkey/Dive-into-DL-PyTorch,自身觉得受益匪浅,在此记录下自己的学习历程。本篇主要记录关于多层... 查看详情

深度强化学习policygradients模型解析,附pytorch完整代码(代码片段)

大家好,今天和各位分享一下基于策略的深度强化学习方法,策略梯度法是对策略进行建模,然后通过梯度上升更新策略网络的参数。我们使用了OpenAI的gym库,基于策略梯度法完成了一个小游戏。完整代码可以从... 查看详情

狂肝两万字带你用pytorch搞深度学习!!!(代码片段)

深度学习基础知识和各种网络结构实战...狂肝两万字带你用pytorch搞深度学习!!!深度学习前言一、基本数据:Tensor1.1Tensor的创建1.2torch.FloatTensor1.3torch.IntTensor1.4torch.randn1.5torch.range1.6torch.zeros/ones/empty二、Tenso 查看详情

自然语言处理pytorch基础入门(必备基础知识)(代码片段)

PyTorch基础实践PyTorch基础安装PyTorch创建张量张量类型和大小张量操作索引,切片和连接张量和计算图CUDA张量练习Solutions总结PyTorch基础在本书中,我们广泛地使用PyTorch来实现我们的深度学习模型。PyTorch是一个开源、社区... 查看详情