Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
Y
yolov5
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Administrator
yolov5
Commits
70cb5650
提交
70cb5650
authored
2月 27, 2023
作者:
1051780106@qq.com
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
create: 注释
上级
4751345d
显示空白字符变更
内嵌
并排
正在显示
8 个修改的文件
包含
149 行增加
和
70 行删除
+149
-70
coco128.yaml
data/coco128.yaml
+3
-0
detect.py
detect.py
+54
-17
yolo.py
models/yolo.py
+2
-1
train.py
train.py
+64
-33
augmentations.py
utils/augmentations.py
+7
-6
dataloaders.py
utils/dataloaders.py
+9
-8
general.py
utils/general.py
+2
-2
torch_utils.py
utils/torch_utils.py
+8
-3
没有找到文件。
data/coco128.yaml
浏览文件 @
70cb5650
...
...
@@ -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, ..]
#数据集存放目录
path
:
../datasets/coco128
# dataset root dir
#训练集存放目录
train
:
images/train2017
# train images (relative to 'path') 128 images
#验证集存放目录
val
:
images/train2017
# val images (relative to 'path') 128 images
test
:
# test images (optional)
...
...
detect.py
浏览文件 @
70cb5650
...
...
@@ -36,11 +36,13 @@ from pathlib import Path
import
torch
# 当前执行的detect.py的绝对路径,E:\Study\yolo\yolov5\detect.py
FILE
=
Path
(
__file__
)
.
resolve
()
# parents[0],获取detect.py的父路径,即E:\Study\yolo\yolov5
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
ROOT
=
Path
(
os
.
path
.
relpath
(
ROOT
,
Path
.
cwd
()))
# relative
ROOT
=
Path
(
os
.
path
.
relpath
(
ROOT
,
Path
.
cwd
()))
# relative
,绝对路径转换成相对路径
from
models.common
import
DetectMultiBackend
from
utils.dataloaders
import
IMG_FORMATS
,
VID_FORMATS
,
LoadImages
,
LoadScreenshots
,
LoadStreams
...
...
@@ -80,27 +82,38 @@ def run(
dnn
=
False
,
# use OpenCV DNN for ONNX inference
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
# 判断是不是文件地址,Path()查看文件的路径,suffix提取文件的后缀
is_file
=
Path
(
source
)
.
suffix
[
1
:]
in
(
IMG_FORMATS
+
VID_FORMATS
)
# 判断是不是网络流地址
is_url
=
source
.
lower
()
.
startswith
((
'rtsp://'
,
'rtmp://'
,
'http://'
,
'https://'
))
# 判断source参数是不是摄像头地址或者网络流地址
webcam
=
source
.
isnumeric
()
or
source
.
endswith
(
'.streams'
)
or
(
is_url
and
not
is_file
)
screenshot
=
source
.
lower
()
.
startswith
(
'screen'
)
if
is_url
and
is_file
:
# 如果是网络流文件,则下载
source
=
check_file
(
source
)
# download
# 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
# Load model
# Load model,加载模型的权重
# 选择系统的设备,CPU/GPU
device
=
select_device
(
device
)
# 通过框架(此处weights为.pt文件,所以是pytorch)加载模型
model
=
DetectMultiBackend
(
weights
,
device
=
device
,
dnn
=
dnn
,
data
=
data
,
fp16
=
half
)
# 加载好模型中,读取模型的步长,类别名,是不是pytorch
stride
,
names
,
pt
=
model
.
stride
,
model
.
names
,
model
.
pt
# 图片size是不是满足32倍数
imgsz
=
check_img_size
(
imgsz
,
s
=
stride
)
# check image size
# Dataloader
bs
=
1
# batch_size
# 定义Dataloader模块,用来加载待预测的图片
bs
=
1
# batch_size,每次输入1张图片
if
webcam
:
view_img
=
check_imshow
(
warn
=
True
)
dataset
=
LoadStreams
(
source
,
img_size
=
imgsz
,
stride
=
stride
,
auto
=
pt
,
vid_stride
=
vid_stride
)
...
...
@@ -108,34 +121,39 @@ def run(
elif
screenshot
:
dataset
=
LoadScreenshots
(
source
,
img_size
=
imgsz
,
stride
=
stride
,
auto
=
pt
)
else
:
# 加载图片
dataset
=
LoadImages
(
source
,
img_size
=
imgsz
,
stride
=
stride
,
auto
=
pt
,
vid_stride
=
vid_stride
)
vid_path
,
vid_writer
=
[
None
]
*
bs
,
[
None
]
*
bs
# 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
())
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
]:
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
/=
255
# 0 - 255 to 0.0 - 1.0
im
/=
255
# 0 - 255 to 0.0 - 1.0
归一化操作
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
with
dt
[
1
]:
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
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)
# pred = utils.general.apply_classifier(pred, classifier_model, im, im0s)
# 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
if
webcam
:
# batch_size >= 1
p
,
im0
,
frame
=
path
[
i
],
im0s
[
i
]
.
copy
(),
dataset
.
count
...
...
@@ -144,14 +162,20 @@ def run(
p
,
im0
,
frame
=
path
,
im0s
.
copy
(),
getattr
(
dataset
,
'frame'
,
0
)
p
=
Path
(
p
)
# to Path
# 图片的保存路径
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
# s中加上图片size
s
+=
'
%
gx
%
g '
%
im
.
shape
[
2
:]
# print string
# 获得原图宽高大小
gn
=
torch
.
tensor
(
im0
.
shape
)[[
1
,
0
,
1
,
0
]]
# normalization gain whwh
# 判断是否裁剪检测框的图片
imc
=
im0
.
copy
()
if
save_crop
else
im0
# for save_crop
# 定义绘图工具,专门画框的
annotator
=
Annotator
(
im0
,
line_width
=
line_thickness
,
example
=
str
(
names
))
if
len
(
det
):
# Rescale boxes from img_size to im0 size
# 坐标映射,将640,480的图片上面宽的坐标映射到原图上
det
[:,
:
4
]
=
scale_boxes
(
im
.
shape
[
2
:],
det
[:,
:
4
],
im0
.
shape
)
.
round
()
# Print results
...
...
@@ -175,6 +199,7 @@ def run(
save_one_box
(
xyxy
,
imc
,
file
=
save_dir
/
'crops'
/
names
[
c
]
/
f
'{p.stem}.jpg'
,
BGR
=
True
)
# Stream results
# 从annotator中返回画好框的图片
im0
=
annotator
.
result
()
if
view_img
:
if
platform
.
system
()
==
'Linux'
and
p
not
in
windows
:
...
...
@@ -184,7 +209,7 @@ def run(
cv2
.
imshow
(
str
(
p
),
im0
)
cv2
.
waitKey
(
1
)
# 1 millisecond
# Save results (image with detections)
# Save results (image with detections)
保存图片
if
save_img
:
if
dataset
.
mode
==
'image'
:
cv2
.
imwrite
(
save_path
,
im0
)
...
...
@@ -207,7 +232,8 @@ def run(
LOGGER
.
info
(
f
"{s}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms"
)
# 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:
%.1
fms pre-process,
%.1
fms inference,
%.1
fms NMS per image at shape {(1, 3, *imgsz)}'
%
t
)
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
''
...
...
@@ -217,10 +243,14 @@ def run(
def
parse_opt
():
# 定义命令行可以传入的参数
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
(
'--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
(
'--conf-thres'
,
type
=
float
,
default
=
0.25
,
help
=
'confidence threshold'
)
parser
.
add_argument
(
'--iou-thres'
,
type
=
float
,
default
=
0.45
,
help
=
'NMS IoU threshold'
)
...
...
@@ -247,15 +277,22 @@ def parse_opt():
parser
.
add_argument
(
'--vid-stride'
,
type
=
int
,
default
=
1
,
help
=
'video frame-rate stride'
)
opt
=
parser
.
parse_args
()
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
))
return
opt
def
main
(
opt
):
# 检测包有没有成功安装
check_requirements
(
exclude
=
(
'tensorboard'
,
'thop'
))
# 执行run函数,并传入参数
run
(
**
vars
(
opt
))
if
__name__
==
'__main__'
:
# 解析命令行传入的参数
opt
=
parse_opt
()
# 执行main函数
main
(
opt
)
models/yolo.py
浏览文件 @
70cb5650
...
...
@@ -163,7 +163,7 @@ class BaseModel(nn.Module):
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
super
()
.
__init__
()
if
isinstance
(
cfg
,
dict
):
...
...
@@ -357,6 +357,7 @@ def parse_model(d, ch): # model_dict, input_channels(3)
if
__name__
==
'__main__'
:
# 定义参数信息
parser
=
argparse
.
ArgumentParser
()
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'
)
...
...
train.py
浏览文件 @
70cb5650
...
...
@@ -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
,
smart_resume
,
torch_distributed_zero_first
)
# 分布式训练需要的,初学默认
LOCAL_RANK
=
int
(
os
.
getenv
(
'LOCAL_RANK'
,
-
1
))
# https://pytorch.org/docs/stable/elastic/run.html
RANK
=
int
(
os
.
getenv
(
'RANK'
,
-
1
))
WORLD_SIZE
=
int
(
os
.
getenv
(
'WORLD_SIZE'
,
1
))
...
...
@@ -68,26 +69,33 @@ GIT_INFO = check_git_info()
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
=
\
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
=
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
# last最后一轮的训练文件,best中间最好的一轮
last
,
best
=
w
/
'last.pt'
,
w
/
'best.pt'
# Hyperparameters
# 加载训练过程中需要使用到的超参数
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
()))
opt
.
hyp
=
hyp
.
copy
()
# for saving hyps to checkpoints
# Save run settings
if
not
evolve
:
# 保存所有用到的超参数
yaml_save
(
save_dir
/
'hyp.yaml'
,
hyp
)
# 保存执行脚本所使用的参数
yaml_save
(
save_dir
/
'opt.yaml'
,
vars
(
opt
))
# Loggers
...
...
@@ -106,33 +114,44 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
# Config
plots
=
not
evolve
and
not
opt
.
noplots
# create plots
# 判断是否支持cuda
cuda
=
device
.
type
!=
'cpu'
# 初始随机化种子
init_seeds
(
opt
.
seed
+
1
+
RANK
,
deterministic
=
True
)
with
torch_distributed_zero_first
(
LOCAL_RANK
):
# 获取数据集
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
# 取出类名
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
# Model
check_suffix
(
weights
,
'.pt'
)
# check weights
pretrained
=
weights
.
endswith
(
'.pt'
)
# Model
模型加载
check_suffix
(
weights
,
'.pt'
)
# check weights
,判断权重是否以pt结尾
pretrained
=
weights
.
endswith
(
'.pt'
)
# 使用.pt结尾的权重
if
pretrained
:
with
torch_distributed_zero_first
(
LOCAL_RANK
):
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
# 根据yaml文件创建一个新的model
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和自己创建的模型有多少参数是相同的
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
# Freeze
# 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
():
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
v
.
requires_grad
=
False
# Image size
# 获取模型图片最长边和32的最大值
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
# 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
)
loggers
.
on_params_update
({
'batch_size'
:
batch_size
})
# Optimizer
nbs
=
64
# nominal batch size
accumulate
=
max
(
round
(
nbs
/
batch_size
),
1
)
# accumulate loss before optimizing
hyp
[
'weight_decay'
]
*=
batch_size
*
accumulate
/
nbs
# scale weight_decay
# Optimizer
创建训练过程中所使用的优化器
nbs
=
64
# nominal batch size
定义名义上的batch size
accumulate
=
max
(
round
(
nbs
/
batch_size
),
1
)
# accumulate loss before optimizing
存放累积次数
hyp
[
'weight_decay'
]
*=
batch_size
*
accumulate
/
nbs
# scale weight_decay
对权重衰减的超参数进行缩放
optimizer
=
smart_optimizer
(
model
,
opt
.
optimizer
,
hyp
[
'lr0'
],
hyp
[
'momentum'
],
hyp
[
'weight_decay'
])
# Scheduler
# Scheduler
模型训练过程中,学习率变化的策略
if
opt
.
cos_lr
:
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
# 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)
# EMA
# EMA
对模型使用指数移动平均
ema
=
ModelEMA
(
model
)
if
RANK
in
{
-
1
,
0
}
else
None
# Resume
# Resume
从预训练的文件中加载一些信息
best_fitness
,
start_epoch
=
0.0
,
0
if
pretrained
:
if
resume
:
best_fitness
,
start_epoch
,
epochs
=
smart_resume
(
ckpt
,
optimizer
,
ema
,
weights
,
epochs
,
resume
)
del
ckpt
,
csd
# DP mode
# DP mode
多GPU训练涉及到的
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
'
'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
LOGGER
.
info
(
'Using SyncBatchNorm()'
)
# Trainloader
# 自定义训练集以及训练集所使用到的数据加载器
train_loader
,
dataset
=
create_dataloader
(
train_path
,
imgsz
,
batch_size
//
WORLD_SIZE
,
...
...
@@ -201,12 +226,13 @@ def train(hyp, opt, device, callbacks): # hyp is path/to/hyp.yaml or hyp dictio
prefix
=
colorstr
(
'train: '
),
shuffle
=
True
,
seed
=
opt
.
seed
)
labels
=
np
.
concatenate
(
dataset
.
labels
,
0
)
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}'
# Process 0
if
RANK
in
{
-
1
,
0
}:
# 自定义验证集的数据集以及验证集所使用到的数据加载器
val_loader
=
create_dataloader
(
val_path
,
imgsz
,
batch_size
//
WORLD_SIZE
*
2
,
...
...
@@ -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
)
# DDP mode
# DDP mode
多卡训练
if
cuda
and
RANK
!=
-
1
:
model
=
smart_DDP
(
model
)
# Model attributes
nl
=
de_parallel
(
model
)
.
model
[
-
1
]
.
nl
# number of detection layers (to scale hyps)
hyp
[
'box'
]
*=
3
/
nl
# scale to 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
nl
=
de_parallel
(
model
)
.
model
[
-
1
]
.
nl
# number of detection layers (to scale hyps)
从模型中取出检测层的数量
hyp
[
'box'
]
*=
3
/
nl
# scale to 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
[
'label_smoothing'
]
=
opt
.
label_smoothing
model
.
nc
=
nc
# attach number of classes 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
model
.
names
=
names
# Start training
t0
=
time
.
time
()
t0
=
time
.
time
()
# 统计训练一轮需要的时间
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
last_opt_step
=
-
1
maps
=
np
.
zeros
(
nc
)
# mAP per class
last_opt_step
=
-
1
# 上一次更新参数时计数器的值,批次号
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)
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
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} val
\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
f
'Starting training for {epochs} epochs...'
)
for
epoch
in
range
(
start_epoch
,
epochs
):
# epoch ------------------------------------------------------------------
callbacks
.
run
(
'on_train_epoch_start'
)
model
.
train
()
model
.
train
()
# 模型切换到训练状态
# Update image weights (optional, single-GPU only)
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
dataset
.
indices
=
random
.
choices
(
range
(
dataset
.
n
),
weights
=
iw
,
k
=
dataset
.
n
)
# rand weighted idx
...
...
@@ -481,11 +507,12 @@ def parse_opt(known=False):
def
main
(
opt
,
callbacks
=
Callbacks
()):
# Checks
if
RANK
in
{
-
1
,
0
}:
print_args
(
vars
(
opt
))
check_git_status
()
check_requirements
()
print_args
(
vars
(
opt
))
# 打印参数信息
check_git_status
()
# 检验yolov5 github 有没有更新
check_requirements
()
# 检查requirements有没有安装成功
# Resume (from specified or most recent last.pt)
# resume 从中断中恢复
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
())
opt_yaml
=
last
.
parent
.
parent
/
'opt.yaml'
# train options yaml
...
...
@@ -500,8 +527,10 @@ def main(opt, callbacks=Callbacks()):
if
is_url
(
opt_data
):
opt
.
data
=
check_file
(
opt_data
)
# avoid HUB resume auth timeout
else
:
# data 数据集的文件, weights 预训练权重, 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
# 判断cfg和weights是否都为空,不然报错
assert
len
(
opt
.
cfg
)
or
len
(
opt
.
weights
),
'either --cfg or --weights must be specified'
if
opt
.
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()):
opt
.
save_dir
=
str
(
increment_path
(
Path
(
opt
.
project
)
/
opt
.
name
,
exist_ok
=
opt
.
exist_ok
))
# DDP mode
# 选择CPU/GPU
device
=
select_device
(
opt
.
device
,
batch_size
=
opt
.
batch_size
)
# 是否分布式训练,初学没用到
if
LOCAL_RANK
!=
-
1
:
msg
=
'is not compatible with YOLOv5 Multi-GPU DDP training'
assert
not
opt
.
image_weights
,
f
'--image-weights {msg}'
...
...
@@ -524,7 +555,7 @@ def main(opt, callbacks=Callbacks()):
device
=
torch
.
device
(
'cuda'
,
LOCAL_RANK
)
dist
.
init_process_group
(
backend
=
'nccl'
if
dist
.
is_nccl_available
()
else
'gloo'
)
# Train
# Train
模型训练
if
not
opt
.
evolve
:
train
(
opt
.
hyp
,
opt
,
device
,
callbacks
)
...
...
utils/augmentations.py
浏览文件 @
70cb5650
...
...
@@ -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
):
# Resize and pad image while meeting stride-multiple constraints
shape
=
im
.
shape
[:
2
]
# current shape [height, width]
if
isinstance
(
new_shape
,
int
):
shape
=
im
.
shape
[:
2
]
# current shape [height, width]
原图的高和宽[h,w] (1080,810)
if
isinstance
(
new_shape
,
int
):
# 判断是不是int型的
new_shape
=
(
new_shape
,
new_shape
)
# Scale ratio (new / old)
# 按照新宽高/旧宽高 小的那一个,进行缩放
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)
r
=
min
(
r
,
1.0
)
# Compute padding
ratio
=
r
,
r
# width, height ratios
new_unpad
=
int
(
round
(
shape
[
1
]
*
r
)),
int
(
round
(
shape
[
0
]
*
r
))
dw
,
dh
=
new_shape
[
1
]
-
new_unpad
[
0
],
new_shape
[
0
]
-
new_unpad
[
1
]
# wh padding
if
auto
:
# minimum rectangle
dw
,
dh
=
np
.
mod
(
dw
,
stride
),
np
.
mod
(
dh
,
stride
)
# wh padding
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
160,0
if
auto
:
# minimum rectangle
最小矩形
dw
,
dh
=
np
.
mod
(
dw
,
stride
),
np
.
mod
(
dh
,
stride
)
# wh padding
如果满足32的倍数,自动填充,例如480为32倍数,所以padding就为0,dw=0,dh=0
elif
scaleFill
:
# stretch
dw
,
dh
=
0.0
,
0.0
new_unpad
=
(
new_shape
[
1
],
new_shape
[
0
])
...
...
utils/dataloaders.py
浏览文件 @
70cb5650
...
...
@@ -238,18 +238,18 @@ class LoadScreenshots:
class
LoadImages
:
# 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
path
=
Path
(
path
)
.
read_text
()
.
rsplit
()
files
=
[]
for
p
in
sorted
(
path
)
if
isinstance
(
path
,
(
list
,
tuple
))
else
[
path
]:
p
=
str
(
Path
(
p
)
.
resolve
())
if
'*'
in
p
:
p
=
str
(
Path
(
p
)
.
resolve
())
# 由相对路径得到绝对路径
if
'*'
in
p
:
# 判断路径是否带"*"号
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
elif
os
.
path
.
isfile
(
p
):
files
.
append
(
p
)
# files
elif
os
.
path
.
isfile
(
p
):
# 判断是不是文件
files
.
append
(
p
)
# files
, 变成列表形式
else
:
raise
FileNotFoundError
(
f
'{p} does not exist'
)
...
...
@@ -306,12 +306,13 @@ class LoadImages:
self
.
count
+=
1
im0
=
cv2
.
imread
(
path
)
# BGR
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
:
im
=
self
.
transforms
(
im0
)
# transforms
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
=
np
.
ascontiguousarray
(
im
)
# contiguous
...
...
utils/general.py
浏览文件 @
70cb5650
...
...
@@ -130,12 +130,12 @@ def set_logging(name=LOGGING_NAME, verbose=True):
name
:
{
'class'
:
'logging.StreamHandler'
,
'formatter'
:
name
,
'level'
:
level
,}},
'level'
:
level
,
}},
'loggers'
:
{
name
:
{
'level'
:
level
,
'handlers'
:
[
name
],
'propagate'
:
False
,}}})
'propagate'
:
False
,
}}})
set_logging
(
LOGGING_NAME
)
# run before defining LOGGER
...
...
utils/torch_utils.py
浏览文件 @
70cb5650
...
...
@@ -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
):
# YOLOv5 3-param group optimizer: 0) weights with decay, 1) weights no decay, 2) biases no decay
# 把模型每一层的参数,划分到三个组中
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()
for
v
in
model
.
modules
():
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):
g
[
1
]
.
append
(
p
)
else
:
g
[
0
]
.
append
(
p
)
# weight (with decay)
# 判断训练过程中所使用的优化器的类型
if
name
==
'Adam'
:
optimizer
=
torch
.
optim
.
Adam
(
g
[
2
],
lr
=
lr
,
betas
=
(
momentum
,
0.999
))
# adjust beta1 to momentum
elif
name
==
'AdamW'
:
...
...
@@ -335,11 +337,12 @@ def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5):
elif
name
==
'RMSProp'
:
optimizer
=
torch
.
optim
.
RMSprop
(
g
[
2
],
lr
=
lr
,
momentum
=
momentum
)
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
:
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)
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'
)
...
...
@@ -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
.
updates
=
ckpt
[
'updates'
]
if
resume
:
# 如果resume有值,且开始轮数大于0则接着训练,否则,抛出异常
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}'"
LOGGER
.
info
(
f
'Resuming training from {weights} from epoch {start_epoch} to {epochs} total epochs'
)
if
epochs
<
start_epoch
:
# 如果开始轮数大于epochs,会提示已训练了多少轮,接下来会用使用的轮数进行微调
LOGGER
.
info
(
f
"{weights} has been trained for {ckpt['epoch']} epochs. Fine-tuning for {epochs} more epochs."
)
epochs
+=
ckpt
[
'epoch'
]
# finetune additional epochs
return
best_fitness
,
start_epoch
,
epochs
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论