提交 70cb5650 authored 作者: 1051780106@qq.com's avatar 1051780106@qq.com

create: 注释

上级 4751345d
...@@ -8,8 +8,11 @@ ...@@ -8,8 +8,11 @@
# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..] # Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
#数据集存放目录
path: ../datasets/coco128 # dataset root dir path: ../datasets/coco128 # dataset root dir
#训练集存放目录
train: images/train2017 # train images (relative to 'path') 128 images train: images/train2017 # train images (relative to 'path') 128 images
#验证集存放目录
val: images/train2017 # val images (relative to 'path') 128 images val: images/train2017 # val images (relative to 'path') 128 images
test: # test images (optional) test: # test images (optional)
......
...@@ -36,11 +36,13 @@ from pathlib import Path ...@@ -36,11 +36,13 @@ from pathlib import Path
import torch import torch
# 当前执行的detect.py的绝对路径,E:\Study\yolo\yolov5\detect.py
FILE = Path(__file__).resolve() FILE = Path(__file__).resolve()
# parents[0],获取detect.py的父路径,即E:\Study\yolo\yolov5
ROOT = FILE.parents[0] # YOLOv5 root directory ROOT = FILE.parents[0] # YOLOv5 root directory
if str(ROOT) not in sys.path: if str(ROOT) not in sys.path: # 模块的查询路径的列表
sys.path.append(str(ROOT)) # add ROOT to PATH sys.path.append(str(ROOT)) # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative,绝对路径转换成相对路径
from models.common import DetectMultiBackend from models.common import DetectMultiBackend
from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams from utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreams
...@@ -80,27 +82,38 @@ def run( ...@@ -80,27 +82,38 @@ def run(
dnn=False, # use OpenCV DNN for ONNX inference dnn=False, # use OpenCV DNN for ONNX inference
vid_stride=1, # video frame-rate stride vid_stride=1, # video frame-rate stride
): ):
source = str(source) # 对传进的参数进行了一些判断操作
source = str(source) # 强制将路径转换为字符串类型, 'python detect.py --source data\\images\\bus.jpg
save_img = not nosave and not source.endswith('.txt') # save inference images save_img = not nosave and not source.endswith('.txt') # save inference images
# 判断是不是文件地址,Path()查看文件的路径,suffix提取文件的后缀
is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS) is_file = Path(source).suffix[1:] in (IMG_FORMATS + VID_FORMATS)
# 判断是不是网络流地址
is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://')) is_url = source.lower().startswith(('rtsp://', 'rtmp://', 'http://', 'https://'))
# 判断source参数是不是摄像头地址或者网络流地址
webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file) webcam = source.isnumeric() or source.endswith('.streams') or (is_url and not is_file)
screenshot = source.lower().startswith('screen') screenshot = source.lower().startswith('screen')
if is_url and is_file: if is_url and is_file:
# 如果是网络流文件,则下载
source = check_file(source) # download source = check_file(source) # download
# Directories # Directories
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run # 新建保存结果的文件夹
save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run, runs\\detect\\exp3
(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir (save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir
# Load model # Load model,加载模型的权重
# 选择系统的设备,CPU/GPU
device = select_device(device) device = select_device(device)
# 通过框架(此处weights为.pt文件,所以是pytorch)加载模型
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half) model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)
# 加载好模型中,读取模型的步长,类别名,是不是pytorch
stride, names, pt = model.stride, model.names, model.pt stride, names, pt = model.stride, model.names, model.pt
# 图片size是不是满足32倍数
imgsz = check_img_size(imgsz, s=stride) # check image size imgsz = check_img_size(imgsz, s=stride) # check image size
# Dataloader # Dataloader
bs = 1 # batch_size # 定义Dataloader模块,用来加载待预测的图片
bs = 1 # batch_size,每次输入1张图片
if webcam: if webcam:
view_img = check_imshow(warn=True) view_img = check_imshow(warn=True)
dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride) dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
...@@ -108,34 +121,39 @@ def run( ...@@ -108,34 +121,39 @@ def run(
elif screenshot: elif screenshot:
dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt) dataset = LoadScreenshots(source, img_size=imgsz, stride=stride, auto=pt)
else: else:
# 加载图片
dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride) dataset = LoadImages(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride)
vid_path, vid_writer = [None] * bs, [None] * bs vid_path, vid_writer = [None] * bs, [None] * bs
# Run inference # Run inference
model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz)) # warmup # 执行模型的推理过程
model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz)) # warmup翻译‘热身’,先给GPU随便传一张让他工作
seen, windows, dt = 0, [], (Profile(), Profile(), Profile()) seen, windows, dt = 0, [], (Profile(), Profile(), Profile())
for path, im, im0s, vid_cap, s in dataset: # 进行图片预测
# im resize后的图片,im0s 原图,vid_cap None,s 打印信息
for path, im, im0s, vid_cap, s in dataset: # “E:\Study\yolo\yolov5\data\images\bus.jpg”
with dt[0]: with dt[0]:
im = torch.from_numpy(im).to(model.device) im = torch.from_numpy(im).to(model.device) # torch.Size([3, 640, 480]) from_numpy()将numpy格式的图片转换问tensor格式
im = im.half() if model.fp16 else im.float() # uint8 to fp16/32 im = im.half() if model.fp16 else im.float() # uint8 to fp16/32
im /= 255 # 0 - 255 to 0.0 - 1.0 im /= 255 # 0 - 255 to 0.0 - 1.0 归一化操作
if len(im.shape) == 3: if len(im.shape) == 3:
im = im[None] # expand for batch dim im = im[None] # expand for batch dim , torch.Size([1, 3, 640, 480])
# Inference # Inference
with dt[1]: with dt[1]:
visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False visualize = increment_path(save_dir / Path(path).stem, mkdir=True) if visualize else False
pred = model(im, augment=augment, visualize=visualize) pred = model(im, augment=augment, visualize=visualize) # 模型预测出来的所有的检测框 # torch.Size([1, 18900, 85]) yolov5预训练权重所输出的85个预测信息,4个坐标信息,1个置信度信息,80个分类
# NMS # NMS
with dt[2]: with dt[2]:
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) # conf_thres 置信度阈值,iou_thres iou阈值, max_det 最大目标数
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) # 过滤后的信息 1, 5, 6 ,1个batch, 5个结果, 6中4个坐标信息,1个置信度信息,1个分类信息
# Second-stage classifier (optional) # Second-stage classifier (optional)
# pred = utils.general.apply_classifier(pred, classifier_model, im, im0s) # pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)
# Process predictions # Process predictions
for i, det in enumerate(pred): # per image for i, det in enumerate(pred): # per image , det 表示5个检测框的预测信息 torch.Size([5,6])
seen += 1 seen += 1
if webcam: # batch_size >= 1 if webcam: # batch_size >= 1
p, im0, frame = path[i], im0s[i].copy(), dataset.count p, im0, frame = path[i], im0s[i].copy(), dataset.count
...@@ -144,14 +162,20 @@ def run( ...@@ -144,14 +162,20 @@ def run(
p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0) p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0)
p = Path(p) # to Path p = Path(p) # to Path
# 图片的保存路径
save_path = str(save_dir / p.name) # im.jpg save_path = str(save_dir / p.name) # im.jpg
txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # im.txt txt_path = str(save_dir / 'labels' / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # im.txt
# s中加上图片size
s += '%gx%g ' % im.shape[2:] # print string s += '%gx%g ' % im.shape[2:] # print string
# 获得原图宽高大小
gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh
# 判断是否裁剪检测框的图片
imc = im0.copy() if save_crop else im0 # for save_crop imc = im0.copy() if save_crop else im0 # for save_crop
# 定义绘图工具,专门画框的
annotator = Annotator(im0, line_width=line_thickness, example=str(names)) annotator = Annotator(im0, line_width=line_thickness, example=str(names))
if len(det): if len(det):
# Rescale boxes from img_size to im0 size # Rescale boxes from img_size to im0 size
# 坐标映射,将640,480的图片上面宽的坐标映射到原图上
det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round() det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()
# Print results # Print results
...@@ -175,6 +199,7 @@ def run( ...@@ -175,6 +199,7 @@ def run(
save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True) save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)
# Stream results # Stream results
# 从annotator中返回画好框的图片
im0 = annotator.result() im0 = annotator.result()
if view_img: if view_img:
if platform.system() == 'Linux' and p not in windows: if platform.system() == 'Linux' and p not in windows:
...@@ -184,7 +209,7 @@ def run( ...@@ -184,7 +209,7 @@ def run(
cv2.imshow(str(p), im0) cv2.imshow(str(p), im0)
cv2.waitKey(1) # 1 millisecond cv2.waitKey(1) # 1 millisecond
# Save results (image with detections) # Save results (image with detections) 保存图片
if save_img: if save_img:
if dataset.mode == 'image': if dataset.mode == 'image':
cv2.imwrite(save_path, im0) cv2.imwrite(save_path, im0)
...@@ -207,7 +232,8 @@ def run( ...@@ -207,7 +232,8 @@ def run(
LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms") LOGGER.info(f"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms")
# Print results # Print results
t = tuple(x.t / seen * 1E3 for x in dt) # speeds per image # 最终打印出输出信息
t = tuple(x.t / seen * 1E3 for x in dt) # speeds per image 打印平均时间
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t) LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {(1, 3, *imgsz)}' % t)
if save_txt or save_img: if save_txt or save_img:
s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '' s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else ''
...@@ -217,10 +243,14 @@ def run( ...@@ -217,10 +243,14 @@ def run(
def parse_opt(): def parse_opt():
# 定义命令行可以传入的参数
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--weights', nargs='+', type=str, default='runs/train/exp4/weights/best.pt', help='model path or triton URL') # 权重
parser.add_argument('--weights', nargs='+', type=str, default='runs/train/exp4/weights/best.pt',
help='model path or triton URL')
parser.add_argument('--source', type=str, default='owndata/test/video', help='file/dir/URL/glob/screen/0(webcam)') parser.add_argument('--source', type=str, default='owndata/test/video', help='file/dir/URL/glob/screen/0(webcam)')
parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='(optional) dataset.yaml path') parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='(optional) dataset.yaml path')
#
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w') parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold') parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold')
parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold') parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
...@@ -247,15 +277,22 @@ def parse_opt(): ...@@ -247,15 +277,22 @@ def parse_opt():
parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride') parser.add_argument('--vid-stride', type=int, default=1, help='video frame-rate stride')
opt = parser.parse_args() opt = parser.parse_args()
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
# 打印所以的参数信息
# detect: weights=runs/train/exp4/weights/best.pt, source=owndata/test/video, data=data\coco128.yaml, imgsz=[640, 640], conf_thres=0.25, iou_thres=0.45, max_det=1000, device=, view_img=False, save_txt=False, save_conf=False, save_crop=False, nosave=False, classes=None, agnostic_nms=False, augment=False, visualize=False, update=False, project=runs\detect, name=exp, exist_ok=False, line_thickness=3, hide_labels=False, hide_conf=False, half=False, dnn=False, vid_stride=1
# YOLOv5 v7.0-112-g4751345d Python-3.7.16 torch-1.13.1 CPU
print_args(vars(opt)) print_args(vars(opt))
return opt return opt
def main(opt): def main(opt):
# 检测包有没有成功安装
check_requirements(exclude=('tensorboard', 'thop')) check_requirements(exclude=('tensorboard', 'thop'))
# 执行run函数,并传入参数
run(**vars(opt)) run(**vars(opt))
if __name__ == '__main__': if __name__ == '__main__':
# 解析命令行传入的参数
opt = parse_opt() opt = parse_opt()
# 执行main函数
main(opt) main(opt)
...@@ -163,7 +163,7 @@ class BaseModel(nn.Module): ...@@ -163,7 +163,7 @@ class BaseModel(nn.Module):
class DetectionModel(BaseModel): class DetectionModel(BaseModel):
# YOLOv5 detection model # YOLOv5 detection model 模型初始化
def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classes def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classes
super().__init__() super().__init__()
if isinstance(cfg, dict): if isinstance(cfg, dict):
...@@ -357,6 +357,7 @@ def parse_model(d, ch): # model_dict, input_channels(3) ...@@ -357,6 +357,7 @@ def parse_model(d, ch): # model_dict, input_channels(3)
if __name__ == '__main__': if __name__ == '__main__':
# 定义参数信息
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--cfg', type=str, default='yolov5s.yaml', help='model.yaml') parser.add_argument('--cfg', type=str, default='yolov5s.yaml', help='model.yaml')
parser.add_argument('--batch-size', type=int, default=1, help='total batch size for all GPUs') parser.add_argument('--batch-size', type=int, default=1, help='total batch size for all GPUs')
......
...@@ -61,6 +61,7 @@ from utils.plots import plot_evolve ...@@ -61,6 +61,7 @@ from utils.plots import plot_evolve
from utils.torch_utils import (EarlyStopping, ModelEMA, de_parallel, select_device, smart_DDP, smart_optimizer, from utils.torch_utils import (EarlyStopping, ModelEMA, de_parallel, select_device, smart_DDP, smart_optimizer,
smart_resume, torch_distributed_zero_first) smart_resume, torch_distributed_zero_first)
# 分布式训练需要的,初学默认
LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html
RANK = int(os.getenv('RANK', -1)) RANK = int(os.getenv('RANK', -1))
WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1)) WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1))
...@@ -68,26 +69,33 @@ GIT_INFO = check_git_info() ...@@ -68,26 +69,33 @@ GIT_INFO = check_git_info()
def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictionary def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictionary
# 读取opt内的参数
save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze = \ 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, \ 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 opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
# 查找日志记录器中是否有该名称函数,有则执行
callbacks.run('on_pretrain_routine_start') callbacks.run('on_pretrain_routine_start')
# Directories # Directories
w = save_dir / 'weights' # weights dir w = save_dir / 'weights' # weights dir 训练过程中产生的权重文件保存地址
(w.parent if evolve else w).mkdir(parents=True, exist_ok=True) # make dir (w.parent if evolve else w).mkdir(parents=True, exist_ok=True) # make dir
# last最后一轮的训练文件,best中间最好的一轮
last, best = w / 'last.pt', w / 'best.pt' last, best = w / 'last.pt', w / 'best.pt'
# Hyperparameters # Hyperparameters
# 加载训练过程中需要使用到的超参数
if isinstance(hyp, str): if isinstance(hyp, str):
with open(hyp, errors='ignore') as f: with open(hyp, errors='ignore') as f:
hyp = yaml.safe_load(f) # load hyps dict hyp = yaml.safe_load(f) # load hyps dict
# 打印超参数
LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items())) LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items()))
opt.hyp = hyp.copy() # for saving hyps to checkpoints opt.hyp = hyp.copy() # for saving hyps to checkpoints
# Save run settings # Save run settings
if not evolve: if not evolve:
# 保存所有用到的超参数
yaml_save(save_dir / 'hyp.yaml', hyp) yaml_save(save_dir / 'hyp.yaml', hyp)
# 保存执行脚本所使用的参数
yaml_save(save_dir / 'opt.yaml', vars(opt)) yaml_save(save_dir / 'opt.yaml', vars(opt))
# Loggers # Loggers
...@@ -106,33 +114,44 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio ...@@ -106,33 +114,44 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
# Config # Config
plots = not evolve and not opt.noplots # create plots plots = not evolve and not opt.noplots # create plots
# 判断是否支持cuda
cuda = device.type != 'cpu' cuda = device.type != 'cpu'
# 初始随机化种子
init_seeds(opt.seed + 1 + RANK, deterministic=True) init_seeds(opt.seed + 1 + RANK, deterministic=True)
with torch_distributed_zero_first(LOCAL_RANK): with torch_distributed_zero_first(LOCAL_RANK):
# 获取数据集
data_dict = data_dict or check_dataset(data) # check if None data_dict = data_dict or check_dataset(data) # check if None
train_path, val_path = data_dict['train'], data_dict['val'] train_path, val_path = data_dict['train'], data_dict['val']
# 类名数
nc = 1 if single_cls else int(data_dict['nc']) # number of classes nc = 1 if single_cls else int(data_dict['nc']) # number of classes
# 取出类名
names = {0: 'item'} if single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names 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') # COCO dataset is_coco = isinstance(val_path, str) and val_path.endswith('coco/val2017.txt') # COCO dataset
# Model # Model 模型加载
check_suffix(weights, '.pt') # check weights check_suffix(weights, '.pt') # check weights,判断权重是否以pt结尾
pretrained = weights.endswith('.pt') pretrained = weights.endswith('.pt') # 使用.pt结尾的权重
if pretrained: if pretrained:
with torch_distributed_zero_first(LOCAL_RANK): with torch_distributed_zero_first(LOCAL_RANK):
weights = attempt_download(weights) # download if not found locally 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 ckpt = torch.load(weights, map_location='cpu') # load checkpoint to CPU to avoid CUDA memory leak
# 根据yaml文件创建一个新的model
model = Model(cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create 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 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 = ckpt['model'].float().state_dict() # checkpoint state_dict as FP32
# csd和自己创建的模型有多少参数是相同的
csd = intersect_dicts(csd, model.state_dict(), exclude=exclude) # intersect csd = intersect_dicts(csd, model.state_dict(), exclude=exclude) # intersect
# 加载所有相同的
model.load_state_dict(csd, strict=False) # load model.load_state_dict(csd, strict=False) # load
LOGGER.info(f'Transferred {len(csd)}/{len(model.state_dict())} items from {weights}') # report LOGGER.info(f'Transferred {len(csd)}/{len(model.state_dict())} items from {weights}') # report
else: else:
model = Model(cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create model = Model(cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
amp = check_amp(model) # check AMP amp = check_amp(model) # check AMP
# Freeze # Freeze 手动冻结哪些层
freeze = [f'model.{x}.' for x in (freeze if len(freeze) > 1 else range(freeze[0]))] # layers to freeze 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(): for k, v in model.named_parameters():
v.requires_grad = True # train all layers v.requires_grad = True # train all layers
...@@ -142,38 +161,43 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio ...@@ -142,38 +161,43 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
v.requires_grad = False v.requires_grad = False
# Image size # Image size
# 获取模型图片最长边和32的最大值
gs = max(int(model.stride.max()), 32) # grid size (max stride) gs = max(int(model.stride.max()), 32) # grid size (max stride)
# 不满足32倍数,会自动补全成32的倍数
imgsz = check_img_size(opt.imgsz, gs, floor=gs * 2) # verify imgsz is gs-multiple imgsz = check_img_size(opt.imgsz, gs, floor=gs * 2) # verify imgsz is gs-multiple
# Batch size # Batch size
if RANK == -1 and batch_size == -1: # single-GPU only, estimate best batch size if RANK == -1 and batch_size == -1: # single-GPU only, estimate best batch size
# 自动计算batch_size
batch_size = check_train_batch_size(model, imgsz, amp) batch_size = check_train_batch_size(model, imgsz, amp)
loggers.on_params_update({'batch_size': batch_size}) loggers.on_params_update({'batch_size': batch_size})
# Optimizer # Optimizer 创建训练过程中所使用的优化器
nbs = 64 # nominal batch size nbs = 64 # nominal batch size 定义名义上的batch size
accumulate = max(round(nbs / batch_size), 1) # accumulate loss before optimizing accumulate = max(round(nbs / batch_size), 1) # accumulate loss before optimizing 存放累积次数
hyp['weight_decay'] *= batch_size * accumulate / nbs # scale weight_decay hyp['weight_decay'] *= batch_size * accumulate / nbs # scale weight_decay 对权重衰减的超参数进行缩放
optimizer = smart_optimizer(model, opt.optimizer, hyp['lr0'], hyp['momentum'], hyp['weight_decay']) optimizer = smart_optimizer(model, opt.optimizer, hyp['lr0'], hyp['momentum'], hyp['weight_decay'])
# Scheduler # Scheduler 模型训练过程中,学习率变化的策略
if opt.cos_lr: if opt.cos_lr:
lf = one_cycle(1, hyp['lrf'], epochs) # cosine 1->hyp['lrf'] lf = one_cycle(1, hyp['lrf'], epochs) # cosine 1->hyp['lrf']
else: else:
lf = lambda x: (1 - x / epochs) * (1.0 - hyp['lrf']) + hyp['lrf'] # linear # lf 学习率因子 学习率需要
lf = lambda x: (1 - x / epochs) * (1.0 - hyp['lrf']) + hyp[
'lrf'] # linear 线性变化策略 ((hyp['lrf']-1.0)/epochs)x+1 y=kx+b的形式 x可以理解为在训练时训练到第几轮了
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs) scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs)
# EMA # EMA 对模型使用指数移动平均
ema = ModelEMA(model) if RANK in {-1, 0} else None ema = ModelEMA(model) if RANK in {-1, 0} else None
# Resume # Resume 从预训练的文件中加载一些信息
best_fitness, start_epoch = 0.0, 0 best_fitness, start_epoch = 0.0, 0
if pretrained: if pretrained:
if resume: if resume:
best_fitness, start_epoch, epochs = smart_resume(ckpt, optimizer, ema, weights, epochs, resume) best_fitness, start_epoch, epochs = smart_resume(ckpt, optimizer, ema, weights, epochs, resume)
del ckpt, csd del ckpt, csd
# DP mode # DP mode 多GPU训练涉及到的
if cuda and RANK == -1 and torch.cuda.device_count() > 1: if cuda and RANK == -1 and torch.cuda.device_count() > 1:
LOGGER.warning('WARNING ⚠️ DP not recommended, use torch.distributed.run for best DDP Multi-GPU results.\n' LOGGER.warning('WARNING ⚠️ DP not recommended, use torch.distributed.run for best DDP Multi-GPU results.\n'
'See Multi-GPU Tutorial at https://github.com/ultralytics/yolov5/issues/475 to get started.') 'See Multi-GPU Tutorial at https://github.com/ultralytics/yolov5/issues/475 to get started.')
...@@ -185,6 +209,7 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio ...@@ -185,6 +209,7 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
LOGGER.info('Using SyncBatchNorm()') LOGGER.info('Using SyncBatchNorm()')
# Trainloader # Trainloader
# 自定义训练集以及训练集所使用到的数据加载器
train_loader, dataset = create_dataloader(train_path, train_loader, dataset = create_dataloader(train_path,
imgsz, imgsz,
batch_size // WORLD_SIZE, batch_size // WORLD_SIZE,
...@@ -201,12 +226,13 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio ...@@ -201,12 +226,13 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
prefix=colorstr('train: '), prefix=colorstr('train: '),
shuffle=True, shuffle=True,
seed=opt.seed) seed=opt.seed)
labels = np.concatenate(dataset.labels, 0) labels = np.concatenate(dataset.labels, 0) # 计算标签的最大类别号
mlc = int(labels[:, 0].max()) # max label class 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}' assert mlc < nc, f'Label class {mlc} exceeds nc={nc} in {data}. Possible class labels are 0-{nc - 1}'
# Process 0 # Process 0
if RANK in {-1, 0}: if RANK in {-1, 0}:
# 自定义验证集的数据集以及验证集所使用到的数据加载器
val_loader = create_dataloader(val_path, val_loader = create_dataloader(val_path,
imgsz, imgsz,
batch_size // WORLD_SIZE * 2, batch_size // WORLD_SIZE * 2,
...@@ -227,15 +253,15 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio ...@@ -227,15 +253,15 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
callbacks.run('on_pretrain_routine_end', labels, names) callbacks.run('on_pretrain_routine_end', labels, names)
# DDP mode # DDP mode 多卡训练
if cuda and RANK != -1: if cuda and RANK != -1:
model = smart_DDP(model) model = smart_DDP(model)
# Model attributes # Model attributes
nl = de_parallel(model).model[-1].nl # number of detection layers (to scale hyps) nl = de_parallel(model).model[-1].nl # number of detection layers (to scale hyps) 从模型中取出检测层的数量
hyp['box'] *= 3 / nl # scale to layers hyp['box'] *= 3 / nl # scale to layers 缩放
hyp['cls'] *= nc / 80 * 3 / nl # scale to classes and layers hyp['cls'] *= nc / 80 * 3 / nl # scale to classes and layers 缩放
hyp['obj'] *= (imgsz / 640) ** 2 * 3 / nl # scale to image size and layers hyp['obj'] *= (imgsz / 640) ** 2 * 3 / nl # scale to image size and layers 缩放
hyp['label_smoothing'] = opt.label_smoothing hyp['label_smoothing'] = opt.label_smoothing
model.nc = nc # attach number of classes to model model.nc = nc # attach number of classes to model
model.hyp = hyp # attach hyperparameters to model model.hyp = hyp # attach hyperparameters to model
...@@ -243,17 +269,17 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio ...@@ -243,17 +269,17 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
model.names = names model.names = names
# Start training # Start training
t0 = time.time() t0 = time.time() # 统计训练一轮需要的时间
nb = len(train_loader) # number of batches nb = len(train_loader) # number of batches
nw = max(round(hyp['warmup_epochs'] * nb), 100) # number of warmup iterations, max(3 epochs, 100 iterations) nw = max(round(hyp['warmup_epochs'] * nb), 100) # number of warmup iterations, max(3 epochs, 100 iterations) warmup的迭代此处
# nw = min(nw, (epochs - start_epoch) / 2 * nb) # limit warmup to < 1/2 of training # nw = min(nw, (epochs - start_epoch) / 2 * nb) # limit warmup to < 1/2 of training
last_opt_step = -1 last_opt_step = -1 # 上一次更新参数时计数器的值,批次号
maps = np.zeros(nc) # mAP per class maps = np.zeros(nc) # mAP per class 存放训练过程中计算出来的每一类的mAP值
results = (0, 0, 0, 0, 0, 0, 0) # P, R, mAP@.5, mAP@.5-.95, val_loss(box, obj, cls) 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 scheduler.last_epoch = start_epoch - 1 # do not move
scaler = torch.cuda.amp.GradScaler(enabled=amp) scaler = torch.cuda.amp.GradScaler(enabled=amp) # 训练过程中使用自动混合精度去训练
stopper, stop = EarlyStopping(patience=opt.patience), False stopper, stop = EarlyStopping(patience=opt.patience), False # 如果连续训练几轮都没效果,会提前终止训练
compute_loss = ComputeLoss(model) # init loss class compute_loss = ComputeLoss(model) # init loss class # 定义损失函数
callbacks.run('on_train_start') callbacks.run('on_train_start')
LOGGER.info(f'Image sizes {imgsz} train, {imgsz} val\n' LOGGER.info(f'Image sizes {imgsz} train, {imgsz} val\n'
f'Using {train_loader.num_workers * WORLD_SIZE} dataloader workers\n' f'Using {train_loader.num_workers * WORLD_SIZE} dataloader workers\n'
...@@ -261,11 +287,11 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio ...@@ -261,11 +287,11 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
f'Starting training for {epochs} epochs...') f'Starting training for {epochs} epochs...')
for epoch in range(start_epoch, epochs): # epoch ------------------------------------------------------------------ for epoch in range(start_epoch, epochs): # epoch ------------------------------------------------------------------
callbacks.run('on_train_epoch_start') callbacks.run('on_train_epoch_start')
model.train() model.train() # 模型切换到训练状态
# Update image weights (optional, single-GPU only) # Update image weights (optional, single-GPU only)
if opt.image_weights: if opt.image_weights:
cw = model.class_weights.cpu().numpy() * (1 - maps) ** 2 / nc # class weights 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 iw = labels_to_image_weights(dataset.labels, nc=nc, class_weights=cw) # image weights
dataset.indices = random.choices(range(dataset.n), weights=iw, k=dataset.n) # rand weighted idx dataset.indices = random.choices(range(dataset.n), weights=iw, k=dataset.n) # rand weighted idx
...@@ -481,11 +507,12 @@ def parse_opt(known=False): ...@@ -481,11 +507,12 @@ def parse_opt(known=False):
def main(opt, callbacks=Callbacks()): def main(opt, callbacks=Callbacks()):
# Checks # Checks
if RANK in {-1, 0}: if RANK in {-1, 0}:
print_args(vars(opt)) print_args(vars(opt)) # 打印参数信息
check_git_status() check_git_status() # 检验yolov5 github 有没有更新
check_requirements() check_requirements() # 检查requirements有没有安装成功
# Resume (from specified or most recent last.pt) # Resume (from specified or most recent last.pt)
# resume 从中断中恢复
if opt.resume and not check_comet_resume(opt) and not opt.evolve: if opt.resume and not check_comet_resume(opt) and not opt.evolve:
last = Path(check_file(opt.resume) if isinstance(opt.resume, str) else get_latest_run()) last = Path(check_file(opt.resume) if isinstance(opt.resume, str) else get_latest_run())
opt_yaml = last.parent.parent / 'opt.yaml' # train options yaml opt_yaml = last.parent.parent / 'opt.yaml' # train options yaml
...@@ -500,8 +527,10 @@ def main(opt, callbacks=Callbacks()): ...@@ -500,8 +527,10 @@ def main(opt, callbacks=Callbacks()):
if is_url(opt_data): if is_url(opt_data):
opt.data = check_file(opt_data) # avoid HUB resume auth timeout opt.data = check_file(opt_data) # avoid HUB resume auth timeout
else: else:
# data 数据集的文件, weights 预训练权重, project 训练结果保存的路径
opt.data, opt.cfg, opt.hyp, opt.weights, opt.project = \ 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 check_file(opt.data), check_yaml(opt.cfg), check_yaml(opt.hyp), str(opt.weights), str(opt.project) # checks
# 判断cfg和weights是否都为空,不然报错
assert len(opt.cfg) or len(opt.weights), 'either --cfg or --weights must be specified' assert len(opt.cfg) or len(opt.weights), 'either --cfg or --weights must be specified'
if opt.evolve: if opt.evolve:
if opt.project == str(ROOT / 'runs/train'): # if default project name, rename to runs/evolve if opt.project == str(ROOT / 'runs/train'): # if default project name, rename to runs/evolve
...@@ -512,7 +541,9 @@ def main(opt, callbacks=Callbacks()): ...@@ -512,7 +541,9 @@ def main(opt, callbacks=Callbacks()):
opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok)) opt.save_dir = str(increment_path(Path(opt.project) / opt.name, exist_ok=opt.exist_ok))
# DDP mode # DDP mode
# 选择CPU/GPU
device = select_device(opt.device, batch_size=opt.batch_size) device = select_device(opt.device, batch_size=opt.batch_size)
# 是否分布式训练,初学没用到
if LOCAL_RANK != -1: if LOCAL_RANK != -1:
msg = 'is not compatible with YOLOv5 Multi-GPU DDP training' msg = 'is not compatible with YOLOv5 Multi-GPU DDP training'
assert not opt.image_weights, f'--image-weights {msg}' assert not opt.image_weights, f'--image-weights {msg}'
...@@ -524,7 +555,7 @@ def main(opt, callbacks=Callbacks()): ...@@ -524,7 +555,7 @@ def main(opt, callbacks=Callbacks()):
device = torch.device('cuda', LOCAL_RANK) device = torch.device('cuda', LOCAL_RANK)
dist.init_process_group(backend='nccl' if dist.is_nccl_available() else 'gloo') dist.init_process_group(backend='nccl' if dist.is_nccl_available() else 'gloo')
# Train # Train 模型训练
if not opt.evolve: if not opt.evolve:
train(opt.hyp, opt, device, callbacks) train(opt.hyp, opt, device, callbacks)
......
...@@ -110,21 +110,22 @@ def replicate(im, labels): ...@@ -110,21 +110,22 @@ def replicate(im, labels):
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32): def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
# Resize and pad image while meeting stride-multiple constraints # Resize and pad image while meeting stride-multiple constraints
shape = im.shape[:2] # current shape [height, width] shape = im.shape[:2] # current shape [height, width] 原图的高和宽[h,w] (1080,810)
if isinstance(new_shape, int): if isinstance(new_shape, int): # 判断是不是int型的
new_shape = (new_shape, new_shape) new_shape = (new_shape, new_shape)
# Scale ratio (new / old) # Scale ratio (new / old)
# 按照新宽高/旧宽高 小的那一个,进行缩放
r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
if not scaleup: # only scale down, do not scale up (for better val mAP) if not scaleup: # only scale down, do not scale up (for better val mAP)
r = min(r, 1.0) r = min(r, 1.0)
# Compute padding # Compute padding
ratio = r, r # width, height ratios ratio = r, r # width, height ratios
new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) # 求出缩放后的宽高 [width,height] 新图的尺寸 (480,640)
dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding 160,0
if auto: # minimum rectangle if auto: # minimum rectangle 最小矩形
dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding 如果满足32的倍数,自动填充,例如480为32倍数,所以padding就为0,dw=0,dh=0
elif scaleFill: # stretch elif scaleFill: # stretch
dw, dh = 0.0, 0.0 dw, dh = 0.0, 0.0
new_unpad = (new_shape[1], new_shape[0]) new_unpad = (new_shape[1], new_shape[0])
......
...@@ -238,18 +238,18 @@ class LoadScreenshots: ...@@ -238,18 +238,18 @@ class LoadScreenshots:
class LoadImages: class LoadImages:
# YOLOv5 image/video dataloader, i.e. `python detect.py --source image.jpg/vid.mp4` # YOLOv5 image/video dataloader, i.e. `python detect.py --source image.jpg/vid.mp4`
def __init__(self, path, img_size=640, stride=32, auto=True, transforms=None, vid_stride=1): def __init__(self, path, img_size=640, stride=32, auto=True, transforms=None, vid_stride=1): # path:“data\\images\\bus.jpg”,img_size:[640,640]
if isinstance(path, str) and Path(path).suffix == '.txt': # *.txt file with img/vid/dir on each line if isinstance(path, str) and Path(path).suffix == '.txt': # *.txt file with img/vid/dir on each line
path = Path(path).read_text().rsplit() path = Path(path).read_text().rsplit()
files = [] files = []
for p in sorted(path) if isinstance(path, (list, tuple)) else [path]: for p in sorted(path) if isinstance(path, (list, tuple)) else [path]:
p = str(Path(p).resolve()) p = str(Path(p).resolve()) # 由相对路径得到绝对路径
if '*' in p: if '*' in p: # 判断路径是否带"*"号
files.extend(sorted(glob.glob(p, recursive=True))) # glob files.extend(sorted(glob.glob(p, recursive=True))) # glob
elif os.path.isdir(p): elif os.path.isdir(p): # 判断路径是否是文件夹
files.extend(sorted(glob.glob(os.path.join(p, '*.*')))) # dir files.extend(sorted(glob.glob(os.path.join(p, '*.*')))) # dir
elif os.path.isfile(p): elif os.path.isfile(p): # 判断是不是文件
files.append(p) # files files.append(p) # files, 变成列表形式
else: else:
raise FileNotFoundError(f'{p} does not exist') raise FileNotFoundError(f'{p} does not exist')
...@@ -306,12 +306,13 @@ class LoadImages: ...@@ -306,12 +306,13 @@ class LoadImages:
self.count += 1 self.count += 1
im0 = cv2.imread(path) # BGR im0 = cv2.imread(path) # BGR
assert im0 is not None, f'Image Not Found {path}' assert im0 is not None, f'Image Not Found {path}'
s = f'image {self.count}/{self.nf} {path}: ' s = f'image {self.count}/{self.nf} {path}: ' # 打印图片输出到第几张了
if self.transforms: if self.transforms:
im = self.transforms(im0) # transforms im = self.transforms(im0) # transforms
else: else:
im = letterbox(im0, self.img_size, stride=self.stride, auto=self.auto)[0] # padded resize # 将原图变成特定大小的图片,resize
im = letterbox(im0, self.img_size, stride=self.stride, auto=self.auto)[0] # padded resize (640,480,3)
im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
im = np.ascontiguousarray(im) # contiguous im = np.ascontiguousarray(im) # contiguous
......
...@@ -130,12 +130,12 @@ def set_logging(name=LOGGING_NAME, verbose=True): ...@@ -130,12 +130,12 @@ def set_logging(name=LOGGING_NAME, verbose=True):
name: { name: {
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
'formatter': name, 'formatter': name,
'level': level,}}, 'level': level, }},
'loggers': { 'loggers': {
name: { name: {
'level': level, 'level': level,
'handlers': [name], 'handlers': [name],
'propagate': False,}}}) 'propagate': False, }}})
set_logging(LOGGING_NAME) # run before defining LOGGER set_logging(LOGGING_NAME) # run before defining LOGGER
......
...@@ -317,7 +317,9 @@ def copy_attr(a, b, include=(), exclude=()): ...@@ -317,7 +317,9 @@ def copy_attr(a, b, include=(), exclude=()):
def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5): def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5):
# YOLOv5 3-param group optimizer: 0) weights with decay, 1) weights no decay, 2) biases no decay # YOLOv5 3-param group optimizer: 0) weights with decay, 1) weights no decay, 2) biases no decay
# 把模型每一层的参数,划分到三个组中
g = [], [], [] # optimizer parameter groups g = [], [], [] # optimizer parameter groups
# g[0]存所有卷积层的w参数,g[1] bn层的w,g[2]所有层的偏置项b
bn = tuple(v for k, v in nn.__dict__.items() if 'Norm' in k) # normalization layers, i.e. BatchNorm2d() bn = tuple(v for k, v in nn.__dict__.items() if 'Norm' in k) # normalization layers, i.e. BatchNorm2d()
for v in model.modules(): for v in model.modules():
for p_name, p in v.named_parameters(recurse=0): for p_name, p in v.named_parameters(recurse=0):
...@@ -327,7 +329,7 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5): ...@@ -327,7 +329,7 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5):
g[1].append(p) g[1].append(p)
else: else:
g[0].append(p) # weight (with decay) g[0].append(p) # weight (with decay)
# 判断训练过程中所使用的优化器的类型
if name == 'Adam': if name == 'Adam':
optimizer = torch.optim.Adam(g[2], lr=lr, betas=(momentum, 0.999)) # adjust beta1 to momentum optimizer = torch.optim.Adam(g[2], lr=lr, betas=(momentum, 0.999)) # adjust beta1 to momentum
elif name == 'AdamW': elif name == 'AdamW':
...@@ -335,11 +337,12 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5): ...@@ -335,11 +337,12 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5):
elif name == 'RMSProp': elif name == 'RMSProp':
optimizer = torch.optim.RMSprop(g[2], lr=lr, momentum=momentum) optimizer = torch.optim.RMSprop(g[2], lr=lr, momentum=momentum)
elif name == 'SGD': elif name == 'SGD':
optimizer = torch.optim.SGD(g[2], lr=lr, momentum=momentum, nesterov=True) # 默认随机梯度下降法
optimizer = torch.optim.SGD(g[2], lr=lr, momentum=momentum, nesterov=True) # lr 学习率, momentum 动量值
else: else:
raise NotImplementedError(f'Optimizer {name} not implemented.') raise NotImplementedError(f'Optimizer {name} not implemented.')
optimizer.add_param_group({'params': g[0], 'weight_decay': decay}) # add g0 with weight_decay optimizer.add_param_group({'params': g[0], 'weight_decay': decay}) # add g0 with weight_decay weight_decay标明要对卷积层的w进行权重衰减
optimizer.add_param_group({'params': g[1], 'weight_decay': 0.0}) # add g1 (BatchNorm2d weights) optimizer.add_param_group({'params': g[1], 'weight_decay': 0.0}) # add g1 (BatchNorm2d weights)
LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__}(lr={lr}) with parameter groups " LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__}(lr={lr}) with parameter groups "
f'{len(g[1])} weight(decay=0.0), {len(g[0])} weight(decay={decay}), {len(g[2])} bias') f'{len(g[1])} weight(decay=0.0), {len(g[0])} weight(decay={decay}), {len(g[2])} bias')
...@@ -369,10 +372,12 @@ def smart_resume(ckpt, optimizer, ema=None, weights='yolov5s.pt', epochs=300, re ...@@ -369,10 +372,12 @@ def smart_resume(ckpt, optimizer, ema=None, weights='yolov5s.pt', epochs=300, re
ema.ema.load_state_dict(ckpt['ema'].float().state_dict()) # EMA ema.ema.load_state_dict(ckpt['ema'].float().state_dict()) # EMA
ema.updates = ckpt['updates'] ema.updates = ckpt['updates']
if resume: if resume:
# 如果resume有值,且开始轮数大于0则接着训练,否则,抛出异常
assert start_epoch > 0, f'{weights} training to {epochs} epochs is finished, nothing to resume.\n' \ assert start_epoch > 0, f'{weights} training to {epochs} epochs is finished, nothing to resume.\n' \
f"Start a new training without --resume, i.e. 'python train.py --weights {weights}'" f"Start a new training without --resume, i.e. 'python train.py --weights {weights}'"
LOGGER.info(f'Resuming training from {weights} from epoch {start_epoch} to {epochs} total epochs') LOGGER.info(f'Resuming training from {weights} from epoch {start_epoch} to {epochs} total epochs')
if epochs < start_epoch: if epochs < start_epoch:
# 如果开始轮数大于epochs,会提示已训练了多少轮,接下来会用使用的轮数进行微调
LOGGER.info(f"{weights} has been trained for {ckpt['epoch']} epochs. Fine-tuning for {epochs} more epochs.") LOGGER.info(f"{weights} has been trained for {ckpt['epoch']} epochs. Fine-tuning for {epochs} more epochs.")
epochs += ckpt['epoch'] # finetune additional epochs epochs += ckpt['epoch'] # finetune additional epochs
return best_fitness, start_epoch, epochs return best_fitness, start_epoch, epochs
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论