Pytorch实现yolov3(train)训练代码详解(二)
上篇我们讲解了如何进行数据预处理,读取数据。接下来我们一起分析yolov3训练过程与training procedure。想真正读懂这个部分,要对inference部分有所了解。
数据加载
def train(cfg,data_cfg,img_size=416,resume=False,epochs=273, # 500200 batches at bs 64, dataset length 117263batch_size=16,accumulate=1,multi_scale=False,freeze_backbone=False,transfer=False # Transfer learning (train only YOLO layers)
):init_seeds()weights = 'weights' + os.sep #python是跨平台的。在Windows上,文件的路径分隔符是'\',在Linux上是'/'。latest = weights + 'latest.pt'best = weights + 'best.pt'device = torch_utils.select_device()# Configure runtrain_path = parse_data_cfg(data_cfg)['train'] #data_cfg='data/coco.data' train=../coco/trainvalno5k.txt# Initialize modelmodel = Darknet(cfg, img_size).to(device)# Optimizeroptimizer = optim.SGD(model.parameters(), lr=hyp['lr0'], momentum=hyp['momentum'], weight_decay=hyp['weight_decay'])cutoff = -1 # backbone reaches to cutoff layerstart_epoch = 0best_loss = float('inf')nf = int(model.module_defs[model.yolo_layers[0] - 1]['filters']) # yolo layer size (i.e. 255)if resume: # Load previously saved modelif transfer: # Transfer learningchkpt = torch.load(weights + 'yolov3.pt', map_location=device)model.load_state_dict({k: v for k, v in chkpt['model'].items() if v.numel() > 1 and v.shape[0] != 255},strict=False)for p in model.parameters():p.requires_grad = True if p.shape[0] == nf else Falseelse: # resume from latest.ptchkpt = torch.load(latest, map_location=device) # load checkpointmodel.load_state_dict(chkpt['model'])start_epoch = chkpt['epoch'] + 1if chkpt['optimizer'] is not None:optimizer.load_state_dict(chkpt['optimizer'])best_loss = chkpt['best_loss']del chkptelse: # Initialize model with backbone (optional)if '-tiny.cfg' in cfg:cutoff = load_darknet_weights(model, weights + 'yolov3-tiny.conv.15')else:cutoff = load_darknet_weights(model, weights + 'darknet53.conv.74')lf = lambda x: 1 - 10 ** (hyp['lrf'] * (1 - x / epochs)) # inv exp ramp to lr0 * 1e-2scheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lf, last_epoch=start_epoch - 1)dataset = LoadImagesAndLabels(train_path, img_size=img_size, augment=True)# dataset.__getitem__(0)# Initialize distributed trainingif torch.cuda.device_count() > 1:dist.init_process_group(backend=opt.backend, init_method=opt.dist_url, world_size=opt.world_size, rank=opt.rank)model = torch.nn.parallel.DistributedDataParallel(model)sampler = torch.utils.data.distributed.DistributedSampler(dataset)else:sampler = None# Dataloaderdataloader = DataLoader(dataset,batch_size=batch_size,num_workers=opt.num_workers,shuffle=True,pin_memory=True,collate_fn=dataset.collate_fn,sampler=sampler)
这一段代码实现了上一篇讲的数据加载过程,以及路径配置和模式选择,具体我不赘述,如果想了解的话可以在评论问我。
train以及loss
for epoch in range(start_epoch, epochs):model.train()print(('\n%8s%12s' + '%10s' * 7) % ('Epoch', 'Batch', 'xy', 'wh', 'conf', 'cls', 'total', 'nTargets', 'time'))# Update schedulerscheduler.step()# Freeze backbone at epoch 0, unfreeze at epoch 1if freeze_backbone and epoch < 2:for name, p in model.named_parameters():if int(name.split('.')[1]) < cutoff: # if layer < 75p.requires_grad = False if epoch == 0 else Truemloss = torch.zeros(5).to(device) # mean lossesfor i, (imgs, targets, _, _) in enumerate(dataloader):imgs = imgs.to(device)targets = targets.to(device)nt = len(targets)# if nt == 0: # if no targets continue# continue# Plot images with bounding boxesif epoch == 0 and i == 0:plot_images(imgs=imgs, targets=targets, fname='train_batch0.jpg')# SGD burn-inif epoch == 0 and i <= n_burnin:lr = hyp['lr0'] * (i / n_burnin) ** 4for x in optimizer.param_groups:x['lr'] = lr# Run modelpred = model(imgs)# Compute lossloss, loss_items = compute_loss(pred, targets, model)
train的过程:
1:设置epoch参数,它决定了所有数据所需要训练的轮数。
2:进入epoch的for循环后,讲model设置为train,然后for i, (imgs, targets, _, _) in enumerate(dataloader):获取数据预处理后的数据和labels,这里要注意数据和labels都resize成416*416了(与txt中的不同)。
3:将取出的数据imgs传入model中,model就是yolov3的darknet,它有3个yolo层,每个yolo层都会输出一个特征映射图(dimention如(bs, 3, 13, 13, 85))bs=batch_size,3指每一个像素点存在3种anchor,13*13是它的维度,85=xywh+conf+classes。
class YOLOLayer(nn.Module):#x = module[0](x, img_size)def __init__(self, anchors, nc, img_size, yolo_layer, cfg):super(YOLOLayer, self).__init__()self.anchors = torch.Tensor(anchors)self.na = len(anchors) # number of anchors (3)self.nc = nc # number of classes (80)self.img_size = 0if ONNX_EXPORT: # grids must be computed in __init__stride = [32, 16, 8][yolo_layer] # stride of this layerif cfg.endswith('yolov3-tiny.cfg'):stride *= 2ng = (int(img_size[0] / stride), int(img_size[1] / stride)) # number grid pointscreate_grids(self, max(img_size), ng)def forward(self, p, img_size, var=None):if ONNX_EXPORT:bs = 1 # batch sizeelse:bs, nx, ny = p.shape[0], p.shape[-2], p.shape[-1]if self.img_size != img_size:create_grids(self, img_size, (nx, ny), p.device)# p.view(bs, 255, 13, 13) -- > (bs, 3, 13, 13, 85) # (bs, anchors, grid, grid, classes + xywh)p = p.view(bs, self.na, self.nc + 5, self.nx, self.ny).permute(0, 1, 3, 4, 2).contiguous() # predictionif self.training:return p
4:loss, loss_items = compute_loss(pred, targets, model),三个yolo层的输出最终与labels产生的targets运算得到loss。
5:用设置好的optimizer对loss进行BP。
6:保存最终的参数。
loss的构成:
首先,我从yolov3的算法思想讲起,让大家对整体思路有所了解,再具体到代码层面,这样大家可以感受代码复现算法的过程,从而真正理解yolov3。这对大家再相应背景下训练自己的数据也好,改造yolov3也好,都有直接的帮助。
yolov3算法思想:首先设计darknet,darknet是resnet的变形,在imagenet数据集上进行训练。然后去除最后的全连接,并在模型上增加了三个维度13,26,52的输出,即yolo层(为了增加小模型的检测精度),再用coco数据集进行微调。coco数据集的标签包括了class与ground truth(xywh)。loss由四个部分组成lxy,lwh,lcls,lconf组成(代码中会解释每个成分)。
这里就有个重要的疑问了,一个尺度的feature map有三个anchors,那么对于某个ground truth框,究竟是哪个anchor负责匹配它呢?和YOLOv1一样,对于训练图片中的ground truth,若其中心点落在某个cell内,那么该cell内的3个anchor box负责预测它,具体是哪个anchor box预测它,需要在训练中确定,即由那个与ground truth的IOU最大的anchor box预测它,而剩余的2个anchor box不与该ground truth匹配。YOLOv3需要假定每个cell至多含有一个grounth truth,而在实际上基本不会出现多于1个的情况。与ground truth匹配的anchor box计算坐标误差、置信度误差(此时target为1)以及分类误差,而其它的anchor box只计算置信度误差(此时target为0)。
def compute_loss(p, targets, model): # predictions, targets, modelft = torch.cuda.FloatTensor if p[0].is_cuda else torch.Tensorlxy, lwh, lcls, lconf = ft([0]), ft([0]), ft([0]), ft([0])txy, twh, tcls, indices = build_targets(model, targets)#在13 26 52维度中找到大于iou阈值最适合的anchor box 作为targets#txy[维度(0:2),(x,y)] twh[维度(0:2),(w,h)] indices=[0,anchor索引,gi,gj]# Define criteriaMSE = nn.MSELoss()CE = nn.CrossEntropyLoss()BCE = nn.BCEWithLogitsLoss()# Compute lossesh = model.hyp # hyperparametersbs = p[0].shape[0] # batch sizek = h['k'] * bs # loss gainfor i, pi0 in enumerate(p): # layer i predictions, ib, a, gj, gi = indices[i] # image, anchor, gridx, gridytconf = torch.zeros_like(pi0[..., 0]) # conf# Compute lossesif len(b): # number of targetspi = pi0[b, a, gj, gi] # predictions closest to anchors 找到p中与targets对应的数据lxytconf[b, a, gj, gi] = 1 # conf# pi[..., 2:4] = torch.sigmoid(pi[..., 2:4]) # wh power loss (uncomment)lxy += (k * h['xy']) * MSE(torch.sigmoid(pi[..., 0:2]), txy[i]) # xy losslwh += (k * h['wh']) * MSE(pi[..., 2:4], twh[i]) # wh yolo losslcls += (k * h['cls']) * CE(pi[..., 5:], tcls[i]) # class_conf loss# pos_weight = ft([gp[i] / min(gp) * 4.])# BCE = nn.BCEWithLogitsLoss(pos_weight=pos_weight)lconf += (k * h['conf']) * BCE(pi0[..., 4], tconf) # obj_conf lossloss = lxy + lwh + lconf + lclsreturn loss, torch.cat((lxy, lwh, lconf, lcls, loss)).detach()
下面我们具体看看代码。
txy, twh, tcls, indices = build_targets(model, targets),build_targets函数返回了我们需要参数,我们看看这个函数具体再操作什么。
def build_targets(model, targets):# targets = [image, class, x(归一后的中心), y, w(归一后的宽), h] [ 0.00000, 20.00000, 0.72913, 0.48770, 0.13595, 0.08381]iou_thres = model.hyp['iou_t'] # hyperparameterif type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel):model = model.modulent = len(targets)txy, twh, tcls, indices = [], [], [], []for i in model.yolo_layers:layer = model.module_list[i][0]#layer->YOLOLayer()# iou of targets-anchorst, a = targets, []gwh = targets[:, 4:6] * layer.ng #layer.ng就是yolo层输出维度13 26 52,gwh将原来的wh还原到13*13的图上if nt:iou = [wh_iou(x, gwh) for x in layer.anchor_vec] #anchor_vec是anchor box的whiou, a = torch.stack(iou, 0).max(0) # best iou and anchor找到每一层与label->wh,iou最大的anchor,a是anchor的索引# reject below threshold ious (OPTIONAL, increases P, lowers R)reject = Trueif reject:j = iou > iou_threst, a, gwh = targets[j], a[j], gwh[j]# Indices targets = [image, class, x(归一后的中心), y, w(归一后的宽), h]b, c = t[:, :2].long().t() # target image, classgxy = t[:, 2:4] * layer.nggi, gj = gxy.long().t() # grid_i, grid_jindices.append((b, a, gj, gi))# XY coordinatestxy.append(gxy - gxy.floor())#在yolov3里是Gx,Gy减去grid cell左上角坐标Cx,Cy# Width and heighttwh.append(torch.log(gwh / layer.anchor_vec[a])) # wh yolo method# twh.append((gwh / layer.anchor_vec[a]) ** (1 / 3) / 2) # wh power method# Classtcls.append(c)if c.shape[0]:assert c.max() <= layer.nc, 'Target classes exceed model classes'return txy, twh, tcls, indices
build_targets(model, targets)需要两个参数,model是模型,targets是从labels读取resize后的标签参数,具体的targets = [image, class, x(归一后的中心), y, w(归一后的宽), h],iou_thres是我们需要设置的超参数,作用我们后面会讲。
for i in model.yolo_layers:我们知道有3个yolo层,i是层数的索引,targets[:, 4:6]是ground truth的宽和高,所以gwh将原来的wh还原到13*13的特征图上。
iou = [wh_iou(x, gwh) for x in layer.anchor_vec],anchor_vec是anchor box的wh,我们求出每一层三个anchor box与ground truth的iou,然后找到iou最大的anchor box,用a来记录它的索引,删去小于iou_thres的anchor。用b记录imges,c记录calss类别,gxy记录对应feature map的中心点,gi,gj记录哪个格子负责这个ground truth。
txy=gxy - gxy.floor(),在yolov3里是Gx,Gy减去grid cell左上角坐标Cx,Cy得到txy。
twh=torch.log(gwh / layer.anchor_vec[a])对应上面公式反推过来的。txy,twh是我们loss的标签,它是真正坐标的offset,所以我们bp优化得到的ixy还需要做相应的变换才能得到真正的box。
tcls=c,c就是calss类别。这样我们就理解build_targets返回的标签参数意义了。
for i, pi0 in enumerate(p): # layer i predictions, ib, a, gj, gi = indices[i] # image, anchor, gridx, gridytconf = torch.zeros_like(pi0[..., 0]) # conf# Compute lossesif len(b): # number of targetspi = pi0[b, a, gj, gi] # predictions closest to anchors 找到p中与targets对应的数据lxytconf[b, a, gj, gi] = 1 # conf# pi[..., 2:4] = torch.sigmoid(pi[..., 2:4]) # wh power loss (uncomment)lxy += (k * h['xy']) * MSE(torch.sigmoid(pi[..., 0:2]), txy[i]) # xy losslwh += (k * h['wh']) * MSE(pi[..., 2:4], twh[i]) # wh yolo losslcls += (k * h['cls']) * CE(pi[..., 5:], tcls[i]) # class_conf loss# pos_weight = ft([gp[i] / min(gp) * 4.])# BCE = nn.BCEWithLogitsLoss(pos_weight=pos_weight)lconf += (k * h['conf']) * BCE(pi0[..., 4], tconf) # obj_conf lossloss = lxy + lwh + lconf + lcls
for i, pi0 in enumerate( p ):p是我们yolo层返回的特征图,i=0时,p=[[bs,3,13,13,85],[bs,3,26,26,85],[bs,3,52,52,85]].
b, a, gj, gi = indices[i],从返回的标签中读取最佳anchor,与格子坐标,因为我们知道每个ground truth只对应一个anchor box,这些索引就是为了找到那个anchor box。
pi = pi0[b, a, gj, gi],pi0=(b,3,13,13,85)找到pi0中对应ground truth的数据。
tconf[b, a, gj, gi] = 1 ,将对应位置的confience设置为1,其余都为0。
lxy += (k * h[‘xy’]) * MSE(torch.sigmoid(pi[…, 0:2]), txy[i]),与ground truth匹配的anchor box计算坐标误差、置信度误差(此时target为1)以及分类误差,而其它的anchor box只计算置信度误差(此时target为0)。这里pi只记录了与ground truth匹配的anchor box的信息。sigmoid是将xy限制在0-1,因为中心坐标落在格子内。MSE就是均方差。
lwh += (k * h[‘wh’]) * MSE(pi[…, 2:4], twh[i]) 如上。
lcls += (k * h[‘cls’]) * CE(pi[…, 5:], tcls[i]):只计算与ground truth匹配的anchor box的分类误差。CE是交叉熵。
lconf += (k * h[‘conf’]) * BCE(pi0[…, 4], tconf)。tconf把对应ground truth的置信度设置为1,没有与ground truth对应都设置为0.
这样我们就求得了loss,有些地方可能描述不太好,当然也有理解不到位的,欢迎讨论。
Pytorch实现yolov3(train)训练代码详解(二)相关推荐
- Pytorch | yolov3原理及代码详解(二)
阅前可看: Pytorch | yolov3原理及代码详解(一) https://blog.csdn.net/qq_24739717/article/details/92399359 分析代码: ht ...
- Pytorch|YOWO原理及代码详解(二)
Pytorch|YOWO原理及代码详解(二) 本博客上接,Pytorch|YOWO原理及代码详解(一),阅前可看. 1.正式训练 if opt.evaluate:logging('evaluating ...
- 【Image captioning】Show, Attend, and Tell 从零到掌握之三--train.py代码详解
[Image captioning]Show, Attend, and Tell 从零到掌握之三–train.py代码详解 作者:安静到无声 个人主页 作者简介:人工智能和硬件设计博士生.CSDN与阿 ...
- Pytorch | yolov3原理及代码详解(一)
YOLO相关原理 : https://blog.csdn.net/leviopku/article/details/82660381 https://www.jianshu.com/p/d13ae10 ...
- 【CV】Pytorch一小时入门教程-代码详解
目录 一.关键部分代码分解 1.定义网络 2.损失函数(代价函数) 3.更新权值 二.训练完整的分类器 1.数据处理 2. 训练模型(代码详解) CPU训练 GPU训练 CPU版本与GPU版本代码区别 ...
- PyTorch 迁移学习 (Transfer Learning) 代码详解
PyTorch 迁移学习 代码详解 概述 为什么使用迁移学习 更好的结果 节省时间 加载模型 ResNet152 冻层实现 模型初始化 获取需更新参数 训练模型 获取数据 完整代码 概述 迁移学习 ( ...
- 【Pytorch】构建VOC2012数据集代码详解
目录 数据集 图片读入 预处理 crop 标签和像素点颜色 随机翻转 噪声 标准化 torch.utils.data.Dataset()和torch.utils.data.DataLoader() t ...
- ELMo代码详解(二)
ELMo代码解读笔记2:模型代码 2.模型代码介绍 模型代码主要包括以下几个部分:1.构建word embedding; 2.构建word_char embedding的准备; 3.语言模型介绍( ...
- [Pytorch系列-61]:循环神经网络 - 中文新闻文本分类详解-3-CNN网络训练与评估代码详解
作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客 本文网址:https://blog.csdn.net/HiWangWenBing/article/detai ...
最新文章
- 在main()之前,IAR都做了啥?
- ESP-TOUCH编码规则及解码
- Fluent Ribbon 第三步 应用程序菜单
- SAP CRM和C4C的订单Number range
- maven项目发布到tomcat里lib包没有发布的问题
- git php自动发布,使用 Git Hooks 实现自动部署PHP项目
- c语言程序设计字符处理周信东,“电子科技大学出版社(周信东主编)”的C语言程序设计实验-整理代码-.doc...
- 匿名对象方案与实体对象方案对比
- System学习笔记007---win10连接阿里云出现_远程连接提示要求的函数不受支持如何解决
- Python + selenium之组织unittest单元测试用例
- C++ 好的博客??
- c语言微信昵称大全女生,微信名字大全女生可爱
- C语言循环语句中 i++, ++i, i--, --i的使用
- MiniGUI源码分析:GDI(1)-- GDI概览及Surface
- Book Sharing
- 成都拓嘉启远:拼多多上产品清单的条件
- 奔三之际,任性一把 ——从华为南研所裸辞后的一些体会和感想
- 2017第三届美亚杯全国电子数据取证大赛团队赛write up
- 无线网卡的工作模式--ath9k网卡驱动开发总结(一)
- stm32独立看门狗
热门文章
- Godfather POJ - 3107 (求树的重心)
- C++ 数据结构之队列queue (henu.hjy)
- 自动驾驶系统进阶与项目实战(三)基于全卷积神经网络的点云三维目标检测和ROS实战
- CSS in Depth 学习札记之:猫头鹰选择器
- 逐梦....圈圈圈圈圈
- 安排 , 2021新冠疫情防控指挥作战平台(视频+课件+代码+资料)
- 服务器怎么部署静态网站,纯静态网站部署服务器
- python培训价格多少钱
- Git 经验总结及 Git GitHub 学习指南
- AI黑科技 | 宏碁研发智能穿戴设备:智能佛珠