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 @@
...
@@ -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)
...
...
detect.py
浏览文件 @
70cb5650
...
@@ -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:
%.1
fms pre-process,
%.1
fms inference,
%.1
fms NMS per image at shape {(1, 3, *imgsz)}'
%
t
)
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
:
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
)
models/yolo.py
浏览文件 @
70cb5650
...
@@ -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'
)
...
...
train.py
浏览文件 @
70cb5650
...
@@ -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
)
...
...
utils/augmentations.py
浏览文件 @
70cb5650
...
@@ -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
])
...
...
utils/dataloaders.py
浏览文件 @
70cb5650
...
@@ -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
...
...
utils/general.py
浏览文件 @
70cb5650
...
@@ -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
...
...
utils/torch_utils.py
浏览文件 @
70cb5650
...
@@ -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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论