源码中有RPN模型,其实囊括了前面的特征提取部分,我们先来看下,他这个模型的结构,我们才能明白输入输出是怎么来的,还是train_frcnn.py
:
# 图片维度顺序的改变
if K.image_dim_ordering() == 'th':
input_shape_img = (3, None, None)
else:
input_shape_img = (None, None, 3)
# 图片输入
img_input = Input(shape=input_shape_img) # (?, ?, ?, 3)
# roi输入
roi_input = Input(shape=(None, 4)) # ( ?, ?,4)
# define the base network (resnet here, can be VGG, Inception, etc)
# 主干网络共享层
shared_layers = nn.nn_base(img_input, trainable=True) # (?, ?, ?, 512)
# define the RPN, built on the base layers
# 定义RPN网络的锚框数
num_anchors = len(C.anchor_box_scales) * len(C.anchor_box_ratios) # 9
# 创建RPN网络
rpn_output = nn.rpn(shared_layers, num_anchors) # [(?, ?, ?, 9),(?, ?, ?, 36),(?, ?, ?,512)]
# RPN模型 输入是img_input,rpn_output[:2] rpn_output[:2]=[x_class, x_regr]
model_rpn = Model(img_input, rpn_output[:2])
我们可以看到,这个就是RPN模型的结构,输入是img_input,形状就是图片(None,原图高,原图宽,3),输出是 rpn_output[:2],形状是[(1,特征图高,特征图宽,9),(1,特征图高,特征图宽,36)],也就是说我们输入层是一张图片的,输出是特征图上每个点的9个锚框的分类和坐标宽高的36个回归梯度(偏移量),也就是端到端的形式,把前面的vgg网络也涵盖进去了,我们训练RPN网络参数就是希望他输出的9个概率和36个回归梯度能比较准,能识别出物体,也能修正好锚框位置。
作为灵魂画师,我画了个图,来表示9个锚框的分类,是否是物体。左边是特征图做分类后的样子,特征图产生了9通道,每个点有9个通道,每个通道对应一个锚框的概率值,表示锚框内是否有物体的概率,回归梯度也是同样的道理:
解释下这里的回归梯度其实就是偏移量(dx,dy,dw,dh),简单理解说每个锚框的x,y,w,h去线性使用这个偏移量(dx,dy,dw,dh),缩放或者平移,得到了新的预测框G’,我们希望这个G‘跟标注框G的x,y,w,h越接近越好,也就是G‘和G的差距越小越好,差距越小重合度越高,差距就可以定义损失函数,通过梯度下降法降低这个损失函数去使得偏移量越来越好,理论上最终G’和G重合了,此时的偏移量是我们要找的。这个也是坐标回归的一个原理,其实看公式还是有点绕的,我就那么简单的解释了,希望对你帮助,看完后最好还是去看下公式,公式里还有一些细节问题,比如消除误差对不同尺寸大小的影响,比如宽高缩放是正的,所以系数取e等等。
有了上面的基础铺垫,我们可以继续将PRN网络的损失函数了,还是train_frcnn.py
,这个是编译定义的损失函数:
# 两个损失,是否有物体 回归
model_rpn.compile(optimizer=optimizer, loss=[losses.rpn_loss_cls(num_anchors), losses.rpn_loss_regr(num_anchors)])
我们来看看具体里面是什么:
#RPN回归损失 用了smooth l1损失函数
def rpn_loss_regr(num_anchors):
def rpn_loss_regr_fixed_num(y_true, y_pred):
# 维度顺序不一样
if K.image_dim_ordering() == 'th':
x = y_true[:, 4 * num_anchors:, :, :] - y_pred
x_abs = K.abs(x)
x_bool = K.less_equal(x_abs, 1.0)
return lambda_rpn_regr * K.sum(
y_true[:, :4 * num_anchors, :, :] * (x_bool * (0.5 * x * x) + (1 - x_bool) * (x_abs - 0.5))) / K.sum(epsilon + y_true[:, :4 * num_anchors, :, :])
else:
# y_true 维度是(None, 高,宽,72) 其中72里面前36位是9个锚框对应的回归梯度的有效位,其实就是是不是物体,
# 是物体取1,不是取0,后36位才是回归梯度
# 所以求和就是把所是物体的回归梯度值加起来,然后除以是物体的个数求平均
# 真实和预测之间的回归梯度值的差
x = y_true[:, :, :, 4 * num_anchors:] - y_pred
# 取绝对值
x_abs = K.abs(x)
# 判断每个元素是否小于1
x_bool = K.cast(K.less_equal(x_abs, 1.0), tf.float32)
# 乘以这个y_true[:, :, :, :4 * num_anchors]是标志位,表示这个值是不是物体的,值是0或者1,表示只计算有物体是1,没有物体的其实是0
return lambda_rpn_regr * K.sum(
y_true[:, :, :, :4 * num_anchors] * (x_bool * (0.5 * x * x) + (1 - x_bool) * (x_abs - 0.5))) / K.sum(epsilon + y_true[:, :, :, :4 * num_anchors])
return rpn_loss_regr_fixed_num
# RPN 分类损失,二分类是否有物体
def rpn_loss_cls(num_anchors):
def rpn_loss_cls_fixed_num(y_true, y_pred):
if K.image_dim_ordering() == 'tf':
# 分类的时候只需要考虑有效的标签,也就是前面9位,后面9位是具体分类值,是不是物体,用于交叉熵计算
return lambda_rpn_class * K.sum(y_true[:, :, :, :num_anchors] * K.binary_crossentropy(y_pred[:, :, :, :], y_true[:, :, :, num_anchors:])) / K.sum(epsilon + y_true[:, :, :, :num_anchors])
else:
return lambda_rpn_class * K.sum(y_true[:, :num_anchors, :, :] * K.binary_crossentropy(y_pred[:, :, :, :], y_true[:, num_anchors:, :, :])) / K.sum(epsilon + y_true[:, :num_anchors, :, :])
return rpn_loss_cls_fixed_num
先看看rpn_loss_regr
这个方法,其实这个就是回归损失,其实就是这个公式:
λ r e g 1 N r e g ∑ i p i ∗ L r e g ( t i , t i ∗ ) \lambda_{reg} \frac{1}{N_{reg}}\sum_ip_i^*L_{reg}(t_i,t_i^*) λregNreg1i∑pi∗Lreg(ti,ti∗)
其中 p i ∗ p_i^* pi∗是个二分类取值1和0, λ r e g \lambda_{reg} λreg是权重系数,调节哪个损失更加重要, t i t_i ti就是PRN网络输出的偏移量的向量dx,dy,dw,dh,4维的, t i ∗ t_i ^* ti∗就是真实框和锚框之间的偏移量,也就是衡量他们之间的差距,其实就是衡量上面所说的G’和G的差距,只要让RPN输出的偏移量等于真实框对于锚框的偏移量,那就等于偏移量作用与锚框之后的结果和真实框就重合了。
我们再可以看tf那部分:
# 真实和预测之间的差
x = y_true[:, :, :, 4 * num_anchors:] - y_pred
# 取绝对值
x_abs = K.abs(x)
# 判断每个元素是否小于1
x_bool = K.cast(K.less_equal(x_abs, 1.0), tf.float32)
return lambda_rpn_regr * K.sum(
y_true[:, :, :, :4 * num_anchors] * (x_bool * (0.5 * x * x) + (1 - x_bool) * (x_abs - 0.5))) / K.sum(epsilon + y_true[:, :, :, :4 * num_anchors]),
因为使用了Smooth L1误差函数,所以才有绝对值判断,小于1判断,这样做使得损失对于误差小的时候变换不敏感,具体原因可以看这篇文章:
还有为什么最后是y_true求和的来算个数的,因为y_true里面前36位是回归梯度的有效位,也就是是不是物体,不是就别算了,取值是0和1,计算的总共次数就是有效位为1的个数,也就求和就是个数,后36位才是真正的偏移量,暂时做个了解,先简单说明下这个式子而已。
再看看rpn_loss_cls
这个方法,这个是分类损失,二分类,是不是背景,或者说有没有物体:
# RPN 分类损失,二分类是否有物体
def rpn_loss_cls(num_anchors):
def rpn_loss_cls_fixed_num(y_true, y_pred):
if K.image_dim_ordering() == 'tf':
# 分类的时候只需要考虑有效的标签,也就是前面9位,后面9位是具体分类值,是不是物体,用于交叉熵计算
return lambda_rpn_class * K.sum(y_true[:, :, :, :num_anchors] * K.binary_crossentropy(y_pred[:, :, :, :], y_true[:, :, :, num_anchors:])) / K.sum(epsilon + y_true[:, :, :, :num_anchors])
else:
return lambda_rpn_class * K.sum(y_true[:, :num_anchors, :, :] * K.binary_crossentropy(y_pred[:, :, :, :], y_true[:, num_anchors:, :, :])) / K.sum(epsilon + y_true[:, :num_anchors, :, :])
return rpn_loss_cls_fixed_num
这个就是二分类交叉熵损失,这里的y_true前9位也是有效位,其实就是只算正负样本,不算中立的样本,后就9位才是真的分类,所以可以求和来求个数。
至此,我们将了RPN的整个模型的输入,输出,损失函数,他其实是包括了特征提取网路的,不单单只是做背景分类和偏移量回归,先了解整个网路,对前面的部分有个好的理解,对于后面的处理有很大的帮助,因为越后面越复杂,所以前面需要有比较清晰的认识。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵,部分图片来自网络,侵删。