当前位置:人工智能 > 使用PyTorch构建卷积神经网络

使用PyTorch构建卷积神经网络

  • 发布:2023-10-07 09:57

使用PyTorch构建卷积神经网络

本文是加拿大麦吉尔大学本科生、滑铁卢大学硕士林毅在实习期间所做的工作。发帖和分享主要针对PyTorch的初学者。

本文为PyTorch基础教程,适合有一定机器学习、深度学习和神经网络理论基础,接触过卷积神经网络,还没有使用PyTorch搭建神经网络的同学。本文将分为以下几个部分:

  1. 基础卷积知识
  2. PyTorch 基础教程
  3. 使用 Pytorch 构建 CNN
  4. 优化CNN模型

0。 图像卷积基础知识

这部分参考了MIT的卷积图像课程,讲得很清楚。

图像卷积是一种处理图像的方式。首先,存储一张M×N像素的图像,也是一个M×N矩阵。卷积过程是利用卷积核(卷积核),通常是K×K矩阵,对原始图像的每个像素进行卷积计算,得到新的M×N图像。这个卷积过程可以对原始图像进行不同的操作:模糊、锐化、描边等,而这些操作都是由卷积核的属性决定的(这一点我们不会深入研究,有兴趣的读者可以直接观看视频)。我们用一个简单的例子来解释一下卷积的具体计算方法。

如图所示,这是一个3×3的卷积核,矩阵的每个元素都是1/9。在这一步计算中,原始图像的像素对应的卷积核的中心点就是我们要计算的目标。像素周围3×3范围内的每个元素都乘以卷积核位置对应的值(这里都是1/9)。这一步又称为元素相乘(矩阵元素相乘),最终得到9。乘积之和得到新图像中像素的值。卷积核扫完所有像素后得到的新图像如下

也就是说,卷积核在计算新像素时会考虑像素周围K×K范围内的信息。如何处理这些信息是由卷积核决定的。在上面的例子中,卷积核的每个值都是1/9并且均匀分布,所以我们会得到一个比原始图像更模糊的图像。

在图神经网络CNN中,CNN会学习一个或多个卷积核,以最大化图像中捕获的信息

1。 Pytroch 基础知识

1.1 安装 PyTorch

本节主要介绍如何使用conda安装PyTorch。 PyTorch官网也推荐Anaconda作为管理Python包的工具,因为它的兼容性可以说非常优秀。

先决条件

  • Python 3.0 +
  • Anaconda 已安装
  • 步骤

    1. 首先登录Pytorch官网:https://www.sychzs.cn/

    2. 找到 安装 PyTorch 的一部分

    1. 安装设置仅供参考:
  • PyTorch 构建:选择 稳定 (1.x.1)
  • 您的操作系统:根据读者的操作系统,笔者选择Linux系统作为参考
  • 包装:推荐Anaconda
  • 语言:根据语言环境,笔者选择Python。本文剩余部分将基于Python语言进行讲解
  • 计算平台:PyTorch支持GPU上计算:如果没有GPU支持,则选择CPU;如果虚拟机/服务器有 GPU 支持,则选择 CUDA (10 ,
    1. 最后复制运行此命令中的命令即可运行。
    conda 安装 pytorch torchvision torchaudio cudatoolkit=10.2 -c pytorch
    

    作者还会介绍如何判断平台当前是否正在使用CPU/GPU进行计算。

    1.2 张量

    现在让我们开始使用 PyTorch。首先介绍的是Pytorch中最重要的数据结构:Tensors。 PyTorch中所有的模型输入、输出和模型参数都以Tensor

    的形式存储

    Tensors 实际上与 NumPy 中的 numpy.array 非常相似,但最大的区别在于 tensor 可以在 GPU 或其他硬件加速器上计算。此外,tensor还专门针对PyTorch模块之一自动微分进行了优化。如果读者之前使用过NumPy,那么整个Tensors系统上手并不困难!

    进口火炬
    将 numpy 导入为 np
    

    1.2.1 查看计算平台

    如前所述,PyTorch 支持在 GPU 上进行计算。那么如何检查GPU是否可用呢?我们只需要执行以下命令:

    device = torch.device("cuda:0" if www.sychzs.cn_available() else "cpu")
    打印(设备)
    

    出:

    CPU
    

    说明作者的虚拟机不支持或者没有cuda进行计算。如果输出为cuda:0,则可以使用GPU进行计算。下面的文章将教你如何将变量迁移到cuda。

    1.2.2 创建张量

    除了直接从数据创建Tensor之外,还可以与numpy.array

    进行相互转换
    数据 = [[1, 2], [3, 4]]
    x_tensor = torch.tensor(数据)
    
    np_arr = np.array(数据)
    x_from_np = torch.from_numpy(np_arr) # numpy --> 张量
    x_from_tensor = x_data.numpy() # 张量 --> numpy
    

    同时,tensor也有很多类似于numpy.array的属性:

    print(f"张量形状:{x_tensor.shape}")
    print(f"张量的数据类型:{x_tensor.dtype}")
    print(f"设备张量存储在:{x_tensor.device}")
    

    出:

    张量形状:torch.Size([2, 2])
    张量的数据类型:www.sychzs.cn64
    设备张量存储在:cpu
    

    1.2.3 张量运算

    相信大家从上面可以看出,numpy.arraytensor在很多指令上都高度相似,比如分割、数学运算等,作者就不介绍了这里一一介绍Tensor的一些操作说明。您可以参考此链接了解详细信息。这里我只讲解上面提到的Tensor迁移到GPU的方法,这个方法比较重要。

    所有 Tensors 将默认在 CPU 上创建。如果要使用GPU计算Tensor,必须使用.to()命令将Tensor迁移到GPU:

    # 如果可用,我们将张量移至 GPU
    如果 www.sychzs.cn_available():
    x_tensor = x_www.sychzs.cn('cuda')
    

    或者在创建Tensor时使用之前声明的device变量来完成迁移:

    x = torch.tensor(data).to(device)
    

    1.3 数据集和数据加载器

    讲完Tensor的一些基本介绍,我们来看看PyTorch有哪些处理数据集的模块。当我们在做深度学习项目时,通常希望将数据集的处理部分和训练部分分开。这样我们的代码就可以具有高可读性和高可修改性。

    PyTorch 提供了两个非常优秀的数据模块:torch.utils.data.Datasettorch.utils.data.Dataloader。它们在PyTorch深度学习过程中的作用如下:我们使用Dataset来定制数据集,然后将其传递给Dataloader来迭代数据集并将其提供给模型为了训练。

    进口火炬
    从 www.sychzs.cn 导入数据集,DataLoader
    从 torchvision 导入数据集
    从 torchvision.transforms 导入 ToTensor将 matplotlib.pyplot 导入为 plt
    

    1.3.1 下载初步数据

    PyTorch 准备了很多数据供我们使用和玩转,可以从torchvision.datasets 下载。这里不再赘述,你可以参考这个链接的第一部分。

    1.3.2 自定义数据集

    这里我就用之前做过的一个项目来看看DatasetDataloadeer的强大之处。

    首先介绍一下项目背景:数据源中的每条数据都是由MNIST Digits转变而来的多位数图片,即一张图片最多有5位数字。训练目标是能够识别图片中的数字(最多 5 个)。我们给出的想法是,将训练集中的每个数据(即图片)切割成由单个数字组成的图片,并将其作为新的数据集进行训练(train);在识别(预测)阶段,原始图片也是经过裁剪后一一识别出来的。

    这里省略预处理部分。经过预处理后,数据集被重构为 280,000 张个位数图像。其中,每张图片为1*28*28 numpy.array

    第一步是使用torch.utils.data.Dataset导入这些数据集:任何自定义数据集都需要继承该类并重写相关方法。

    类CustomedDataSet(数据集):
    def __init__(self,train=True,train_x=None,train_y=None,test_x=None,test_y=None,val=False,transform=None):
    self.train = 火车self.val = val
    self.transform = 变换
    如果自我训练:
    self.dataset=train_x
    self.labels=train_y
    埃利夫瓦尔:
    self.dataset=test_x
    self.labels=test_y
    别的:
    self.dataset= test_x
    def __getitem__(自身,索引):
    如果自我训练:
    return torch.Tensor(self.dataset[index].astype(float)).to(device), self.labels[index].to(device)
    elif self.val:
    return torch.Tensor(self.dataset[index].astype(float)).to(device), self.labels[index].to(device)
    别的:
    return torch.Tensor(self.dataset[index].astype(float)).to(device)
    def __len__(自身):
    返回 self.dataset.shape[0]
    

    作者前期已经对数据进行了预处理,并且已经完成了读取数据文件(.h5)的部分,所以整个Class看起来比较简单明了。自定义 Dataset类必须实现 3 个函数: __init____getitem__ 和 ​​__len__。我们一一解释。

  • __init__(self, ) :

    和普通的Class一样,创建Dataset时会执行__init__函数。该函数的输入变量是完全自由的,没有任何限制。如果需要从外部文件读取数据,通常在这部分完成,以避免后续函数中重复读取。这里我们只导入numpy.array。其中,transform将在下一章介绍。

  • __getitem__(自身,索引)

    __getitem__ 函数根据索引index返回数据集中的数据。这里,我们将数据从numpy.array更改为tensor,迁移到device,然后输出。除了最基本的提取数据之外,我们还可以根据需要对这个函数中的数据进行一些更改。如果一开始就初始化了transform,这里也会使用它。

  • __len__(自己)

    此函数返回数据集中的数据总量。

  • 不难看出,Dataset给了用户非常高的自由度:如何读取数据集,如何简单地处理和改变数据集(操作、分割、重塑、正则) ...) 都可以在这个类中完成,而无需编写大量额外的分散块。使用方便,统一管理。

    train_set = CustomedDataSet(train_x = new_train_x, train_y = new_train_y)
    val_set = CustomedDataSet(test_x = new_val_x, test_y = new_val_y, train=False, val = True)
    

    1.3.3 DataLoader

    Dataset 负责建立从索引到样本的映射,而 DataLoader 负责以特定方式从数据集中迭代生成批量样本集合。

    DataLoader(数据集,batch_size = 1,shuffle = False,采样器=无,
    batch_sampler=无,num_workers=0,collat​​e_fn=无,
    pin_memory=False,drop_last=False,超时=0,
    worker_init_fn=无)
    

    我们重点关注几个比较重要的参数,其余的在这个环节详细介绍。

  • dataset:定义的Dataset类用作数据集。

  • batch_size(可选):一个批次包含多少个样本,默认 = 1。

  • shuffle(可选):每个epoch的batch样本是否随机,默认= False。

  • Sampler(可选):Sampler类可以自定义数据集中的采样方法。如果是这样,则必须shuffle=False

  • 采样器作为自定义采样器,它与数据集一样可编辑。我不会在这篇文章中详细介绍。你可以参考这个博客。

    train_loader = DataLoader(数据集=train_set,
    批量大小=60],
    随机播放=真)
    val_loader = DataLoader(数据集=val_set,
    批量大小=5,
    随机播放=假)
    

    这里的batch_size其实是CNN的超参数之一,需要优化。和机器学习模型一样,深度学习模型也需要通过 Cross Validation 来优化模型,参数调整过程也非常相似。这部分在本文中不会详细介绍。 val_setval_loader是CV中的验证集,不需要shuffle。

    创建DataLoader之后,调用起来非常方便。当需要迭代数据集时,调用Python内置函数enumerateiter Just

    对于batch_idx,枚举(train_loader)中的(数据,目标):
    ...
    ...
    

    枚举过程中,数据加载器实际上根据其参数指定的策略来调用其数据集的__getitem__sampler方法。这里需要注意的是,第一项enumerate输出batch_idx不是单个数据的索引,而是整个batch的索引。请确保不要使用此索引来定位单个数据集。

    1.4 变换

    如上所述,我们可以使用torchvision.transform对我们的数据集进行一些更改(正则化、切片、重塑...),使其符合训练模型输入的格式,或者进一步提高模型训练的质量。让我们简单看一下两个变换的例子。想要了解更多的读者可以参考官网给出的说明,或者这个中文解释。

    1.4.1 ToTensor()

    ToTensor()可以将PIL图像或numpy.array转换为FloatTensor,范围为[0, 1]缩放图像的像素强度值。

    1.4.2 拉姆达转换

    Lambda 转换可以使用任何定义的 lambda 函数。这里我们举一个使用lamder函数完成10个分类标签的one-hot编码的例子。

    target_transform = Lambda(lambda y: torch.zeros(
    10、dtype=torch.float).scatter_(dim=0,index=torch.tensor(y),value=1))
    

    transform 可用作下载预备数据集时的调用参数,或用作自定义数据集的 __init__ 输入。接下来我们看一个下载初步数据的例子。

    来自torchvision导入数据集
    从 torchvision.transforms 导入 ToTensor、Lambda
    ds = 数据集.FashionMNIST(根=“数据”,
    火车=真,
    下载=真,
    变换=ToTensor(),
    target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))
    )
    

    这里transorm仅用于特征转换,而target_transform用于标签转换。

    2。 构建和训练 CNN 神经网络

    准备好数据集后,我们可以开始构建卷积神经网络(CNN)。我们先看一个 CNN 的例子,然后逐步解释。

    进口炬视
    将 matplotlib.pyplot 导入为 plt
    从火炬导入 nn,优化
    导入 torch.nn.function 作为 F
    
    类 CNN(nn.Module):
    def __init__(自身):
    超级(网络,自我).__init__()
    self.conv1 = nn.Conv2d(1,10,5)
    self.conv2 = nn.Conv2d(10,20,3)
    self.fc1 = nn.Linear(20*10*10,500)
    self.fc2 = nn.Linear(500, 11)
    def 前向(自身,x):
    输入大小 = x.size(0)
    # 输入:批次*1*28*28,输出:批次*10*24*24(28-5+1)
    x = self.conv1(x)
    # 输出:批次*10*24*24
    x = F.relu(x)
    # 输入:批次*10*24*24,输出:批次*10*12*12
    x = F.max_pool2d(x,2,2)
    # 输入:批次*10*12*12,输出:批次*20*10*10 (12-3+1)
    x = self.conv2(x)x = F.relu(x)
    # 20*10*10 = 2000
    x = x.view(input_size,-1)
    # 输入:批次*2000 输出:批次*500
    x = self.fc1(x)
    x = F.relu(x)
    # 输入:批次*500 输出:批次*10
    x = self.fc2(x)
    返回 F.log_softmax(x)
    

    我们首先通过继承nn.Module来定义我们的CNN类,然后在__init__中创建CNN的每一层。神经网络的所有操作都是通过forward函数来实现。在这个CNN例子中,一共有两个2维卷积层和两个全连接的线性层,它们通过一些激活函数连接起来,最后输出softmax分类结果。

    这里,作者将所有神经网络层放在___init___中,所有激活函数放在forward中。当然,你也可以使用nn.Sequential()将这些激活函数和神经网络层按顺序放置在__init__中。

    2.1 卷积层(2d)

    因为这是一篇CNN文章,所以我们重点讨论卷积层。刚才的例子就是作者在上一篇文章中提到的个人项目中使用的CNN。因为要识别的是二维图像,所以使用了二维卷积层torch.nn.Conv2d。这里我们重点关注这个Class。

    torch.nn.Conv2d(in_channels, out_channels, kernel_size,
    步长=1,填充=0,膨胀=1,组=1,
    偏差=真,padding_mode='零')
    

    我们来看看关键参数:

  • in_channels (int): 输入图像通道号

  • out_channels (int): 卷积产生的通道数

  • kernel_size(整数或元组):卷积核大小

  • stride(int,可选):卷积步长,默认为1

  • 我们来看一个操作示例:

    输入 = torch.randn(1,1,28,28)
    conv1 = nn.Conv2d(1,10,5)
    输出= 卷积1(输入)
    打印(输入.形状)
    打印(输出.形状)
    

    出:

    火炬.Size([1, 1, 28, 28])
    火炬.Size([1, 10, 24, 24])
    

    输入图像为(1*1*28*28):前1是batch size,这里可以忽略。这是单通道、28*28 的图像。卷积层的输入也是单通道,需要和图像的通道数一致!输出为10个通道,卷积核大小为5*5。所以我们的输出自然就是(1*10*24*24):其中batch size = 1不变,图像就变成了(24*24),24 = 28 – 5 + 1。

    了解了卷积层的运行模式,我们来了解一下最基本的用法:创建了Net类后,我们直接进入输入,然后就可以通过了 forward函数得到结果。当然,这个例子只是为了演示。模型还没有经过训练,所以每次的输出并不准确。

    网络 = Net()
    输出=模型(输入)
    pred = 输出.argmax(1)
    打印(预测)
    

    出:

    张量([9])
    

    2.2 训练CNN

    到目前为止,我们已经导入了预处理的数据,创建了一个可以迭代数据的Dataloader,并构建了我们的卷积神经网络。在训练 CNN 之前,我们还缺少最后两个组件:损失函数(损失函数)和优化器(优化器)。

    首先我们先定义这个模型的hyperparameter,这篇文章不做Cross Validation调参的介绍和应用。:

    learning_rate = 1e-3
    batch_size = 60
    epochs = 5
    

    2.2.1 损失函数

    我们都知道损失函数是计算预估值和实际值时间的损失量,也是在训练过程中想要优化的对象。通常所用的损函数有:回归任务用到的nn.MSELoss(均方误差)以及分类任务用到的nn.NLLLoss(负对数似然)。而nn.CrossEntropyLoss前两者的结合,对一直以来熟悉机器学习的读者应该也不会陌生。这里我们因为是分类任务,所以选择负对数似然。

    loss = F.nll_loss(output, target)
    

    2.2.2 优化器

    大家对优化算法应该也不陌生,最常见的优化算法就是梯度下降(Gradient Descent)。我们这里采用的是随机梯度下降(Stochastic Gradient Descent)。

    optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
    

    在一个训练循环中,优化有3个步骤:

    1. 执行www.sychzs.cn_grad来清空系统中积累的梯度,

    2. 通过调用loss.backward()反向传播预测损失。PyTorch会储存每一个参数对应的损失梯度。

    3. 当得到损失梯度以后,调用optimizer.step()来实现优化和调整参数。

    2.2.3 完整实现

    训练:

    def train(network, train_loader, optimizer):
    network.train()
    for batch_idx, (data, target) in enumerate(train_loader):
    www.sychzs.cn_grad()
    output = network(data)
    loss = F.nll_loss(output, target)
    loss.backward()
    optimizer.step()
    

    这里要重点提一句:network.train()的作用是启动 Batch Normalization 和 Dropout。在训练一个模型时,这个指令可以完全改变训练的结果,请根据业务需求来选择是否使用。

  • Batch Normalization

    其作用对网络中间的每层进行归一化处理,并且使用变换重构(Batch Normalization Transform)保证每层提取的特征分布不会被破坏。训练时是针对每个mini-batch的,但是测试是针对单张图片的,即不存在batch的概念。由于网络训练完成后参数是固定的,每个batch的均值和方差是不变的,因此直接结算所有batch的均值和方差。

    Batch Normalization有非常多的好处,其中最直接的还是训练提速:该算法背后的理论可以支持我们选择比较大的初始学习率。我们也知道当学习率变大时,随机梯度下降算法收敛得也就越快。这样我们可以不用繁琐地调整学习率,大大提高了优化模型的效率。

    想要进一步了解 BatchNormalization 的同学可以移步这篇博客。有英语基础的同学也可以直接阅读这个算法的源文献:《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》。

  • Dropout

    其作用克服Overfitting,在每个训练批次中,通过忽略一半的特征检测器,可以明显的减少过拟合现象。

  • 准确度:

    def accuracy(epoch_idx, test_loader, network, set_type = None):
    correct = 0
    with www.sychzs.cn_grad():
    for data, target in test_loader:
    outputs = network(data)
    _, predicted = torch.max(www.sychzs.cn, 1)
    correct += (predicted == target).sum().item()
    if set_type == "train":
    print('\nEpoch{}: Train accuracy: {}/{} ({:.0f}%)\n'.format(
    epoch_idx, correct, len(test_loader.dataset),
    100. * correct / len(test_loader.dataset)))
    if set_type == "val":
    print('\nEpoch{}: Test accuracy: {}/{} ({:.0f}%)\n'.format(
    epoch_idx, correct, len(test_loader.dataset),
    100. * correct / len(test_loader.dataset)))
    return correct / len(test_loader.dataset)
    
    train_set = CustomedDataSet(train_x = new_train_x, train_y = new_train_y)
    val_set = CustomedDataSet(test_x = new_val_x, test_y = new_val_y, train=False, val = True)
    train_loader = DataLoader(dataset=train_set,batch_size=104,shuffle=True,)
    val_loader = DataLoader(dataset=val_set,batch_size=batch_size_test,shuffle=False)
    network = Net().to(device)
    optimizer = optim.SGD(network.parameters(), lr=learning_rate,momentum=momentum)
    for i in range(1,n_epochs+1):
    print(f"Epoch {t+1}\n-------------------------------")
    train(epoch_idx = i, train_loader = train_loader, optimizer = optimizer, network = network)
    train_accuracy = accuracy(epoch_idx=i, test_loader = train_loader, network = network, set_type = "train")
    val_accuracy = accuracy(epoch_idx=i, test_loader = val_loader, network = network, set_type = "val")
    

    2.3 保存和导入

    Pytorch支持把训练好的神经网络用以下两种不同的方式保存到到本地路径;相对的,导入方式也不太一样:

  • 保存整个神经网络
  • www.sychzs.cn(network, PATH) # PATH variable should be stated
    trained_net = torch.load(PATH) # PATH same as above
    
  • 保存网络中的参数

    这样做的好处是速度快,占用的空间少。但是导入网络参数时,要先创建一个网络变量:

  • www.sychzs.cn(network.state_dict(), PATH) #保存参数
    
    trained_net = Net().to(device) #先创建一个网络变量
    trained_net.load_state_dict(torch.load('model_weights.pth')) # 再导入参数
    trained_net.eval()
    

    这里也是要提一嘴 Network.eval() 的作用:

    不启用 Batch Normalization(BN) 和 Dropout,保证BN和dropout不发生变化,pytorch框架会自动把BN和Dropout固定住,不会取平均,而是用训练好的值,不然的话,一旦test的batch_size过小,很容易就会被BN层影响结果。

    3. 优化CNN模型

    大家都知道任何机器学习和深度学习的模型都需要通过 Cross Validation 反复训练找到预测精准度最高的 hyperparameter 组合。像是 Grid Search 和 Random Search 相信大家也都不陌生了,不在这篇文章里做详细的介绍和实现了。这里给大家介绍CNN模型中的hyperparameter。

    3.1 CNN结构

    对于任何类型的神经网络来说,网络的结构(Architecture)都是影响预测精准性的第一因素。举个例子,对于图像识别来说,CNN肯定比一般的全连接线性网络有效,因为卷积层的性质就是用来解析图像的。那要用多少层卷积层?多少层线性层?卷积层的滤波和通道数分别是什么?

    这些复杂的结构型问题显然不能通过 Cross Validation 来做出决策和判断。一个优秀的神经网络结构的背后是庞大的理论和科研支持。因此,笔者不建议大家花费大量的精力对一个CNN的结构进行优化,而是以需求为出发点去寻找目前为止被广泛认可的神经网络模型。

  • VGG – 图像识别(定位任务)

    VGG的论文《Very Deep Convolutional Networks for Large-Scale Visual Recognition》探讨了CNN的深度对于图片识别精准度的影响。论文中对比了了从11到19层不同的CNN模型,其中16层的CNN(VGG16)被广泛使用,也同时被收录在PyTorch里可以直接调用。

  • GoogLeNet – 图像识别(分类任务)

    如果你觉得VGG的19层已经很深了的话,那你就错了!Google研发的GoogLeNet深度已经达到了22层!但是,GoogLeNet属于稀疏CNN,其大小比VGG16小很多:GoogleNet参数为500万个(5M),VGG16参数是138M。其思想是:在神经网络结构中,去除“无效的”的激活函数,并使用很多Inception模块,利用不同大小的卷积核实现不同尺度的感知。

  • 3.2 其他的Hyperparameter

    除了结构之外,其他的Hyperparameters就需要我们自己用Grid Search, Random Search去优化了。那这些hyperparametes包括:

  • Epochs数:即迭代次数

  • Learnin Rate(学习率):即优化器的学习率。

  • 优化器:学习率是跟着优化器走的。上文中用的是随机梯度下降(SGD),除了学习率以外还可以调整 **动量(momentem)**和 批样本(mini batch size);在ADAM优化器里用到的是自适应性的学习率,就有其他的hyperparameters出现了。

  • 结语

    这还是一篇基础向的文章,希望能够帮到刚刚接触PyTorch的人。

    References

    1. PyTorch 官网教程

    2. Liang’s Blog

    3. 《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》

    4. 《Very Deep Convolutional Networks for Large-Scale Visual Recognition》

    5. GoogLeNet

    来源:juhanishen

    相关文章

    最新资讯