GoogLeNet——网络实战
Posted AI浩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了GoogLeNet——网络实战相关的知识,希望对你有一定的参考价值。
文章目录
🐇🐇🐇🐇🐇🐇 🐇 欢迎阅读 【AI浩】 的博客🐇 👍 阅读完毕,可以动动小手赞一下👍 🌻 发现错误,直接评论区中指正吧🌻 📆 这是一篇讲解GoogLeNet实战的文章📆 💯专栏目录: 经典主干网络精讲与实战💯
摘要🐇
GoogLeNet作为2014年ILSVRC在分类任务上的冠军,以6.65%的错误率力压VGGNet等模型,在分类的准确率上面相比过去两届冠军ZFNet和AlexNet都有很大的提升。在前面的文章我介绍了GoogLeNet的网络结构和数据集的制作,这篇文章我将和大家一起完成GoogLeNet模型的实战。
数据集选用第三篇 制作数据集制作的数据集,模型用Pytorh自带的GoogLeNet。在这篇文章中,我尽量的简化代码,只保留最基本的逻辑,让每一个初学者能够看明白。通过这篇文章你能学到:
1、如何训练模型?
2、如何推理?
3、如何读取数据集、处理数据集?
4、如何使用余弦退火调整学习率?
5、如何保存权重文件和整个模型文件?
6、如何使用评价指标,如ACC、ReCall等指标评价模型。
7、如何使用matplotlib.pyplot绘制acc和loss曲线图?
1 项目结构🐇
GoogLeNet_Demo
├─trainvals
│ ├─双子座
│ ├─双鱼座
│ ├─处女座
│ ├─天秤座
│ ├─天蝎座
│ ├─射手座
│ ├─山羊座
│ ├─巨蟹座
│ ├─水瓶座
│ ├─狮子座
│ ├─白羊座
│ └─金牛座
├─test
├─makedata.py
├─mean_std.py
├─train.py
└─test.py
trainvals:数据集,接下来我们将其划分为训练集和验证集。
test:测试集
makedata.py:划分数据集的方法
mean_std.py:计算mean和std的值。
ema.py:EMA脚本
train.py:训练GoogLeNet模型
2 划分训练集和测试集🐇
import glob
import os
import shutil
from sklearn.model_selection import train_test_split
image_list=glob.glob('trainvals/*/*.*')
print(image_list)
file_dir='data'
if os.path.exists(file_dir):
print('true')
shutil.rmtree(file_dir)#删除再建立
os.makedirs(file_dir)
else:
os.makedirs(file_dir)
trainval_files, val_files = train_test_split(image_list, test_size=0.2, random_state=42)
train_dir='train'
val_dir='val'
train_root=os.path.join(file_dir,train_dir)
val_root=os.path.join(file_dir,val_dir)
for file in trainval_files:
file_class=file.replace("\\\\","/").split('/')[-2]
file_name=file.replace("\\\\","/").split('/')[-1]
file_class=os.path.join(train_root,file_class)
if not os.path.isdir(file_class):
os.makedirs(file_class)
shutil.copy(file, file_class + '/' + file_name)
for file in val_files:
file_class=file.replace("\\\\","/").split('/')[-2]
file_name=file.replace("\\\\","/").split('/')[-1]
file_class=os.path.join(val_root,file_class)
if not os.path.isdir(file_class):
os.makedirs(file_class)
shutil.copy(file, file_class + '/' + file_name)
核心思路:
- 1、将所有的图片读取到list中。
- 2、sklearn.model_selection的train_test_split方法将list切分。这一步需要安装sklearn这个库,安装命令:
pip install sklearn
- 3、保存切分结果。
运行结果:
如上结果,我们就将训练集和测试集划分好了。
3 计算mean和Standard🐇
使用深度学习进行图像分类或者图像检测时,首先需要对图像进行数据预处理,常见的对图像的预处理有两种办法,一种标准化处理,另一种是归一化处理。
3.1 标准化的作用🐇
图像标准化是将数据通过去均值实现中心化的处理,根据凸优化理论与数据概率分布相关知识,数据中心化符合数据分布规律,更容易取得训练之后的泛化效果。
3.2 归一化的作用🐇
归一化不改变图像信息,只是把像素从0-255变成0~1的范围,加快训练网络的收敛性,使得所有样本的输入信号其均值接近于0或与其均方差相比很小。
总之,为了使模型更加快速的收敛和具有更好的泛化性,我们需要计算出mean和std的值,新建mean_std.py,插入代码:
from torchvision.datasets import ImageFolder
import torch
from torchvision import transforms
def get_mean_and_std(train_data):
train_loader = torch.utils.data.DataLoader(
train_data, batch_size=1, shuffle=False, num_workers=0,
pin_memory=True)
mean = torch.zeros(3)
std = torch.zeros(3)
for X, _ in train_loader:
for d in range(3):
mean[d] += X[:, d, :, :].mean()
std[d] += X[:, d, :, :].std()
mean.div_(len(train_data))
std.div_(len(train_data))
return list(mean.numpy()), list(std.numpy())
if __name__ == '__main__':
train_dataset = ImageFolder(root=r'data1', transform=transforms.ToTensor())
print(get_mean_and_std(train_dataset))
运行结果:
([0.48214436, 0.42969334, 0.33318862], [0.2642221, 0.23746745, 0.21696019])
生成两个list,一个list是mean的结果,一个list是std的结果。
4 训练🐇
新建train.py脚本。
4.1 导入项目使用的库🐇
import torch.optim as optim
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.optim
import torch.utils.data
import torch.utils.data.distributed
import torchvision.transforms as transforms
from torchvision.models import googlenet
import os
from torchvision import datasets
import json
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
4.2 设置随机因子🐇
设置随机因子后,再次训练时,图像的加载顺序不会改变,能够更好的复现训练结果。代码如下:
def seed_everything(seed=42):
os.environ['PYHTONHASHSEED'] = str(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.backends.cudnn.deterministic = True
4.3 设置全局参数🐇
if __name__ == '__main__':
#创建保存模型的文件夹
file_dir = 'checkpoints/googlenet'
if os.path.exists(file_dir):
print('true')
os.makedirs(file_dir,exist_ok=True)
else:
os.makedirs(file_dir)
# 设置全局参数
model_lr = 1e-4
BATCH_SIZE = 16
EPOCHS = 1000
DEVICE = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
classes = 12
resume = None
Best_ACC = 0 #记录最高得分
SEED=42
seed_everything(42)
start_epoch=1
设置存放权重文件的文件夹,如果文件夹存在删除再建立。
接下来,设置全局参数,比如:学习率、BatchSize、epoch等参数,判断环境中是否存在GPU,如果没有则使用CPU。
注:建议使用GPU,CPU太慢了。
参数的详细解释:
model_lr:学习率,根据实际情况做调整。
BATCH_SIZE:batchsize,根据显卡的大小设置。
EPOCHS:epoch的个数,一般300够用。
classes:类别个数。
resume:再次训练的模型路径,如果不为None,则表示加载resume指向的模型继续训练。
Best_ACC:记录最高ACC得分。
start_epoch:开始的epoch,默认是1,如果重新训练时,需要给start_epoch重新赋值。
SEED:随机因子,数值可以随意设定,但是设置后,不要随意更改,更改后,图片加载的顺序会改变,影响测试结果。
4.4 图像预处理与增强🐇
# 数据预处理7
transform = transforms.Compose([
transforms.RandomRotation(10),
transforms.GaussianBlur(kernel_size=(5,5),sigma=(0.1, 3.0)),
transforms.ColorJitter(brightness=0.5, contrast=0.5, saturation=0.5),
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.44127703, 0.4712498, 0.43714803], std= [0.18507297, 0.18050247, 0.16784933])
])
transform_test = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.44127703, 0.4712498, 0.43714803], std= [0.18507297, 0.18050247, 0.16784933])
])
数据处理和增强比较简单,加入了随机10度的旋转、高斯模糊、色彩饱和度明亮度的变化等比较常用的增强手段,做了Resize、归一化和标准化处理。
这里注意下Resize的大小,由于选用的GoogLeNet模型输入是224×224的大小,所以要Resize为224×224。
4.5 读取数据🐇
# 读取数据
dataset_train = datasets.ImageFolder('data/train', transform=transform)
dataset_test = datasets.ImageFolder("data/val", transform=transform_test)
with open('class.txt', 'w') as file:
file.write(str(dataset_train.class_to_idx))
with open('class.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(dataset_train.class_to_idx))
# 导入数据
train_loader = torch.utils.data.DataLoader(dataset_train, batch_size=BATCH_SIZE,pin_memory=True, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset_test, batch_size=BATCH_SIZE,pin_memory=True, shuffle=False)
-
使用pytorch默认读取数据的方式,然后将dataset_train.class_to_idx打印出来,预测的时候要用到。
-
对于train_loader和test_loader ,pin_memory设置为True,可以加快运行速度,训练需要将shuffle设置为True,用于将图片加载的顺序打乱,验证时不用将图片的顺序打乱。
-
将dataset_train.class_to_idx保存到txt文件或者json文件中。
class_to_idx的结果:
'双子座': 0, '双鱼座': 1, '处女座': 2, '天秤座': 3, '天蝎座': 4, '射手座': 5, '山羊座': 6, '巨蟹座': 7, '水瓶座': 8, '狮子座': 9, '白羊座': 10, '金牛座': 11
4.6 设置Loss🐇
# 实例化模型并且移动到GPU
criterion_val = torch.nn.CrossEntropyLoss()
设置loss函数,loss函数设置为交叉熵。
4.7 设置模型🐇
# 设置模型
model_ft = googlenet(pretrained=True,aux_logits=True)
print(model_ft)
num_ftrs=model_ft.fc.in_features
model_ft.fc =nn.Linear(num_ftrs,classes)
num_ftrs_aux1 = model_ft.aux1.fc2.in_features
model_ft.aux1.fc2 = nn.Linear(num_ftrs_aux1, classes)
num_ftrs_aux2=model_ft.aux2.fc2.in_features
model_ft.aux2.fc2 = nn.Linear(num_ftrs_aux2, classes)
if resume:
model = torch.load(resume)
model_ft.load_state_dict(model['state_dict'])
Best_ACC = model['Best_ACC']
start_epoch = model['epoch'] + 1
model_ft.to(DEVICE)
print(model_ft)
- 设置模型为googlenet,pretrained设置为true,表示加载预训练模型,aux_logits设置为True,则表示启用辅助分类器。 将model_ft.fc 这个全连接层的输出修改为classes,将model_ft.aux1.fc2和 model_ft.aux2.fc2这两个辅助分类器的输出也改为classes。执行
print(model_ft)
,查看是否修改成功,如下图所示:
- 如果resume为True,则加载模型接着resume指向的模型接着训练,使用模型里的Best_ACC初始化Best_ACC,使用epoch参数初始化start_epoch。
4.7.1 如何确定改哪层?🐇
很多人有这样的疑问,不同的模型,最后一层全连接都不相同,我们如何找到最后一层的全连接层?我通过这篇文章揭晓一下:
model_ft = alexnet(pretrained=True)
print(model_ft)
首先,我们声明模型的对象后,使用print直接打印模型,然后寻找最后一层:
通过上图可以看到,最后一层的全连接在classifier这个Sequential下面,index为6的位置。
所以,我们先将这个层的in_features取出来,代码如下:
num_ftrs=model_ft.classifier[6].in_features
然后,给他重新赋个全连接,代码如下:
model_ft.classifier[6]=nn.Linear(num_ftrs,classes)
4.8 设置优化器和学习率调整算法🐇
# 选择简单暴力的Adam优化器,学习率调低
optimizer = optim.AdamW(model_ft.parameters(),lr=model_lr)
cosine_schedule = optim.lr_scheduler.CosineAnnealingLR(optimizer=optimizer, T_max=20, eta_min=1e-6)
- 优化器设置为adamW。
- 学习率调整策略选择为余弦退火。
4.9 训练函数
# 定义训练过程
def train(model, device, train_loader, optimizer, epoch):
model.train()
sum_loss = 0
correct=0 #预测正确的数量
total_num = len(train_loader.dataset)
print(total_num, len(train_loader))
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device,non_blocking=True), target.to(device,non_blocking=True)
output, aux1, aux2 = model(data)
loss_out = criterion(output, target)
loss_aux = criterion(aux2, target)
loss = loss_out * 0.7 + loss_aux * 0.3
optimizer.zero_grad()
loss.backward()
optimizer.step()
print_loss = loss.data.item()
_, pred = torch.max(output.data, 1)
correct += torch.sum(pred == target)
sum_loss += print_loss
if (batch_idx + 1) % 10 == 0:
print('Train Epoch: [/ (:.0f%)]\\tLoss: :.6f'.format(
epoch, (batch_idx + 1) * len(data), len(train_loader.dataset),
100. * (batch_idx + 1) / len(train_loader), loss.item()))
ave_loss = sum_loss / len(train_loader)
correct = correct.data.item()
acc = correct / total_num
print('epoch:,loss:'.format(epoch, ave_loss))
return ave_loss,acc
训练的主要步骤:
1、 model.train()切换成训练模型。启用 BatchNormalization 和 Dropout。初始化sum_loss 为0,计算训练集图片的数量赋给total_num。correct设置为0
2、进入循环,将data和target放入device上,non_blocking设置为True。如果pin_memory=True的话,将数据放入GPU的时候,也应该把non_blocking打开,这样就只把数据放入GPU而不取出,访问时间会大大减少。
如果pin_memory=False时,则将non_blocking设置为False。
3、data输入model,输出预测结果,然后再计算loss。这里注意,我们除了计算output的loss,还要计算一个辅助分类器的loss,根据论文描述选择其中一个辅助分类器,按照7:3的比例配置loss。
4、 optimizer.zero_grad() 梯度清零,把loss关于weight的导数变成0。
5、反向传播求梯度。
6、获取loss,并赋值给print_loss 。
7、torch.sum(pred == target)计算当前Batch内,预测正确的数量,然后累加到correct 。
8、sum_loss 累加print_loss ,求得总的loss。所以,单个epoch的loss就是总的sum_loss 除以train_loader的长度。
等待一个epoch训练完成后,计算平均loss。然后将其打印出来。并返回loss和acc。
4.10 验证函数🐇
验证过程增加了对预测数据和Label数据的保存,所以,需要定义两个list保存,然后将其返回!
# 验证过程
def val(model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
total_num = len(test_loader.dataset)
print(total_num, len(test_loader))
val_list = []
pred_list = []
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device, non_blocking=True), target.to(device, non_blocking=True)
for t in target:
val_list.append(t.data.item())
output = model(data)
loss = criterion(output, target)
_, pred = torch.max(output.data, 1)
for p in pred:
pred_list4.5 GoogLeNet CNNtensorflow实现——python实战