MNIST手写数字识别代码及部分函数用法解释

这是我参考视频教程copy的代码,教程里没有说明每行语句的具体用途,为了理解这段代码,我查找了一些使我困惑的函数和语句的具体用途,用注释和引用块的方式给出,充当备忘。

import torch
# torch.nn用于构建神经网络的基本结构
import torch.nn as nn
# torch.nn.functional,一个神经网络基础函数包。
# 包含卷积,池化,非线性激活函数,归一化函数,线性函数,Dropout函数等
import torch.nn.functional as F
# torch.optim是一个实现了各种优化算法的库。
# 支持大部分常用的方法,并且接口具备足够的通用性,使得未来能够集成更加复杂的方法。
import torch.optim as potim
# torchvision包含了目前流行的数据集,模型结构和常用的图片转换工具。
# torchvision.datasets为数据集
# torchvision.transforms可以对图像进行变换
from torchvision import datasets, transforms
import numpy as np

print("PyTorch Version: ", torch.__version__)
# 上面代码块的输出
PyTorch Version:  1.0.0
# datasets
mnist_data = datasets.MNIST("./mnist_data",
                            train=True,
                            download=True,
                            transform=transforms.Compose(
                                [transforms.ToTensor()]))

mnist_data[0][0].shape
# 上面代码块的输出
torch.Size([1, 28, 28])

torchvision.datasets.MNIST(root, train=True, transform=None, target_transform=None, download=False)
加载MNIST数据集并进行基本操作
root:数据集在本地的保存位置
train(bool, optional):True则该数据集为训练集,False则是测试集。默认为True
transform(callable, optional):一个转换函数,将PIL图像转换为别的形式输出
target_transform(callable, optional):一个函数,输入为target,输出对其的转换。例子,输入的是图片标注的string,输出为word的索引。
download(bool, optional):True = 从互联网上下载数据集,并把数据集放在root目录下。如果数据集之前下载过,将处理过的数据(mnist.py中有相关函数)放在processed文件夹下

torchvision.transforms.Compose(transforms)
对数据集进行一系列转换的函数
transforms:由多个transform组成的列表,依次使用这些transform进行转换

torchvision.transforms.ToTensor()
把一个取值范围是[0,255]的PIL.Image或者shape为(H,W,C)的numpy.ndarray,转换成形状为[C,H,W],取值范围是[0,1.0]的torch.FloadTensor
为什么ndarray是(H, W, C)而Tensor是[C, H, W]:C-通道数,H-高度,W-宽度。ndarray和Tensor表示的是同一张图片,但是二者默认的表示顺序不同。ndarray侧重真实矩阵形式表示,而Tensor面向神经网络训练时的需要(分通道)。


# torch.nn.Module是所有网络的基类
# pytorch下写的任何神经网络都应该继承这个基类
class Net(nn.Module):
    # 初始化一些需要用到的层
    def __init__(self):
        # 在__init__方法中首先执行父类的__init__():super(Net, self).__init__()
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5, 1)
        self.conv2 = nn.Conv2d(20, 50, 5, 1)
        self.fc1 = nn.Linear(4 * 4 * 50, 500)
        self.fc2 = nn.Linear(500, 10)

    # torch.nn.Module的一个抽象方法,所有继承自torch.nn.Module的子类都应该实现该方法
    # 用该方法定义网络具体结构
    # 网络的具体结构为(共6层):
    # 输入 -> 卷积层(relu)-> 最大值池化 -> 卷积层(relu)-> 最大值池化 -> 全连接层(relu)-> 全连接层(log_softmax)-> 输出
    # 该方法隐式执行。如下两行语句将会执行该方法,并将forward(input_tensor)的输出赋值给result
    # net = Net()
    # result = net(input_tensor)
    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2, 2)
        x = x.view(-1, 4 * 4 * 50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

torch.nn.Conv2d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True)
二维卷积层
in_channels(int):输入通道数。手写数字图像是黑白图像,只有一个通道,所以第一个卷积层的输入通道数为1
out_channels(int):输出通道数。这个算法预先设定好了第一个卷积层输出通道为20个,第二个输出通道为50个
kernel_size(int or tuple):卷积核的尺寸。输入为int时卷积核为方形,输入为tuple时可以任意指定形状
stride(int or tuple, optional):卷积的步长。输入为int时任何方向步长相等,输入为tuple时,第一维表示行步长,第二维表示列步长。默认值是1。教程在这里输入了1,可能是习惯吧,习惯了设定不同的步长
padding(int or tuple, optional):为输入图像的每一维的两侧填充0的行数(简而言之,对图像进行零填充以控制图像大小)。tuple与stride的效果同
dilation(int or tuple, optional):卷积核元素之间的间距。比如一个3X3卷积核:在dilation=1时,卷积核在原始图像的3X3的小区域内卷积;在dilation=2时,卷积核的9个元素平均分布在原始图像的5X5的区域内做一次卷积
groups(int, optional):in_channels和out_channels都需要能被groups整除。默认值为1,此时代表每一个输入通道都通过卷积连接到每一个输出通道。当groups=2时,代表输入通道和输出通道平均分成两组,分别进行卷积,然后连接到一起
bias(bool, optional):为True时,在输出时添加一个可学习的偏置

dilation参数的一个例子

torch.nn.Linear(in_features, out_features, bias=True)
对输入做一个线性变换,也就是全连接层
in_features:每个样本输入的大小
out_features:每个样本输出的大小,即该层神经元个数

torch.nn.functional.relu(input, inplace=False)
relu函数,即小于零时置零,大于零时不变
input(Tensor):输入
inplace(bool, optional):选择是否就地操作,即是分配一个新的内存空间还是直接在input上操作。默认为False。

torch.nn.functional.max_pool2d(input, kernel_size, stride=None, padding=0, dilation=1, ceil_mode=False, return_indices=False)
最大值池化
input(Tensor):输入
kernel_size(int or tuple):最大值池化的窗口大小
stride(int or tuple):移动的步长
padding(int or tuple, optional):为输入图像的每一维的两侧填充0的行数
dilation(int or tuple, optional):窗口元素之间的间距。
ceil_mode(bool, optional):计算shape时是否使用向上取整,False则使用向下取整。默认为False
return_indices(bool, optional):是否输出最大值索引(做上池化时需要这个索引),默认为False

torch.nn.functional.log_softmax(input, dim=None, _stacklevel=3, dtype=None)
等效于log(softmax(input))
input(Tensor):输入
dim(int):用来计算log_softmax的那个维度。dim=0时对每一列操作,dim=1时对每一行操作
_stacklevel(int, optional):出现warnings时的stack追踪深度,默认为3
dtype(torch.dtype, optional):如果指定了,就在操作之前将input中元素强转为dtype类型,常用于防止溢出

torch.nn.functional.nll_loss(input, target, weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
input:输入
target:正确分类标签
reduction(string, optional):参数为'none' | 'mean' | 'sum','none'时不做任何处理,'mean'时输出平均数,'sum'时输出总和


def train(model, device, train_loader, optimizer, epoch):
    model.train() # 将模块设置为训练模式,默认参数为True即训练模式,False则为evaluation模式(测试模式)
    for idx, (data, target) in enumerate(train_loader):
        # torch.Tensor.to()将Tensor转换为对应dtype或device版本
        data, target = data.to(device), target.to(device)
        # 将数据传入模型中进行一次训练
        pred = model(data)
        # 计算训练结果与真实值之间的负对数似然损失函数(The negative log likelihood loss)
        loss = F.nll_loss(pred, target)

        # SGD
        # 将梯度设置为0
        optimizer.zero_grad()
        # 反向传播
        loss.backward()
        # 进行一次优化
        optimizer.step()

        if idx % 100 == 0:
            print("Train Epoch: {}, iteration: {} Loss: {}".format(
                epoch, idx, loss.item()))


def test(model, device, test_loader):
    model.eval()  # 将模型设置为测试模式
    total_loss = 0.0
    correct = 0.0
    # 在不需计算grad的情况下运行
    # 因为在计算过程中可能会出现require_grad = True的情形,此时会给grad分配内存空间
    with torch.no_grad():
        for idx, (data, target) in enumerate(test_loader):
            data, target = data.to(device), target.to(device)
            output = model(data)  # 数据经过训练后的模型的结果
            total_loss += F.nll_loss(output, target, reduction="sum").item()  # 计算loss
            pred = output.argmax(dim=1)  # 返回output中所有元素最大值的索引,也就是最终被分到第几类
            correct += pred.eq(target.view_as(pred)).sum().item()  # 计算正确的个数

    total_loss /= len(test_loader.dataset)
    acc = correct / len(test_loader.dataset) * 100.
    print("Test loss: {}, Accuracy: {}".format(total_loss, acc))

torch.optim.Optimizer.zero_grad(set_to_none: bool = False)
将梯度初始化为0
set_to_none(bool, optional):将梯度设置为None,节约空间。但是需要访问grad时会出错,建议保持False

optimizer,zero_grad(),backward(),step()各方法的意义
这部分还有很多可以写,但是个人对这部分的理解有限,所以用的是别人的解释。建议如果有能力的话,自己查找相关资料


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
batch_size = 32
train_dataloader = torch.utils.data.DataLoader(datasets.MNIST(
    "./mnist_data",
    train=True,
    download=True,
    # 此处的0.1307是样本均值,0.3081是样本方差。Normalize进行均值归一化操作
    transform=transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.1307, ), (0.3081, ))])),
                                               batch_size=batch_size,
                                               shuffle=True,
                                               num_workers=1,
                                               pin_memory=True)

test_dataloader = torch.utils.data.DataLoader(datasets.MNIST(
    "./mnist_data",
    train=False,
    download=True,
    transform=transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.1307, ), (0.3081, ))])),
                                              batch_size=batch_size,
                                              shuffle=False,
                                              num_workers=1,
                                              pin_memory=True)

lr = 0.01  # 学习率
momentum = 0.5  # 动量
model = Net().to(device)  # 模型
optimizer = torch.optim.SGD(model.parameters(), lr=lr,
                            momentum=momentum)  # 使用随机梯度下降
num_epochs = 2  # 训练轮数

for epoch in range(num_epochs):
    train(model, device, train_dataloader, optimizer, epoch)
    test(model, device, test_dataloader)

# 保存
torch.save(model.state_dict(), "mnist_cnn.pt")
# 上面代码块的输出
Train Epoch: 0, iteration: 0 Loss: 2.31730055809021
Train Epoch: 0, iteration: 100 Loss: 0.47498029470443726
Train Epoch: 0, iteration: 200 Loss: 0.21783362329006195
Train Epoch: 0, iteration: 300 Loss: 0.14298346638679504
Train Epoch: 0, iteration: 400 Loss: 0.12032605707645416
Train Epoch: 0, iteration: 500 Loss: 0.2093348652124405
Train Epoch: 0, iteration: 600 Loss: 0.07504405081272125
Train Epoch: 0, iteration: 700 Loss: 0.09818609058856964
Train Epoch: 0, iteration: 800 Loss: 0.0559423565864563
Train Epoch: 0, iteration: 900 Loss: 0.01989009976387024
Train Epoch: 0, iteration: 1000 Loss: 0.03885611891746521
Train Epoch: 0, iteration: 1100 Loss: 0.1089647114276886
Train Epoch: 0, iteration: 1200 Loss: 0.01422014832496643
Train Epoch: 0, iteration: 1300 Loss: 0.0961468443274498
Train Epoch: 0, iteration: 1400 Loss: 0.05515772104263306
Train Epoch: 0, iteration: 1500 Loss: 0.16151171922683716
Train Epoch: 0, iteration: 1600 Loss: 0.08142450451850891
Train Epoch: 0, iteration: 1700 Loss: 0.12128034234046936
Train Epoch: 0, iteration: 1800 Loss: 0.02070571482181549
Test loss: 0.07619362027645112, Accuracy: 97.55
Train Epoch: 1, iteration: 0 Loss: 0.01667921245098114
Train Epoch: 1, iteration: 100 Loss: 0.051861390471458435
Train Epoch: 1, iteration: 200 Loss: 0.0019468367099761963
Train Epoch: 1, iteration: 300 Loss: 0.004166126251220703
Train Epoch: 1, iteration: 400 Loss: 0.05624380707740784
Train Epoch: 1, iteration: 500 Loss: 0.05534198880195618
Train Epoch: 1, iteration: 600 Loss: 0.01171034574508667
Train Epoch: 1, iteration: 700 Loss: 0.01633147895336151
Train Epoch: 1, iteration: 800 Loss: 0.06951265037059784
Train Epoch: 1, iteration: 900 Loss: 0.055513106286525726
Train Epoch: 1, iteration: 1000 Loss: 0.03756368160247803
Train Epoch: 1, iteration: 1100 Loss: 0.05086361616849899
Train Epoch: 1, iteration: 1200 Loss: 0.02783718705177307
Train Epoch: 1, iteration: 1300 Loss: 0.056053027510643005
Train Epoch: 1, iteration: 1400 Loss: 0.11548101156949997
Train Epoch: 1, iteration: 1500 Loss: 0.011726364493370056
Train Epoch: 1, iteration: 1600 Loss: 0.1298655867576599
Train Epoch: 1, iteration: 1700 Loss: 0.07312324643135071
Train Epoch: 1, iteration: 1800 Loss: 0.0066553205251693726
Test loss: 0.04361509647369385, Accuracy: 98.67

torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=False, sampler=None, num_workers=0, collate_fn=<function default_collate>, pin_memory=False, drop_last=False)
dataset(Dataset):加载数据的数据集。
batch_size(int, optional):每个batch加载多少个样本(默认: 1)。
shuffle(bool, optional):设置为True时会在每个epoch重新打乱数据(默认: False).
sampler(Sampler, optional):定义从数据集中提取样本的策略。如果指定,则忽略shuffle参数。
num_workers(int, optional):用多少个子进程加载数据。0表示数据将在主进程中加载(默认: 0)
collate_fn(callable, optional):没看懂,默认就行,不影响
pin_memory(bool, optional):如果为True,则DataLoader在将tensor返回之前将其复制到CUDA固定的内存中。
drop_last(bool, optional):如果数据集大小不能被batch size整除,则设置为True后可删除最后一个不完整的batch。如果设为False并且数据集的大小不能被batch size整除,则最后一个batch将更小。(默认: False)

torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)
params(iterable):待优化参数的iterable或者是定义了参数组的dict
lr(float):学习率
momentum(float, optional):动量因子(默认:0)
weight_decay(float, optional):权重衰减(L2惩罚)(默认:0)
dampening(float, optional):动量的抑制因子(默认:0)
nesterov(bool, optional):使用Nesterov动量(默认:False)
关于动量方面,查看Adam算法

torch.save(obj, f, pickle_module=<module 'pickle' from '/home/jenkins/miniconda/lib/python3.5/pickle.py'>, pickle_protocol=2)
将模型保存为硬盘文件
obj:想要保存的对象。此处若是module则保存整个网络状态,加载时使用model=torch.load(mymodel.pt)
此处若是module.state_dict()则只保存参数,不保存网络结构,加载时需先定义好网络结构,然后再load,也即
model = My_model(*args, **kwargs)
model.load_state_dict(torch.load(mymodel.pt))
f:类文件对象 (返回文件描述符)或一个保存文件名的字符串
pickle_module:用于pickling元数据和对象的模块
pickle_protocol:指定pickle protocal 可以覆盖默认参数
保存的文件有三种文件名:.pth,.pt,.pkl,三者除了文件名后缀之外无任何区别。pytorch约定使用.pt文件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容