YOLO v1 计算流程–基于pytorch
个人理解TOLO v1的计算有如下几个关键部分:
1.图像预处理
YOLO v1要求图像的大小是一致的448 * 448 因此读取图像后需要对图像进行预处理 2.图像的前向传播
前向传播部分由两部分组成:特征提取和输出构建 特征提取可以使用原文章中基于DartNet的特征提取方式,也可以采用其他网络诸如VGG或者ResNet等 输出构建时YOLO v1的精华,是YOLO网络的主要思想核心,基于划分好的网格构建bounding box、confidence和class维度,每个网格构建一套 3.损失函数的计算:
损失函数均基于均方根误差计算 YOLO v1的损失函数包括三部分:bounding box坐标位置损失、置信度损失(网格中是否包含object中心),class损失(基于one hot编码计算)
图像预处理和划分
将图像resize为
448
×
448
×
3
448\times 448 \times 3
4 4 8 × 4 4 8 × 3 的大小,resize的目的 是为了满足全连接层的固定 输入维度的要求
代码:
将448的图像划分为
7
×
7
7\times 7
7 × 7 的格点,每个格点边长为64个像素点图解: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7qmIDoI-1635915412647)(./1635745220607.png)]
将图像输入到YOLO v1网络前向传播:
由于YOLO前边的网络主要作用是特征提取,因此使用resnet的全连接之前的部分代替,通过加载torchvision中的已经训练好的resnet34,并固定参数,后边跟YOLOv1后半部分卷积结构和全连接层变化结构 每个网格 要预测2个bounding box,每个bounding box除了要预测位置之外,还要附带预测一个confiden值,每个网格还要预测C个类别的分数
网格有
7
×
7
7\times 7
7 × 7 每个网格2个边界框 也就是每个bounding box 预测5个值,其中四个为位置参数(相对值,相对于整个图像大小来说),confidence值,这里有两个,就是
2
×
(
4
+
1
)
2 \times (4+1)
2 × ( 4 + 1 ) VOC数据集有20个类别,则类别数为20 所以一共有参数
7
×
7
×
(
20
+
2
×
(
4
+
1
)
)
7 \times 7 \times(20+2\times (4+1))
7 × 7 × ( 2 0 + 2 × ( 4 + 1 ) ) *
from torchvision. models import resnet34
import torch. nn as nn
import torch
class YOLOv1_resnet ( nn. Module) :
def __init__ ( self, num_box, class_num) :
super ( YOLOv1_resnet, self) . __init__( )
self. num_box = num_box
self. class_num = class_num
resnet = resnest_model( )
resnet_out_channel = resnet. fc. in_features
self. resnet = nn. Sequential( * list ( resnet. children( ) ) [ : - 2 ] )
'''定义YOLO的最后的卷积层'''
self. conv_layer = nn. Sequential(
nn. Conv2d( resnet_out_channel, 1024 , 3 , padding= 1 ) ,
nn. BatchNorm2d( 1024 ) ,
nn. LeakyReLU( ) ,
nn. Conv2d( 1024 , 1024 , 3 , stride= 2 , padding= 1 ) ,
nn. BatchNorm2d( 1024 ) ,
nn. LeakyReLU( ) ,
nn. Conv2d( 1024 , 1024 , 3 , padding= 1 ) ,
nn. BatchNorm2d( 1024 ) ,
nn. LeakyReLU( ) ,
)
'''全连接层,三维变量拉平进行处理'''
self. dense_layers = nn. Sequential(
nn. Linear( 7 * 7 * 1024 , 4096 ) ,
nn. LeakyReLU( ) ,
nn. Linear( 4096 , 7 * 7 * 30 ) )
def forward ( self, x) :
out = self. resnet( x)
out = self. conv_layer( out)
out = out. view( out. size( ) [ 0 ] , - 1 )
out = self. dense_layers( out)
return out. reshape( - 1 , ( 5 * self. num_box+ self. class_num) , 7 , 7 )
def set_parameter_requires_grad ( model) :
for param in model. parameters( ) :
param. requires_grad = False
def resnest_model ( ) :
model_ft = resnet34( pretrained= True )
set_parameter_requires_grad( model_ft)
num_ftrs = model_ft. fc. in_features
return model_ft
if __name__ == '__main__' :
x = torch. randn( ( 1 , 3 , 448 , 448 ) )
net = YOLOv1_resnet( 2 , 20 )
print ( net)
y = net( x)
print ( y. size( ) )
3)损失函数的计算
数据前向传播后,要计算损失才能后向传播,因此需要根据定义计算损失函数,在YOLO v1中损失函数由三个部分组成,均使用平方和计算: 使用误差平方和计算 bounding box损失
L
l
o
c
(
l
,
g
)
=
∑
i
=
0
s
2
∑
j
=
0
B
1
i
,
j
o
b
j
[
(
x
i
−
x
^
i
)
2
+
(
y
i
−
y
^
i
)
2
]
+
λ
c
o
o
r
d
∑
i
=
0
s
2
∑
j
=
0
B
1
i
,
j
o
b
j
[
(
(
w
i
)
−
w
^
i
)
2
+
(
(
h
i
)
−
h
^
i
)
2
]
L_{loc}(l,g)=\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(x_i-\hat x_i)^2+(y_i-\hat y_i)^2]+\lambda_{coord}\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(\sqrt{(w_i)}-\sqrt{\hat w_i})^2+(\sqrt{(h_i)}-\sqrt{\hat h_i})^2]
L l o c ( l , g ) = ∑ i = 0 s 2 ∑ j = 0 B 1 i , j o b j [ ( x i − x ^ i ) 2 + ( y i − y ^ i ) 2 ] + λ c o o r d ∑ i = 0 s 2 ∑ j = 0 B 1 i , j o b j [ ( ( w i )
− w ^ i
) 2 + ( ( h i )
− h ^ i
) 2 ] confidence 损失
L
c
o
n
f
(
o
,
c
)
=
∑
i
=
0
s
2
∑
j
=
0
B
1
i
,
j
o
b
j
[
(
C
i
−
C
^
i
)
2
+
λ
n
o
o
b
j
∑
i
=
0
s
2
∑
j
=
0
B
1
i
,
j
n
o
o
b
j
[
(
C
i
−
C
^
i
)
2
L_{conf}(o,c)=\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{obj}_{i,j}[(C_i-\hat C_i)^2+\lambda_{noobj}\sum^{s^2}_{i=0} \sum_{j=0}^B 1^{noobj}_{i,j}[(C_i-\hat C_i)^2
L c o n f ( o , c ) = ∑ i = 0 s 2 ∑ j = 0 B 1 i , j o b j [ ( C i − C ^ i ) 2 + λ n o o b j ∑ i = 0 s 2 ∑ j = 0 B 1 i , j n o o b j [ ( C i − C ^ i ) 2 classes 损失
L
c
l
a
(
O
,
C
)
=
∑
i
=
0
s
2
1
i
o
b
j
∑
c
∈
c
l
a
s
s
e
s
(
p
i
(
c
)
−
p
i
^
(
c
)
)
L_{cla(O,C)}=\sum^{s^2}_{i=0}1^{obj}_{i}\sum_{c \in classes(p_i(c)-\hat {p_i}(c))}
L c l a ( O , C ) = ∑ i = 0 s 2 1 i o b j ∑ c ∈ c l a s s e s ( p i ( c ) − p i ^ ( c ) ) 总的损失为所有三种损失的损失相加
L
(
o
,
c
,
O
,
C
,
l
,
g
)
=
λ
1
L
c
o
n
f
(
o
,
c
)
+
λ
2
L
c
l
a
(
O
,
C
)
+
λ
3
L
l
o
c
(
l
,
g
)
L(o,c,O,C,l,g)=\lambda_1L_{conf}(o,c)+\lambda_2L_{cla(O,C)}+\lambda_3L_{loc}(l,g)
L ( o , c , O , C , l , g ) = λ 1 L c o n f ( o , c ) + λ 2 L c l a ( O , C ) + λ 3 L l o c ( l , g )
在计算bounding box损失的时候涉及到一个问题:anchors的选取
anchors生成的过程如下:
锚定过程:
对于输入图像的每个对象,先找到其中心点,寻找中心点所在的划分好的网格 (7*7),则该包含物体中心点的网格中confidence=1,其他48个网格中confidence为0,在YOLO中称为中心点所在的网格对预测该对象负责 bounding box坐标:
YOLO中并不会预设anchors坐标或者高宽比,而是锚定中心点之后,在结果中直接生成,组成输出结果的8个特征 ,不过每个网格cell生成两组boxes坐标,每组坐标有4个,分别为相对中心点坐标(x,y),相对高度和宽度为(w, h),其中相对中心坐标时相对于中心所在的子网格,w,h为相对于整幅图的宽度和高度比例,如下图:
由于有两个boxes,因此需要对boxes进行筛选,选取最优的boxes的坐标计算误差,筛选采用IoU 来进行: IoU :
I
o
U
=
i
n
t
e
r
s
e
c
t
i
o
n
(
A
,
B
)
u
n
i
o
n
(
A
,
B
)
IoU =\frac{intersection(A,B)}{union(A,B)}
I o U = u n i o n ( A , B ) i n t e r s e c t i o n ( A , B ) 物理意义为:IoU为交集部分面积与并集部分面积之比,当两个Box完全重合时IoU=1,不相交时IoU=0 本部分代码采用沐神 课程中的代码,在我之前的blog中有详细解析,链接https://blog.csdn.net/qq_34992900/article/details/120705041?spm=1001.2014.3001.5501:
def box_iou ( boxes1, boxes2) :
'''
args:
boxes1: tensor
[num_boxes, 4]
boxes2: 同上
'''
box_area = lambda boxes: ( ( boxes[ : , 2 ] - boxes[ : , 0 ] ) * ( boxes[ : , 3 ] - boxes[ : , 1 ] ) )
areas1 = box_area( boxes1)
areas2 = box_area( boxes2)
inter_upperlefts = torch. max ( boxes1[ : , None , : 2 ] , boxes2[ : , : 2 ] )
inter_lowerrights = torch. min ( boxes1[ : , None , 2 : ] , boxes2[ : 2 : ] )
inters = ( inter_lowerrights - inter_upperlefts) . clamp( min = 0 )
inter_areas = inters[ : , : , 0 ] * inters[ : , : , 1 ]
union_areas = areas1[ : , None ] + areas2 - inter_areas
return inter_areas / union_areas
定义Loss函数 为了方便计算Loss,将网络输出为
7
×
7
×
30
7 \times 7 \times 30
7 × 7 × 3 0 的数据格式与label计算,label转化为与网络输出一致的格式
7
×
7
×
30
7 \times 7 \times 30
7 × 7 × 3 0 得到; 在计算中先计算格点预设两个边框与真实边框的IoU值,选择最大IoU边框,进一步计算边框坐标误差 上述步骤如下:
根据confidence判断网格中是否包含object的中心 若存在中心,则将中心–高宽的形式,转为左上角和右下角坐标的形式 通过上述IoU计算大赛分别计算两个bounding box与label的大小 选取IoU最大的计算边框坐标误差,IoU小的bounding box则计入到网格没有object的误差,计算分类误差,概率值为IoU 计算置信度误差 计算分类误差 代码如下:
class Loss_yolov1 ( nn. Module) :
def __init__ ( self) :
super ( Loss_yolov1, self) . __init__( )
def box_center_to_conner ( self, boxes, n, m, grid_num) :
"""转换 YOLO v1的bounding box
从中间,宽度,高度)转换(左上,右下)"""
cx, cy, w, h = boxes[ : , 0 ] , boxes[ : , 1 ] , boxes[ : , 2 ] , boxes[ : , 3 ]
x1 = ( cx + n) / grid_num - w/ 2
y1 = ( cy + m) / grid_num - h/ 2
x2 = ( cx + n) / grid_num + w/ 2
y2 = ( cy + m) / grid_num + w/ 2
boxes = torch. stack( ( x1, y1, x2, y2) , axis= - 1 )
return boxes
def forward ( self, pred, labels) :
"""
:param pred: (batchsize,30,7,7)的网络输出数据
:param labels: (batchsize,30,7,7)的样本标签数据
:return: 当前批次样本的平均损失
"""
num_gridx, num_gridy = labels. size( ) [ - 2 : ]
num_b = 2
num_cls = 20
noobj_confi_loss_2 = 0 .
coor_loss = 0 .
obj_confi_loss = 0 .
class_loss = 0 .
n_batch = labels. size( ) [ 0 ]
iloss_1 = 0 .
for i in range ( n_batch) :
for n in range ( 7 ) :
for m in range ( 7 ) :
if labels[ i, 4 , m, n] == 1 :
bbox1_pred_xyxy = self. box_center_to_conner(
pred[ i, : 4 , m, n] , n, m, num_gridx)
bbox2_pred_xyxy = self. box_center_to_conner(
pred[ i, 4 : 8 , m, n] , n, m, num_gridx)
bbox_gt_xyxy = self. box_center_to_conner(
labels[ i, : 4 , m, n] , n, m, num_gridx)
iou1 = box_iou( bbox1_pred_xyxy, bbox_gt_xyxy)
iou2 = box_iou( bbox2_pred_xyxy, bbox_gt_xyxy)
box_pred = pred[ i, : 4 , m, n] if iou1 >= iou2 else pred[ i, 4 : 8 , m, n]
icoor_loss = (
torch. sum ( ( box_pred[ 0 : 2 , : ] - labels[ i, 0 : 2 , m, n] ) ** 2 ) + torch. sum ( ( pred[ i, 2 : 4 , m, n] . sqrt( ) - labels[ i, 2 : 4 , m, n] . sqrt( ) ) ** 2 ) )
iobj_confi_loss = ( pred[ i, 4 , m, n] - iou1) ** 2
inoobj_confi_loss_1 = ( ( pred[ i, 9 , m, n] - iou2) ** 2 )
iclass_loss = torch. sum ( ( pred[ i, 10 : , m, n] - labels[ i, 10 : , m, n] ) ** 2 )
iloss_1 += 5 * icoor_loss + iobj_confi_loss + iclass_loss + 0.5 * inoobj_confi_loss_1
else :
noobj_confi_loss_2 += 0.5 * torch. sum ( pred[ i, [ 4 , 9 ] , m, n] ** 2 )
loss = iloss_1 + 0.5 * noobj_confi_loss_2
return loss/ n_batch
loss_fn = Loss_yolov1( )
pred = torch. randn( ( 4 , 30 , 7 , 7 ) )
label = torch. randn( ( 4 , 30 , 7 , 7 ) )
loss = loss_fn( pred, label)
print ( loss)
参考:
大量参考了该仓代码:https://github.com/lavendelion/YOLOv1-from-scratch 部分参考了李沐深度学习课程代码