学习笔记:jupyter实现AlexNet(部分ImageNet数据集)

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传入)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值