CTPN Loss

在我的理解中Loss應該是整個模型中相當重要的一部分橘原。
一般而言深度學習模型解決問題的整體流程:
1利赋、問題的定義,也就是說task是什么堡距,或者說背景是什么
2甲锡、根據(jù)task設(shè)計模型
3、根據(jù)模型和task去設(shè)計Loss羽戒。一般來說看到Loss就可以知道這個模型在優(yōu)化什么缤沦,解決什么問題。
CTPN模型的輸出有兩個易稠,一個是檢測框是不是文本(分類)缸废,一個是檢測框的大小和位置(回歸)。所以CTPN的Loss一個是分類Loss驶社,一個是回歸Loss企量。
其實這部分是整個CTPN中最為復雜的地方。包括了gt標簽的制作衬吆。我個人覺得目標檢測的gt挺難處理的梁钾,雖然邏輯不難,但是代碼實現(xiàn)有點復雜逊抡。
下面是loss函數(shù)的代碼姆泻。其中輸入bbox_pred, cls_pred, bbox, im_info零酪。bbox_pred, cls_pred是我們模型的輸出,bbox拇勃,im_info是通過dataloader得到的gt四苇。

def loss(bbox_pred, cls_pred, bbox, im_info):

    rpn_data = anchor_target_layer(cls_pred, bbox, im_info, "anchor_target_layer")

    # classification loss
    # transpose: (1, H, W, A x d) -> (1, H, WxA, d)
    cls_pred_shape = tf.shape(cls_pred)
    cls_pred_reshape = tf.reshape(cls_pred, [cls_pred_shape[0], cls_pred_shape[1], -1, 2])
    rpn_cls_score = tf.reshape(cls_pred_reshape, [-1, 2])
    rpn_label = tf.reshape(rpn_data[0], [-1])
    # ignore_label(-1)
    fg_keep = tf.equal(rpn_label, 1)
    rpn_keep = tf.where(tf.not_equal(rpn_label, -1))
    rpn_cls_score = tf.gather(rpn_cls_score, rpn_keep)
    rpn_label = tf.gather(rpn_label, rpn_keep)
    rpn_cross_entropy_n = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=rpn_label, logits=rpn_cls_score)

    # box loss
    rpn_bbox_pred = bbox_pred
    rpn_bbox_targets = rpn_data[1]
    rpn_bbox_inside_weights = rpn_data[2]
    rpn_bbox_outside_weights = rpn_data[3]

    rpn_bbox_pred = tf.gather(tf.reshape(rpn_bbox_pred, [-1, 4]), rpn_keep)  # shape (N, 4)
    rpn_bbox_targets = tf.gather(tf.reshape(rpn_bbox_targets, [-1, 4]), rpn_keep)
    rpn_bbox_inside_weights = tf.gather(tf.reshape(rpn_bbox_inside_weights, [-1, 4]), rpn_keep)
    rpn_bbox_outside_weights = tf.gather(tf.reshape(rpn_bbox_outside_weights, [-1, 4]), rpn_keep)

    rpn_loss_box_n = tf.reduce_sum(rpn_bbox_outside_weights * smooth_l1_dist(
        rpn_bbox_inside_weights * (rpn_bbox_pred - rpn_bbox_targets)), reduction_indices=[1])

    rpn_loss_box = tf.reduce_sum(rpn_loss_box_n) / (tf.reduce_sum(tf.cast(fg_keep, tf.float32)) + 1)
    rpn_cross_entropy = tf.reduce_mean(rpn_cross_entropy_n)

    model_loss = rpn_cross_entropy + rpn_loss_box

    regularization_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
    total_loss = tf.add_n(regularization_losses) + model_loss

    tf.summary.scalar('model_loss', model_loss)
    tf.summary.scalar('total_loss', total_loss)
    tf.summary.scalar('rpn_cross_entropy', rpn_cross_entropy)
    tf.summary.scalar('rpn_loss_box', rpn_loss_box)

    return total_loss, model_loss, rpn_cross_entropy, rpn_loss_box

我們先來看第一句,調(diào)用了anchor_target_layer函數(shù)方咆。

rpn_data = anchor_target_layer(cls_pred, bbox, im_info, "anchor_target_layer")

anchor_target_layer函數(shù)如下月腋。其中需要注意的點是,它使用了tf.py_func()函數(shù)瓣赂。這個函數(shù)的作用增加tensorflow編程的靈活性榆骚。tensorflow是靜態(tài)圖,可以這么理解煌集,tensorflow數(shù)據(jù)在一個個操作之間流動妓肢,而這些操作是定死的,實現(xiàn)這些操作你得用tensorflow的方法苫纤,不能用普通的方法碉钠。比如說print,你直接用print是沒有輸出的卷拘,得用tf.Print()喊废。然后使用 tf.py_func可以在tensorflow中操作numpy array,增加了靈活性栗弟。不過tf.py_func輸出的是numpy你得把輸出轉(zhuǎn)成tensor才能用污筷。
也就是說下面這個函數(shù)其實是在調(diào)用anchor_target_layer_py方法。

def anchor_target_layer(cls_pred, bbox, im_info, scope_name):
    with tf.variable_scope(scope_name) as scope:
        # 'rpn_cls_score', 'gt_boxes', 'im_info'
        rpn_labels, rpn_bbox_targets, rpn_bbox_inside_weights, rpn_bbox_outside_weights = \
            tf.py_func(anchor_target_layer_py,
                       [cls_pred, bbox, im_info, [16, ], [16]],
                       [tf.float32, tf.float32, tf.float32, tf.float32])

        rpn_labels = tf.convert_to_tensor(tf.cast(rpn_labels, tf.int32),
                                          name='rpn_labels')
        rpn_bbox_targets = tf.convert_to_tensor(rpn_bbox_targets,
                                                name='rpn_bbox_targets')
        rpn_bbox_inside_weights = tf.convert_to_tensor(rpn_bbox_inside_weights,
                                                       name='rpn_bbox_inside_weights')
        rpn_bbox_outside_weights = tf.convert_to_tensor(rpn_bbox_outside_weights,
                                                        name='rpn_bbox_outside_weights')

        return [rpn_labels, rpn_bbox_targets, rpn_bbox_inside_weights, rpn_bbox_outside_weights]

然后我們接著來看anchor_target_layer_py方法横腿。代碼太長了我就不放上來了颓屑。挑選其中比較有趣的一部分講一下。
首先第一步是生成anchor耿焊。我們先明確一下一個anchor的表達形式揪惦。一個anchor可以看成由(x_min, y_min, x_max, y_max)組成。
第一步就是生成base anchor罗侯。

    _anchors = generate_anchors(scales=np.array(anchor_scales))  # 生成基本的anchor,一共10個

這里調(diào)用generate_anchors函數(shù)器腋。這個函數(shù)比較簡單我就不介紹了。一共生成了10個base anchor钩杰,如下面這列表纫塌。
這里anchor的寬度是固定死的16, 高度是[11, 16, 23, 33, 48, 68, 97, 139, 198, 283]讲弄。
[[ 0 2 15 13]
[ 0 0 15 15]
[ 0 -4 15 19]
[ 0 -9 15 24]
[ 0 -16 15 31]
[ 0 -26 15 41]
[ 0 -41 15 56]
[ 0 -62 15 77]
[ 0 -91 15 106]
[ 0 -134 15 149]]
我們要注意的是措左,這里生成的anchor還需要和特征圖上的每一個位置配合起來。
假設(shè)我們特征圖上只有4個區(qū)域避除,用左上頂點的坐標表示分別是
[[0 0]]
[[1 0]]
[[0 1]]
[[1 1]]
然后我們需要注意怎披,經(jīng)過VGG16之后的特征圖大小是原圖是1/16胸嘁。那么映射到原圖上就是
[[ 0 0]]
[[16 0]]
[[ 0 16]]
[[16 16]]
那么我們現(xiàn)在對于這樣的每一個區(qū)域都配備上述的base anchor。我們只需要把每一個區(qū)域的左上頂點的坐標和anchor相加即可凉逛。就相當于做平移性宏。base anchor相當于是0,0點的anchor状飞。
現(xiàn)在開始上代碼毫胜。

 shift_x = np.arange(0, width) * _feat_stride
 shift_y = np.arange(0, height) * _feat_stride
 shift_x, shift_y = np.meshgrid(shift_x, shift_y)  # in W H order
 # K is H x W
 shifts = np.vstack(
        (shift_x.ravel(), shift_y.ravel(), shift_x.ravel(), shift_y.ravel())
    ).transpose() 
 # add A anchors (1, A, 4) to
 # cell K shifts (K, 1, 4) to get
 # shift anchors (K, A, 4)
 # reshape to (K*A, 4) shifted anchors
 A = _num_anchors  
 K = shifts.shape[0]  
 all_anchors = (_anchors.reshape((1, A, 4)) +
                shifts.reshape((1, K, 4)).transpose((1, 0, 2)))  
 all_anchors = all_anchors.reshape((K * A, 4))

其中width,height诬辈,是特征圖的寬和高酵使。_feat_stride=16。因為特征圖是原圖1/16焙糟。
shift_x, shift_y 是圖上點的x坐標和y坐標凝化。

np.vstack((shift_x.ravel(), shift_y.ravel(), shift_x.ravel(), shift_y.ravel()))

上述代碼的結(jié)果如下。
[[x1, x2, x3, x4...]
[y1, y2, y3, y4...]
[x1, x2, x3, x4...]
[y1, y2, y3, y4...]]

np.vstack((shift_x.ravel(), shift_y.ravel(), shift_x.ravel(), shift_y.ravel())).transpose()

加上一個transpose()之后結(jié)果如下酬荞。是不是感覺很神奇,python 的np真的好使瞧哟。
[[x1, y1, x1, y1]
[x2, y2, x2, y2]
...]
這樣我們就得到了原圖上每一個16X16區(qū)域的左上頂點的坐標混巧。

all_anchors = (_anchors.reshape((1, A, 4)) +
                   shifts.reshape((1, K, 4)).transpose((1, 0, 2)))

這一步就是為每一個區(qū)域配備base anchor。使用np的矩陣加法可以輕松實現(xiàn)這一步勤揩。
當然還要把超出圖像范圍的anchor給刪除咧党。經(jīng)過這些操作anchor就已經(jīng)配置完畢了。
后面的代碼
下一步是為anchor上標簽陨亡。策略是anchor與gt的overlap大于0.7的為正樣本傍衡,每個位置上的10個anchor中與gt overlap最大的也為正樣本。其余的為負樣本负蠕。
并不是所有的樣本都會被使用來訓練蛙埂,對正負樣本采用,在Faster RCNN中正負樣本最終的比例是1:3遮糖。
這其中需要注意的是在計算框的回歸loss的時候只需要正樣本的loss绣的,代碼中采用bbox_inside_weights來控制,就是正樣本為1欲账,負樣本為0這樣負樣本的loss就為0屡江。
分類loss和框回歸loss之間的比例也需要控制,這里使用bbox_outside_weights來調(diào)節(jié)赛不。
到這里訓練數(shù)據(jù)準備完畢惩嘉,也就是rpn_data準備完畢。

后面就是一些比較常規(guī)的操作踢故。分類loss使用的是交叉熵文黎,框回歸loss使用的是smoothL1惹苗。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市臊诊,隨后出現(xiàn)的幾起案子鸽粉,更是在濱河造成了極大的恐慌,老刑警劉巖抓艳,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件触机,死亡現(xiàn)場離奇詭異,居然都是意外死亡玷或,警方通過查閱死者的電腦和手機儡首,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偏友,“玉大人蔬胯,你說我怎么就攤上這事∥凰” “怎么了氛濒?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鹅髓。 經(jīng)常有香客問我舞竿,道長,這世上最難降的妖魔是什么窿冯? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任骗奖,我火速辦了婚禮,結(jié)果婚禮上醒串,老公的妹妹穿的比我還像新娘执桌。我一直安慰自己,他們只是感情好芜赌,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布仰挣。 她就那樣靜靜地躺著,像睡著了一般较鼓。 火紅的嫁衣襯著肌膚如雪椎木。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天博烂,我揣著相機與錄音香椎,去河邊找鬼。 笑死禽篱,一個胖子當著我的面吹牛畜伐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播躺率,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼玛界,長吁一口氣:“原來是場噩夢啊……” “哼万矾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起慎框,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤良狈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后笨枯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體薪丁,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年馅精,在試婚紗的時候發(fā)現(xiàn)自己被綠了严嗜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡洲敢,死狀恐怖漫玄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情压彭,我是刑警寧澤睦优,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站壮不,受9級特大地震影響刨秆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忆畅,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尸执。 院中可真熱鬧家凯,春花似錦、人聲如沸如失。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽褪贵。三九已至掂之,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脆丁,已是汗流浹背世舰。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留槽卫,地道東北人跟压。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像歼培,于是被迫代替她去往敵國和親震蒋。 傳聞我的和親對象是個殘疾皇子茸塞,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348