PyTorch 搭建神经网络 (MNIST)[含注释]

from __future__ import print_function
# 如果某个版本中出现了某个新的功能特性,而且这个特性和当前版本中使用的不兼容,也就是它在该版本中不是语言标准,那么我如果想要使用的话就需要从future模块导入。
import argparse
# argparse 是 Python 内置的一个用于命令项选项与参数解析的模块,通过在程序中定义好我们需要的参数,argparse 将会从 sys.argv 中解析出这些参数,并自动生成帮助和使用信息。
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
# 数据集加载预处理操作
from torch.optim.lr_scheduler import StepLR
# 提供了一些根据epoch训练次数来调整学习率(learning rate)的方法


"""Pytorch中神经网络模块化接口nn的了解"""
"""
torch.nn是专门为神经网络设计的模块化接口。nn构建于autograd之上,可以用来定义和运行神经网络。
nn.Module是nn中十分重要的类,包含网络各层的定义及forward方法。
定义自已的网络:
   需要继承nn.Module类,并实现forward方法。
   一般把网络中具有可学习参数的层放在构造函数__init__()中,
   不具有可学习参数的层(如ReLU)可放在构造函数中,也可不放在构造函数中(而在forward中使用nn.functional来代替)

   只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现(利用Autograd)。
   在forward函数中可以使用任何Variable支持的函数,毕竟在整个pytorch构建的图中,是Variable在流动。还可以使用
   if,for,print,log等python语法.

   注:Pytorch基于nn.Module构建的模型中,只支持mini-batch的Variable输入方式,
   比如,只有一张输入图片,也需要变成 N x C x H x W 的形式:

   input_image = torch.FloatTensor(1, 28, 28)
   input_image = Variable(input_image)
   input_image = input_image.unsqueeze(0)   # 1 x 1 x 28 x 28
   
我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__构造函数和forward这两个方法。但有一些注意技巧:

(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中,当然我也可以吧不具有参数的层也放在里面;

(2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替
   
(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
模板:
from torch import nn
import torch.nn.functional as F
class net_name(nn.Module):
   def __init__(self):
       super(net_name, self).__init__()
       # 可以添加各种网络层,例如添加self.conv1 = nn.Conv2d(3,10,3)  即:in_channels=3, out_channels=10, kernel_size=3
       self.conv1 = nn.Conv2d(3, 10, 3)
       # 具体每种层的参数可以去查看文档
       
   def forward(self, x):
       # 定义向前传播
       out = self.conv1(x)
       return out


神经网络的实例化调用
data = .....  #输入数据
# 实例化一个对象
module = net_name()
# 前向传播
module(data)  
# 而不是使用下面的
# module.forward(data)  

"""
class Net(nn.Module):
   def __init__(self):
       # nn.Module的子类函数必须在构造函数中执行父类的构造函数
       super(Net, self).__init__()
       # 等价与nn.Module.__init__(),调用父类的构造函数
       self.conv1 = nn.Conv2d(1, 32, 3, 1) # (3*3*1)*32 1为输入通道数,32为输出通道数,3为卷积核大小,1为步长
       self.conv2 = nn.Conv2d(32, 64, 3, 1) # (3*3*32)*64
       # nn.Conv2d返回的是一个Conv2d class的一个对象,该类中包含forward函数的实现
       # 当调用self.conv1(input)的时候,就会调用该类的forward函数
       # 卷积运算图像尺寸变小了,原始是mxn的话,卷积核sxs,卷积后尺寸为:(m-s+1)x(n-s+1)。
       self.dropout1 = nn.Dropout(0.25)
       # 我们在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征
       self.dropout2 = nn.Dropout(0.5)
       self.fc1 = nn.Linear(9216, 128)
       '''
       in_features指的是输入的二维张量的大小,即输入的[batch_size, size]中的size。

       out_features指的是输出的二维张量的大小,即输出的二维张量的形状为[batch_size,output_size],当然,它也代表了该全连接层的神经元个数。

       从输入输出的张量的shape角度来理解,相当于一个输入为[batch_size, in_features]的张量变换成了[batch_size, out_features]的输出张量。
       '''
       self.fc2 = nn.Linear(128, 10)

   def forward(self, x):
       x = self.conv1(x)
       x = F.relu(x)
       x = self.conv2(x)
       x = F.relu(x) # 强制稀疏处理
       x = F.max_pool2d(x, 2) # X为卷积核大小,2为步长
       x = self.dropout1(x)
       x = torch.flatten(x, 1) #降维,与后面的全连接层进行计算
       x = self.fc1(x)
       x = F.relu(x)
       x = self.dropout2(x)
       x = self.fc2(x)
       output = F.log_softmax(x, dim=1)
       '''
       Softmax函数常用的用法是指定参数dim就可以:

(1)dim=0:对每一列的所有元素进行softmax运算,并使得每一列所有元素和为1。

(2)dim=1:对每一行的所有元素进行softmax运算,并使得每一行所有元素和为1。
LogSoftmax其实就是对softmax的结果进行log,即Log(Softmax(x))
       '''
       return output

# 分批次读入数据,优化器梯度置零,数据通过网络,计算损失,反向传播,权重更新
def train(args, model, device, train_loader, optimizer, epoch):
   model.train()
   # 设置train模式
   for batch_idx, (data, target) in enumerate(train_loader): # enumerate枚举
       # 分批读入数据
       data, target = data.to(device), target.to(device)
       optimizer.zero_grad()
       # 优化器梯度置0
       output = model(data)
       # 前向传播
       loss = F.nll_loss(output, target)
       # 计算损失
       loss.backward()
       # 反向传播
       optimizer.step()
       # 权重更新
       if batch_idx % args.log_interval == 0:
           print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
               epoch, batch_idx * len(data), len(train_loader.dataset),
               100. * batch_idx / len(train_loader), loss.item()))
           if args.dry_run:
               break

# 设置为test模式,这是要防止改变已训练好的权重。接着在with torch.no_grad()中让数据通过网络,计算损失和预测是否正确即可
def test(model, device, test_loader):
   model.eval()
   # 设置test模式
   test_loss = 0
   correct = 0
   with torch.no_grad():
       # 不进行图的构建,即没有grad_fn属性
       for data, target in test_loader:
           data, target = data.to(device), target.to(device)
           output = model(data)
           # 前向传播
           test_loss += F.nll_loss(output, target, reduction='sum').item()  # sum up batch loss
           pred = output.argmax(dim=1, keepdim=True)  # get the index of the max log-probability
           correct += pred.eq(target.view_as(pred)).sum().item()

   test_loss /= len(test_loader.dataset)

   print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
       test_loss, correct, len(test_loader.dataset),
       100. * correct / len(test_loader.dataset)))


def main():
   # Training settings
   # 声明一个parser
   parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
   #添加参数
   parser.add_argument('--batch-size', type=int, default=64, metavar='N',
                       help='input batch size for training (default: 64)')
   parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
                       help='input batch size for testing (default: 1000)')
   parser.add_argument('--epochs', type=int, default=14, metavar='N',
                       help='number of epochs to train (default: 14)')
   parser.add_argument('--lr', type=float, default=1.0, metavar='LR',
                       help='learning rate (default: 1.0)')
   parser.add_argument('--gamma', type=float, default=0.7, metavar='M',
                       help='Learning rate step gamma (default: 0.7)')
   parser.add_argument('--no-cuda', action='store_true', default=False,
                       help='disables CUDA training')
   parser.add_argument('--dry-run', action='store_true', default=False,
                       help='quickly check a single pass')
   parser.add_argument('--seed', type=int, default=1, metavar='S',
                       help='random seed (default: 1)')
   parser.add_argument('--log-interval', type=int, default=10, metavar='N',
                       help='how many batches to wait before logging training status')
   parser.add_argument('--save-model', action='store_true', default=False,
                       help='For Saving the current Model')
   args = parser.parse_args()
   # 读取命令行参数
   use_cuda = not args.no_cuda and torch.cuda.is_available()

   torch.manual_seed(args.seed)

   device = torch.device("cuda" if use_cuda else "cpu")

   train_kwargs = {'batch_size': args.batch_size}
   test_kwargs = {'batch_size': args.test_batch_size}
   if use_cuda:
       cuda_kwargs = {'num_workers': 1,
                      'pin_memory': True,
                      'shuffle': True}
       train_kwargs.update(cuda_kwargs)
       test_kwargs.update(cuda_kwargs)

   transform=transforms.Compose([
       transforms.ToTensor(),
       transforms.Normalize((0.1307,), (0.3081,))
       ])
   dataset1 = datasets.MNIST('../data', train=True, download=True,
                      transform=transform)
   dataset2 = datasets.MNIST('../data', train=False,
                      transform=transform)
   train_loader = torch.utils.data.DataLoader(dataset1,**train_kwargs)
   test_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs)

   model = Net().to(device)
   optimizer = optim.Adadelta(model.parameters(), lr=args.lr)

   scheduler = StepLR(optimizer, step_size=1, gamma=args.gamma)
   for epoch in range(1, args.epochs + 1):
       train(args, model, device, train_loader, optimizer, epoch)
       test(model, device, test_loader)
       scheduler.step()

   if args.save_model:
       torch.save(model.state_dict(), "mnist_cnn.pt")


if __name__ == '__main__':

   main() 

你可能感兴趣的:(pytorch,神经网络,深度学习)