pytorch入门:神经网络工具箱

神经网络核心组件

  • 层:神经网络的基本结构,将输入张量转换为输出张量
  • 模型:层构成的网络
  • 损失函数:参数学习的目标函数,通过最小化损失函数来学习各种参数
  • 优化器:如何使损失函数最小,这就是优化器
    在这里插入图片描述
    多个层连接在一起构成一个模型或网络,输入数据通过这个模型转换为预测值,然后损失函数把预测值与真实值进行比较,得到损失值(可以是距离,概率值等),该损失值用于衡量预测值与目标结果的匹配或相似度,优化器利用损失值更新权重参数,是损失值减少,这是一个循环过程,当损失值达到一个阈值或循环次数达到指定次数,循环结束。

pytorch 的 nn 工具箱,对这些组件都有现成的包或类,化身调包侠。。

实现神经网络实例

在这里插入图片描述
构建网络可以基于 Module类或函数(nn.functional) ,nn 中大多数层(Layer)在functional 中都有与之相对应的函数。nn.fuctional 中函数与 nn.Module 中的 Layer 的主要区别就是后者继承 Module 类,会自动提取可学习的参数,而 nn.funcitonal 更像是纯函数,两者功能相同,性能也差不多。

在卷积层,全连接层,Dropout 层等还有可学习的参数,一般使用nn.Module ,激活函数,池化层不含可学习参数,可以是使用nn.functional 中对应的函数。

又来搞 MNIST

  1. 使用内置的函数下载mnist 数据
  2. 利用 torchvision 对数据进行预处理,调用 torch.utils 建立一个数据迭代器。
  3. 可视化源数据
  4. 利用nn工具箱构建神经网络模型
  5. 实例化模型,定义损失函数及优化器
  6. 训练模型
  7. 可视化结果

两个隐藏层,每层使用relu 作为激活函数,使用torch.max(out,1) 找输出out 的最大值对应索引作为预测值
在这里插入图片描述

# 导入一堆没见过的模块。。
import numpy as np
import torch
# 导入 pytorch 内置的 mnist 数据
from torchvision.datasets import mnist 
#import torchvision
#导入预处理模块
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
#导入nn及优化器
import torch.nn.functional as F
import torch.optim as optim
from torch import nn

from tensorboardX import SummaryWriter

# 定义一些超参数
train_batch_size = 64
test_batch_size = 128
learning_rate = 0.01
num_epoches = 20

#定义预处理函数
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.5], [0.5])])
'''
compose就是将一些变换组合在一起,比如这里转换成张量,在归一化。
归一化传入 mean std 的数量要对应图片的通道,RGB 就要是[m1,m2,m3],[s1,s2,s2],
	torchvision.transforms.Normalize(mean,std,inplace = False )
	output[channel] = (input[channel] - mean[channel]) / std[channel]
'''
#下载数据,并对数据进行预处理
train_dataset = mnist.MNIST('./data', train=True, transform=transform, download=True)
test_dataset = mnist.MNIST('./data', train=False, transform=transform)
#得到一个生成器
train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
'''
数据生成器,组合数据集和采样器,参数:bach_size 批量加载数据,shuffle,每个epoch 都会打乱数据默认False。
'''
examples = enumerate(test_loader)
batch_idx, (example_data, example_targets) = next(examples)  # 生成器的用法。
example_data.shape
>>> torch.Size([128, 1, 28, 28])    # 我记得pytorch的通道排列满特殊的
构建模型

数据预处理后进行构建网络,创建模型

class Net(nn.Module):
# 使用Sequential() 函数将网络的层组合到一起,
	def __init__(self,in_dim,n_hidden_1,n_hidden_2,out_dim):
		super(Net,self).__init__()
		self.layer1 = nn.Sequential(nn.Linear(in_dim,n_hidden_1),nn.BatchNormald(n_hidden_1))
		self.layer2 = nn.Sequential(nn.Linear(n_hidden_1,n_hidden_2),nn.BatchNorm1d(n_hidden_2))
        self.layer3 = nn.Sequential(nn.Linear(n_hidden_2, out_dim))
        
    def forward(self, x):
        x = F.relu(self.layer1(x))    # 激活函数
        x = F.relu(self.layer2(x))
        x = self.layer3(x)
        return x

lr = 0.01
momentum = 0.9
#实例化模型
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#if torch.cuda.device_count() > 1:
#    print("Let's use", torch.cuda.device_count(), "GPUs")
#    # dim = 0 [20, xxx] -> [10, ...], [10, ...] on 2GPUs
#    model = nn.DataParallel(model)
model = Net(28 * 28, 300, 100, 10)    # 考虑一下这里的维度变换
model.to(device)
'''
model:
Net(
  (layer1): Sequential(
    (0): Linear(in_features=784, out_features=300, bias=True)   # 维度都对应上了。
    (1): BatchNorm1d(300, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (layer2): Sequential(
    (0): Linear(in_features=300, out_features=100, bias=True)
    (1): BatchNorm1d(100, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
  (layer3): Sequential(
    (0): Linear(in_features=100, out_features=10, bias=True)
  )
)
'''


# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)   # 随机梯度下降
'''
for param in model.parameters():    # 返回参数的生成器
    print(type(param), param.size())
<class 'torch.nn.parameter.Parameter'> torch.Size([300, 784])
<class 'torch.nn.parameter.Parameter'> torch.Size([300])
<class 'torch.nn.parameter.Parameter'> torch.Size([300])
<class 'torch.nn.parameter.Parameter'> torch.Size([300])
<class 'torch.nn.parameter.Parameter'> torch.Size([100, 300])
<class 'torch.nn.parameter.Parameter'> torch.Size([100])
<class 'torch.nn.parameter.Parameter'> torch.Size([100])
<class 'torch.nn.parameter.Parameter'> torch.Size([100])
<class 'torch.nn.parameter.Parameter'> torch.Size([10, 100])
<class 'torch.nn.parameter.Parameter'> torch.Size([10])
'''

# 开始训练
losses = []
acces = []
eval_losses = []
eval_acces = []
writer = SummaryWriter(log_dir='logs',comment='train-loss')

for epoch in range(num_epoches):
    train_loss = 0
    train_acc = 0
    model.train()
    #动态修改参数学习率
    if epoch%5==0:
        optimizer.param_groups[0]['lr']*=0.9      # 5次一减小
        print(optimizer.param_groups[0]['lr'])
    for img, label in train_loader:
        img=img.to(device)
        label = label.to(device)       # 应该就是把这些tensor移动到显存中(用cuda的话)
        img = img.view(img.size(0), -1)
        # 前向传播
        out = model(img)
        loss = criterion(out, label)   # 计算一下损失
        # 反向传播
        optimizer.zero_grad()  # 将所有已优化torch.Tensors的梯度设置为零,,,还是官方手册好使啊
        loss.backward()
        optimizer.step()       # 执行单个优化步骤(参数更新)。
        # 记录误差
        train_loss += loss.item()
        # 保存loss的数据与epoch数值
        writer.add_scalar('Train', train_loss/len(train_loader), epoch)
        # 计算分类的准确率
        _, pred = out.max(1)
        num_correct = (pred == label).sum().item()
        acc = num_correct / img.shape[0]
        train_acc += acc
        
    losses.append(train_loss / len(train_loader))
    acces.append(train_acc / len(train_loader))
    # 在测试集上检验效果
    eval_loss = 0
    eval_acc = 0
    #net.eval() # 将模型改为预测模式,就是 with no_grad():不在进行参数的更新。
    model.eval()
    for img, label in test_loader:
        img=img.to(device)
        label = label.to(device)
        img = img.view(img.size(0), -1)
        out = model(img)
        loss = criterion(out, label)
        # 记录误差
        eval_loss += loss.item()
        # 记录准确率
        _, pred = out.max(1)
        num_correct = (pred == label).sum().item()
        acc = num_correct / img.shape[0]
        eval_acc += acc
        
    eval_losses.append(eval_loss / len(test_loader))
    eval_acces.append(eval_acc / len(test_loader))
    print('epoch: {}, Train Loss: {:.4f}, Train Acc: {:.4f}, Test Loss: {:.4f}, Test Acc: {:.4f}'
          .format(epoch, train_loss / len(train_loader), train_acc / len(train_loader), 
                     eval_loss / len(test_loader), eval_acc / len(test_loader)))
 # 最后一个次的结果
epoch: 19, Train Loss: 0.0035, Train Acc: 0.9992, Test Loss: 0.0562, Test Acc: 0.9844

这样就完成了一个简单的神经网络,这里只有两层,迭代了20次,正确率也很高。

plt.title('train loss')
plt.plot(np.arange(len(losses)), losses)
#plt.plot(np.arange(len(eval_losses)), eval_losses)
#plt.legend(['Train Loss', 'Test Loss'], loc='upper right')
plt.legend(['Train Loss'], loc='upper right')

在这里插入图片描述
你应该会发现多了一个 logs 文件夹,里面有看不懂的文件。。。这个SummaryWriter可以将PyTorch模型和指标记录到目录中,以便在TensorBoard UI中进行可视化。
在命令行中输入 :tensorboard --logdir 。。。。。\logs 需要下载tensorboard 的。然后会出现一个 地址 复制到浏览器打开就行。

如何构建神经网络

上文中使用 nn 工具箱构建了一个神经网络,虽然步骤很多,但关键就是选择网络层,构建网络,选择损失和优化器。nn 工具箱中,有很多可以直接引用的网络,比如全连接,卷积层,循环层,正则化层,激活层等。

定义好这些层后,就可以构建网络层。上文中使用了 torch.nn.Sequential() 来构建网络层。使用起来就像搭积木一样,非常的方便。

class Net1(torch.nn.Module):
    def __init__(self):
        super(Net1, self).__init__()
        self.conv = torch.nn.Sequential(    # 这是通过字典的方式构建网络并给每层一个名字
            OrderedDict(
                [
                    ("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1)),
                    ("relu1", torch.nn.ReLU()),
                    ("pool", torch.nn.MaxPool2d(2))
                ]
            ))

        self.dense = torch.nn.Sequential(
            OrderedDict([
                ("dense1", torch.nn.Linear(32 * 3 * 3, 128)),
                ("relu2", torch.nn.ReLU()),
                ("dense2", torch.nn.Linear(128, 10))
            ])
        )

定义好每层后,通过前向传播将这些层穿起来,这就是定义 forward 函数,forward() 需要把输入层,网络层,输出层连接起来,实现信息的前向传播。

在forward函数中,有些层来自nn.Module,也可以使用nn.functional定 义。来自nn.Module的需要实例化,而使用nn.functional定义的可以直接使 用。

前向传播定义好后,接下来就是反向传播。关键利用的是复合函数的链式求导法则。这时的自动反向传播功能就很方便了,直接让损失函数backward() 就行。反向中,优化器十分重要,比如SGD

层,模型,损失函数,优化器都定义好后就可以训练模型了。训练模型通常要让模型处于训练模式,即调用 Model.train() 。调用这个会把所有的模式设置为训练模式,如果是测试和验证阶段,就调用 model.eval() ,将training属性设置为False。

默认梯度是累加的,需要手动把梯度初始化或清零,optimizer.zero_grad() 。训练过程中,正向传播生成网络的输出,计算输出值和实际值之间的损失。调用 loss.backward() 自动生成梯度,然后使用optimizer.step() 执行优化器,将梯度传播回每个网络。

如果希望使用GPU 训练,就需要把模型,训练数据,测试数据发送到GPU 上,就是to(device) 。

神经网络工具箱nn

之前使用自动求导和tensor实现机器学习时,需要很多设置,叶子节点的 requires_grad 设置为True,然后使用 backward,在从 grad 属性中提取梯度,对于大规模的网络,Autograd 太过底层和繁琐。nn 就是一个有效的工具,可以简单的解决这个问题。nn 工具箱中由两个重要模块。nn.Model nn.functional 。

  1. nn.Module
    是一种数据结构,可以是神经网络的某个层,也可以是包含多层的神经网络。实际使用中就是继承这个模块,生成自己的网络/层。nn中实现的全连接层,卷积层啥的就是 nn.Module 的子类,可以自动检测自己的 Parameter,并将其作为学习参数,且怎对GPU 运行进行了 cuDNN 优化。

  2. nn.funcitonal
    nn 中的层,一类是继承了 nn.Module,命名为nn.Xxx,比如 nn.Linear,nn.Conv2d,nn.CrossEntorpyLoss 等,另一类就是 nn.funtional 中的函数,命名为 nn.funtional.linear, nn.functional.conv2d 等。两者性能是相同的,就是在具体使用中有差别:

  • nn.Xxx 继承了nn.Module ,需要先实例化并传入参数,然后以函数调用的方式调用实例化的对象并传入输入 数据。可以与nn.Sequential 结合使用,nn.functional 就不行
  • nn.Xxx 不需要自己定义和管理 weight, bias 参数,而 nn.functional.xxx 需要自己定义 weight,bias 参数,每次调用都需要手动传入 weight, bias 等参数,不利于代码复用。
  • Dropout操作(就是将一定的训练单元按一定概率暂时舍弃)在训练和测试阶段是不同的,使用nn.Xxx 定义 Dropout,调用 model.eval() 后自动实现状态的转换,而是用 nn.function.xxx 就没有这个功能。

推荐:具有学习参数的(conv2d linear bn) 使用 nn.Xxx方式,没有学习参数的(maxpool, loss func ,activation func) 采用nn.function.xxx。比如上文中的激活函数 F.relu

优化器

常用的优化方法封装在 torch.optim 里,也可以自己定义优化方法。所有方法继承了 optim.Optimizer,并实现了自己的优化步骤。最常用的就是梯度下降法了。之前使用的带 momentum 动量参数的 SGD 就是改良版的。。

使用优化器的步骤:

  1. 实例化优化器:
import torch.optim as optim
optimizer = optim.SGD(model.parametiers(), momentum = momentum)
  1. 将输入数据传递给神经网络 Net 实例化的model 中,自动执行 forward函数,得到out输出值,然后用out 与 label 计算损失
  2. 默认梯度是累加的,在梯度反向之前清空梯度。 optimizer.zero_grad()
  3. 反向传播 ,基于损失进行反向 loss.backward()
  4. 更新参数,基于当期梯度(.grad 属性中)更新参数 optimizer.step()

动态修改学习率参数

修改参数的方法可以通过 optimizer.params_groups 或新建optimizer。后者比较简单,而且轻量。但新的优化器会初始化动力等状态信息,这对于使用动量的优化器有可能造成收敛中的震荡。

optimizer.param_groups 是一元素的列表,里面是6元素的字典,包括权重参数,lr,momentum等参数。

优化器比较

深度学习中常用自适应优化器,性能好,鲁棒性,泛化能力都比较强。

import torch
import torch.utils.data as Data
import torch.nn.functional as F
import matplotlib.pyplot as plt
%matplotlib inline
 
# 超参数
LR = 0.01
BATCH_SIZE = 32
EPOCH = 12
 
# 生成训练数据
# torch.unsqueeze() 的作用是将一维变二维,torch只能处理二维的数据
x = torch.unsqueeze(torch.linspace(-1, 1, 1000), dim=1)  
# 0.1 * torch.normal(x.size())增加噪点
y = x.pow(2) + 0.1 * torch.normal(torch.zeros(*x.size()))

torch_dataset = Data.TensorDataset(x,y)
#得到一个批量的生成器
loader = Data.DataLoader(dataset=torch_dataset, batch_size=BATCH_SIZE, shuffle=True)
 
 
class Net2(torch.nn.Module):
    # 初始化
    def __init__(self):
        super(Net2, self).__init__()
        self.hidden = torch.nn.Linear(1, 20)
        self.predict = torch.nn.Linear(20, 1)
 
    # 前向传递
    def forward(self, x):
        x = F.relu(self.hidden(x))
        x = self.predict(x)
        return x
 
net_SGD = Net2()
net_Momentum = Net2()    # 4种优化器
net_RMSProp = Net2()
net_Adam = Net2()
 
nets = [net_SGD, net_Momentum, net_RMSProp, net_Adam]
 
opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.9)
opt_RMSProp = torch.optim.RMSprop(net_RMSProp.parameters(), lr=LR, alpha=0.9)
opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
optimizers = [opt_SGD, opt_Momentum, opt_RMSProp, opt_Adam]
 
loss_func = torch.nn.MSELoss()
 
loss_his = [[], [], [], []]  # 记录损失
 
for epoch in range(EPOCH):
    for step, (batch_x, batch_y) in enumerate(loader):
        for net, opt,l_his in zip(nets, optimizers, loss_his):
            output = net(batch_x)  				# get output for every net
            loss = loss_func(output, batch_y)  # compute loss for every net
            opt.zero_grad() 				 # clear gradients for next train
            loss.backward()  					# backpropagation, compute gradients
            opt.step() 						 # apply gradients
            l_his.append(loss.data.numpy())  # loss recoder
labels = ['SGD', 'Momentum', 'RMSprop', 'Adam']
for i, l_his in enumerate(loss_his):
    plt.plot(l_his, label=labels[i])
plt.legend(loc='best')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.ylim((0, 0.2))
plt.show()

在这里插入图片描述
SGD 不是最好的啊。。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值