PyTorch-模型建立

Posted love the future

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyTorch-模型建立相关的知识,希望对你有一定的参考价值。

文章目录

Fashion-MNIST数据集介绍

数据是深度学习的主要组成部分,让我们的神经网络从数据中学习是我们的任务。
了解一个数据集需要知道的一些知识?

  • 谁创建了数据集?
  • 数据集是如何创建的?
  • 使用了哪些转换?
  • 数据集有什么目的?
  • 可能的意外后果?
  • 数据集有偏见吗?
  • 数据集是否存在道德问题?

什么是MNIST?

MNIST数据集是美国国家标准与技术研究所(National Institute of Standards and Technology)修改后的数据库,是一个著名的手写数字数据集,通常用于培训机器学习的图像处理系统。NIST代表国家标准与技术研究所。
M 在MNIST代表modified,这是因为NIST有一个原始的数字数据集,它被修改为MNIST。

MNIST因数据集的使用频率而闻名。这很常见,有两个原因:

  • 初学者使用它是因为它很简单
  • 研究人员用它来衡量(比较)不同的模型。
  • 该数据集由70000张手写数字图像组成,分割如下:6万张训练图片和10000张测试图像这些图片最初是由美国人口普查局员工和美国高中生创作的。
  • MNIST已经得到了广泛的应用,图像识别技术也有了很大的改进,以至于人们认为数据集过于简单。这就是为什么要创建Fashion MNIST数据集。

什么是Fashion-MNIST?

Fashion MNIST顾名思义就是一个时装项目的数据集。具体而言,该数据集包含以下十类时尚项目:


Fashion MNIST基于Zalando网站上的分类。Zalando是一家总部位于德国的跨国时尚商业公司,成立于2008年。这就是为什么我们可以在GitHub URL中看到 zalando research,在那里可以下载Fashion MNIST数据集。Zalando Research是创建该数据集的公司内部的团队。

是什么让MNIST成为Fashion MNIST?

fashion MNIST数据集以MNIST命名的原因是创作者试图用fashion MNIST替换MNIST。出于这个原因,时装数据集被设计成尽可能接近原始MNIST数据集,同时由于其数据比手写图像更复杂,导致训练难度更高。将在本文中看到Fashion MNIST镜像原始数据集的具体方式,但我们已经看到了类的数量。MNIST–有10个类(每个数字0-9对应一个)Fashion MNIST-有10个类(这是有意的)
论文地址:https://arxiv.org/pdf/1708.07747.pdf
数据集地址:https://github.com/zalandoresearch/fashion-mnist.
在这篇论文的摘要中可以看到:我们展示了Fashion MNIST,这是一个新的数据集,包含来自10个类别的70000件时装产品的28×28灰度图像,每个类别有7000张图像。训练集有60000张图像,测试集有10000张图像。Fashion MNIST旨在作为原始MNIST数据集的直接替代品,用于对机器学习算法进行测试,因为它共享相同的图像大小、数据格式以及训练和测试拆分结构。
另外,MNIST如此受欢迎的原因与它的规模有关,它允许深度学习研究人员快速检查并原型化他们的算法。此外,所有机器学习库(如scikit learn)和深度学习框架(如Tensorflow、PyTorch)都提供了辅助函数和方便的示例,可以使用MNIST开箱即用。
PyTorch确实为我们提供了一个名为torchvision的软件包,使我们能够轻松开始MNIST和Fashion MNIST。

Fashion MNIST是怎么创建的?

与MNIST数据集不同,时装集不是手工绘制的,但数据集中的图像是Zalando网站上的真实图像。然而,它们已被转换为更符合MNIST规范。这是网站上每张图片的一般转换过程:

  • 转换为PNG
  • 修剪
  • 调整
  • 大小
  • 锐化
  • 延长
  • 否定
  • 灰度

通过PyTorch进行数据集加载

提取、转换和加载即Extract, Transform, And Load (ETL)
建立一个项目的一般过程:

  • 准备数据
  • 建立模型
  • 训练模型
  • 分析模型的结果

数据准备

我们将从准备数据开始。为了准备数据,我们将遵循一个大致被称为ETL的过程。

  • 从数据源中提取数据。
  • 将数据转换为理想的格式。
  • 将数据加载到合适的结构中。
    ETL过程可以被认为是一个分形过程,因为它可以应用于各种规模。这个过程可以在小范围内应用,比如单个程序,也可以在大范围内应用,一直到企业级,在企业级有巨大的系统处理每个单独的部件。
    首先需要导入所有必要的库:
  • torch:是顶级PyTorch软件包和tensor库。
  • torch.nn :包含用于构建神经网络的模块和可扩展类的子包。
  • torch.autograd :支持PyTorch中所有的可微张量运算的子包
  • torch.nn.functional :一种功能接口,包含用于构建神经网络的典型操作,如损失函数、激活函数和卷积运算
  • torch.optim :包含标准优化操作(如SGD和Adam)的子包。
  • torch.utils :工具包,包含数据集和数据加载程序等实用程序类的子包,使数据预处理更容易
  • torchvision :一个软件包,提供对流行数据集、模型架构和计算机视觉图像转换的访问。
  • torchvision.transforms:转换包,含用于图像处理的常见转换的接口
    pdb是Python调试器,带注释的导入是一个本地文件,用于绘制混淆矩阵,最后一行设置PyTorch print语句的打印选项。
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F

import torchvision
import torchvision.transforms as transforms

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.metrics import confusion_matrix
#from plotcm import plot_confusion_matrix

数据准备:
Extract –从源文件中获取时尚Fashion MNIST图像数据。
Transform – 将数据转换为张量形式
Load – 将数据放入对象中,使其易于访问.
为此,PyTorch提供了两个类:

  • torch.utils.data.Dataset:用于表示数据集的抽象类。
  • torch.utils.data.DataLoader: 包装数据集并提供对底层数据的访问
    抽象类是一个Python类,它有我们必须实现的方法,因此我们可以通过创建扩展dataset类功能的子类来创建自定义dataset。
    为了使用PyTorch创建自定义数据集,我们通过创建实现这些必需方法的子类来扩展dataset类。这样做之后,我们的新子类就可以被传递给PyTorch数据加载程序对象。
    我们将使用torchvision软件包内置的fashion MNIST数据集,因此我们的项目不必编写自定义的子类。只需知道Fashion MNIST内置的dataset类是在幕后完成这项工作的。
    所有Dataset的子类必须重写提供数据集大小的__len__方法,getitem,支持从0到len(self)的整数索引。
    具体来说,需要实现两种方法。
  • 返回数据集长度的__len__方法,
  • 以及从数据集中特定索引位置的数据集中获取元素的__getitem__方法。

torchvision包提供了下面的具体功能:

  • Datasets (like MNIST and Fashion-MNIST)
  • Models (like VGG16)
  • Transforms(转换函数)
  • Utils(工具包)
    Fashion MNIST数据集只是扩展MNIST数据集并覆盖URL。
    以下是PyTorch的torchvision源代码中的类定义:
class FashionMNIST(MNIST):
    """`Fashion-MNIST `_ Dataset.

    Args:
        root (string): Root directory of dataset where ``processed/training.pt``
            and  ``processed/test.pt`` exist.
        train (bool, optional): If True, creates dataset from ``training.pt``,
            otherwise from ``test.pt``.
        download (bool, optional): If true, downloads the dataset from the internet and
            puts it in root directory. If dataset is already downloaded, it is not
            downloaded again.
        transform (callable, optional): A function/transform that  takes in an PIL image
            and returns a transformed version. E.g, ``transforms.RandomCrop``
        target_transform (callable, optional): A function/transform that takes in the
            target and transforms it.
    """
    urls = [
        'http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz',
        'http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz',
        'http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz',
        'http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz',
    ]

利用torchvision可以简化操作
加载数据集只需要执行以下的代码:

train_set = torchvision.datasets.FashionMNIST(
    root='./data'
    ,train=True
    ,download=True
    ,transform=transforms.Compose([
        transforms.ToTensor()
    ])
)

参数介绍:

  • root 数据所在的磁盘位置
  • train 数据集是否为训练集
  • download 数据是否下载,第一次的时候,数据会从网上进行下载。
  • transform 在数据元素上执行的转换组合。即讲数据元素转换成Tensor。
    因为我们想把图像转换成张量,所以我们使用内置的转换。由于这个数据集将用于训练,我们将把实例命名为train_set。当我们第一次运行此代码时,Fashion MNIST数据集将在本地下载。后续通话会在下载数据之前检查数据。因此,我们不必担心重复下载或重复网络通话。

PyTorch DataLoader
创建DataLoader:

train_loader = torch.utils.data.DataLoader(train_set
    ,batch_size=1000
    ,shuffle=True
)

参数介绍:

  • batch_size :一个批次的大小(1000 )
  • shuffle :是否进行乱序(本例中选择的是)
  • num_workers :进程数量(默认 0 表示只是用主线程)

Datasets 和 DataLoaders

从上文中我们已经得到了两个对象实例:train_set和train_loader
要查看训练集中有多少图像,我们可以使用Python中的len()函数检查数据集的长度
如果想查看每个图像的标签可以使用 train_set.targets。
如果想查看数据集中每个标签的数量,可以使用PyTorch中bincount()函数

len(train_set)
train_set.targets
train_set.targets.bincount()  #表示每一类有6000张图像。
#tensor([6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000])

类的平衡性:
通过上面的数据表明Fashion MNIST数据集在每个类中的样本数量上是一致的。这意味着每个类有6000个样品。因此,这个数据集被认为是平衡的。如果类具有不同数量的样本,我们会将该集合称为不平衡数据集。
类不平衡是一个常见的问题,但在Fashion MNIST数据集中,数据集是平衡的。

访问训练集中的数据:
为了从训练集中访问单个元素,我们首先将train_set对象传递给Python的iter()内置函数,该函数返回一个表示数据流的对象。
对于数据流,我们可以使用Python内置的next()函数来获取数据流中的下一个数据元素。我们希望从中得到一个样本,因此我们将相应地命名结果:

sample = next(iter(train_set))
len(sample)#2

将样本传递给len()函数后,我们可以看到样本包含两个项,这是因为数据集包含图像标签对。我们从训练集中检索的每个样本都包含作为张量的图像数据和作为张量的相应标签。(特征值,标签)的形式
由于样本是 sequence类型(tuple),我们可以使用sequence unpacking来分配图像和标签。现在,我们将检查图像和标签的类型,将其分解成PyTorch.Tensor类型

sample = next(iter(train_set))
len(sample)
type(sample)
image, label = sample
type(image)#torch.Tensor
type(label)#int

查看数据的大小:
image是shape为[1, 28, 28]的张量,
label是标量张量值。

print(image.shape)#torch.Size([1, 28, 28])
print(torch.tensor(label).shape)#torch.Size([])

其中1表示的是颜色通道数为1 ,即为灰度图像。讲image压缩到28*28,然后绘制这张图像得到下面的结果,就是第一张图像的标签值为9,也就是第九类。

image.squeeze().shape #torch.Size([28, 28])
plt.imshow(image.squeeze(), cmap="gray")
torch.tensor(label)
#tensor(9)

批量处理

在上文中,得到图像的方式是通过使用 iter()、next()对train_set进行操作,我们也可以利用data_loader进行批量操作图像。
重新定义了一个加载器,batch=10值小一点,我们加载器中得到一批,就像我们在训练集中看到的那样。我们使用iter()和next()函数进行访问。在使用数据加载器时,有一件事需要注意。如果shuffle=True,则每次调用next时,批处理都会不同。当shuffle=True时,训练集中的第一个样本将在第一次调用next时返回。默认情况下,shuffle功能处于关闭状态。
由于batch_size=10,因此正在处理一批10幅图像和10个相应的标签。这就是为什么我们在变量名上使用复数。这些类型是我们期望的张量。然而,形状与我们在单个样本中看到的不同。我们没有使用单个标量值作为标签,而是使用了一个具有10个值的秩为1张量。包含图像数据的张量中每个维度的大小由以下每个值定义:(batch size, number of color channels, image height, image width)

display_loader = torch.utils.data.DataLoader(
    train_set, batch_size=10
)
batch = next(iter(display_loader))
print('len:', len(batch))  #len: 2
images, labels = batch
print('types:', type(images), type(labels)) #types: <class 'torch.Tensor'> <class 'torch.Tensor'>
print('shapes:', images.shape, labels.shape) #shapes: torch.Size([10, 1, 28, 28]) torch.Size([10])
images[0].shape #torch.Size([1, 28, 28])
labels[0] #9

要绘制一批图像,我们可以使用torchvision.utils.make_grid()函数创建一个网格:

grid = torchvision.utils.make_grid(images, nrow=10)
plt.figure(figsize=(15,15))
plt.imshow(np.transpose(grid, (1,2,0)))
# plt.imshow(grid.permute(1,2,0))
print('labels:', labels)#labels: tensor([9, 0, 0, 3, 0, 2, 7, 2, 5, 5])


使用PyTorch中的DataLoader绘制图像
这是另外一种绘制图像的方法。

how_many_to_plot = 20

train_loader_iter = torch.utils.data.DataLoader(
    train_set, batch_size=1, shuffle=True
)

plt.figure(figsize=(50,50))
for i, batch in enumerate(train_loader_iter, start=1):
    image, label = batch
    plt.subplot(10,10,i)
    plt.imshow(image.reshape(28,28), cmap='gray')
    plt.axis('off')
    plt.title(train_set.classes[label.item()], fontsize=28)
    if (i >= how_many_to_plot): break
plt.show()

模型建立

python中的面向对象(OOP)

准备好数据之后,就是建立模型,通常我们也说网络
为了在PyTorch中建立神经网络,我们扩展了Torch.nn.module的PyTorch类。这意味着我们需要在Python中使用一点面向对象编程(OOP)。更多关于Python的面向对象知识:https://docs.python.org/3/tutorial/classes.html
当我们编写程序或构建软件时,有两个关键组件,代码和数据。通过面向对象编程,我们将程序设计和结构定位于对象。对象是使用类在代码中定义的。类是定义对象的规范或规范,该规范指定类的每个对象应具有的数据和代码。
当我们创建一个类的对象时,我们称该对象为该类的实例,给定类的所有实例都有两个核心组件:

  • Methods(方法)
  • Attributes(属性)
    方法代表代码,而属性代表数据,因此方法和属性由类定义。在一个给定的程序中,许多对象(即给定类的实例)可以同时存在,并且所有实例都将具有相同的可用属性和相同的可用方法。从这个角度来看,它们是一致的。同一类的对象之间的差异是对象中包含的每个属性的值。每个对象都有自己的属性值。这些值确定对象的内部状态。每个对象的代码和数据都封装在对象中。
class Lizard: #class declaration
    def __init__(self, name): #class constructor (code)
        self.name = name #attribute (data)

    def set_name(self, name): #method declaration (code)
        self.name = name #method implementation (code)

第一行声明类并指定类名,在本例中为testCNN。
第二行定义了一个称为类构造函数的特殊方法。在创建类的新实例时,会调用类构造函数。参数有self和name。

  • self参数使我们能够创建存储或封装在对象中的属性值。当我们调用这个构造函数或任何其他方法时,我们不会传递self参数。Python会自动为我们做到这一点。
  • 调用方可以任意传递任何其他参数的参数值(如name),这些传入方法的传递值可以在计算中使用,也可以在以后通过self进行保存和访问。
    在完成构造函数之后,我们可以创建任意数量的方法,比如这里的方法set_name(),它允许调用者更改存储在self中的name值。我们在这里需要做的就是调用该方法并为名称传递一个新值。
test = testCNN('deep')
print(test.name)#deep
test.set_name('CNN')
print(test.name)#CNN

我们通过指定类名(testCNN)并传递构造函数参数(deep)来创建类的对象实例。构造函数将接收这些参数,并且构造函数代码将运行以保存传递的名称。
然后我们可以访问并打印名称,还可以调用set_name()方法来更改名称。一个程序中可以存在多个testCNN实例,每个实例都包含自己的数据。
从面向对象的角度来看,这个设置的重要部分是属性和方法被组织并包含在一个对象中

现在让我们切换一下,看看面向对象编程如何与Py Torch相适应。

PyTorch中的torch.nn

在构建神经网络的时候,我们通常会利用PyTorch中的torch.nn软件包,里面包含了构建网络的基本组件,导入方法如下:

import torch.nn as nn

构建神经网络最主要的组件就是层,在PyTorch的神经网络库包含帮助我们构建层的类。nn.Module类
深层神经网络是使用多层结构构建的。这就是网络深度的原因。神经网络中的每一层都有两个主要组成部分:

  • A transformation (code)
  • A collection of weights (data)
    与生活中的许多事情一样,这一事实使层成为使用OOP来表示对象的最佳候选对象。OOP是面向对象编程的缩写。
    事实上,PyTorch就是这样。在nn包中,有一个名为Module的类,它是包含所有层的神经网络模块的基类。
    这意味着PyTorch中的所有层都扩展了nn.Module类并继承nn.Module中的所有内置功能。在OOP中,这个概念被称为继承。
    甚至神经网络也扩展了nn.Module类。神经网络和其中的层扩展了nn.Module类。这意味着我们如果需要在PyTorch中构建新层或神经网络,就必须extend nn.Module类。

nn.Module中的forward()方法

前向传播:当我们把一个张量作为输入传递给我们的网络时,张量通过每一层变换向前流动,直到张量到达输出层。张量通过网络向前流动的过程称为向前传递。
每一层都有自己的transformation(代码),张量向前通过每一层。每一层中的转换函数定义了整个网络的整体前向传播过程。
前向传播的目标是将输入转换或映射到正确的预测输出类,并且在训练过程中,各层权重(数据)的更新方式会导致映射进行调整,以使输出更接近正确的预测。
这意味着在PyTorch的nn.Module类中都有一个forward()方法,所以当我们构建层和网络时,我们必须提供forward()方法的实现。前向传播的过程实际就是这种数据变换的过程。

PyTorch中的nn.functional包

当我们实现nn.Module子类的forward()方法时,我们通常使用nn.functional中的函数,这个软件包为我们提供了许多可以用于构建层的神经网络操作。事实上,许多nn.Module类使用nn.functional中函数执行其操作的功能。
nn.functional包中包含nn.Module 类的方法用于实现其forward()函数。
建立神经网络的步骤:

  • 1.继承(extend)nn.Module基类
  • 2.将层定义为类的属性:在类构造函数中,使用torch.nn的预构建层将网络层定义为类属性
  • 3.实现forward()方法。使用网络的层属性以及nn.functional中的操作来定义网络前向传递的函数API。
class Network:
    def __init__(self):
        self.layer = None

    def forward(self, t):
        t = self.layer(t)
        return t

这为我们提供了一个简单的Network类,它在构造函数中有一个虚拟层,并为forward函数提供了一个虚拟实现。forward()函数的实现采用张量t,并使用虚拟层对其进行变换。转换张量后,返回新的张量。
但是该类尚未extend nn.Module类。让Network类extend到 nn.Module类中,我们还必须做两件事:

  • 1.在第一行的Network后面加上括号class Network(nn.Module):
  • 2.在构造函数的第3行插入对super类(父类)构造函数的调用。
class Network(nn.Module): # line 1
    def __init__(self):
        super().__init__() # line 3
        self.layer = None

    def forward(self, t):
        t = self.layer(t)
        return t

上面的 Network类由于extend了nn.Module类,因此具有nn.Module的所有功能。

定义神经网络的层作为类的属性

目前,我们的网络类有一个单独的虚拟层作为属性。现在让我们用PyTorch的nn库中为我们预先构建的一些真实层来替换它。我们正在构建一个CNN,所以我们将使用两种类型的层,即线性层和卷积层。

class Network(nn.Module):
    def __init__(self):
        super().__init__() #父类的构造函数
        #两个卷积层
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)

		#两个全连接层
        self.fc1 = nn.Linear(in_features=12 * 4 * 4, out_features=120)
        self.fc2 = nn.Linear(in_features=120, out_features=60)
        #输出层
        self.out = nn.Linear(in_features=60, out_features=10)

    def forward(self, t):
        # implement the forward pass
        return t

现在,我们有一个名为Network的Python类,它extend了PyTorch的nn.Module类。在我们的网络类中,我们有五个定义为属性的层。我们有两个卷积层self.conv1和self.conv2和三个线性层self.fc1,self.fc2,self.out.
我们在fc1和fc2中使用缩写fc,因为线性层也称为全连接层。它们还有第三个名字,我们有时可能会听到它叫“稠密”。所以线性、密集和全连接都是指同一类型的层。PyTorch使用单词linear,因此使用nn.linear类名来进行实现。out作为输出层。

网络中的层layer

在上文中,我们定义了2个卷积层和3个线性层,我们的每一层都扩展了PyTorch的神经网络Module类。对于每一层,有两个主要项目封装在里面,一个正向函数定义和一个权重张量。每个层中的权重张量包含在网络在训练过程中学习时更新的权重值,这就是我们将层指定为Network类中的属性的原因。
PyTorch的Module类跟踪每层中的权重张量。执行此跟踪的代码存在于nn.Module类中,由于我们extend了神经网络模块类,我们自动继承了这个功能。继承是我们上次讨论过的面向对象概念之一。我们所要做的就是利用这个功能,在我们的网络模块中将我们的层指定为属性,模块基类将看到这一点,并将权重注册为我们网络的可学习参数。

Parameter Vs Argument

parameter和argument这两个词有什么区别呢?
parameter在函数定义中用作占位符,而argument是传递给函数的实际值。parameter可以看作是函数内部的局部变量。
在我们的网络中,names是parameter,指定的值(value)是argument。

两种类型的Parameters

  • 超参数(Hyperparameters)
  • 数据相关超参数(Data dependent hyperparameters)
    在神经网络中,Parameters是一个很松散的术语,无论是那种类型的Parameters,需要记住的就是Parameters是一个占位符,最终将保留或者具有一个值。
    构造层时,我们将每个Parameters的值传递给层的构造函数。我们的卷积层有三个参数,线性层有两个参数。
Convolutional layers
	in_channels
	out_channels
	kernel_size
Linear layers
	in_features
	out_features

Hyperparameters

一般来说,超参数是指其值是手动和任意选择的参数。作为神经网络程序员,我们选择超参数值主要基于尝试和错误,并越来越多地利用过去证明有效的值。为了构建CNN层,这些是我们手动选择的参数。这意味着我们只需选择这些参数的值。在神经网络编程中,这是很常见的,我们通常测试和调整这些参数,以找到最有效的值。

  • kernel_size:设置过滤器的高度和宽度。
  • out_channels:设置过滤器的深度(也就是过滤器的个数)。这是过滤器中的内核数。一个内核产生一个输出通道。
  • out_features:设置输出张量的大小。
    一种经常出现的模式是,我们在添加conv层时增加out_channels(6,12),在切换到线性层后,我们在过滤到输出类的数量时缩小out_features(120,60,10)。所有这些参数都会影响我们网络的架构。具体来说,这些参数直接影响层内的权重张量。

Data dependent hyperparameters

  • in_channels
  • in_features
  • out_features
    依赖于数据的超参数是其值依赖于数据的参数。突出的前两个数据相关超参数是第一卷积层的in_channels和输出层的out_features。
    你看,第一个卷积层的in_channels(1)取决于组成训练集的图像中存在的颜色通道的数量。因为我们处理的是灰度图像,所以我们知道这个值应该是1。
    输出层的out_features取决于我们的训练集中存在的类的数量。由于Fashion MNIST数据集中有10类服装,我们知道需要10个输出特性。
    一般来说,一层的输入是前一层的输出,因此conv层中的所有in_channels和线性层中的in_features取决于来自前一层的数据。
    当我们从conv层切换到线性层时,我们必须Flatten张量。这就是为什么我们有1244。12个来自前一层的输出通道数,但为什么我们有两个4?

Kernel和Filter

在深度学习中经常交替使用filter和kernel这两个词。然而,这两个概念在技术上有区别。
Kernel是2D张量(单个Kernel),Filter是包含内核集合的3D张量(包含多少个Kernel)。我们对单个通道应用内核,对多个通道应用过滤器。

关于参数总结

self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)

self.fc1 = nn.Linear(in_features=12 * 4 * 4, out_features=120)
self.fc2 = nn.Linear(in_features=120, out_features=60)
self.out = nn.Linear(in_features=60, out_features=10)

weight-神经网络的可学习参数

可学习参数是在训练过程中学习其值的参数。对于可学习的参数,我们通常从一组任意值开始,然后随着网络学习,这些值以迭代方式更新。事实上,当我们说一个网络正在学习时,我们的具体意思是网络正在学习可学习参数的适当值。适当的值是使损失函数最小化的值。
那这些可学习的参数在哪里?
这些可学习参数是网络的权重,存在于网络中的每一层。
在上文中定义的Network类,我们可以实例化:

network = Network()    

实例化Network类的对象时,我们需要键入类名,后跟括号。当执行此代码时,会执行__init__构造函数中的代码,将我们的层指定为属性,然后返回。因此我们就可以通过Network类的实例变量访问该对象。
可以通过print函数打印出网络的结构。因为Network继承了nn.Module类,里面有方法实现了这个打印功能(类似tostring功能)。如果不继承nn.Module,打印出来的结果就不是这个

print(network)
# Network(
#     (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
#     (conv2): Conv2d(6, 12, kernel_size=(5, 5), stride=(1, 1))
#     (fc1): Linear(in_features=192, out_features=120, bias=True)
#     (fc2): Linear(in_features=120, out_features=60, bias=True)
#     (out): Linear(in_features=60, out_features=10, bias=True)
# )

重写类方法

所有Python类都会自动继承Object类。如果我们想为我们的对象提供自定义字符串表示,我们可以这样做,但我们需要引入另一个面向对象的概念,称为覆盖(override)。当我们继承一个类时,我们得到了它的所有功能,为了补充这一点,我们可以添加额外的功能。然而,我们也可以通过改变现有功能来改变其行为,从而覆盖现有功能。重写__repr__方法即可。

def __repr__(self):
    return "structure"

在OOP编程时,有构造函数__init__方法,以及它是如何构造对象的一种特殊Python方法。我们还会遇到其他一些特殊的方法,repr;就是其中之一。所有特殊的OOP Python方法通常都有双下划线的前置和后置.

访问网络的层

和很多的编程语言类似,python中也是通过点(.)来获得对象的属性方法。
下面时通过network对象访问网络中的每一层。

network.conv1
# Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))

network.conv2
# Conv2d(6, 12, kernel_size=(5, 5), stride=(1, 1))

network.fc1
# Linear(in_features=192, out_features=120, bias=True)

network.fc2                                    
# Linear(in_features=120, out_features=60, bias=True)

network.out
# Linear(in_features=60, out_features=10, bias=True)

得到每一层之后可以访问每一层中的权重

network.conv1.weight
# Parameter containing:
# tensor([[[[ 0.0692,  0.1029, -0.1793,  0.0495,  0.0619],
#             [ 0.1860,  0.0503, -0.1270, -0.1240, -0.0872],
#           [-0.1924, -0.0684, -0.0028,  0.1031, -0.1053],
#         [-0.0607,  0.1332,  0.0191,  0.1069, -0.0977],
#       [ 0.0095, -0.1570,  0.1730,  0.0674, -0.1589]]],
# 
#         [[[-0.1392,  0.1141, -0.0658,  0.1015,  0.0060],
#             [-0.0519,  0.0341,  0.1161,  0.1492, -0.0370],
#             [ 0.1077,  0.1146,  0.0707,  0.0927,  0.0192],
#             [-0.0656,  0.0929, -0.1735,  0.1019, -0.0546],
#             [ 0.0647, -0.0521, -0.0687,  0.1053, -0.0613]]],
# 
#         [[[-0.1066, -0.0885,  0.1483, -0.0563,  0.0517],
#             [ 0.0266,  0.0752, -0.1901, -0.0931, -0.0657],
#             [ 0.0502, -0.0652,  0.0523, -0.0789, -0.0471],
#            [-0.0800,  0.1297, -0.0205,  0.0450, -0.1029],
#             [-0.1542,  0.1634, -0.0448,  0.0998, -0.1385]]],
# 
#         [[[-0.0943,  0.0256,  0.1632, -0.0361, -0.0557],
#             [ 0.1083, -0.1647,  0.0846, -0.0163,  0.0068],
#             [-0.1241,  0.1761,  0.1914,  0.1492,  0.1270],
#             [ 0.1583,  0.0905,

以上是关于PyTorch-模型建立的主要内容,如果未能解决你的问题,请参考以下文章

PyTorch学习笔记 8. 实现线性回归模型

Pytorch 线性回归损失增加

Pytorch模型训练实用教程学习笔记:三损失函数汇总

如何在一个模型中训练多个损失,并在 pytorch 中选择冻结网络的某些部分?

Pytorch 变压器模型中的大的爆炸性损失

Pytorch保留验证集上最好的模型