文章目录

  • Switch to CNN
  • `nn.Sequential`
  • Wrapping `DataLoader`
  • Using your GPU
  • Closing thoughts

原文在这里。

因为MNIST是个分类问题,所以使用逻辑回归是合适的,同时又是个图像问题,所以使用CNN也是合理的。
接下来我们转向CNN,这里展示了两种方式,因为之前的代码已经把model封装起来了,而其余部分并没有涉及到模型本身,所以基本上只要重新定义一个模型,其余部分都不需要改动。

Switch to CNN
class Mnist_CNN(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1)self.conv2 = nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1)self.conv3 = nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1)def forward(self, xb):xb = xb.view(-1, 1, 28, 28)xb = F.relu(self.conv1(xb))xb = F.relu(self.conv2(xb))xb = F.relu(self.conv3(xb))xb = F.avg_pool2d(xb, 4)return xb.view(-1, xb.size(1))
  • 首先,当然,模型类的名字从Mnist_Logistic改成了Mnist_CNN,名字的变化已经意味着这里从逻辑回归转向了CNN,或者说卷积神经网络。
  • 其次,还是两个函数,一个__init__,一个forward,当然这两个函数定义与逻辑回归的模型肯定是不一样的。大体上,我们可以把__init__理解为定义了一个model的框架,而forward则是在这个框架上处理数据的过程。
  • 这里用Conv2d定义了一个3层的卷积网络,注意__init__定义中并没有对输入数据做任何假定,只是定义了卷积网络本身的层次。
  • self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1)表示这一层的输入channel(in_channels)是1,因为是第一层,输入的是黑白图片,所以in_channels是1,输出channel(out_channels)是16,每次计算 k e r n e l _ s i z e × k e r n e l _ s i z e kernel\_size\times kernel\_size kernel_size×kernel_size也就是 3 × 3 3\times3 3×3的格子,步长为2,以及需要填充边缘,因为padding_mode为缺省值'zeros',所以是用0来填充。其余两层的定义是类似的,就不废话了。
  • 至于xb.view(-1, 1, 28, 28),Tensor.view的文档在这里,就这里而言,是把一个torch.Size([64, 784])的张量变形为一个torch.Size([64, 1, 28, 28])的张量。参数列表中的-1表示这个值由其他参数推断,因为变形前后的数据的数量必须保持一致,所以这里就推算出来是64: 64 × 784 = 64 × 1 × 28 × 28 64\times784=64\times 1\times 28\times 28 64×784=64×1×28×28,这个64是每个批次的大小。很容易能够看出来,这里是相当于把本来拉成了一个784维的行向量的 28 × 28 28 \times 28 28×28的图片恢复成了 28 × 28 28 \times 28 28×28。毕竟我们使用的是 3 × 3 3\times3 3×3的卷积核,不能用来处理一个 1 × 784 1\times 784 1×784的行向量。
  • 注意这里的reluavg_pool2d使用的是torch.nn.functional中的函数。李宏毅老师说以前算力不够的时候,在卷积层之间会加入一些pooling层,但是现在一般都不需要加了。而这里加入的这个avg_pool2d更主要的作用我看还是生成一个合适维度的张量。

接下来,原来的程序再做一些改动,其实也就只需要更新一下get_model()

def get_model():#model = Mnist_Logistic()model = Mnist_CNN()return model, optim.SGD(model.parameters(), lr=lr,momentum=0.9)lr = 0.1

lr的变化无所谓的,而momentum之前已经提到过了,其他的保持原样即可。

nn.Sequential

接下来是通过nn.Sequential,一个Sequential对象里面包含着许多模块,并且会以线性方式,或者说依照顺序来执行这些模块。
先看代码:

class Lambda(nn.Module):def __init__(self, func):super().__init__()self.func = funcdef forward(self, x):return self.func(x)def preprocess(x):return x.view(-1, 1, 28, 28)model = nn.Sequential(Lambda(preprocess),nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.AvgPool2d(4),Lambda(lambda x: x.view(x.size(0), -1)),
)opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)fit(epochs, model, loss_func, opt, train_dl, valid_dl)

nn.Sequential文档在这里。先看看model这个Sequential对象的定义。
根据文档,Sequential的声明是这样的:
class torch.nn.Sequential(*args: Module),也就是说参数列表是一个nn.Module列表,如前面所说,pytorch中的Module代表一个类,这里的参数类型中的Module则明确的是pytorch中作为所有神经网络模块的基类的那个Module,当然,使用基类作为paramater的目的就是动态绑定,这就不废话了。

所以,我们需要自己创建一个nn.Module的子类Lambda,用来封装view这个函数的调用,并不是说函数就不能作为对象使用,只不过在Sequential中需要的是Module对象,而不是其他任意的对象。因此,前面class Lambdapreprocess定义的目的所在就很清楚了。

当然,需要自定义一层的原因也是因为pytorch中没有一个view层,或者说没有提供一个Module来实现view这个操作。

上面Sequential的参数列表中出现的Lambda(lambda x: x.view(x.size(0), -1))有必要提一句,就是小写字母开头的这个lambda,表示一个lambda表达式,这是内置的机制,跟大写字母开头的Lambda并没有什么关联。至于为什么这里要用Lambda来作为这个view层的名字,不得而知,就这么用吧。

我们这里只看到了构造modelSequential的参数列表,知道这个Module对象里边有些什么Module,在前面代码我们也看到了最终对model的使用方式是model(xb),这一点,到目前都是一致的,于是很容易就能想到对于一个Sequential对象,这样的调用方式也是一致的,正如Sequential的定义所示,其中定义了这么一个函数或者说方法:

def forward(self, input):for module in self:input = module(input)return input

于是,就很清楚了,这也就是线性的意思。

Wrapping DataLoader

前面的CNN只能用于MNIST,因为:

  • 输入限定是 28 × 28 28\times 28 28×28
  • CNN最末层输出的是 4 × 4 4\times 4 4×4的网格,这限制了最后使用的pooling核,当然,这条限制是双向的。
    以上两条都与输入的数据集的特性有关,从正常的编程逻辑来说,这种数据与操作的深度耦合是需要排除的。
    先看代码:
def preprocess(x, y):return x.view(-1, 1, 28, 28), yclass WrappedDataLoader:def __init__(self, dl, func):self.dl = dlself.func = funcdef __len__(self):return len(self.dl)def __iter__(self):batches = iter(self.dl)for b in batches:yield (self.func(*b))train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)model = nn.Sequential(nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.AdaptiveAvgPool2d(1),Lambda(lambda x: x.view(x.size(0), -1)),
)opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

显然,移除最后pooling层的限制很简单,只是把nn.AvgPool2d(4)换成了nn.AdaptiveAvgPool2d(1),而移除第一条限制,则需要把DataLoader做一个封装,如上面代码中所示。
这里只是需要理解关于preprocess(x, y)的定义中的几个数字,毕竟我们程序的操作不能完全脱离数据而存在,这几个数字是可以或者说需要根据数据的特性更改的。
换句话说,我们处理的终究是2维的图片,天然就是一个 行 × 列 行\times列 行×列的结构,而像(N,C,H,W)这样的元祖,本身也是CNN相关的参数的通用形式。
简单说就是,x.view(-1, 1, 28, 28)中的这几个数字是可以根据数据集的特性手工更改的,对于一门像C或者C++那样的编译型语言来说,这样改当然是不可接受的,但是对于python这样的解释型语言或者说脚本语言来说却是很正常的。

Using your GPU

最后的改进就是使用GPU,程序包括两个方面,数据和操作,所以做下面的这些改动就可以了:

dev = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")def preprocess(x, y):return x.view(-1, 1, 28, 28).to(dev), y.to(dev)model.to(dev)

最后,完整的代码变成了这样:

import torch
import numpy as np
import requests
import pickle
import gzip
import torch.nn.functional as F
from torch import nn
from torch import optim
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
from pathlib import Path
from matplotlib import pyplotDATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"
PATH.mkdir(parents=True, exist_ok=True)
FILENAME = "mnist.pkl.gz"with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")#URL = "https://github.com/pytorch/tutorials/raw/master/_static/"
URL = "https://resources.oreilly.com/live-training/inside-unsupervised-learning/-/raw/9f262477e62c3f5a0aa7eb788e557fc7ad1310de/data/mnist_data/"if not (PATH / FILENAME).exists():content = requests.get(URL + FILENAME).content(PATH / FILENAME).open("wb").write(content)x_train, y_train, x_valid, y_valid = map(torch.tensor, (x_train, y_train, x_valid, y_valid)
)def get_data(train_ds, valid_ds, bs):return (DataLoader(train_ds, batch_size=bs, shuffle=True),DataLoader(valid_ds, batch_size=bs * 2),)def loss_batch(model, loss_func, xb, yb, opt=None):loss = loss_func(model(xb), yb)if opt is not None:loss.backward()opt.step()opt.zero_grad()return loss.item(), len(xb)def fit(epochs, model, loss_func, opt, train_dl, valid_dl):for epoch in range(epochs):model.train()for xb, yb in train_dl:loss_batch(model, loss_func, xb, yb, opt)model.eval()with torch.no_grad():losses, nums = zip(*[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl])val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)print(epoch, val_loss)def preprocess(x, y):return x.view(-1, 1, 28, 28).to(dev), y.to(dev)class WrappedDataLoader:def __init__(self, dl, func):self.dl = dlself.func = funcdef __len__(self):return len(self.dl)def __iter__(self):batches = iter(self.dl)for b in batches:yield (self.func(*b))model = nn.Sequential(nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),nn.ReLU(),nn.AdaptiveAvgPool2d(1),Lambda(lambda x: x.view(x.size(0), -1)),
)lr = 0.1
epochs = 2  # how many epochs to train for
bs = 64
dev = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")train_ds = TensorDataset(x_train, y_train)
valid_ds = TensorDataset(x_valid, y_valid)train_dl, valid_dl = get_data(train_ds, valid_ds, bs)train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)model.to(dev)
opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)
loss_func = F.cross_entropyfit(epochs, model, loss_func, opt, train_dl, valid_dl)
Closing thoughts

大体上,使用nn的过程就如前面所示,其中所使用的一些模块总结如下,为了不至于因为中英文转换产生什么误解,就保留原文如下,毕竟前面都解释过了:

  • torch.nn

    • Module: creates a callable which behaves like a function, but can also contain state(such as neural net layer weights). It knows what Parameter (s) it contains and can zero all their gradients, loop through them for weight updates, etc.

    • Parameter: a wrapper for a tensor that tells a Module that it has weights that need updating during backprop. Only tensors with the requires_grad attribute set are updated

    • functional: a module(usually imported into the F namespace by convention) which contains activation functions, loss functions, etc, as well as non-stateful versions of layers such as convolutional and linear layers.

  • torch.optim: Contains optimizers such as SGD, which update the weights of Parameter during the backward step
  • Dataset: An abstract interface of objects with a len and a getitem, including classes provided with Pytorch such as TensorDataset
  • DataLoader: Takes any Dataset and creates an iterator which returns batches of data.

关于pytorch官网教程中的What is torch.nn really?(三)相关推荐

  1. [pytorch] 官网教程+注释

    pytorch官网教程+注释 Classifier import torch import torchvision import torchvision.transforms as transform ...

  2. [PyTorch] 官网教程之神经网络

    官网中文文档 神经网络 文章目录 核心代码 卷积 卷积 + 分类 网络架构 核心代码 首先介绍一下 torch.nn.Conv2d(),传入参数的含义如下: in_channels # 输入通道数 o ...

  3. pytorch官网教程:cifar10代码理解

    import torch import torchvision import torchvision.transforms as transforms import matplotlib.pyplot ...

  4. pytorch官网教程:autograd代码理解

    # Autograd: 自动求导机制#PyTorch 中所有神经网络的核心是 autograd 包,torch.Tensor是这个包的核心类. #如果设置 .requires_grad 为 True, ...

  5. pytorch官网教程:tensor代码理解

    #tensor from __future__ import print_function import torch #创建一个 5x3 矩阵, 但是未初始化 x = torch.empty(5,3) ...

  6. CMake学习笔记(一)——CMake官网教程

    CMake学习笔记(一)--CMake官网教程 前言: 经历了一星期痛苦的交叉编译,笔者深刻认知到Linux下make的重要性.所以准备放缓两三天自己的工作进度,并学习一下CMake与Makefile ...

  7. 02/03_Pytorch安装、Conda安装Pythorch,换源、pytorch官网、验证、安装jupyter、卸载、安装、启动jupyter、配置Jupyter notebook、使用

    1.2.Pytorch安装 1.2.1.Conda安装Pythorch,换源 1 conda添加清华镜像源 查看源 conda config --show-sources 由于从官方的conda源中下 ...

  8. 官宣!.NET官网发布中⽂版

    #官宣 .NET简体中文版 .NET的官网终于正式发布简体中⽂版本了,⽽且是作为官方支持的第一梯队!这是一个影响未来中国.NET技术生态的大事.我承认好的英文是从事IT技术的重要基础,但本地化语言能帮 ...

  9. MNE溯源fieldtrip官网教程

    MNE溯源fieldtrip官网教程 Introduction 在本教程中,您可以找到有关如何使用最小范数估计进行源重构的信息,以重构单个主题的事件相关字段(MEG).我们将使用预处理教程中描述的数据 ...

最新文章

  1. 关于封装了gevent的request grequest库的使用与讨论
  2. 【Linux】一步一步学Linux——curl命令(193)
  3. 省选考试防爆0注意事项(PART1考试习惯)
  4. netsh interface portproxy 转发不生效_SecureCRT远程端口转发不生效的解决方法
  5. jquery 事件对象属性小结
  6. Linux下nginx安装与配置
  7. 2.9 bitset
  8. leetcode887 鸡蛋掉落
  9. 史上最全 yum 入门使用教程和常见错误解决办法
  10. 基于 Windows Mobile 的 Pocket PC 和 Smartphone 的开发工具简介
  11. vba 判断控件有无_6小时,写了一篇适合Excel小白学的VBA入门教程
  12. CTS(22)---GMS认证-Android8.x新增cts测试(VTS下测试GSI版本)
  13. python 函数调用之后,赋值不变,只是引用。全局变量和局部变量(一分钟读懂)
  14. 量化感知训练_一文速览EMNLP 2020中的Transformer量化论文
  15. 查看windows系统热键占用情况
  16. tcpdump 抓包让wireshark来分析
  17. 别了MongoDB?
  18. 经典排序算法(二十一)--Cycle Sort
  19. 滞后问题_富锂正极材料的电压滞后问题
  20. (转载)学好 Python 的 11 个优秀资源

热门文章

  1. 磁盘柜支持linux,linux 连接磁盘柜
  2. Java--->Ajax(上)
  3. CityMaker学习教程14 水面图层的创建
  4. 【今日学长】来自柚子帮学长--留学生自用英语写作润色工具分享
  5. PNP+NMOS用于充电的原理解释
  6. Every derived table must have its own alias(sql语句错误解决方法)
  7. 全国计算机建模三等奖,青春榜样 | 吴昊 : 守得云开见月明
  8. 字符串分割(split),将字符串按照指定字符进行分割。split(String regex)和split(String regex, int limit)
  9. ggplot2简明教程
  10. 在win10中安装并配置多个JDK不同的版本