> 文档中心 > GoogLeNet——网络实战

GoogLeNet——网络实战

文章目录

  • 摘要🐇
  • 1 项目结构🐇
  • 2 划分训练集和测试集🐇
  • 3 计算mean和Standard🐇
    • 3.1 标准化的作用🐇
    • 3.2 归一化的作用🐇
  • 4 训练🐇
    • 4.1 导入项目使用的库🐇
    • 4.2 设置随机因子🐇
    • 4.3 设置全局参数🐇
    • 4.4 图像预处理与增强🐇
    • 4.5 读取数据🐇
    • 4.6 设置Loss🐇
    • 4.7 设置模型🐇
      • 4.7.1 如何确定改哪层?🐇
    • 4.8 设置优化器和学习率调整算法🐇
    • 4.9 训练函数
    • 4.10 验证函数🐇
    • 4.11 训练、验证、保存模型🐇
    • 4.12 继续训练🐇
  • 4.13 结果展示🐇
  • 5 测试🐇
  • 总结🐇

🐇🐇🐇🐇🐇🐇 🐇 欢迎阅读 【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 globimport osimport shutilfrom sklearn.model_selection import train_test_splitimage_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 ImageFolderimport torchfrom torchvision import transformsdef 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 optimimport torchimport torch.nn as nnimport torch.nn.parallelimport torch.optimimport torch.utils.dataimport torch.utils.data.distributedimport torchvision.transforms as transformsfrom torchvision.models import googlenetimport osfrom torchvision import datasetsimport jsonimport matplotlib.pyplot as pltimport warningswarnings.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_list.append(p.data.item())     correct += torch.sum(pred == target)     print_loss = loss.data.item()     test_loss += print_loss correct = correct.data.item() acc = correct / total_num avgloss = test_loss / len(test_loader) print('\nVal set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(     avgloss, correct, len(test_loader.dataset), 100 * acc))    return val_list, pred_list,avgloss,acc

验证的过程和训练的过程大致相似,主要步骤:

1、model.eval(),切换验证模型,不启用 BatchNormalization 和 Dropout。

2、定义参数:
test_loss : 测试的loss
correct :统计正确类别的数量。
total_num:验证集图片的总数。
val_list :保存验证集的Label数据。
pred_list :保存预测的Label数据。

3、torch.no_grad():反向传播时就不会自动求导了。

4、进入循环,迭代test_loader:

将label保存到val_list。

将data和target放入device上,non_blocking设置为True。

遍历target,将Label保存到val_list 。

将data输入到model中,求出预测值,然后输入到loss函数中,求出loss。在验证集中,不用求辅助分类器的loss。

调用torch.max函数,将预测值转为对应的label。

遍历pred,将预测的Label保存到pred_list。

correct += torch.sum(pred == target),计算出识别对的数量,并累加到correct 变量上。

5、acc = correct / total_num,计算出acc。 avgloss = test_loss / len(test_loader)计算loss。 最后返回val_list, pred_list,loss,acc

在这里插入图片描述

4.11 训练、验证、保存模型🐇

   # 训练    log_dir = {}    train_loss_list, val_loss_list, train_acc_list, val_acc_list, epoch_list = [], [], [], [], []    for epoch in range(start_epoch, EPOCHS + 1): epoch_list.append(epoch) train_loss, train_acc=train(model_ft, DEVICE, train_loader, optimizer, epoch) train_loss_list.append(train_loss) train_acc_list.append(train_acc) log_dir['train_acc'] = train_acc_list log_dir['train_loss'] = train_loss_list val_list, pred_list, val_loss, val_acc =val(model_ft, DEVICE, test_loader) val_loss_list.append(val_loss) val_acc_list.append(val_acc) log_dir['val_acc'] = val_acc_list log_dir['val_loss'] = val_loss_list log_dir['best_acc'] = Best_ACC if val_acc>=Best_ACC:     Best_ACC=val_acc     torch.save(model_ft, file_dir + "/" + 'best.pth') state = {     'epoch': epoch,     'state_dict': model_ft.state_dict(),     'Best_ACC': Best_ACC } torch.save(state, file_dir + "/" + 'model_' + str(epoch) + '_' + str(round(val_acc, 3)) + '.pth') fig = plt.figure(1) plt.plot(epoch_list, train_loss_list, 'r-', label=u'Train Loss') # 显示图例 plt.plot(epoch_list, val_loss_list, 'b-', label=u'Val Loss') plt.legend(["Train Loss", "Val Loss"], loc="upper right") plt.xlabel(u'epoch') plt.ylabel(u'loss') plt.title('Model Loss ') plt.savefig(file_dir + "/loss.png") plt.close(1) fig2 = plt.figure(2) plt.plot(epoch_list, train_acc_list, 'r-', label=u'Train Acc') plt.plot(epoch_list, val_acc_list, 'b-', label=u'Val Acc') plt.legend(["Train Acc", "Val Acc"], loc="lower right") plt.title("Model Acc") plt.ylabel("acc") plt.xlabel("epoch") plt.savefig(file_dir + "/acc.png") plt.close(2)

循环调用train函数和val函数,train函数返回train_loss, train_acc,val函数返回val_list, pred_list, val_loss, val_acc。loss和acc用于绘制曲线。
将val_list, pred_list和dataset_train.class_to_idx传入模型,计算模型指标。
判断acc是否大于Best_ACC,如果大于则保存模型,这里保存的是整个模型。
接下来是保存每个epoch的模型,新建state ,字典的参数:

  • epoch:当前的epoch。
  • state_dict:权重参数。 model_ft.state_dict(),只保存模型的权重参数。
  • Best_ACC:Best_ACC的数值。
    然后,调用 torch.save保存模型。

最后使用plt.plot绘制loss和acc曲线图
在这里插入图片描述

4.12 继续训练🐇

将resume设置为模型的路径即可开启继续训练。如下:
GoogLeNet——网络实战

 resume = 'checkpoints/GoogLeNet/model_430_0.92.pth'

然后点击train.py,就可以继续训练了。如下图:
在这里插入图片描述

4.13 结果展示🐇

loss曲线图:
GoogLeNet——网络实战

acc曲线图:
GoogLeNet——网络实战

5 测试🐇

测试,我们采用一种通用的方式。测试集存放的目录如下图:

GoogLeNet_demo├─test│  ├─白羊座_2.jpg.jpg│  ├─处女座_136.jpg│  ├─金牛座_142.jpg│  ├ ......└─test.py

代码如下:

import torch.utils.data.distributedimport torchvision.transforms as transformsfrom PIL import Imagefrom torch.autograd import Variableimport osclasses = ('双子座', '双鱼座', '处女座', '天秤座',    '天蝎座', '射手座', '山羊座',    '巨蟹座', '水瓶座', '狮子座', '白羊座', '金牛座')transform_test = transforms.Compose([    transforms.Resize((224, 224)),    transforms.ToTensor(),    transforms.Normalize(mean=[0.51819474, 0.5250407, 0.4945761], std=[0.24228974, 0.24347611, 0.2530049])])DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")model=torch.load('checkpoints/googlenet/best.pth')model.eval()model.to(DEVICE)path = 'test/'testList = os.listdir(path)for file in testList:    img = Image.open(path + file).convert('RGB')    img = transform_test(img)    img.unsqueeze_(0)    img = Variable(img).to(DEVICE)    out = model(img)    # Predict    _, pred = torch.max(out.data, 1)    print('Image Name:{},predict:{}'.format(file, classes[pred.data.item()]))

测试的主要逻辑:

1、定义类别,这个类别的顺序和训练时的类别顺序对应,一定不要改变顺序!!!!

2、定义transforms,transforms和验证集的transforms一样即可,别做数据增强。

3、 加载model,并将模型放在DEVICE里,

4、循环 读取图片并预测图片的类别,在这里注意,读取图片用PIL库的Image。不要用cv2,transforms不支持。循环里面的主要逻辑:

  • 使用Image.open读取图片,将其转为RGB格式。
  • 使用transform_test对图片做归一化和标椎化。
  • img.unsqueeze_(0) 增加一个维度,由(3,224,224)变为(1,3,224,224)
  • Variable(img).to(DEVICE):将数据放入DEVICE中。
  • model(img):执行预测。
  • _, pred = torch.max(out.data, 1):获取预测值的最大下角标。

运行结果:

在这里插入图片描述

总结🐇

到这里,这篇文章就写完了,通过这篇文章,你能学到:
1、如何训练模型?
2、如何推理?
3、如何读取数据集、处理数据集?
4、如何使用余弦退火调整学习率?
5、如何保存权重文件和整个模型文件?
6、如何使用评价指标,如ACC、ReCall等指标评价模型。
7、如何使用matplotlib.pyplot绘制acc和loss曲线图?
有没有发现,我们的实战代码正在逐渐的丰富起来。我打算通过这种逐步增加难度的方式,让大家更容易接受!
在这里插入图片描述

麦克风网