思想和特点
之前的目标检测算法均需要多个步骤实现目标的分类和定位。如RCNN系列,首先需要进行region proposal,RCNN到Faster RCNN模块逐步将其他任务整合到网络,最终将region proposal也用网络来实现,但是仍然是分步骤实现的。分步骤实现的缺点是实现复杂,运行速度慢。
YOLO的核心思想就是利用整张图作为网络的输入,直接在输出层回归bounding box的位置和bounding box所属的类别。
而YOLO采用回归的方法将定位,分类等步骤统一到了一个网络,比之前RCNN系列的网络简洁。YOLO最突出的特点速度飞快,能够达到实时的效果。
网络结构分析
首先放一张论文YOLO-v1原图
下面的序号 1. 2. 3. ⋯ 1.\ 2.\ 3.\ \cdots 1. 2. 3. ⋯表示总层数, C 1 , C 2 , ⋯ C1,\ C2, \cdots C1, C2,⋯表示第几个卷积层, 64 @ 7 × 7 × 3 64@7\times7\times3 64@7×7×3表示有64个这样的卷积核,没有写出参数默认 s t r i d e = 1 stride=1 stride=1,卷积层 p a d = f i l t e r S i z e − 1 2 pad = \frac {filterSize - 1}{2} pad=2filterSize−1。
-
INPUT: i m a g e = 448 × 448 × 3 image=448\times448\times3 image=448×448×3。
-
C1: i n p u t = 448 × 448 × 3 , f i l t e r s = 64 @ 7 × 7 × 3 , s t r i d e = 2 , p a d = 3 → L e a k y R e L U , o u t p u t = 224 × 224 × 64 input=448\times448\times3, filters=64@7\times7\times3, stride=2, pad=3 \rightarrow Leaky\ ReLU, output=224\times224\times64 input=448×448×3,filters=64@7×7×3,stride=2,pad=3→Leaky ReLU,output=224×224×64
-
MAX_POOl1: i n p u t = 224 × 224 × 64 , w i n d o w = 2 × 2 , s t r i d e = 2 , o u t p u t = 112 × 112 × 64 input=224 \times 224 \times 64, window=2 \times 2, stride=2,output=112 \times 112 \times 64 input=224×224×64,window=2×2,stride=2,output=112×112×64
-
C2: i n p u t = 112 × 112 × 64 , f i l t e r s = 192 @ 3 × 3 × 64 → L e a k y R e L U , o u t p u t = 112 × 112 × 192 input=112 \times 112 \times 64, filters=192@3 \times 3 \times 64 \rightarrow Leaky\ ReLU,output=112 \times 112 \times 192 input=112×112×64,filters=192@3×3×64→Leaky ReLU,output=112×112×192
-
MAX_POOL2: i n p u t = 112 × 112 × 192 , w i n d o w = 2 × 2 , s t r i d e = 2 , o u t p u t = 56 × 56 × 192 input=112 \times 112 \times 192, window=2 \times 2, stride=2,output=56 \times 56 \times 192 input=112×112×192,window=2×2,stride=2,output=56×56×192
-
C3: i n p u t = 56 × 56 × 192 , f i l t e r s = 128 @ 1 × 1 × 192 → L e a k y R e L U , o u t p u t = 56 × 56 × 128 input=56 \times 56 \times 192, filters=128@1 \times 1 \times 192 \rightarrow Leaky\ ReLU, output=56 \times 56 \times 128 input=56×56×192,filters=128@1×1×192→Leaky ReLU,output=56×56×128
-
C4: i n p u t = 56 × 56 × 128 , f i l t e r s = 256 @ 3 × 3 × 128 → L e a k y R e L U , o u t p u t = 56 × 56 × 256 input=56 \times 56 \times 128, filters=256@3\times3\times128 \rightarrow Leaky\ ReLU, output=56\times56\times256 input=56×56×128,filters=256@3×3×128→Leaky ReLU,output=56×56×256
-
C5: i n p u t = 56 × 56 × 256 , f i l t e r s = 256 @ 1 × 1 × 256 → L e a k y R e L U , o u t p u t = 56 × 56 × 256 input=56\times56\times256, filters=256@1\times1\times256\rightarrow Leaky\ ReLU,output=56\times 56\times256 input=56×56×256,filters=256@1×1×256→Leaky ReLU,output=56×56×256
-
C6: i n p u t = 56 × 56 × 256 , f i l t e r s = 512 @ 3 × 3 × 256 → L e a k y R e L U , o u t p u t = 56 × 56 × 512 input=56\times 56\times 256, filters=512@3\times3\times256\rightarrow Leaky\ ReLU, output=56\times 56\times512 input=56×56×256,filters=512@3×3×256→Leaky ReLU,output=56×56×512
-
MAX_POOL3: i n p u t = 56 × 56 × 512 , w i n d o w = 2 × 2 , s t r i d e = 2 , o u t p u t = 28 × 28 × 512 input= 56 \times 56 \times 512, window=2 \times 2, stride=2, output=28 \times 28 \times 512 input=56×56×512,window=2×2,stride=2,output=28×28×512
-
C7: i n p u t = 28 × 28 × 512 , f i l t e r s = 256 @ 1 × 1 × 512 → L e a k y R e L U , o u t p u t = 28 × 28 × 256 input=28\times28\times512,filters=256@1\times1\times512 \rightarrow Leaky\ ReLU, output=28\times28\times256 input=28×28×512,filters=256@1×1×512→Leaky ReLU,output=28×28×256
-
C8: i n p u t = 28 × 28 × 256 , f i l t e r s = 512 @ 3 × 3 × 256 → L e a k y R e L U , o u t p u t = 28 × 28 × 512 input=28\times28\times256,filters=512@3\times3\times256 \rightarrow Leaky\ ReLU, output=28\times28\times512 input=28×28×256,filters=512@3×3×256→Leaky ReLU,output=28×28×512
-
C9: i n p u t = 28 × 28 × 512 , f i l t e r s = 256 @ 1 × 1 × 512 → L e a k y R e L U , o u t p u t = 28 × 28 × 256 input=28\times28\times512,filters=256@1\times1\times512 \rightarrow Leaky\ ReLU, output=28\times28\times256 input=28×28×512,filters=256@1×1×512→Leaky ReLU,output=28×28×256
-
C10: i n p u t = 28 × 28 × 256 , f i l t e r s = 512 @ 3 × 3 × 256 → L e a k y R e L U , o u t p u t = 28 × 28 × 512 input=28\times28\times256,filters=512@3\times3\times256 \rightarrow Leaky\ ReLU, output=28\times28\times512 input=28×28×256,filters=512@3×3×256→Leaky ReLU,output=28×28×512
-
C11: i n p u t = 28 × 28 × 512 , f i l t e r s = 256 @ 1 × 1 × 512 → L e a k y R e L U , o u t p u t = 28 × 28 × 256 input=28 \times 28 \times 512, filters=256@ 1 \times 1 \times 512 \rightarrow Leaky\ ReLU, output=28 \times 28 \times 256 input=28×28×512,filters=256@1×1×512→Leaky ReLU,output=28×28×256
-
C12: i n p u t = 28 × 28 × 256 , f i l t e r s = 512 @ 3 × 3 × 256 → L e a k y R e L U , o u t p u t = 28 × 28 × 512 input=28 \times 28 \times 256, filters=512@ 3 \times 3 \times 256 \rightarrow Leaky\ ReLU, output=28 \times 28 \times 512 input=28×28×256,filters=512@3×3×256→Leaky ReLU,output=28×28×512
-
C13: i n p u t = 28 × 28 × 512 , f i l t e r s = 256 @ 1 × 1 × 512 → L e a k y R e L U , o u t p u t = 28 × 28 × 256 input=28 \times 28 \times 512, filters=256@ 1 \times 1 \times 512 \rightarrow Leaky\ ReLU, output=28 \times 28 \times 256 input=28×28×512,filters=256@1×1×512→Leaky ReLU,output=28×28×256
-
C14: i n p u t = 28 × 28 × 256 , f i l t e r s = 512 @ 3 × 3 × 256 → L e a k y R e L U , o u t p u t = 28 × 28 × 512 input=28 \times 28 \times 256, filters=512@ 3 \times 3 \times 256 \rightarrow Leaky\ ReLU, output=28 \times 28 \times 512 input=28×28×256,filters=512@3×3×256→Leaky ReLU,output=28×28×512
-
C15: i n p u t = 28 × 28 × 512 , f i l t e r s = 256 @ 1 × 1 × 512 → L e a k y R e L U , o u t p u t = 28 × 28 × 256 input=28 \times 28 \times 512, filters=256@ 1 \times 1 \times 512 \rightarrow Leaky\ ReLU, output=28 \times 28 \times 256 input=28×28×512,filters=256@1×1×512→Leaky ReLU,output=28×28×256
-
C16: i n p u t = 28 × 28 × 256 , f i l t e r s = 1024 @ 3 × 3 × 256 → L e a k y R e L U , o u t p u t = 28 × 28 × 1024 input=28 \times 28 \times 256, filters=1024@ 3 \times 3 \times 256 \rightarrow Leaky\ ReLU, output=28 \times 28 \times 1024 input=28×28×256,filters=1024@3×3×256→Leaky ReLU,output=28×28×1024
-
MAX_POOL4: i n p u t = 28 × 28 × 1024 , w i n d o w = 2 × 2 , s t r i d e = 2 , o u t p u t = 14 × 14 × 1024 input= 28 \times 28 \times 1024, window=2 \times 2, stride=2, output=14 \times 14 \times 1024 input=28×28×1024,window=2×2,stride=2,output=14×14×1024
-
C17: i n p u t = 14 × 14 × 1024 , f i l t e r s = 512 @ 1 × 1 × 1024 → L e a k y R e L U , o u t p u t = 14 × 14 × 512 input=14 \times 14 \times 1024, filters=512@ 1 \times 1 \times 1024 \rightarrow Leaky\ ReLU, output=14 \times 14 \times 512 input=14×14×1024,filters=512@1×1×1024→Leaky ReLU,output=14×14×512
-
C18: i n p u t = 14 × 14 × 512 , f i l t e r s = 1024 @ 3 × 3 × 512 → L e a k y R e L U , o u t p u t = 14 × 14 × 1024 input=14 \times 14 \times 512, filters=1024@ 3 \times 3 \times 512 \rightarrow Leaky\ ReLU, output=14 \times 14 \times 1024 input=14×14×512,filters=1024@3×3×512→Leaky ReLU,output=14×14×1024
-
C19: i n p u t = 14 × 14 × 1024 , f i l t e r s = 512 @ 1 × 1 × 1024 → L e a k y R e L U , o u t p u t = 14 × 14 × 512 input=14 \times 14 \times 1024, filters=512@ 1 \times 1 \times 1024 \rightarrow Leaky\ ReLU, output=14 \times 14 \times 512 input=14×14×1024,filters=512@1×1×1024→Leaky ReLU,output=14×14×512
-
C20: i n p u t = 14 × 14 × 512 , f i l t e r s = 1024 @ 3 × 3 × 512 → L e a k y R e L U , o u t p u t = 14 × 14 × 1024 input=14 \times 14 \times 512, filters=1024@ 3 \times 3 \times 512 \rightarrow Leaky\ ReLU, output=14 \times 14 \times 1024 input=14×14×512,filters=1024@3×3×512→Leaky ReLU,output=14×14×1024
-
C21: i n p u t = 14 × 14 × 1024 , f i l t e r s = 1024 @ 3 × 3 × 1024 → L e a k y R e L U , o u t p u t = 14 × 14 × 1024 input=14 \times 14 \times 1024, filters=1024@ 3 \times 3 \times 1024 \rightarrow Leaky\ ReLU, output=14 \times 14 \times 1024 input=14×14×1024,filters=1024@3×3×1024→Leaky ReLU,output=14×14×1024
-
C22: i n p u t = 14 × 14 × 1024 , f i l t e r s = 1024 @ 3 × 3 × 1024 , s t r i d e = 2 → L e a k y R e L U , o u t p u t = 7 × 7 × 1024 input=14 \times 14 \times 1024, filters=1024@ 3 \times 3 \times 1024, stride=2\rightarrow Leaky\ ReLU, output=7 \times 7 \times 1024 input=14×14×1024,filters=1024@3×3×1024,stride=2→Leaky ReLU,output=7×7×1024
-
C23: i n p u t = 7 × 7 × 1024 , f i l t e r s = 1024 @ 3 × 3 × 1024 → L e a k y R e L U , o u t p u t = 7 × 7 × 1024 input=7 \times 7 \times 1024, filters=1024@ 3 \times 3 \times 1024 \rightarrow Leaky\ ReLU, output=7 \times 7 \times 1024 input=7×7×1024,filters=1024@3×3×1024→Leaky ReLU,output=7×7×1024
-
C24: i n p u t = 7 × 7 × 1024 , f i l t e r s = 1024 @ 3 × 3 × 1024 → L e a k y R e L U , o u t p u t = 7 × 7 × 1024 input=7 \times 7 \times 1024, filters=1024@ 3 \times 3 \times 1024 \rightarrow Leaky\ ReLU, output=7 \times 7 \times 1024 input=7×7×1024,filters=1024@3×3×1024→Leaky ReLU,output=7×7×1024
-
FC1: i n p u t = 7 × 7 × 1024 = 50176 , w e i g h t = 512 × 50176 → L e a k y R e L U , o u t p u t = 512 input = 7 \times 7 \times 1024 = 50176, weight = 512\times 50176 \rightarrow Leaky\ ReLU, output=512 input=7×7×1024=50176,weight=512×50176→Leaky ReLU,output=512
-
FC2: i n p u t = 512 , w e i g h t = 4096 × 512 , d r o p _ p r o b = 0.5 → L e a k y R e L U , o u t p u t = 4096 input = 512, weight = 4096\times 512, drop\_prob = 0.5 \rightarrow Leaky\ ReLU, output=4096 input=512,weight=4096×512,drop_prob=0.5→Leaky ReLU,output=4096
-
OUTPUT: i n p u t = ? d r o p _ d i m s , o u t p u t = 7 × 7 × 30 input=?drop\_dims, output = 7\times 7 \times 30 input=?drop_dims,output=7×7×30
可以发现,YOLO运用了24个卷积层,4个最大池化层,2个全连接层。 3 × 3 3\times 3 3×3卷积层用于提取特征, 1 × 1 1\times 1 1×1卷积层是为了跨通道信息整合,池化层用于缩小特征图 ( w i d t h , h e i g t ) (width,heigt) (width,heigt)两个维度,全连接层用于预测物体的位置和类别概率。
另外,YOLO-v1中采用的激活函数是Leaky ReLU,公式如下: ϕ ( x ) = { x , if x > 0 0.1 x , otherwise \phi(x)=\begin{cases} x, & \text{ if } x>0\\ 0.1x, & \text{ otherwise} \end{cases} ϕ(x)={x,0.1x, if x>0 otherwise画一下,长这样
import matplotlib.pyplot as plt
import numpy as np
def leaky_relu(x):
return np.where(x > 0, x, 0.1*x)
x = np.linspace(-10, 10)
plt.plot(x, leaky_relu(x))
详细分析
网络输出
将输入图像分成 S × S S\times S S×S的格子,一个物体的中心点落在哪个格子,这个格子就负责检测该物体。每个格子有B个Bounding Box(后面称Bbox),每个Bbox有5个值 ( x , y , w i d t h , h e i g h t , c o n f i d e n c e ) (x,y,width,height,confidence) (x,y,width,height,confidence),下面逐一解释这五个值的含义:
-
( x , y ) ∈ [ 0 , 1 ] (x, y) \in [0, 1] (x,y)∈[0,1]表示Bbox中心点相对该格子左上角的坐标(相对于格子高宽归一化之后的坐标);
-
( h e i g h t , w i d t h ) ∈ [ 0 , 1 ] (height, width) \in [0, 1] (height,width)∈[0,1]表示相对于整张图片高宽归一化之后的高宽;
-
c o n f i d e n c e confidence confidence是置信度,描述了该Bbox含有物体的确信程度和该Bbox包含物体的精准程度,公式为 c o n f i d e n c e = P r ( O b j e c t ) ∗ I O U p r e d t r u t h confidence = Pr(Object)*IOU_{pred}^{truth} confidence=Pr(Object)∗IOUpredtruth当有物体存在时 P r ( O b j e c t ) = 1 Pr(Object)=1 Pr(Object)=1,此时 c o n f i d e n c e = I O U p r e d t r u t h = p r e d ∩ t r u t h p r e d ∪ t r u t h confidence = IOU_{pred}^{truth}= \frac {pred\cap truth}{pred\cup truth} confidence=IOUpredtruth=pred∪truthpred∩truth,即预测框和真实框的重合比例;当没有物体时 P r ( O b j e c t ) = 0 Pr(Object)=0 Pr(Object)=0,即 c o n f i d e n c e = 0 confidence = 0 confidence=0。
每一个框还要预测C个类别出现在该格子中的条件概率 P r ( C l a s s i ∣ O b j e c t ) Pr(Class_i|Object) Pr(Classi∣Object),并且一个格子只负责预测一个物体而不是B个。
现在让我们来统计一下一张图片经过网络最终的输出有哪些。首先有 S × S S\times S S×S个格子,每个格子有B个Bounding Box,每个Bounding Box包含上述 ( x , y , w i d t h , h e i g h t , c o n f i d e n c e ) (x,y,width,height,confidence) (x,y,width,height,confidence)这样5个参数,在加上每个格子包含C个含有物体的条件概率,所以总共输出为 S × S × ( B × 5 + C ) S\times S \times (B\times 5 + C) S×S×(B×5+C)。论文中 S = 7 , B = 2 , C = 20 S=7,B=2,C=20 S=7,B=2,C=20,所以输出是一个 7 × 7 × ( 2 × 5 + 20 ) = 7 × 7 × 30 7\times 7 \times (2\times 5 + 20) = 7\times 7 \times 30 7×7×(2×5+20)=7×7×30的张量。
训练
训练过程
- 预训练。使用 ImageNet 1000 类数据训练YOLO网络的前20个卷积层+1个average池化层+1个全连接层。训练图像分辨率resize到224x224。
- 用步骤1.得到的前20个卷积层网络参数来初始化YOLO模型前20个卷积层的网络参数,然后用 VOC 20 类标注数据进行YOLO模型训练。检测通常需要有细密纹理的视觉信息,所以为提高图像精度,在训练检测模型时,将输入图像分辨率从224 × 224 resize到448x448。
损失函数
训练主要围绕损失函数展开讨论,下面是损失函数
l o s s = λ c o o r d ∑ 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 ] + ∑ 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 + ∑ i = 0 S 2 1 i o b j ∑ c ∈ c l a s s e s ( p i ( c ) − p ^ i ( c ) ) 2 \begin{aligned} loss &= \lambda_{coord}\sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj}\left [(x_i - \hat{x}_i)^2 + y_i - \hat{y}_i)^2\right ]\\ &+ \lambda_{coord}\sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj}\left [(\sqrt{w_i} - \sqrt{\hat{w}_i})^2 + \sqrt{h_i} - \sqrt{\hat{h}_i})^2\right ] \\ &+ \sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{obj}(C_i -\hat{C}_i)^2\\ &+ \lambda_{noobj}\sum_{i=0}^{S^2}\sum_{j=0}^B \mathbb{1}_{ij}^{noobj}(C_i -\hat{C}_i)^2\\ &+ \sum_{i=0}^{S^2}\mathbb{1}_{i}^{obj} \sum_{c \in classes}(p_i(c)-\hat{p}_i(c))^2 \end{aligned} loss=λcoordi=0∑S2j=0∑B1ijobj[(xi−x^i)2+yi−y^i)2]+λcoordi=0∑S2j=0∑B1ijobj[(wi−w^i)2+hi−h^i)2]+i=0∑S2j=0∑B1ijobj(Ci−C^i)2+λnoobji=0∑S2j=0∑B1ijnoobj(Ci−C^i)2+i=0∑S21iobjc∈classes∑(pi(c)−p^i(c))2
为了便于分析,下面将损失函数各部分解释标注在图上
-
损失函数采用的是误差平方和的形式,这样便于优化,主要由三部分组成:坐标,置信度,类别。所以YOLO能够用一个网络同时进行定位和识别。
-
① YOLO-v1使用误差平方和作为损失函数,但是直接把定位误差平方和与分类误差平方等权值相加显然不合理,同时还考虑到在很多的格子里根本没有物体,这将导致没有物体的这些格子的置信度分数为0,这将会压制包含对象的单元格的梯度,这将导致在训练前期容易发散。为了修正这两个问题我们在定位误差项前面乘以 λ c o o r d = 5 \lambda_{coord}=5 λcoord=5,在没有物体的置信度误差项前面乘以系数 λ n o o b j = 0.5 \lambda_{noobj}=0.5 λnoobj=0.5。
-
② 为什么要在 w i d t h , h e i g h t width,height width,height上加根号?原文是想反映大的Bbox中的小偏差比小Bbox中小。画个图可以解释:
x = np.linspace(0,5)
plt.plot(x,x)
plt.plot(x,np.sqrt(x))
从加根号可以看出,在 w i d t h , h e i g h t width,height width,height较大时,根号具有缓解增长的作用。
-
③ 1 i j o b j \mathbb{1}_{ij}^{obj} 1ijobj第 i i i个格子的第 j j j个Bbox有负责的Object, 所以坐标惩罚项实际惩罚的是有物体的那些Bbox,如果该Bbox没有负责某一个物体,则该部分损失值为0。
-
④ 1 i j n o o b j \mathbb{1}_{ij}^{noobj} 1ijnoobj表示第 i i i个格子的第 j j j个Bbox没有负责的Object, 在置信度惩罚项中我们对有没有物体的Bbox都要惩罚,但是没有物体的项乘以了系数 λ n o o b j \lambda_{noobj} λnoobj来降低它的权重。
-
⑤ 1 i o b j \mathbb{1}_{i}^{obj} 1iobj表示第 i i i个格子中有Object,这个时候需要分别计算属于各个类的概率。
其他细节
-
在训练时采用的是小批迭代动量梯度下降。动量mc=0.9,衰减率是0.0005。学习速率在需要各个阶段动态调整,如果在初期迭代学习率过高将导致梯度不稳定而发散。
-
为了防止过拟合,仍然采用了dropout正则化技术(第一个全连接层后面,drop_prob=0.5)和数据增强(尺度缩放,图像变换,调节曝光度和饱和度等)。
预测
经过网络,输出是 7 × 7 × 30 7\times 7\times 30 7×7×30,其中 30 = ( x , y , w i d t h , h e i g h t , c o n f i d e n c e ) × 2 + 20 个 类 别 的 条 件 概 率 30 = (x, y, width, height, confidence)\times2 + 20个类别的条件概率 30=(x,y,width,height,confidence)×2+20个类别的条件概率。
For c i c_i ci in C:
- 对 c i c_i ci的98个bbox按置信度分数降序排列。
- 选择 c i c_i ci预测框中置信度最大(排序后为第一个)的那个bbox然后挨个计算其与剩余bbox的IOU,如果IOU值大于一定阈值(如0.5),则重合度过高,就将该类别置信度值置为0。
- 重复2.直到处理完 c i c_i ci的所有检测框。