问题描述:
训练周期epoch=100
学习率= [0.1, 0.01, 0.001, 0.0001]
(1) 不同学习率下的训练精度曲线;
(2) 不同学习率下的训练Loss曲线;
(3) 不同学习率下的验证精度曲线;
(4) 不同学习率下的验证Loss曲线
数据集:
MNIST
训练周期:
epoch=100
网络构造:定义一个三层全连接网络:
class MyNet(nn.Module):
# (5.2) 定义网络有哪些层
def __init__(self) -> None:
super().__init__()
self.flatten = nn.Flatten() # 将28*28 图像变为784一维向量
# 第一个全连接层 Full Connection(FC)
self.fc1 = nn.Linear(in_features=784, out_features=512)
# 第二个
self.fc2 = nn.Linear(in_features=512, out_features=10)
# 第三个
# self.fc3 = nn.Linear(in_features=,out_features=)
# (5.3) 定义数据在网络中的流向
# x - 28*28
def forward(self, x):
x = self.flatten(x) # 输出 784,对应图的Layer1
x = self.fc1(x) # 输出 512 对应layer2
out = self.fc2(x) # 输出 10 对应layer3
return out
损失函数:
loss_fn = nn.CrossEntropyLoss()
优化器:随机梯度下降SGD
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
全部代码:
# coding:utf-8
from concurrent.futures import ThreadPoolExecutor
from torch.utils.data import DataLoader, random_split
from torchvision import datasets
import matplotlib.pyplot as plt
from torchvision.transforms import ToTensor
from torch import nn
import torch
class MyNet(nn.Module):
# (5.2) 定义网络有哪些层
def __init__(self) -> None:
super().__init__()
self.flatten = nn.Flatten() # 将28*28 图像变为784一维向量
# 第一个全连接层 Full Connection(FC)
self.fc1 = nn.Linear(in_features=784, out_features=512)
# 第二个
self.fc2 = nn.Linear(in_features=512, out_features=10)
# 第三个
# self.fc3 = nn.Linear(in_features=,out_features=)
# (5.3) 定义数据在网络中的流向
# x - 28*28
def forward(self, x):
x = self.flatten(x) # 输出 784,对应图的Layer1
x = self.fc1(x) # 输出 512 对应layer2
out = self.fc2(x) # 输出 10 对应layer3
return out
# loss_list:记录每一个周期产生的平均
def train(dataloader, net, loss_fn, optomizer):
size = len(dataloader.dataset)
epoch_loss = 0.0
batch_num = len(dataloader) # 有多少个batch
net.train()
# 精度=预测正确的数量/总数量
correct = 0
# 一个batch一个batch的训练网络
for batch_index, (X, y) in enumerate(dataloader):
# 统计一个周期的平均loss
X, y = X.to(device), y.to(device) # 在GPU上运行X,y #-----------------------调用gpu----------------------------
# --->
pred = net(X)
# 衡量y y_hat之间的loss(差距)
# y 128 pred 128*10 CrossEntropyLoss
loss = loss_fn(pred, y)
# 利用loss的信息 利用优化器 更新网络全部参数 <---
optomizer.zero_grad()
loss.backward()
optomizer.step()
epoch_loss += loss.item() # 每一个batch产生一个loss 加起来
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
# batch 太多 打印的有点多的
if batch_index % 100 == 0:
print(f'[{batch_index :>5d}/{batch_num :>5d}],loss:{loss.item()}') # item将单个tensor转化成数字
avg_loss = epoch_loss / batch_num
avg_accuracy = correct / size
return avg_accuracy, avg_loss
# 评估网络
def net_test(dataloader, net, loss_fn):
size = len(dataloader.dataset)
correct = 0
# 当前处于评估阶段
net.eval()
with torch.no_grad():
for X, y in dataloader:
X, y = X.to(device), y.to(device) # -----------------------调用gpu----------------------------
# 每次遍历都是一个batch
pred = net(X)
# 当前Batch中正确数量
correct += (pred.argmax(1) == y).type(torch.int).sum().item()
# print(correct)
accuracy = correct / size
print(f'Test Accuracy = {accuracy * 100}%')
# 验证网络
def val(dataloader, net, loss_fn):
size = len(dataloader.dataset)
batch_num = len(dataloader)
val_losses = 0
net.eval()
with torch.no_grad():
correct = 0
for X, y in dataloader:
X, y = X.to(device), y.to(device) # -----------------------调用gpu----------------------------
pred = net(X)
loss = loss_fn(pred, y)
val_losses += loss.item() # 一个batch一个loss
correct += (pred.argmax(1) == y).type(torch.int).sum().item() # 一个batch正确数量
accuracy = correct / size
avg = val_losses / batch_num
return accuracy, avg
# 训练集
train_ds = datasets.MNIST(
root='data', # 数据下载路径
download=True, # 保存在本地
train=True,
transform=ToTensor(), # 将原始数据转化为Tensor格式
)
# 划分 训练集+验证集
train_ds, val_ds = random_split(train_ds, [50000, 10000])
# 测试集
test_ds = datasets.MNIST(
root='data', # 数据下载路径
download=True, # 保存在本地
train=False,
transform=ToTensor(),
)
# 训练集
train_loader = DataLoader(
dataset=train_ds,
batch_size=128,
shuffle=True,
)
# 验证集
val_loader = DataLoader(
dataset=val_ds,
batch_size=128,
)
test_loader = DataLoader(
dataset=test_ds,
batch_size=128
)
if __name__ == '__main__':
# pool=ThreadPoolExecutor(4)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
net = MyNet().to(device) # to()在GPU上运行该网络 #-----------------------调用gpu----------------------------
optimizer = torch.optim.SGD(net.parameters(), lr=0.0001) # 随机梯度下降算法
loss_fn = nn.CrossEntropyLoss()
train_acc_list, train_loss_list = [], []
val_accuracy_list, val_loss_list = [], []
try:
for epoch in range(100):
print('-' * 100)
print(f'epoch:{epoch + 1}')
train_acc, train_loss = train(train_loader, net, loss_fn, optimizer)
train_acc_list.append(train_acc)
train_loss_list.append(train_loss)
print(f'Train acc:{train_acc},Train val:{train_loss}')
# 验证集验证
val_accuracy, val_loss = val(val_loader, net, loss_fn)
val_accuracy_list.append(val_accuracy)
val_loss_list.append(val_loss)
except:
print('error')
finally:
# 模型评估
net_test(test_loader, net, loss_fn)
# 可视化
plt.figure()
plt.subplot(1, 1,1)
plt.plot([i for i in range(1, 101)], train_acc_list, label='train_acc')
plt.plot([i for i in range(1, 101)], train_loss_list, label='train_loss')
# plt.xlabel('Epoch')
# plt.ylabel('Loss | accuracy')
# plt.title(f'lr={}')
# plt.legend()
# plt.subplot(1, 2, 2)
plt.plot([i for i in range(1, 101)], val_accuracy_list, label='val_accuracy')
plt.plot([i for i in range(1, 101)], val_loss_list, label='val_loss')
plt.xlabel('Epoch')
plt.ylabel('Loss | accuracy')
plt.title(f'lr=0.1')
plt.legend()
plt.show()
解决方法:
pytorch的learning rate定义主要有三点:
(1)parameter group级,通过[{'params': xxx, 'lr':xx}]对不同的参数组进行分别定义初始学习率
(2)optimizer级,通过optimizer的lr关键字参数定义,比如 torch.optim.SGD(lr=100),该级别低于parameter group级,仅对没有指定lr的parameter group有效
(3)scheduler级,通过torch.optim.lr_scheduler()进行定义。不同于上面两级针对initial learning rate 的情况,该级别是通过epoch联动的一种learning rate scheduler,适用于后期收缩学习率促进模型收敛。在此级别,各个parameter group可以分别定义。另外需要注意,如果决定采用scheduler,在optimizer.step()之后,要调用scheduler.step()更新optimizer的学习率。
当lr(learning rate)分别为0.1, 0.01, 0.001, 0.0001时,分别对网络训练100周期,可以得到如下的训练集准确度及其损失函数和验证集准确度及其损失函数曲线如下图所示:
Lr=0.1
Lr=0.01
Lr=0.001
Lr=0.0001
总结:
学习率对于深度学习来说尤为重要,一个合适的学习率不仅能加速训练的拟合,还能更好地逼近最优解。学习率(Learning rate)作为监督学习以及深度学习中重要的超参,其决定着目标函数能否收敛到局部最小值以及何时收敛到最小值。合适的学习率能够使目标函数在合适的时间内收敛到局部最小值。
由上面4个图可以看出,当学习率较高时,网络模型收敛的速度明显较快,但是这样子得出的网络模型并不稳定,在训练集和验证集的表现的准确度和损失离散型较强,得出的网络模型连续性和可靠性不高,学习率设置的过大时,梯度可能会在最小值附近来回震荡,可能导致无法收敛。而当学习率设置的过小时,此时网络模型收敛过程将十分缓慢,网络模型容易陷入局部最优解出不来。
所以在梯度下降更新参数的时,我们往往需要定义一个学习率来控制参数更新的步幅大小,常用的学习率有0.01、0.001以及0.0001等,学习率越大则参数更新幅度越大。一般来说,我们希望在训练初期学习率大一些,使得网络收敛迅速,在训练后期使学习率小一些,使得网络更好的收敛到最优解。因此我们最好能够动态的更新学习率在训练网络模型时。