释放双眼,带上耳机,听听看~!
本文介绍了如何运行train.py文件,包括加载包的导入、函数头部加载、程序入口判断和顺序执行流程。同时也讲解了参数解析的过程。适合对Python程序执行和深度学习模型训练感兴趣的读者。
运行train.py的流程:
- 加载包的导入
- 加载每个函数的头部
- 找到程序入口
if __name__ == "__main__"
- 按顺序执行
main
程序入口
if __name__ == "__main__":
opt = parse_opt()
main(opt)
parse_opt
加载参数
def parse_opt(known=False):
parser = argparse.ArgumentParser()
parser.add_argument('--weights', type=str, default=ROOT / 'weights/yolov5s.pt', help='initial weights path')
parser.add_argument('--cfg', type=str, default=ROOT / 'models/yolov5schange2.yaml', help='model.yaml path')
parser.add_argument('--data', type=str, default=ROOT / 'data/custom.yaml', help='dataset.yaml path')
parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-high.yaml', help='hyperparameters path')
parser.add_argument('--epochs', type=int, default=5, help='total training epochs')
parser.add_argument('--batch-size', type=int, default=-1, help='total batch size for all GPUs, -1 for autobatch')
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=680, help='train, val image size (pixels)')
parser.add_argument('--rect', action='store_true', help='rectangular training')
# resume是最后一次训练的模型路径,例如:runs/train/核阳三分类hyp_high/exp_changedeep19/weights/best.pt
parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
parser.add_argument('--noval', action='store_true', help='only validate final epoch')
parser.add_argument('--noautoanchor', action='store_true', help='disable AutoAnchor')
parser.add_argument('--noplots', action='store_true', help='save no plot files')
parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyperparameters for x generations')
parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
parser.add_argument('--cache', type=str, nargs='?', const='ram', help='image --cache ram/disk')
parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer')
parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)')
parser.add_argument('--project', default=ROOT / 'runs/train/study', help='save to project/name')
parser.add_argument('--name', default='exp', help='save to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--quad', action='store_true', help='quad dataloader')
parser.add_argument('--cos-lr', action='store_true', help='cosine LR scheduler')
parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
parser.add_argument('--patience', type=int, default=300, help='EarlyStopping patience (epochs without improvement)')
parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='Freeze layers: backbone=10, first3=0 1 2')
parser.add_argument('--save-period', type=int, default=-1, help='Save checkpoint every x epochs (disabled if < 1)')
parser.add_argument('--seed', type=int, default=0, help='Global training seed')
parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify')
# Logger arguments
# 使用tensorboard的可视化工具
parser.add_argument('--entity', default=None, help='Entity')
parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='Upload data, "val" option')
parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval')
parser.add_argument('--artifact_alias', type=str, default='latest', help='Version of dataset artifact to use')
return parser.parse_known_args()[0] if known else parser.parse_args()
main
配置参数,调用train
函数
我这里把train.py
简约了一些。
def main(opt, callbacks=Callbacks()):
# 检查分布式训练的环境,若RANK为-1或0,打印参数并检查github仓库和依赖库
# 若进程编号为-1或0 (全局变量RANK=-1)
if RANK in {-1, 0}:
print_args(vars(opt))
# 检查YOLOv5的github仓库中是否更新,若已更新,给出提示
check_git_status()
# 检测依赖库
check_requirements()
# 配置参数
opt.data, opt.cfg, opt.hyp, opt.weights, opt.project = check_file(opt.data), check_yaml(opt.cfg), check_yaml(opt.hyp), str(opt.weights), str(opt.project) # checks
# 建立训练的存储路径
opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))
# 选择设备(cpu/gpu/mps)及设备号
device = select_device(opt.device, batch_size=opt.batch_size)
# 训练
train(opt.hyp, opt, device, callbacks)
train
训练
一、训练前的准备
1. 创建路径;加载参数;初始化日志记录器实例;检查数据规范
# save_dir="project"+"name" save=runs/train/study/exp
save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze =
Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg,
opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
callbacks.run('on_pretrain_routine_start')
# Directories
# 定义权重存储路径:w=runs/train/study/exp/weights
w = save_dir / 'weights' # weights dir
# 创建权重存储路径:如果是进化算法:则只创建父路径
(w.parent if evolve else w).mkdir(parents=True, exist_ok=True) # make dir
# 命名权重路径
last, best = w / 'last.pt', w / 'best.pt'
# Hyperparameters
# hyp="data/hyps/hyp.scratch-high.yaml"
# type(hyp)=str 从形参加载进来的参数是字符串类型的路径
# 判断若hyp为字符串类型,那么将它作为文件打开,并加载其中的内容形成字典
if isinstance(hyp, str):
with open(hyp, errors='ignore') as f:
hyp = yaml.safe_load(f) # load hyps dict
# 日志输出超参数
LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items()))
# type(hyp)=dict 保存当前的参数到opt中(和之前不同,现在是字典类型的内容,之前是字符串类型的路径)
opt.hyp = hyp.copy() # for saving hyps to checkpoints
# Save run settings
if not evolve:
#保存当前的hyp和opt为yaml文件
yaml_save(save_dir / 'hyp.yaml', hyp)
yaml_save(save_dir / 'opt.yaml', vars(opt))
# Loggers
data_dict = None
if RANK in {-1, 0}:
# 初始化日志记录器实例
loggers = Loggers(save_dir, weights, opt, hyp, LOGGER) # loggers instance
# Register actions
# 遍历日志记录器中的所有方法:将日志记录器中的方法与字符串进行绑定
for k in methods(loggers):
callbacks.register_action(k, callback=getattr(loggers, k))
data_dict = loggers.remote_dataset # data_dict为None
# Config
# 如果不使用进化算法并且绘图为TRUE,训练结束时将训练数据绘图
plots = not evolve and not opt.noplots
cuda = device.type != 'cpu'
init_seeds(opt.seed + 1 + RANK, deterministic=True)
# torch_distributed_zero_first用于同步不同进程的数据管理器
with torch_distributed_zero_first(LOCAL_RANK):
# Data
# check_dataset的作用是检查和生成作用:
# 检查:
# 1.检查data是否是zip,并解压
# 2.检查data字典中的key,train、val、test是否存在
# 3.检查data字典中names的key0,1,2是否为整型
# 4.检查val的数据下载
# 生成:
# 1.将data.yaml读取为data字典
# 2.将data[names]的value若是列表或元组则转化为字典类型
# 3.将data[nc]赋值为data[names]的长度
# 4.将data的train、val、test的路径变成绝对路径
data_dict = data_dict or check_dataset(data) # check if None
# 训练路径和验证路径
train_path, val_path = data_dict['train'], data_dict['val']
# 类别
nc = 1 if single_cls else int(data_dict['nc']) # number of classes
# 类别名{0:HP,1:LP,2:NE}
names = {0: 'item'} if single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names
# 判断是否为COCO数据集
is_coco = isinstance(val_path, str) and val_path.endswith('coco/val2017.txt')
2. 准备模型
check_suffix(weights, '.pt') # check weights
pretrained = weights.endswith('.pt')
if pretrained:
# torch_distributed_zero_first用于同步不同进程的数据管理器
with torch_distributed_zero_first(LOCAL_RANK):
# 检查本地是否存在weights(权重文件),若不存在,则在官网下载。
weights = attempt_download(weights) # download if not found locally
# 加载预训练权重文件
ckpt = torch.load(weights, map_location='cpu') # load checkpoint to CPU to avoid CUDA memory leak
# 若resume存在,cfg=None,则按照ckpt['model'].yaml创建模型骨架
# 若resume不存在,则按照cfg创建模型骨架
model = Model(cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
exclude = ['anchor'] if (cfg or hyp.get('anchors')) and not resume else [] # exclude keys
csd = ckpt['model'].float().state_dict() # checkpoint state_dict as FP32
csd = intersect_dicts(csd, model.state_dict(), exclude=exclude) # intersect
# 创建好模型之后,模型加载参数
model.load_state_dict(csd, strict=False) # load
LOGGER.info(f'Transferred {len(csd)}/{len(model.state_dict())} items from {weights}') # report
else:
model = Model(cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
amp = check_amp(model) # check AMP
3. 冻结参数
# 冻结参数:若freeze为!=0 ,则代表网络训练时不进行参数的更新操作。
freeze = [f'model.{x}.' for x in (freeze if len(freeze) > 1 else range(freeze[0]))] # layers to freeze
for k, v in model.named_parameters():
v.requires_grad = True # train all layers
# v.register_hook(lambda x: torch.nan_to_num(x)) # NaN to 0 (commented for erratic training results)
if any(x in k for x in freeze):
LOGGER.info(f'freezing {k}')
v.requires_grad = False
4. 检查图像大小;检查训练批次大小
# Image size
# gs=32
gs = max(int(model.stride.max()), 32) # grid size (max stride)
# check_img_size会将图像大小尺寸判断是不是32的倍数,若不是则warning提示,并改成32倍
imgsz = check_img_size(opt.imgsz, gs, floor=gs * 2) # verify imgsz is gs-multiple
# Batch size
# batch_size=-1则会调用check_train_batch_size,表示会根据内存使用情况来设置batch_size的大小
if RANK == -1 and batch_size == -1: # single-GPU only, estimate best batch size
batch_size = check_train_batch_size(model, imgsz, amp)
loggers.on_params_update({"batch_size": batch_size})
5. 配置优化器
# Optimizer
# nbs为标准batch_size,模型梯度累积次数在达到accumulate次之后,会更新模型,变相扩大bs
nbs = 64 # nominal batch size
# round函数指得是四舍五入
accumulate = max(round(nbs / batch_size), 1) # accumulate loss before optimizing
# 根据accumulate设置权重衰减参数
hyp['weight_decay'] *= batch_size * accumulate / nbs # scale weight_decay
# 使用opt.optimizer优化器(我们选择的是AdamW),学习率值=lr0,动量值=hyp['momentum'],权重衰减值=hyp['weight_decay'],
optimizer = smart_optimizer(model, opt.optimizer, hyp['lr0'], hyp['momentum'], hyp['weight_decay'])
# Scheduler
if opt.cos_lr:
#one_cycle也是一个lambda函数
lf = one_cycle(1, hyp['lrf'], epochs) # cosine 1->hyp['lrf']
else:
lf = lambda x: (1 - x / epochs) * (1.0 - hyp['lrf']) + hyp['lrf'] # linear
# 为优化器设置学习率变化策略☆☆☆
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs)
6. 加载数据集
# Trainloader
train_loader, dataset = create_dataloader(train_path,
imgsz,
batch_size // WORLD_SIZE,
gs,
single_cls,
hyp=hyp,
augment=True,
cache=None if opt.cache == 'val' else opt.cache,
rect=opt.rect,
rank=LOCAL_RANK,
workers=workers,
image_weights=opt.image_weights,
quad=opt.quad,
prefix=colorstr('train: '),
shuffle=True)
labels = np.concatenate(dataset.labels, 0)
mlc = int(labels[:, 0].max()) # max label class
assert mlc < nc, f'Label class {mlc} exceeds nc={nc} in {data}. Possible class labels are 0-{nc - 1}'
# Valloader
if RANK in {-1, 0}:
val_loader = create_dataloader(val_path,
imgsz,
batch_size // WORLD_SIZE * 2,
gs,
single_cls,
hyp=hyp,
cache=None if noval else opt.cache,
rect=True,
rank=-1,
workers=workers * 2,
pad=0.5,
prefix=colorstr('val: '))[0]
if not resume:
if not opt.noautoanchor:
check_anchors(dataset, model=model, thr=hyp['anchor_t'], imgsz=imgsz) # run AutoAnchor
model.half().float() # pre-reduce anchor precision
# 预训练配置结束,输出相应的日志
callbacks.run('on_pretrain_routine_end', labels, names)
# DDP mode
if cuda and RANK != -1:
model = smart_DDP(model)
7. 加载模型属性
# 检测头个数
nl = de_parallel(model).model[-1].nl # number of detection layers (to scale hyps)
# 为了平衡损失函数,会在损失函数面前×一个权重,这三个超参数为各自损失函数前的权重。
# hyp['box'] 位置损失权重
hyp['box'] *= 3 / nl # scale to layers
# hyp['cls'] 分类损失的权重
hyp['cls'] *= nc / 80 * 3 / nl # scale to classes and layers
# hyp['obj'] 置信度损失的权重
hyp['obj'] *= (imgsz / 640) ** 2 * 3 / nl # scale to image size and layers
# 设置标签平滑
hyp['label_smoothing'] = opt.label_smoothing
# nc=3
model.nc = nc # attach number of classes to model
model.hyp = hyp # attach hyperparameters to model
# 类别权重是使用opt.image_weights的时候使用的
model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) * nc # attach class weights
# 类别名{0:HP,1:LP,2:NE}
model.names = names
8. 定义训练标准属性
# Start training
t0 = time.time()
# nb是batch的数量
nb = len(train_loader) # number of batches
# nw是warmup的迭代次数
nw = max(round(hyp['warmup_epochs'] * nb), 100) # number of warmup iterations, max(3 epochs, 100 iterations)
# nw = min(nw, (epochs - start_epoch) / 2 * nb) # limit warmup to < 1/2 of training
last_opt_step = -1
maps = np.zeros(nc) # mAP per class
results = (0, 0, 0, 0, 0, 0, 0) # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls)
scheduler.last_epoch = start_epoch - 1 # do not move
scaler = torch.cuda.amp.GradScaler(enabled=amp)
stopper, stop = EarlyStopping(patience=opt.patience), False
# 初始化损失计算对象
compute_loss = ComputeLoss(model) # init loss class
callbacks.run('on_train_start')
LOGGER.info(f'Image sizes {imgsz} train, {imgsz} valn'
f'Using {train_loader.num_workers * WORLD_SIZE} dataloader workersn'
f"Logging results to {colorstr('bold', save_dir)}n"
f'Starting training for {epochs} epochs...')
二、训练过程
每个epoch开始:
for epoch in range(start_epoch, epochs): # epoch ------------------------------------------------------------------
callbacks.run('on_train_epoch_start')
model.train()
# Update image weights (optional, single-GPU only)
# 是否采用image_weights:若某一类的准确率不高,那么就会根据这个类别的权重赋加给这个图片上相应类别区域处权重。模型就会对这些较高权重的的区域着重训练。
if opt.image_weights:
# 根据每种类的mAP值计算出一个类别权重,mAP越低,类别权重越高。
cw = model.class_weights.cpu().numpy() * (1 - maps) ** 2 / nc # class weights
# 将类别权重换算到图片的维度,将类别权重换算到图片权重。
iw = labels_to_image_weights(dataset.labels, nc=nc, class_weights=cw) # image weights
# 将图片权重赋加到图片上:这些图片上对于某低mAP类别的区域权重很高
dataset.indices = random.choices(range(dataset.n), weights=iw, k=dataset.n) # rand weighted idx
# mloss用于存放三个损失
mloss = torch.zeros(3, device=device) # mean losses
if RANK != -1:
train_loader.sampler.set_epoch(epoch)
pbar = enumerate(train_loader)
LOGGER.info(('n' + '%11s' * 7) % ('Epoch', 'GPU_mem', 'box_loss', 'obj_loss', 'cls_loss', 'Instances', 'Size'))
if RANK in {-1, 0}:
pbar = tqdm(pbar, total=nb, bar_format=TQDM_BAR_FORMAT) # progress bar
# 清空优化器中的梯度
optimizer.zero_grad()
每个batch开始:
1. Warmup
# 和val.py是一样的:
# i是第i个批次
# imgs是一batch的数据,每张图片的各个通道像素
# targets是一batch的数据,每个目标的[图片索引,0/1/2,x,y,h,w]
# paths是一batch的数据路径组成的列表,len(paths)=batch_sz
for i, (imgs, targets, paths, _) in pbar: # batch -------------------------------------------------------------
callbacks.run('on_train_batch_start')
# ni=当前批次索引+批次数量*epoch索引.我称为累计批次索引,这个批次是每个epoch叠加的.nb是batch的数量.
ni = i + nb * epoch # number integrated batches (since train start)
imgs = imgs.to(device, non_blocking=True).float() / 255 # uint8 to float32, 0-255 to 0.0-1.0
# Warmup
# warmup的作用是:
# 刚开始的几百个批次使用很小的学习率训练,防止大学习率造成刚开始就过拟合了。
# 之后模型稳定下来了,可以使用预设的学习率训练。
# 当累计批次索引在warmup迭代次数内(哦~由此可见nw值得是批次数,而不是epoch数)
if ni <= nw:
xi = [0, nw] # x interp
# compute_loss.gr = np.interp(ni, xi, [0.0, 1.0]) # iou loss ratio (obj_loss = 1.0 or iou)
accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
# 遍历优化器的参数组,使用的是线性插值进行的更新:从0升高至初始学习率。
for j, x in enumerate(optimizer.param_groups):
# bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
x['lr'] = np.interp(ni, xi, [hyp['warmup_bias_lr'] if j == 0 else 0.0, x['initial_lr'] * lf(epoch)])
if 'momentum' in x:
# 将动力值按照线性插值的方式进行更新
x['momentum'] = np.interp(ni, xi, [hyp['warmup_momentum'], hyp['momentum']])
2. Multi-scale
# Multi-scale
# 是否采用multi_scale:多尺度缩放。
if opt.multi_scale:
#随机改变图片的尺寸
sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs # size
sf = sz / max(imgs.shape[2:]) # scale factor
if sf != 1:
ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]] # new shape (stretched to gs-multiple)
imgs = nn.functional.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
3. 前向传播、反向传播
# Forward
with torch.cuda.amp.autocast(amp):
# len(pred)=3 pred指的是每个检测头的feature map 每个像素每个anchor的信息
# preds[0].shape (16,3,176,176,8)
# preds[1].shape (16,3,88,88,8)
# preds[2].shape (16,3,21,21,8)
# 8表示这个anchor的3类别条件概率信息+1置信度信息+x_center+y_center+w+h
# targets是一batch的数据,每个目标的[图片索引, 0/1/2, x, y, h, w]
# targets.shape=(一个batch图片目标数之和,6)
pred = model(imgs) # forward
# compute_loss-->return (lbox + lobj + lcls) * bs, torch.cat((lbox, lobj, lcls)).detach()
# compute_loss中最后计算的 lbox、lobj、lcls是一个平均batch的各项loss(具体而言就是用BCE去计算的),因此(lbox + lobj + lcls) * bs看做是一个batch的数据的总loss。
# loss是一个batch的数据的总loss(loss=[(lbox + lobj + lcls) * bs]),loss_items是一个平均batch的各项loss(loss=[位置损失,置信度损失,类别损失])。
loss, loss_items = compute_loss(pred, targets.to(device)) # loss scaled by batch_size
if RANK != -1:
loss *= WORLD_SIZE # gradient averaged between devices in DDP mode
if opt.quad:
loss *= 4.
# Backward
# scale:使用自动混合精度运算
# 计算梯度
scaler.scale(loss).backward()
# Optimize
# 参数更新并不会每次反向传播后都进行参数更新,
# 只有全局批次达到accumulate数值,才会进行参数更新,否则loss会不断叠加。
# 这样做的目的:为了以更小的batch_size实现更高的batch_size效果。
if ni - last_opt_step >= accumulate:
scaler.unscale_(optimizer) # unscale gradients
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=10.0) # clip gradients
# 更新模型参数:将优化器中的梯度与模型参数计算,更新模型参数
scaler.step(optimizer) # optimizer.step
scaler.update()
# 清空优化器中的梯度
optimizer.zero_grad()
if ema:
ema.update(model)
last_opt_step = ni
所有batch结束;
4. 验证
# Log
if RANK in {-1, 0}:
# mloss=(上个batch的各项loss*i+当前batch的各项loss)/batch数
mloss = (mloss * i + loss_items) / (i + 1) # update mean losses
# 计算显存占用
mem = f'{torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0:.3g}G' # (GB)
pbar.set_description(('%11s' * 2 + '%11.4g' * 5) %
(f'{epoch}/{epochs - 1}', mem, *mloss, targets.shape[0], imgs.shape[-1]))
callbacks.run('on_train_batch_end', model, ni, imgs, targets, paths, list(mloss))
if callbacks.stop_training:
return
# end batch ------------------------------------------------------------------------------------------------
# Scheduler
# 在参数组找到对应的学习率
lr = [x['lr'] for x in optimizer.param_groups] # for loggers
# 学习率更新
scheduler.step()
if RANK in {-1, 0}:
# mAP
print("每经过一次epoch进行一次验证")
callbacks.run('on_train_epoch_end', epoch=epoch)
ema.update_attr(model, include=['yaml', 'nc', 'hyp', 'names', 'stride', 'class_weights'])
final_epoch = (epoch + 1 == epochs) or stopper.possible_stop
if not noval or final_epoch: # Calculate mAP
results, maps, _ = validate.run(data_dict,
batch_size=batch_size // WORLD_SIZE * 2,
imgsz=imgsz,
half=amp,
model=ema.ema,
single_cls=single_cls,
dataloader=val_loader,
save_dir=save_dir,
plots=False,
callbacks=callbacks,
compute_loss=compute_loss)
# Update best mAP
# results=[P, R, mAP@.5, mAP@.5-.95]
# fitness是 0.1乘mAP@0.5 加上 0.9乘mAP@0.5:0.95
fi = fitness(np.array(results).reshape(1, -1)) # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
stop = stopper(epoch=epoch, fitness=fi) # early stop check
if fi > best_fitness:
best_fitness = fi
# log_vals是一个列表,里面包含:[train_mlbox, train_mlobj, train_mlcls, P, R, mAP@.5, mAP@.5-.95, val_lbox, val_lobj, val_lcls, lrbox, lrobj, lrcls]
# train_mlbox指的是训练时的位置损失,每个batch累加的平均损失
# val_lbox指的是验证时的位置损失
log_vals = list(mloss) + list(results) + lr
callbacks.run('on_fit_epoch_end', log_vals, epoch, best_fitness, fi)
5. 存储权重
# Save model
if (not nosave) or (final_epoch and not evolve): # if save
# 将当前训练过程中的所有参数赋值给ckpt
ckpt = {
'epoch': epoch,
'best_fitness': best_fitness,
'model': deepcopy(de_parallel(model)).half(),
'ema': deepcopy(ema.ema).half(),
'updates': ema.updates,
'optimizer': optimizer.state_dict(),
'opt': vars(opt),
'git': GIT_INFO, # {remote, branch, commit} if a git repo
'date': datetime.now().isoformat()}
# Save last, best and delete
torch.save(ckpt, last)
if best_fitness == fi:
# 保存参数和权重
torch.save(ckpt, best)
if opt.save_period > 0 and epoch % opt.save_period == 0:
torch.save(ckpt, w / f'epoch{epoch}.pt')
del ckpt
callbacks.run('on_model_save', last, epoch, final_epoch, best_fitness, fi)
# EarlyStopping
if RANK != -1: # if DDP training
broadcast_list = [stop if RANK == 0 else None]
dist.broadcast_object_list(broadcast_list, 0) # broadcast 'stop' to all ranks
if RANK != 0:
stop = broadcast_list[0]
if stop:
break # must break all DDP ranks
epoch结束;
最后验证一次
if RANK in {-1, 0}:
print("所有epoch结束后进行的验证")
LOGGER.info(f'n{epoch - start_epoch + 1} epochs completed in {(time.time() - t0) / 3600:.3f} hours.')
for f in last, best:
if f.exists():
strip_optimizer(f) # strip optimizers
if f is best:
LOGGER.info(f'nValidating {f}...')
results, _, _ = validate.run(
data_dict,
batch_size=batch_size // WORLD_SIZE * 2,
imgsz=imgsz,
model=attempt_load(f, device).half(),
iou_thres=0.65 if is_coco else 0.60, # best pycocotools at iou 0.65
single_cls=single_cls,
dataloader=val_loader,
save_dir=save_dir,
save_json=is_coco,
verbose=True,
plots=plots,
callbacks=callbacks,
compute_loss=compute_loss) # val best model with plots
if is_coco:
callbacks.run('on_fit_epoch_end', list(mloss) + list(results) + lr, epoch, best_fitness, fi)
callbacks.run('on_train_end', last, best, epoch, results)
torch.cuda.empty_cache()
return results
本网站的内容主要来自互联网上的各种资源,仅供参考和信息分享之用,不代表本网站拥有相关版权或知识产权。如您认为内容侵犯您的权益,请联系我们,我们将尽快采取行动,包括删除或更正。