CV with PyTorch 01 - Introduction to processing data

359 阅读22分钟

使用CNN进行数字图像处理的基本流程

一、引言

计算机视觉是人工智能分支中发展得较为成功的领域,它使计算机能有从数字图像或者视频中获得一些“视觉反映”。神经网络能顺利地应用于计算机视觉任务。

假设您正在开发一个识别印刷文本的系统。您已经使用了一些算法方法来对齐页面并删除文本中的单个字符,现在您需要识别单个字母。这个问题被称为图像分类,因为我们需要将输入的图像分成不同的类。这类问题的其他例子是根据图像自动对明信片进行分类,或根据照片确定送货系统中的产品类型。

在这个模块中,我们将学习如何使用 PyTorch 训练图像分类神经网络模型,PyTorch 是最流行的用于构建神经网络的 Python 库之一。我们将从最简单的模型——一个完全连接的稠密神经网络——和一个手写数字的简单 MNIST 数据集开始。然后,我们将学习卷积神经网络,它被设计用来捕捉2d 图像模式,并切换到更复杂的数据集,CIFAR-10。最后,我们将使用预先训练的网络和迁移学习,使我们能够在相对较小的数据集上训练模型。

在本模块的最后,您将能够训练图像分类模型对真实世界的照片,如猫和狗的数据集,并开发图像分类器为您自己的场景

二、总学习目标

  1. 了解计算机视觉任务最常用的解决方案与神经网络
  2. 理解卷积神经网络(cnn)是如何工作的
  3. 训练一个神经网络来识别手写数字,并对猫和狗进行分类
  4. 了解如何使用迁移学习解决现实世界中的分类问题

三、图像数据处理入门

计算机视觉(CV)是研究计算机如何从数字图像和/或视频中获得某种程度的“理解”的一个领域。“理解”一词有一个相当广泛的含义——它可以从能够区分图片上的猫和狗,到更复杂的任务,如用自然语言描述图像。

计算机视觉最常见的问题包括:

下面的图像都是一副输入图像中的ROI,一般并非指整幅输入图像

  • 图像分类是最简单的任务,当我们需要将图像分类到许多预先定义的类别之一时,例如,在照片上区分猫和狗,或者识别手写的数字。
  • 目标检测是一个有点困难的任务,我们需要在图片上找到已知的对象并定位它们,也就是说,返回每个可识别对象的Bounding box。
  • 分割类似于目标检测,但是我们需要返回一个精确的像素映射来勾勒出每一个可识别的对象,而不是给出Bounding box。

图示:

2-image-data-1.png

我们将重点讨论图像分类任务,以及如何利用神经网络来解决这个问题。和其他任何机器学习任务一样,为了训练一个图像分类模型,我们需要一个有标签的数据集,也就是说,每个类都需要大量的图像。

1. 图像张量

计算机视觉与图像相关。正如您可能知道的,图像由像素组成,因此可以将它们看作是像素的矩形集合(数组)。

在本教程的第一部分中,我们将处理手写数字识别。我们将使用 MNIST 数据集,它由手写数字的灰度图像组成,28x28像素。每个图像可以表示为28x28数组,这个数组的元素将表示相应像素的亮度——或者表示范围0到1(在这种情况下使用浮点数) ,或者表示范围0到255(整数)。一个名为 numpy 的流行 python 库经常用于计算机视觉任务,因为它允许对多维数组进行有效操作。

为了处理彩色图像,我们需要一些方法来表示颜色。在大多数情况下,我们用3个灰度值表示每个像素,对应于红(r)、绿(g)和蓝(b)分量。这种颜色编码称为 RGB,因此大小为WHW * H彩色图像的存储尺寸为 3WH3 * W *H (三个通道)。有时组件(RGB三个通道的)的顺序可能不同,但思想是一样的。

  • 5 * 5 的灰度图(归一处理后)

2-image-data-2.png

  • 5 * 5 的彩色(RGB)图像(归一化)

2-image-data-3.png

使用多维数组来表示图像也有一个优点,因为我们可以使用额外的维度来存储图像序列。例如,为了表示一个由200帧组成的800x600维视频片段,我们可以使用大小为200x3x600x800的张量。 上面的三参数分别代表了:视频帧数(图像个数),图像通道数(RGB,灰度图是1不显示),高度,宽度

多维数组也称为张量。通常,在讨论某些神经网络框架(如 PyTorch)时,我们会提到张量。PyTorch 和 numpy 数组中张量的主要区别在于,如果 GPU 可用,张量支持并行操作。此外,PyTorch 还提供了额外的功能,比如在张量上操作时的自动微分。

2. 导入包并加载 MNIST 数据集

#Import the packages needed.
import torch
import torchvision
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline

PyTorch 有许多数据集可以从库中直接获得。在这里,我们使用著名的 MNIST 手写数据集,在 PyTorch 可以通过 torchvison.datasets.MNIST 中获得此数据集。数据集对象以 Python Imagine Library (PIL)图像的形式返回数据,我们通过传递 transform = ToTensor()参数将其转换为tensors,也就是PIL>tensorsPIL -> tensors

当你使用自己的笔记本电脑时,你也可以尝试使用其他内置的数据集,特别是 FashionMNIST。

from torchvision.transforms import ToTensor
# 训练集
data_train = torchvision.datasets.MNIST(
    './data',
    download=True,
    train=True,
    transform=ToTensor()
)
# 测试集
data_test = torchvision.datasets.MNIST(
    './data',
    download=True,
    train=False,
    transform=ToTensor()
)

3. 可视化数据集

现在我们已经下载了数据集,我们可以可视化一些数字

fig,ax = plt.subplots(1,7)
for i in range(7):
    # view的参数要和图像尺寸保持一致
    ax[i].imshow(data_train[i][0].view(28,28))
    ax[i].set_title(data_train[i][1])
    ax[i].axis('off')

output_27_0.png

figure = plt.figure(figsize=(10, 10))
cols, rows = 5, 5
for i in range(1, cols * rows + 1):
# 下面这句怎么理解?
#     sample_idx = torch.randint(len(training_data), size=(1,)).item()

    My_in = np.random.randint(len(data_train))
# randint 和 numpy一致,就是随机生成整数。第一个可省略默认下限为 0,第二个参数就是上限 + 1,
# 第三个参数是生成张量的shape,要传入元组
# 后面的 item()是将 张量类型转化为 Python的基本类型


# 读取单项样本的图片以及标签
    img, label = data_train[My_in] 
    
# 利用matplot显示各项信息   
    figure.add_subplot(rows, cols, i)
    plt.title(label)
    plt.axis("off")
    plt.imshow(img.squeeze())
plt.show()

output_28_0.png

四、数据集结构

我们总共有6000张训练图片和1000张测试图片。为了顺利地训练和测试,我们要将数据分离出来。我们还想做一些数据探索,以便更好地了解我们的数据是什么样的

每个示例是以下结构中的一个元组:

  • 第一个元素是数字的实际图像,由形状为1x28x28的张量表示
  • 第二个元素是一个标签,它指定哪个数字由张量表示。它是一个包含从0到9的数的张量。

data_train 是一个训练数据集,我们将使用它来训练我们的模型。data_ test 是一个较小的测试数据集,我们可以使用它来验证我们的模型。

print('Training samples:',len(data_train))
print('Test samples:',len(data_test))

# 上面介绍过data_train 和 data_test的张量结构 : [样本序号][样本图像(阵列) / 样本标签]
print('Tensor size:',data_train[0][0].size())
print('First 10 digits are:', [data_train[i][1] for i in range(10)])
Training samples: 60000
Test samples: 10000
Tensor size: torch.Size([1, 28, 28])
First 10 digits are: [5, 0, 4, 1, 9, 2, 1, 3, 1, 4]

图像的所有像素强度用0到1之间的浮点值表示:

print('Min intensity value: ',data_train[0][0].min().item())
print('Max intensity value: ',data_train[0][0].max().item())
Min intensity value:  0.0
Max intensity value:  1.0

1. 加载自己的图片

在大多数实际应用中,你可以把自己的图像放在磁盘上,用来训练你的神经网络。在这种情况下,需要将它们加载到 PyTorch 张量中。

其中一种方法是使用 Python 库进行图像处理,比如 OpenCV、 PIL/Pillow 或 imageio。一旦将映像加载到 numpy 数组中,就可以轻松地将其转换为张量。

  • 注意:

    在你把它们传递给神经网络之前(数据准备的通常惯例),一定要确保所有值都缩放到范围[0, 1] (不是二值,而是区间),所有神经网络中的默认权重初始化都是为了适应这个范围而设计的。我们已经看到上面的 ToTensor 变换自动将整像素值的 PIL/numpy 图像缩放为范围[0, 1] 。

    更好的方法是使用 Torchvision 库的功能,即 Imagefoder。它自动完成所有的预处理步骤,并根据目录结构为图像分配标签。一旦我们开始对我们自己的猫和狗图像进行分类,我们将在本课程的后面看到使用 Imageolder 的例子。

    需要注意的是,所有的图像都应该缩放到相同的大小。如果原始图像具有不同的宽高比,则需要决定如何处理这种缩放——裁剪图像或填充额外空间。

2. 补充

神经网络使用张量,在训练任何模型之前,我们需要将数据集转换成一组张量。这通常需要 rWe 加载训练和测试数据集,我们已经准备好开始训练我们的第一个神经网络!

五、训练密集神经网络

让我们集中讨论手写数字识别问题。这是一个分类问题,因为对于每个输入图像,我们需要指定类——它是哪个数字。

在这个单元中,我们从最简单的图像分类方法开始——一个完全连接的神经网络(也称为感知器)。我们将重述 PyTorch 中神经网络的定义方式,以及训练算法的工作原理。如果你熟悉这些概念-请随意跳到下一单元,在那里我们介绍卷积神经网络(CNNs)。

我们使用 pytorchcv 助手来加载前面单元中讨论过的所有数据。

注意,下面如果提示

'setuptools' is a dependency of conda and cannot be removed from conda's operating environment.

则可按照如下办法解决:

  • 卸载setuptools :pip uninstall setuptools

  • 再更新conda:conda update --force conda

  • 最后安装:conda install setuptools

  • 显示successfully

  • 安装torchinfo

    1.安装方法一: pip install torchinfo

    2.安装方法二 :conda install -c conda-forge torchinfo

import torch
import torch.nn as nn
import torchvision
import matplotlib.pyplot as plt
from torchinfo import summary

# 下面有错,尚未解决
# !wget https://raw.githubusercontent.com/MicrosoftDocs/pytorchfundamentals/main/computer-vision-pytorch/pytorchcv.py
# from pytorchcv import load_mnist, plot_results
# load_mnist()

1. 全连接密集神经网络

PyTorch 中的基本神经网络由若干层组成。最简单的网络应该只包括一个完全连接的层,称为 Linear 层,有784个输入(每个输入图像像素有一个输入)和10个输出(每个类有一个输出)。

3-train-dense-neural-networks-1.png

之前谈起过,我们的输入数字图像的是灰度图,维数是 1 * 28 * 28,也就是每个图像都含有 28 *28 = 784 个像素点,因为线性层需要将输入层规范化为1维向量,所以我们应该在网络中的线性层前面插入另一层,称为 Flatten,用于将不同的张量形状规范化为1维。

在 Flatten 之后,有一个主要的线性层(在 PyTorch 术语中称为稠密层) ,它将784个输入转换为10个输出——每个类一个输出。在输出层的每个输出都是返回输入数字等于对应层数的概率

因为一个完全连接层的输出没有被标准化为0到1之间,所以它不能被认为是概率。而且,如果想要输出是不同位数的概率,它们都需要加起来等于1。为了将输出向量转换为概率向量,分类神经网络中经常使用一个叫做 Softmax 的函数作为最后一个激活函数。例如,softmax([1,1,2])=[0.035,0.25,0.705]softmax([-1,1,2]) = [0.035, 0.25, 0.705]

  • 注意

在 PyTorch 中,我们通常喜欢使用 LogSoftmax 函数,它也可以计算输出概率的对数。为了将输出向量转换为实际的概率,我们需要获取输出的 torch.exp

综上所述,我们网络的体系结构可以用以下层次的顺序来表示:

3-train-dense-neural-networks-3.png

在 PyTorch 中可以使用 Sequential 语法以下面的方式定义它:

net = nn.Sequential(
        nn.Flatten(), 
        nn.Linear(784,10), # 784 inputs, 10 outputs
        nn.LogSoftmax())

整个神经网络的层序如下图所示。对于这个图中的所有向量也转化为张量显示。

3-train-dense-neural-networks-2.png

在这个图的右边,我们还有期望的网络输出,表示为一个热编码向量。利用损失函数将预期输出与实际输出进行比较,得到一个数值——损失——作为输出。在网络训练过程中,我们的目标是通过调整模型参数——层权值来最小化这种损失。

2. 训练网络

以这种方式定义的网络可以将任意数字作为输入,并输出概率向量。让我们看看这个网络是如何运行的,从我们的数据集给它一个数字:

print('Digit to be predicted: ',data_train[0][1])
torch.exp(net(data_train[0][0]))
Digit to be predicted:  5


F:\anaconda3\lib\site-packages\torch\nn\modules\container.py:141: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.
  input = module(input)





tensor([[0.1156, 0.1006, 0.0617, 0.0832, 0.1076, 0.1088, 0.0941, 0.1137, 0.1316,
         0.0829]], grad_fn=<ExpBackward0>)
  • 注意:

因为我们使用 LogSoftmax 作为网络的最终激活,所以我们将网络输出通过torch.exp 来获得概率。

正如你所看到的,这个网络为每个数字预测了相似的概率。这是因为它还没有接受过识别数字的训练。我们需要给它我们的训练数据,以在我们的数据集的基础上训练它。

为了训练模型,我们需要从特定大小的数据集中创建批处理,比如说64。有一个叫做 DataLoader 的对象,它可以自动为我们创建批量数据:

train_loader = torch.utils.data.DataLoader(data_train,batch_size=64)
test_loader = torch.utils.data.DataLoader(data_test,batch_size=64) # we can use larger batch size for testing

3. 训练过程

  1. 我们从输入数据集中获取一个小批量,它由输入数据(特征)和预期结果(标签)组成。
  2. 我们计算了这个小批量的预测结果。
  3. 这个结果和预期结果之间的差异是用一个特殊的函数来计算的,这个函数叫做损失函数。损失函数表示网络的输出与预期输出的差异。我们训练的目的是尽量减少损失。
  4. 我们计算这个损失函数对模型的权重(参数)的偏导数 ,然后用来调整权重来优化网络的性能。调整量由一个叫做学习率的参数控制,优化算法的细节在优化器对象中定义。
  5. 我们重复这些步骤,直到处理完整个数据集。

下面是一个执行一个Epoch训练的函数:

def train_epoch(net,dataloader,lr=0.01,optimizer=None,loss_fn = nn.NLLLoss()):
    optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)
    net.train()
    total_loss,acc,count = 0,0,0
    for features,labels in dataloader:
        optimizer.zero_grad()
        out = net(features)
        loss = loss_fn(out,labels) #cross_entropy(out,labels)
        loss.backward()
        optimizer.step()
        total_loss+=loss
        _,predicted = torch.max(out,1)
        acc+=(predicted==labels).sum()
        count+=len(labels)
    return total_loss.item()/count, acc.item()/count

train_epoch(net,train_loader)
(0.005929932149251302, 0.89255)

由于train_epoch这个函数非常通用,我们将能够在以后的示例中使用它。该函数接受以下参数:

  1. Neural network 神经网络

  2. DataLoader,它定义要训练的数据

  3. 损失函数,是一个测量预期结果和网络产生的结果之间差异的函数。在大多数分类任务中使用 nlloss,因此我们将其设置为默认值。

  4. 优化器,它定义了一个优化算法。最传统的算法是随机梯度下降算法,但是我们将默认使用一个更高级的版本,叫做 Adam。

  5. 学习速率决定了网络学习的速度。在学习过程中,我们多次显示相同的数据,并且每次调整权重。如果学习率过高,新的值就会覆盖旧的值,网络就会表现不好。如果学习率太低,就会导致学习过程非常缓慢。

以下是我们训练内容:

  • 将网络切换到训练模式(net.train ())
  • 检查数据集中的所有批次,并对每个批次执行以下操作:
    • 通过神经网络对这批数据进行预测
    • 计算损失,即预测值和期望值之间的差异
    • 通过调整网络的权重来尽量减少损失(optiizer.step ())
    • 计算正确预测的测试样本数(准确性)

该函数计算并返回每个数据项的平均损失,以及训练准确性(正确猜测案例的百分比)。通过在训练中观察这种损失,我们可以看到网络是否在改善,并从提供的数据中学习。

控制测试数据集的准确性(也称为验证准确性)也很重要。一个好的神经网络具有很多参数,可以在任何训练数据集上以相当高的精度进行预测,但是它可能很难推广到其他数据上。这就是为什么在大多数情况下,我们会留出一部分数据,然后定期检查模型在这些数据上的表现。下面是评估测试数据集上的网络的函数:

def validate(net, dataloader,loss_fn=nn.NLLLoss()):
    net.eval()
    count,acc,loss = 0,0,0
    with torch.no_grad():
        for features,labels in dataloader:
            out = net(features)
            loss += loss_fn(out,labels) 
            pred = torch.max(out,1)[1]
            acc += (pred==labels).sum()
            count += len(labels)
    return loss.item()/count, acc.item()/count

validate(net,test_loader)
(0.005863225936889648, 0.8939)

与训练函数类似,我们在测试数据集上返回平均损失和准确性。

4. 过拟合

通常在训练神经网络时,我们要对模型进行几个Epoch的训练,观察训练和验证精度。在开始的时候,训练和验证的准确性都应该提高,因为网络会在数据集中选择模式。然而,在某些情况下,训练的准确性会增加,而验证的准确性会开始下降。这将是一个过度拟合的迹象,即模型在您的训练数据集上表现良好,但在新数据上表现不佳。

下面是可用于执行训练和验证的训练功能。它打印每个历元的训练和验证精度,并返回可用于绘制图的损失和准确性历史记录。

def train(net,train_loader,test_loader,optimizer=None,lr=0.01,epochs=10,loss_fn=nn.NLLLoss()):
    optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)
    res = { 'train_loss' : [], 'train_acc': [], 'val_loss': [], 'val_acc': []}
    for ep in range(epochs):
        tl,ta = train_epoch(net,train_loader,optimizer=optimizer,lr=lr,loss_fn=loss_fn)
        vl,va = validate(net,test_loader,loss_fn=loss_fn)
        print(f"Epoch {ep:2}, Train acc={ta:.3f}, Val acc={va:.3f}, Train loss={tl:.3f}, Val loss={vl:.3f}")
        res['train_loss'].append(tl)
        res['train_acc'].append(ta)
        res['val_loss'].append(vl)
        res['val_acc'].append(va)
    return res

# Re-initialize the network to start from scratch
net = nn.Sequential(
        nn.Flatten(), 
        nn.Linear(784,10), # 784 inputs, 10 outputs
        nn.LogSoftmax())

hist = train(net,train_loader,test_loader,epochs=5)
Epoch  0, Train acc=0.892, Val acc=0.894, Train loss=0.006, Val loss=0.006
Epoch  1, Train acc=0.910, Val acc=0.899, Train loss=0.005, Val loss=0.006
Epoch  2, Train acc=0.913, Val acc=0.898, Train loss=0.005, Val loss=0.006
Epoch  3, Train acc=0.915, Val acc=0.897, Train loss=0.005, Val loss=0.006
Epoch  4, Train acc=0.916, Val acc=0.897, Train loss=0.005, Val loss=0.006

该函数以每个历元的训练和验证数据的准确性记录消息。它还将这些数据作为字典(称为历史)返回。然后,我们可以将这些数据可视化,以更好地理解我们的模型训练。

plt.figure(figsize=(15,5))
plt.subplot(121)
plt.plot(hist['train_acc'], label='Training acc')
plt.plot(hist['val_acc'], label='Validation acc')
plt.legend()
plt.subplot(122)
plt.plot(hist['train_loss'], label='Training loss')
plt.plot(hist['val_loss'], label='Validation loss')
plt.legend()

<matplotlib.legend.Legend at 0x2b532418280>




output_87_1.png

左边的图表显示了训练准确性的提高(这相当于网络学习对我们的训练数据进行了越来越好的分类) ,而验证准确性开始下降。右边的图表显示了训练损失和验证损失,您可以看到训练损失在减少(意味着它的表现更好) ,验证损失在增加(意味着它的表现更差)。这些图表将表明模型是过拟合的。

5. 可视化网络权重

我们网络中的稠密层也被称为线性层,因为它执行其输入的线性映射,可以定义为 y=Wx+by = Wx + b,其中 WW 是权重矩阵, bb 是偏移量。权重矩阵WW实际上负责我们的网络能做什么,即识别数字。在我们的例子中,它的大小为784×10784 × 10因为它为一个输入图像产生10个输出(每个数字一个输出)。

让我们想象一下我们神经网络的权重,看看它们是什么样子的。当网络比一个层更复杂时,很难像这样直观地显示结果,因为在复杂的网络中,可视化的权重并没有多大意义。然而,在我们的情况下,每10个维度的权重矩阵 WW 对应于个别的数字,因此可以可视化看到数字识别是如何发生的。例如,如果我们想看看我们的数字是否是0,我们将输入数字乘以W[0]W[0],然后将结果通过软最大标准化得到答案。

在下面的代码中,我们将首先得到矩阵WW并将其转化为张量形式,它可以通过调用 net.parameters ()方法(返回W&bW \& b) ,然后调用 next 以获取两个参数中的第一个。然后,我们将回顾每个维度和plot,重塑它的形状为28×2828 × 28。你可以看到10个权重张量维数有点像他们分类的数字的平均形状:

weight_tensor = next(net.parameters())
fig,ax = plt.subplots(1,10,figsize=(15,4))
for i,x in enumerate(weight_tensor):
    ax[i].imshow(x.view(28,28).detach())

output_93_0.png

6. 多层感知器

为了进一步提高准确性,我们可能需要包括一个或多个隐藏层。

3-train-dense-neural-networks-4.png

具体的层次结构如下:

3-train-dense-neural-networks-5.png

这里需要注意的是层之间的非线性激活函数,叫做 ReLU。引入这些非线性激活函数是神经网络获得高表达能力的重要原因之一。事实上,可以从数学上证明,如果一个网络只由一系列线性层组成,它基本上等价于一个线性层。因此在层之间插入非线性函数是很重要的!用于模拟人脑的思维模式。

ReLU 是一个最简单的激活函数,定义如下: ReLU(x)={0,x<0x,x0ReLU(x) =\left\{\begin{matrix} 0 , x < 0\\ x, x \ge 0 \end{matrix}\right.

深度学习中使用的其他激活函数有 sigmoid 和 tanh,但是 ReLU 最常用于计算机视觉,因为它可以快速计算,而使用其他函数并没有带来任何显著的好处。

上述网络可以在 PyTorch 中用下面的代码定义:

net = nn.Sequential(
        nn.Flatten(), 
        nn.Linear(784,100),     # 784 inputs, 100 outputs
        nn.ReLU(),              # Activation Function
        nn.Linear(100,10),      # 100 inputs, 10 outputs
        nn.LogSoftmax(dim=0))

summary(net,input_size=(1,28,28))
==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
Sequential                               --                        --
├─Flatten: 1-1                           [1, 784]                  --
├─Linear: 1-2                            [1, 100]                  78,500
├─ReLU: 1-3                              [1, 100]                  --
├─Linear: 1-4                            [1, 10]                   1,010
├─LogSoftmax: 1-5                        [1, 10]                   --
==========================================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
Total mult-adds (M): 0.08
==========================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.32
Estimated Total Size (MB): 0.32
==========================================================================================

在这里,我们使用 summary ()函数来显示一个网络的详细层次结构,以及其他一些有用的信息。特别是,我们可以看到:

  1. 一个网络的层次结构,以及每个层的输出大小
  2. 每一层的参数个数,以及整个网络的参数个数。网络的参数越多,需要训练的数据样本就越多,而且不会过度拟合。

各层参数计算过程[W00W01W02W03W0nW10W11W12W13W1nW00W01W02W03W2nWm0Wm1Wm2Wm3Wmn]×[x0x1x2xn]+[b0b1b2bm]=[O0O1O2Om]\left[\begin{array}{llll} W_{00} & W_{01} & W_{02} & W_{03} & \dots & W_{0n}\\ W_{10} & W_{11} & W_{12} & W_{13} & \dots & W_{1n}\\ W_{00} & W_{01} & W_{02} & W_{03} & \dots & W_{2n}\\ \dots & \dots & \dots & \dots & & \dots \\ W_{m0} & W_{m1} & W_{m2} & W_{m3} & \dots & W_{mn}\\ \end{array}\right] \times\left[\begin{array}{l} x_0 \\ x_1 \\ x_2 \\ \dots \\ x_n \end{array}\right] + \begin{bmatrix} b_0 \\ b_1 \\ b_2 \\ \dots \\ b_m \end{bmatrix}=\left[\begin{array}{l} O_0 \\ O_1 \\ O_2 \\ \dots \\ O_m \end{array}\right]

可以看出: O=W×x+bO = W \times x + b ,这是一个矩阵运算方程,不是常规的一元线性方程!不要弄混了。

  1. 权重矩阵的列数由输入数据个数控制
  2. 权重矩阵的函数和偏移量个数由输出数据个数控制

让我们看看如何计算参数的个数。第一线性层有784个输入和100个输出。层的定义为W1×x+b1W_1 \times x + b_1,其中 W1W_1 的大小为784 × 100,b1b_1的个数为 100。因此,该层的参数总数为784 × 100 + 100 = 78500。同样,第二层的参数为100 × 10 + 10 = 1010 。激活函数和扁平化层没有参数。

from torch.nn.functional import relu, log_softmax

class MyNet(nn.Module):
    def __init__(self):
        super(MyNet, self).__init__()
        self.flatten = nn.Flatten()
        self.hidden = nn.Linear(784,100)
        self.out = nn.Linear(100,10)

    def forward(self, x):
        x = self.flatten(x)
        x = self.hidden(x)
        x = relu(x)
        x = self.out(x)
        x = log_softmax(x,dim=0)
        return x

net = MyNet()

summary(net,input_size=(1,28,28),device='cpu')
==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
MyNet                                    --                        --
├─Flatten: 1-1                           [1, 784]                  --
├─Linear: 1-2                            [1, 100]                  78,500
├─Linear: 1-3                            [1, 10]                   1,010
==========================================================================================
Total params: 79,510
Trainable params: 79,510
Non-trainable params: 0
Total mult-adds (M): 0.08
==========================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.00
Params size (MB): 0.32
Estimated Total Size (MB): 0.32
==========================================================================================

您可以看到,神经网络的结构与序列定义的网络相同,但定义更为明确。我们的定制神经网络由从 torch.nn.Module模块继承的。

hist = train(net,train_loader,test_loader,epochs=5)

plt.figure(figsize=(15,5))
plt.subplot(121)
plt.plot(hist['train_acc'], label='Training acc')
plt.plot(hist['val_acc'], label='Validation acc')
plt.legend()
plt.subplot(122)
plt.plot(hist['train_loss'], label='Training loss')
plt.plot(hist['val_loss'], label='Validation loss')
plt.legend()
Epoch  0, Train acc=0.928, Val acc=0.948, Train loss=0.035, Val loss=0.034
Epoch  1, Train acc=0.953, Val acc=0.956, Train loss=0.033, Val loss=0.033
Epoch  2, Train acc=0.958, Val acc=0.946, Train loss=0.033, Val loss=0.034
Epoch  3, Train acc=0.961, Val acc=0.955, Train loss=0.033, Val loss=0.033
Epoch  4, Train acc=0.961, Val acc=0.957, Train loss=0.033, Val loss=0.034





<matplotlib.legend.Legend at 0x2b507248fd0>




output_110_2.png

7. 提示

在 PyTorch 中训练神经网络可以通过一个训练循环进行编程。这看起来是一个复杂的过程,但是在现实生活中我们只需要写一次,然后只要这些代码内部不发生变化,就可以多次重用。

我们可以看到,单层和多层密集神经网络表现出相对较好的性能,但如果我们尝试将其应用于真实世界的图像,准确度不会太高。在下一个单元中,我们将介绍卷积的概念,这使我们能够得到更好的图像识别性能。