jupyter实现AlexNet
import os
import numpy as np
import cv2
from torchvision import datasets
from torch.utils.data import DataLoader
from torchvision.transforms import transforms
from torch.utils.data.sampler import SubsetRandomSampler
import warnings
warnings.filterwarnings("ignore")
加载数据
ImageNet_root = r"E:\ImageNet\data\ImageNet2012"
train_dir = "train"
val_dir = "val"
normalizer = transforms.Compose([
transforms.RandomResizedCrop(224), #裁剪
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # normalize归一化
])
def get_imagenet(root: object, train: object = True, transform: object = None,
target_transform: object = None) -> object:
if train:
root = os.path.join(root, train_dir)
else:
root = os.path.join(root, val_dir)
return datasets.ImageFolder(root=root,
transform=transform,
target_transform=target_transform)
### 读取数据集 ###
train_datasets = get_imagenet(ImageNet_root, train=True, transform=normalizer)
test_datasets = get_imagenet(ImageNet_root, train=False, transform=normalizer)
### 数据集的大小 ###
num_train = len(train_datasets)
num_test = len(test_datasets)
num_train, num_test
(12982, 500)
batch_size = 256
### 将训练集打乱并分为验证索引和训练索引 ###
valid_size = 0 # 将百分之0划为验证集(这里由于事先分割好了val作为验证所以多余了)
indices = list(range(num_train)) # 生产list,从0到num_train
np.random.shuffle(indices) # 将索引打乱
split = int(valid_size*num_train) # 计算长度
train_idx = indices[split:None] # 训练集索引
valid_idx = indices[None:split] # 验证集索引
#### 将训练数据集划分为新的训练数据集和验证集 ###
train_sampler = SubsetRandomSampler(train_idx) # 相当于将list转换为Sampler,并随机采样序列
valid_sampler = SubsetRandomSampler(valid_idx) # 相当于将list转换为Sampler,并随机采样序列
### 创建数据Loader,python中的生成器,每次条用返回一个batch ###
train_loader = DataLoader(train_datasets, batch_size=batch_size, sampler=train_sampler,
num_workers=0, pin_memory=True)
val_loader = DataLoader(train_datasets, batch_size=batch_size, sampler=valid_sampler,
num_workers=0, pin_memory=True)
test_loader = DataLoader(test_datasets, batch_size=batch_size, shuffle=True,
num_workers=0, pin_memory=True)
可视化数据
# 构造迭代器,获得训练集中的一批次的数据
dataiter = iter(train_loader)
images, labels = next(dataiter)
# 将数据集中的Tensor张量转换为numpy的array数据类型
images = images.permute(0,2,3,1).numpy()
# 这里images的shape为(batch_size, 3, 224, 224)
# 后续图像显示的时候需要的图片格式为(224,224,3) 提前将其进行转换
# permute 是正对于tensor数据类型,进行的维度转换,(1024,3,224,224)->(1024,224,224,3)
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore")
# 可重复运行,展示不同的图片
fig = plt.figure(figsize=(20,10))
for idx in np.arange(18):
ax = fig.add_subplot(3, 6, idx + 1, xticks=[], yticks=[])
random = np.random.randint(len(images))
ax.imshow(images[random])
ax.set_title(str(labels[random].item()))
定义网络结构
import torch
import torch.nn as nn
class AlexNet(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
# 这里使用一个11*11的更大窗口来捕捉对象。
# 同时,步幅为4,以减少输出的高度和宽度。
nn.Conv2d(3,96, kernel_size=11, stride=4, padding=1),
# 输入为224,kernel_size = 11, padding =1 ,stride = 4
# Wnew = (224 -11 + 2*1 )/4 + 1 = 54.75(pytorch向下取整)所以为54
nn.ReLU(),# output : torch.Size([1024, 96, 54, 54])
nn.MaxPool2d(kernel_size=3, stride=2),
# 输入为54,kernel_size = 3, padding = 0 ,stride = 2
# Wnew = (54 - 3 + 2*0 )/2 +1 = 26.5 (pytorch向下取整)所以为26
# output : torch.Size([1024, 96, 26, 26])
nn.Conv2d(96, 256, kernel_size=5, padding=2),
nn.ReLU(),# output = ([1024, 96, 26, 26])
nn.MaxPool2d(kernel_size=3, stride=2), # output = ([1024, 256, 12, 12])
# 使用三个连续的卷积层和较小的卷积窗口。
# 除了最后的卷积层,输出通道的数量进一步增加。
# 在前两个卷积层之后,汇聚层不用于减少输入的高度和宽度
nn.Conv2d(256, 384, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Flatten(),
# 这里,全连接层的输出数量是LeNet中的好几倍。使用dropout层来减轻过拟合
nn.Linear(6400, 4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 10)
# 原文对于ImageNet最后输出的是1000,由于计算量太大花费时间太多
# 本文只用其中10类进行测试,查看结果
)
def forward(self,x):
x = self.model(x)
return x
net = AlexNet()
if torch.cuda.is_available():
net = net.cuda()
print("*"*10,"Cuda: ",torch.cuda.is_available(),"*"*10)
********** Cuda: True **********
定义损失函数和梯度下降优化器
这里原本使用的优化器为随机梯度下降(stochastic gradient descent,SGD),但是对于学习率的把控不好,因此选择是用Adam。
# 损失函数为交叉熵损失函数
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():
loss_fn = loss_fn.cuda()
print("*"*10,"Cuda: ",torch.cuda.is_available(),"*"*10)
# 定义优化器
optimizer = torch.optim.Adam(net.parameters())
********** Cuda: True **********
训练神经网络
- 1.清除所有梯度
- 2.正向预测,求出模型对数据集的预测分类
- 3.计算损失函数
- 4.反向传播,计算损失函数的每一个权重求导,求得对应权重的梯度
- 5.优化器更新权重
- 6.计算每一轮的平均训练误差和验证误差
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True # 对一些损坏图像的进行处理
total_epoch = 500
# 初始化验证集最小误差为无穷大
valid_loss_min = np.Inf
# 初始化loss_list
train_loss_list = []
valid_loss_list = []
train_acc_list = []
valid_acc_list = []
# 每一轮训练:
for epoch in range(total_epoch):
# 初始化损失
train_loss = 0
valid_loss = 0
train_correct = 0
train_total = 0
valid_correct = 0
valid_total = 0
### 训练阶段 ###
net.train()
for data in train_loader:
imgs, labels = data # 读取数据
if torch.cuda.is_available():
imgs = imgs.cuda()
labels = labels.cuda()
optimizer.zero_grad() # 将梯度归零
output = net(imgs) # 正向预测
loss = loss_fn(output,labels) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 优化器进行更新
train_loss += loss.item()*imgs.size(0) # 计算本批次损失
for i in range(output.shape[0]):
pred = torch.argmax(output[i])
if pred == labels[i].item():
train_correct += 1
train_total += 1
### 验证阶段 ###
net.eval()
for data in test_loader:
imgs, labels = data # 读取数据
if torch.cuda.is_available():
imgs = imgs.cuda()
labels = labels.cuda()
output = net(imgs) # 正向预测
loss = loss_fn(output,labels) # 计算损失
valid_loss += loss.item()*imgs.size(0) # 计算本批次损失
for i in range(output.shape[0]):
pred = torch.argmax(output[i])
if pred == labels[i].item():
valid_correct+= 1
valid_total += 1
# 结束本论的训练和验证,打印训练和验证的指标
# 计算平均训练损失和平均验证损失,存储在列表中
train_loss = train_loss/len(val_loader.dataset)
train_accy = train_correct/train_total
valid_loss = valid_loss/len(test_loader.dataset)
valid_accy = valid_correct/valid_total
train_acc_list.append(train_accy)
valid_acc_list.append(valid_accy)
train_loss_list.append(train_loss)
valid_loss_list.append(valid_loss)
if (epoch + 1) % 10 == 0:
print("----------------------------------------------------")
print("第{}轮 \t训练损失: {:.6f} acc:{:.2f} %\t验证损失:{:.6f} acc:{:.2f} %".format(epoch+1,
train_loss, train_accy*100,
valid_loss, valid_accy*100))
# 损失有降低则保存到本地
if valid_loss <= valid_loss_min:
print("验证误差比上次降低了({:.6f} ---> {:.6f}). 保存模型!".format(valid_loss_min, valid_loss))
torch.save(net.state_dict(), r"./result/AlexNet.pth")
valid_loss_min = valid_loss
----------------------------------------------------
第10轮 训练损失: 1.654902 acc:41.71 % 验证损失:1.797578 acc:37.80 %
验证误差比上次降低了(inf ---> 1.797578). 保存模型!
----------------------------------------------------
第20轮 训练损失: 1.402842 acc:52.78 % 验证损失:1.360429 acc:55.20 %
验证误差比上次降低了(1.797578 ---> 1.360429). 保存模型!
----------------------------------------------------
第30轮 训练损失: 1.268598 acc:57.96 % 验证损失:1.289452 acc:57.00 %
验证误差比上次降低了(1.360429 ---> 1.289452). 保存模型!
----------------------------------------------------
第40轮 训练损失: 1.205633 acc:59.88 % 验证损失:1.244767 acc:58.40 %
验证误差比上次降低了(1.289452 ---> 1.244767). 保存模型!
----------------------------------------------------
第50轮 训练损失: 1.167855 acc:61.54 % 验证损失:1.272040 acc:58.80 %
----------------------------------------------------
第60轮 训练损失: 1.141127 acc:62.48 % 验证损失:1.206864 acc:61.20 %
验证误差比上次降低了(1.244767 ---> 1.206864). 保存模型!
----------------------------------------------------
第70轮 训练损失: 1.097863 acc:63.62 % 验证损失:1.248888 acc:60.00 %
----------------------------------------------------
第80轮 训练损失: 1.051647 acc:65.78 % 验证损失:1.128490 acc:61.40 %
验证误差比上次降低了(1.206864 ---> 1.128490). 保存模型!
----------------------------------------------------
第90轮 训练损失: 1.064107 acc:65.08 % 验证损失:1.111140 acc:64.00 %
验证误差比上次降低了(1.128490 ---> 1.111140). 保存模型!
----------------------------------------------------
第100轮 训练损失: 1.023992 acc:66.97 % 验证损失:1.150746 acc:63.00 %
----------------------------------------------------
第110轮 训练损失: 1.021088 acc:66.56 % 验证损失:1.127476 acc:63.20 %
----------------------------------------------------
第120轮 训练损失: 1.034758 acc:66.32 % 验证损失:1.111344 acc:64.00 %
----------------------------------------------------
第130轮 训练损失: 0.980869 acc:68.15 % 验证损失:1.197125 acc:63.00 %
----------------------------------------------------
第140轮 训练损失: 1.009399 acc:67.16 % 验证损失:1.218727 acc:62.60 %
----------------------------------------------------
第150轮 训练损失: 1.004314 acc:67.17 % 验证损失:1.100907 acc:66.80 %
验证误差比上次降低了(1.111140 ---> 1.100907). 保存模型!
----------------------------------------------------
第160轮 训练损失: 0.987153 acc:67.65 % 验证损失:1.123437 acc:65.40 %
----------------------------------------------------
第170轮 训练损失: 0.966050 acc:68.69 % 验证损失:1.119779 acc:63.60 %
----------------------------------------------------
第180轮 训练损失: 0.984984 acc:68.02 % 验证损失:1.125073 acc:63.40 %
----------------------------------------------------
第190轮 训练损失: 0.974142 acc:68.88 % 验证损失:1.067913 acc:65.40 %
验证误差比上次降低了(1.100907 ---> 1.067913). 保存模型!
----------------------------------------------------
第200轮 训练损失: 0.984885 acc:68.36 % 验证损失:1.068410 acc:62.60 %
----------------------------------------------------
第210轮 训练损失: 0.949738 acc:69.30 % 验证损失:1.140765 acc:63.40 %
----------------------------------------------------
第220轮 训练损失: 0.955743 acc:68.83 % 验证损失:1.066979 acc:66.80 %
验证误差比上次降低了(1.067913 ---> 1.066979). 保存模型!
----------------------------------------------------
第230轮 训练损失: 0.949058 acc:69.61 % 验证损失:1.108363 acc:67.60 %
----------------------------------------------------
第240轮 训练损失: 0.959643 acc:69.37 % 验证损失:1.092590 acc:64.60 %
----------------------------------------------------
第250轮 训练损失: 0.966921 acc:68.86 % 验证损失:1.065056 acc:66.20 %
验证误差比上次降低了(1.066979 ---> 1.065056). 保存模型!
----------------------------------------------------
第260轮 训练损失: 0.952408 acc:69.50 % 验证损失:1.082939 acc:67.00 %
----------------------------------------------------
第270轮 训练损失: 0.957739 acc:68.33 % 验证损失:1.159494 acc:62.00 %
----------------------------------------------------
第280轮 训练损失: 0.941980 acc:69.87 % 验证损失:1.113967 acc:66.80 %
----------------------------------------------------
第290轮 训练损失: 0.954954 acc:69.86 % 验证损失:1.078580 acc:67.00 %
----------------------------------------------------
第300轮 训练损失: 0.950925 acc:69.63 % 验证损失:1.075574 acc:64.60 %
----------------------------------------------------
第310轮 训练损失: 0.973442 acc:68.96 % 验证损失:1.095759 acc:67.40 %
----------------------------------------------------
第320轮 训练损失: 0.919000 acc:70.75 % 验证损失:0.996260 acc:67.60 %
验证误差比上次降低了(1.065056 ---> 0.996260). 保存模型!
----------------------------------------------------
第330轮 训练损失: 0.930577 acc:70.45 % 验证损失:0.988433 acc:72.20 %
验证误差比上次降低了(0.996260 ---> 0.988433). 保存模型!
----------------------------------------------------
第340轮 训练损失: 0.930202 acc:70.06 % 验证损失:1.008915 acc:70.00 %
----------------------------------------------------
第350轮 训练损失: 0.940089 acc:69.61 % 验证损失:1.046859 acc:67.40 %
----------------------------------------------------
第360轮 训练损失: 0.944414 acc:69.73 % 验证损失:1.001023 acc:67.80 %
----------------------------------------------------
第370轮 训练损失: 0.934163 acc:69.84 % 验证损失:1.007215 acc:69.80 %
----------------------------------------------------
第380轮 训练损失: 1.759061 acc:40.80 % 验证损失:1.636144 acc:45.00 %
----------------------------------------------------
第390轮 训练损失: 1.212563 acc:60.35 % 验证损失:1.233773 acc:60.60 %
----------------------------------------------------
第400轮 训练损失: 1.078003 acc:65.14 % 验证损失:1.070338 acc:64.80 %
----------------------------------------------------
第410轮 训练损失: 1.040488 acc:66.53 % 验证损失:1.195387 acc:62.40 %
----------------------------------------------------
第420轮 训练损失: 1.104977 acc:64.64 % 验证损失:1.251884 acc:59.40 %
----------------------------------------------------
第430轮 训练损失: 1.026296 acc:67.45 % 验证损失:1.091027 acc:63.00 %
----------------------------------------------------
第440轮 训练损失: 1.055481 acc:66.94 % 验证损失:1.048315 acc:65.00 %
----------------------------------------------------
第450轮 训练损失: 0.979317 acc:69.39 % 验证损失:1.167682 acc:62.40 %
----------------------------------------------------
第460轮 训练损失: 0.950709 acc:69.31 % 验证损失:1.031637 acc:67.00 %
----------------------------------------------------
第470轮 训练损失: 0.972795 acc:68.73 % 验证损失:1.126938 acc:63.20 %
----------------------------------------------------
第480轮 训练损失: 0.924043 acc:70.76 % 验证损失:1.026253 acc:65.40 %
----------------------------------------------------
第490轮 训练损失: 0.917942 acc:70.81 % 验证损失:1.030939 acc:66.20 %
----------------------------------------------------
第500轮 训练损失: 0.920525 acc:71.34 % 验证损失:1.049176 acc:64.20 %
plt.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
plt.plot(train_loss_list[0:100],label="训练误差")
plt.plot(valid_loss_list[0:100],label="验证误差")
plt.legend() # 将label贴上去
plt.title('训练误差和验证误差变化')
plt.show()
plt.plot(train_acc_list[0:100],label="训练准确率")
plt.plot(valid_acc_list[0:100],label="验证准确率")
plt.legend() # 将label贴上去
plt.title('准确率变化')
plt.show()
总结:
训练了几次对于batch_size的更改,小的batch_size训练比较满,与所了解到mini-batch更好的结论有点相反,但是也可能是特殊情况,在训练中也由于显卡并没有跑满速度感觉很慢,提高batch_size可以增加显存的占用,但是对于训练的结果可能不好。因此针对这次简单的训练引发了几个问题。
1. batch_size应该怎么大小选择?
- 当有足够算力时,选取batch size为32或更小一些,
- 算力不够时,在效率和泛化性之间做trade-off,尽量选择更小的batch size。
2. SGD学习率的选择多少比较合适?
- 在训练过程中,一般根据训练轮数设置动态变化的学习率。
- 刚开始可以调整到0.01~0.001之间,后续再慢慢调整。
4. 需要怎么做才能发挥出显卡的最大性能去跑模型?
可以再DataLoader中设置参数:
num_workers (int, optional): how many subprocesses to use for data
loading.0
means that the data will be loaded in the main process.
(default:0
)
pin_memory (bool, optional): If
True
, the data loader will copy Tensors
into CUDA pinned memory before returning them. If your data elements
are a custom type, or your :attr:collate_fn
returns a batch that is a custom type,
see the example below.*
num_workers代表是cpu计算,用主进程还是多个进程,pin_memory则是将数据放入CUDA中,在GPU上加载数据,使数据更快。一般情况下cpu读取数据会有瓶颈,就是io读取慢,gpu计算完了而下一批数据还没到。形象点形容就是:吃饭的时候,胃(GPU)都已经将食物(数据)消化完了,食物还没从嘴里进来(CPU传入)