神经网络核心组件
- 层:神经网络的基本结构,将输入张量转换为输出张量
- 模型:层构成的网络
- 损失函数:参数学习的目标函数,通过最小化损失函数来学习各种参数
- 优化器:如何使损失函数最小,这就是优化器
多个层连接在一起构成一个模型或网络,输入数据通过这个模型转换为预测值,然后损失函数把预测值与真实值进行比较,得到损失值(可以是距离,概率值等),该损失值用于衡量预测值与目标结果的匹配或相似度,优化器利用损失值更新权重参数,是损失值减少,这是一个循环过程,当损失值达到一个阈值或循环次数达到指定次数,循环结束。
pytorch 的 nn 工具箱,对这些组件都有现成的包或类,化身调包侠。。
实现神经网络实例
构建网络可以基于 Module类或函数(nn.functional) ,nn 中大多数层(Layer)在functional 中都有与之相对应的函数。nn.fuctional 中函数与 nn.Module 中的 Layer 的主要区别就是后者继承 Module 类,会自动提取可学习的参数,而 nn.funcitonal 更像是纯函数,两者功能相同,性能也差不多。
在卷积层,全连接层,Dropout 层等还有可学习的参数,一般使用nn.Module ,激活函数,池化层不含可学习参数,可以是使用nn.functional 中对应的函数。
又来搞 MNIST
- 使用内置的函数下载mnist 数据
- 利用 torchvision 对数据进行预处理,调用 torch.utils 建立一个数据迭代器。
- 可视化源数据
- 利用nn工具箱构建神经网络模型
- 实例化模型,定义损失函数及优化器
- 训练模型
- 可视化结果
两个隐藏层,每层使用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 。
- nn.Module
是一种数据结构,可以是神经网络的某个层,也可以是包含多层的神经网络。实际使用中就是继承这个模块,生成自己的网络/层。nn中实现的全连接层,卷积层啥的就是 nn.Module 的子类,可以自动检测自己的 Parameter,并将其作为学习参数,且怎对GPU 运行进行了 cuDNN 优化。 - 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 就是改良版的。。
使用优化器的步骤:
- 实例化优化器:
import torch.optim as optim
optimizer = optim.SGD(model.parametiers(), momentum = momentum)
- 将输入数据传递给神经网络 Net 实例化的model 中,自动执行 forward函数,得到out输出值,然后用out 与 label 计算损失
- 默认梯度是累加的,在梯度反向之前清空梯度。 optimizer.zero_grad()
- 反向传播 ,基于损失进行反向 loss.backward()
- 更新参数,基于当期梯度(.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 不是最好的啊。。