【我的第一個(gè)目標(biāo)檢測課題—建筑材料識別計(jì)數(shù)系統(tǒng)思考與總結(jié)】3、Retinanet網(wǎng)絡(luò)的學(xué)習(xí)與實(shí)現(xiàn)+擴(kuò)展

2021.1.7下午記


大家新年好~
距離上次寫這個(gè)課題的博客已經(jīng)是去年12.30的時(shí)候了孕暇,是想趁熱打鐵趕快寫的仑撞,無奈1.5/1.6有兩門考試,便只好趕去復(fù)習(xí)暫時(shí)擱置了⊙希現(xiàn)在終于考完試隧哮,而且也馬上到了圖像處理大課題報(bào)告的截止日期了,于是我馬上趕來寫博客了座舍。
之前講的都是課題分析沮翔,環(huán)境配置還有我們是怎么使用Google云盤鏈接Colaboratory用Google服務(wù)器端提供的16G顯存進(jìn)行訓(xùn)練,現(xiàn)在終于來到了最核心的部分了曲秉,也就是對于我們選定的入門網(wǎng)絡(luò)Retinanet網(wǎng)絡(luò)的學(xué)習(xí)和實(shí)現(xiàn)采蚀。
這一篇文章就梳理一下我們對Retinanet網(wǎng)絡(luò)的學(xué)習(xí)和實(shí)現(xiàn)疲牵。
首先還是放一下我們參考的Retinanet網(wǎng)絡(luò)實(shí)現(xiàn)的博客和B站講解鏈接:
【睿智的目標(biāo)檢測17——Keras搭建Retinanet目標(biāo)檢測平臺】
https://blog.csdn.net/weixin_44791964/article/details/104327456
【Keras 搭建自己的Retinanet目標(biāo)檢測平臺(Bubbliiiing 深度學(xué)習(xí) 教程)】(https://www.bilibili.com/video/BV1f741177HD?p=10
以及Retinanet提出來時(shí)發(fā)表的論文鏈接:
【Focal Loss for Dense Object Detection】
https://arxiv.org/pdf/1708.02002.pdf


最初的選擇:Retinanet目標(biāo)檢測算法

1、選擇理由

(1)優(yōu)勢

Retinanet這個(gè)網(wǎng)絡(luò)其實(shí)算是目標(biāo)檢測算法里一個(gè)里程碑的存在吧榆鼠,不過具有里程碑意義的纲爸,還是這個(gè)網(wǎng)絡(luò)所運(yùn)用的損失函數(shù)Focal Loss。Focal Loss這個(gè)損失函數(shù)解決了類別不平衡的問題妆够,大大提升了one-stage算法的檢測精度识啦,使one-stage算法在保持高速處理的同時(shí)精度不再落后于two-stage算法。
我們查閱資料發(fā)現(xiàn)责静,作為單階段網(wǎng)絡(luò)(one-stage)袁滥,Retinanet兼具速度和精度(精度是沒問題的,但目標(biāo)檢測算法發(fā)展到現(xiàn)在灾螃,Retinanet的速度已經(jīng)遠(yuǎn)遠(yuǎn)比不上新提出的算法了,但在當(dāng)時(shí)應(yīng)該可以說是速度和精度兼?zhèn)洌┛玻欠浅D陀玫囊粋€(gè)檢測器腰鬼,現(xiàn)在很多單階段檢測器是以Retinanet為baseline,進(jìn)行各種改進(jìn)塑荒,足見Retinanet的重要熄赡。而且,看了那個(gè)博主的介紹齿税,我們發(fā)現(xiàn)其實(shí)Retinanet網(wǎng)絡(luò)還是比較簡單的彼硫,會比較適合剛剛?cè)腴T的人。綜上凌箕,基于CSDN上的一些博客拧篮,如果要入門目標(biāo)檢測算法,我覺得這個(gè)網(wǎng)絡(luò)就很合適牵舱,不會太舊但也不是太新串绩,作為之后許多目標(biāo)檢測檢測器的baseline,掌握了Retinanet芜壁,想必之后再去學(xué)習(xí)其他的網(wǎng)絡(luò)也就容易許多了礁凡。事實(shí)證明這并沒有錯(cuò),我們在理解并實(shí)現(xiàn)了Retinanet后慧妄,嘗試使用在其基礎(chǔ)上改進(jìn)的新的算法顷牌,比如之前提到的EfficienDet,我們發(fā)現(xiàn)塞淹,不同目標(biāo)檢測算法之間可能只是多使用了一些小trick窟蓝,可能只是在某一部分稍稍進(jìn)行了改進(jìn)。在基本掌握Retinanet以及Retinanet的實(shí)現(xiàn)代碼之后再去看比較新的算法窖铡,確實(shí)容易很多疗锐。


參考

Focal Loss出現(xiàn)前一直保持的局面

看到的2019年的某個(gè)博主閱讀Retinanet提出的論文時(shí)的總結(jié)

以下解釋摘自博客:
https://blog.csdn.net/JNingWei/article/details/80038594?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control

  1. 什么是“類別不平衡”呢坊谁?
    詳細(xì)來說,檢測算法在早期會生成一大波的bbox滑臊。而一幅常規(guī)的圖片中口芍,頂多就那么幾個(gè)object。這意味著雇卷,絕大多數(shù)的bbox屬于background鬓椭。
  2. “類別不平衡”又如何會導(dǎo)致檢測精度低呢?
    因?yàn)閎box數(shù)量爆炸关划。
    正是因?yàn)閎box中屬于background的bbox太多了小染,所以如果分類器無腦地把所有bbox統(tǒng)一歸類為background,accuracy也可以刷得很高贮折。于是乎裤翩,分類器的訓(xùn)練就失敗了。分類器訓(xùn)練失敗调榄,檢測精度自然就低了踊赠。
  3. 那為什么two-stage系就可以避免這個(gè)問題呢?
    因?yàn)閠wo-stage系有RPN罩著每庆。
    第一個(gè)stage的RPN會對anchor進(jìn)行簡單的二分類(只是簡單地區(qū)分是前景還是背景筐带,并不區(qū)別究竟屬于哪個(gè)細(xì)類)。經(jīng)過該輪初篩缤灵,屬于background的bbox被大幅砍削伦籍。雖然其數(shù)量依然遠(yuǎn)大于前景類bbox,但是至少數(shù)量差距已經(jīng)不像最初生成的anchor那樣夸張了腮出。就等于是 從 “類別 極 不平衡” 變成了 “類別 較 不平衡” 帖鸦。
    不過,其實(shí)two-stage系的detector也不能完全避免這個(gè)問題利诺,只能說是在很大程度上減輕了“類別不平衡”對檢測精度所造成的影響富蓄。
    接著到了第二個(gè)stage時(shí),分類器登場慢逾,在初篩過后的bbox上進(jìn)行難度小得多的第二波分類(這次是細(xì)分類)立倍。這樣一來,分類器得到了較好的訓(xùn)練侣滩,最終的檢測精度自然就高啦口注。但是經(jīng)過這么兩個(gè)stage一倒騰,操作復(fù)雜君珠,檢測速度就被嚴(yán)重拖慢了寝志。
  4. 那為什么one-stage系無法避免該問題呢?
    因?yàn)閛ne stage系的detector直接在首波生成的“類別極不平衡”的bbox中就進(jìn)行難度極大的細(xì)分類,意圖直接輸出bbox和標(biāo)簽(分類結(jié)果)材部。而原有交叉熵?fù)p失(CE)作為分類任務(wù)的損失函數(shù)毫缆,無法抗衡“類別極不平衡”,容易導(dǎo)致分類器訓(xùn)練失敗乐导。因此苦丁,one-stage detector雖然保住了檢測速度,卻喪失了檢測精度物臂。

(2)劣勢

當(dāng)然了旺拉,從Retinanet提出所在的論文名:Focal Loss for Dense Object Detection,我們也可以明顯發(fā)現(xiàn)棵磷,其實(shí)這篇論文并不是專門為了提出Retinanet而發(fā)表的蛾狗,這篇論文的核心其實(shí)是提出了一個(gè)新的損失函數(shù):Focal Loss。所以Retinanet的提出其實(shí)就是為了證明Focal Loss這個(gè)損失函數(shù)的有效性仪媒。所以說沉桌,這里的創(chuàng)新并不是網(wǎng)絡(luò)設(shè)計(jì)的創(chuàng)新,而是損失函數(shù)的創(chuàng)新规丽。
為了證明其有效性蒲牧,在設(shè)計(jì)的時(shí)候提取的anchor box數(shù)量很多,將近100K赌莺,這實(shí)際上不利于我們做高速的檢測,但卻可以充分驗(yàn)證Focal Loss這個(gè)損失函數(shù)對征服類別不平衡這一問題的有效性(因?yàn)榫却_實(shí)很高)松嘶∷蚁粒可實(shí)際上我們并不需要這么多的先驗(yàn)框,對每個(gè)先驗(yàn)框都要做判斷做篩選翠订,這無疑會大大降低它的處理速度巢音。這也是我們后面才意識到的,雖然它的精度很高尽超,但是速度確實(shí)比較慢官撼。

2、算法結(jié)構(gòu)

在這里就簡單介紹一下整個(gè)網(wǎng)絡(luò)的結(jié)構(gòu)吧似谁。


Retinanet整體架構(gòu)

(1)主干特征提取網(wǎng)絡(luò):ResNet

ResNet50有兩個(gè)基本的塊傲绣,分別名為Conv Block和Identity Block,其中Conv Block輸入和輸出的維度是不一樣的巩踏,所以不能連續(xù)串聯(lián)秃诵,它的作用是改變網(wǎng)絡(luò)的維度;Identity Block輸入維度和輸出維度相同塞琼,可以串聯(lián)菠净,用于加深網(wǎng)絡(luò)的。

要了解Resnet的構(gòu)成,首先要了解一下殘差網(wǎng)絡(luò)毅往。
Residual net(殘差網(wǎng)絡(luò)):
將靠前若干層的某一層數(shù)據(jù)輸出直接跳過多層引入到后面數(shù)據(jù)層的輸入部分牵咙。
意味著后面的特征層的內(nèi)容會有一部分由其前面的某一層線性貢獻(xiàn)。
其結(jié)構(gòu)如下:


Conv Block的結(jié)構(gòu)如下:



Identity Block的結(jié)構(gòu)如下:



當(dāng)輸入的圖片為600x600x3的時(shí)候攀唯,shape變化與整個(gè)的Resnet網(wǎng)絡(luò)結(jié)構(gòu)如下:

由上圖可以看出洁桌,我們?nèi)〕鲩L寬壓縮了三次、四次革答、五次的結(jié)果輸出來進(jìn)行網(wǎng)絡(luò)金字塔FPN的構(gòu)造战坤。

(2)特征加強(qiáng)網(wǎng)絡(luò):特征金字塔FPN(Feature Pyramid Network)

由最開始放的Retinanet整體結(jié)構(gòu)圖可知,獲得到的特征還需要經(jīng)過圖像金字塔的處理残拐,這樣的結(jié)構(gòu)可以融合多尺度的特征途茫,實(shí)現(xiàn)更有效的預(yù)測。

特征金子塔對主干特征網(wǎng)絡(luò)提取出的特征層進(jìn)一步進(jìn)行融合主要是為了得到更高語義的信息溪食。
此外囊卜,輸出不同層次的特征層是因?yàn)榫矸e多次,小物體的特征可能會丟失错沃,不利于小物體的檢測栅组,但是有利于大物體的檢測。所以進(jìn)行特征融合的話不僅可以提取更豐富的特征信息枢析,而且可以兼顧大物體和小物體的檢測玉掸。

以下則為Retinanet中特征金字塔的構(gòu)造,最終會輸出P3—P7這五層醒叁。


Retinanet中的FPN

(3)目標(biāo)框回歸和分類回歸子網(wǎng)(Class Subnet+Box Subnet)

通過特征金字塔我們可以獲得五個(gè)有效的特征層司浪,分別是P3、P4把沼、P5啊易、P6、P7饮睬,
為了和普通特征層區(qū)分租谈,我們稱之為有效特征層,將這五個(gè)有效的特征層分別傳輸過class+box subnets就可以獲得預(yù)測結(jié)果了捆愁。


class subnet+box subnet

class subnet采用4次256通道的卷積和1次num_priors x num_classes的卷積割去,num_priors指的是該特征層所擁有的先驗(yàn)框數(shù)量,num_classes指的是網(wǎng)絡(luò)一共對多少類的目標(biāo)進(jìn)行檢測牙瓢。

box subnet采用4次256通道的卷積和1次num_priors x 4的卷積劫拗,num_priors指的是該特征層所擁有的先驗(yàn)框數(shù)量,4指的是先驗(yàn)框的調(diào)整情況矾克。

需要注意的是页慷,每個(gè)特征層所用的class subnet是同一個(gè)class subnet憔足;每個(gè)特征層所用的box subnet是同一個(gè)box subnet。
其中:
num_priors x 4的卷積用于預(yù)測該特征層上每一個(gè)網(wǎng)格點(diǎn)上每一個(gè)先驗(yàn)框的變化情況酒繁。num_priors x num_classes的卷積 用于預(yù)測 該特征層上 每一個(gè)網(wǎng)格點(diǎn)上 每一個(gè)預(yù)測框?qū)?yīng)的種類滓彰。

(4)Focal Loss損失函數(shù)

作為Retinanet網(wǎng)絡(luò)中應(yīng)用的最核心內(nèi)容,F(xiàn)ocal Loss當(dāng)然需要好好了解一下州袒。


Focal Loss損失函數(shù)

論文的提出是為了解決類別不平衡導(dǎo)致負(fù)樣本占據(jù)了loss主導(dǎo)地位而模型難以收斂到好的效果的問題
類別不平衡:模型對分類的輸出有可能幾乎正確(easy example揭绑,即在應(yīng)該是0的地方輸出接近0,或該是1的地方輸出接近1郎哭,多數(shù)情況下他匪,背景是easy example,即easy negative)夸研,也可能錯(cuò)得離譜(hard example邦蜜,通常是錯(cuò)誤分類為占多數(shù)的類,通常是包含object的區(qū)域亥至,之所以說hard是因?yàn)檫@部分比較難學(xué))悼沈,幾乎正確的分類loss會很低,而錯(cuò)誤的分類loss會很高姐扮,這本來是很好的絮供。可是當(dāng)object特別稀疏而導(dǎo)致即使對于每個(gè)easy negative 的loss都很低時(shí)茶敏,由于壓倒性的數(shù)量優(yōu)勢壤靶,這部分loss仍然在總的loss中占了絕對主導(dǎo)地位,也就在梯度傳導(dǎo)中占了主導(dǎo)地位惊搏,所以導(dǎo)致原來錯(cuò)誤的hard example幾乎沒有被得到應(yīng)有的改正萍肆。如果能想辦法把錯(cuò)誤分類的loss和正確分類的loss之間的差距再拉大點(diǎn),就能一定程度上改善這種情況胀屿。

于是提出了Focal Loss的概念,如下圖包雀,正確分類的loss變得更低宿崭,錯(cuò)誤分類的loss雖然也降低了,可是相比之下與正確分類的loss差距(從比例上看)拉大了很多才写。


以下是我在看博客時(shí)看到有個(gè)博主從Focal Loss中獲得的啟發(fā)葡兑。確實(shí)啊,并不需要多么復(fù)雜的改進(jìn)也能有很好的效果赞草,最關(guān)鍵的是要關(guān)注到核心問題讹堤,抓住瓶頸背后的真正原因。我還記得當(dāng)時(shí)在調(diào)參時(shí)看了一些調(diào)參的相關(guān)博客厨疙,當(dāng)時(shí)看過一句話洲守,博主說:過早的優(yōu)化是萬惡之源+抓住真正的瓶頸。我覺得印象真的很深刻,邊角的漫無目的優(yōu)化也許并不是最好的辦法梗醇,也許只是在逃避真正的問題知允。在真正的進(jìn)行具體細(xì)節(jié)的優(yōu)化前,確實(shí)應(yīng)該抓住真正的瓶頸叙谨,把最關(guān)鍵的問題解決后温鸽,性能才能明顯提升,之后才應(yīng)該去做細(xì)微的優(yōu)化手负。所以在我看來涤垫,F(xiàn)ocal Loss在目標(biāo)檢測算法發(fā)展史上有里程碑式的意義,就是因?yàn)樗鉀Q了一個(gè)瓶頸問題(one-stage檢測精度總是比two-stage低)竟终。


小trick能有大效果

3蝠猬、部分代碼實(shí)現(xiàn)

整個(gè)程序的框架如下圖所示:


工程文件夾下的所有文件

以下是對各個(gè)文件的作用解釋:
(1).idea、.temp_file衡楞、pycache文件夾:在Pycharm中運(yùn)行所需要的文件夾(新建工程時(shí)會自動加入)
(2)annotations文件夾:存放的是驗(yàn)收時(shí)測試圖片的所有標(biāo)簽文件
(3)img文件夾:存放檢驗(yàn)代碼性能時(shí)的測試集圖片(即驗(yàn)收時(shí)900張圖片的存放地址)
(4)logs文件夾:存放訓(xùn)練過程中生成的權(quán)重文件(模型)
(5)model_data文件夾:如下圖所示吱雏,classes.txt中存放的是類名(rebar\coil\brickwork),還有一個(gè)COCO數(shù)據(jù)集的預(yù)訓(xùn)練權(quán)重,我們就是在該預(yù)訓(xùn)練權(quán)重的基礎(chǔ)上進(jìn)行訓(xùn)練的瘾境。


(6)nets文件夾:存放的是Retinanet網(wǎng)絡(luò)搭建部分的代碼(Resnet\FPN\Classsubnet歧杏、Boxsubnet)
(7)results文件夾:如下圖所示,存放的是訓(xùn)練過程中不同模型的評估結(jié)果迷守。不過需要運(yùn)行另外三個(gè)特定的py文件犬绒。我們在此次完成課題的過程中并沒有使用。

(8)utils文件夾:里面存放的py文件主要是用于獲取先驗(yàn)框的兑凿。
(9)VOCdevkit文件夾:存放VOC格式的數(shù)據(jù)集凯力,如下所示內(nèi)部還有三個(gè)子文件夾和一個(gè).py文件。Annotations存放的是測試集的所有.xml標(biāo)簽文件礼华,ImageSets存放的是訓(xùn)練時(shí)所需要的四個(gè)文件(如下圖)咐鹤。JPEGImages存放的是測試集圖片。voc2retinanet能夠根據(jù)測試集生成下列四個(gè)txt文件圣絮。

(10)2007_test祈惶、2007_train、2007_val也是訓(xùn)練時(shí)需要使用的文件扮匠,可由下面的voc_annotation.py生成捧请。
(11)compute_AP:驗(yàn)收時(shí)在完成對所有圖片的計(jì)數(shù)后,執(zhí)行該函數(shù)可以計(jì)算該模型的AP值和Error rate棒搜。
(12)newfile:由于比賽要求我們把每章圖片的檢測結(jié)果按照特定格式放到一個(gè)txt文件中疹蛉,所以我們新建了newfile.txt用來存放每張圖片對應(yīng)各個(gè)類別的計(jì)數(shù)結(jié)果。
(13)predict:執(zhí)行預(yù)測的py文件
(14)retinanet:存放檢測圖片的核心類和類方法
(15)testGPU:是我為了測試電腦是否正常使用GPU進(jìn)行訓(xùn)練的程序力麸。
(16)train:執(zhí)行訓(xùn)練的文件
(17)video可款、Vision_For_prior:與打開攝像頭的動態(tài)檢測有關(guān)的文件育韩,這次也沒用到
(18)voc_annotation:用于生成上面的三個(gè)2007的文件。

下面就大概看一下以下幾個(gè)比較重要的部分吧

(1)訓(xùn)練網(wǎng)絡(luò)部分代碼及流程(train.py)

文字描述這個(gè)還是不太容易筑舅,不如直接把代碼和注釋貼上來吧座慰,有這些注釋肯定能看懂。簡單來說翠拣,訓(xùn)練部分主要是讀入標(biāo)簽文件和測試文件版仔,然后配置訓(xùn)練的各項(xiàng)參數(shù)。比如常規(guī)的batchsize误墓、epoch蛮粮,此處還設(shè)置了防止過擬合的early stopping(一定世代的loss沒有下降就會認(rèn)為達(dá)到了過擬合點(diǎn))和梯度截?cái)鄥?shù)clipnorm,防止某一次的learning rate過大谜慌。而且訓(xùn)練輪次一共分為兩大輪然想,第一大輪是凍結(jié)了某些參數(shù)(遷移學(xué)習(xí)的思想,不同的項(xiàng)目淺層的特征都差不多欣范,凍結(jié)后參數(shù)比較少可以提升訓(xùn)練效率)变泄,第二大輪是解凍參數(shù)后的情況,一般這一輪的Batch size會設(shè)置得比較小防止參數(shù)過多內(nèi)存溢出恼琼。
我的注釋還是很多妨蛹,因?yàn)橐婚_始很多東西都不懂症副,只好一個(gè)一個(gè)去查考阱。但其實(shí)不會特別麻煩,只要把這個(gè)掌握清楚了宰翅,之后再看其他網(wǎng)絡(luò)的代碼就容易許多了(熟悉了函數(shù)噩死,熟悉了相似的結(jié)構(gòu))颤难。
此外,訓(xùn)練部分還有兩個(gè)步驟需要提到已维。
(1)對真實(shí)框的處理
在預(yù)測部分行嗤,每個(gè)特征層的預(yù)測結(jié)果,num_priors x 4的卷積用于預(yù)測該特征層上每一個(gè)網(wǎng)格點(diǎn)上每一個(gè)先驗(yàn)框的變化情況垛耳。也就是說昂验,我們直接利用retinanet網(wǎng)絡(luò)預(yù)測到的結(jié)果,并不是預(yù)測框在圖片上的真實(shí)位置艾扮,需要解碼才能得到真實(shí)位置。(所謂解碼其實(shí)就是把得到的數(shù)字按一定規(guī)則轉(zhuǎn)換為我們需要的位置值)

而在訓(xùn)練的時(shí)候占婉,我們需要計(jì)算loss函數(shù)泡嘴,這個(gè)loss函數(shù)是相對于Retinanet網(wǎng)絡(luò)的預(yù)測結(jié)果的。我們需要把圖片輸入到當(dāng)前的Retinanet網(wǎng)絡(luò)中逆济,得到預(yù)測結(jié)果酌予;同時(shí)還需要把真實(shí)框的信息磺箕,進(jìn)行編碼,這個(gè)編碼是把真實(shí)框的位置信息格式轉(zhuǎn)化為Retinanet預(yù)測結(jié)果的格式信息抛虫。也就是松靡,我們需要找到每一張用于訓(xùn)練的圖片的每一個(gè)真實(shí)框?qū)?yīng)的先驗(yàn)框,并求出如果想要得到這樣一個(gè)真實(shí)框建椰,我們的預(yù)測結(jié)果應(yīng)該是怎么樣的雕欺。
然后,我們可以獲得真實(shí)框?qū)?yīng)的所有的iou較大先驗(yàn)框棉姐,并計(jì)算真實(shí)框?qū)?yīng)的所有iou較大的先驗(yàn)框應(yīng)該有的預(yù)測結(jié)果屠列。但是由于原始圖片中可能存在多個(gè)真實(shí)框,可能同一個(gè)先驗(yàn)框會與多個(gè)真實(shí)框重合度較高伞矩,我們只取其中與真實(shí)框重合度最高的就可以了笛洛。因此我們還要經(jīng)過一次篩選,將上述代碼獲得的真實(shí)框?qū)?yīng)的所有的iou較大先驗(yàn)框的預(yù)測結(jié)果中乃坤,iou最大的那個(gè)真實(shí)框篩選出來苛让。

而focal會忽略一些重合度相對較高但是不是非常高的先驗(yàn)框,一般將重合度在0.4-0.5之間的先驗(yàn)框進(jìn)行忽略湿诊。
(2)利用處理完的真實(shí)框與對應(yīng)圖片的預(yù)測結(jié)果計(jì)算loss
loss的計(jì)算分為兩個(gè)部分:
1狱杰、Smooth Loss:獲取所有正標(biāo)簽的框的預(yù)測結(jié)果的回歸loss。
2枫吧、Focal Loss:獲取所有未被忽略的種類的預(yù)測結(jié)果的交叉熵loss浦旱。

import nets.retinanet as retinanet
import numpy as np
import keras
from keras.optimizers import Adam
from nets.retinanet_training import Generator
from nets.retinanet_training import focal,smooth_l1 
from keras.callbacks import TensorBoard, ModelCheckpoint,ReduceLROnPlateau, EarlyStopping
from utils.utils import BBoxUtility
from utils.anchors import get_anchors
import tensorflow as tf

 #某一次報(bào)錯(cuò)的時(shí)候修改,這是為了程序開始運(yùn)行九杂,需要GPU的時(shí)候才申請GPU的使用颁湖。本電腦的GPU為2G,運(yùn)行極容易出現(xiàn)內(nèi)存溢出的現(xiàn)象例隆,后面改用Google的colab平臺進(jìn)行訓(xùn)練
config = tf.compat.v1.ConfigProto(gpu_options=tf.compat.v1.GPUOptions(allow_growth=True))
sess = tf.compat.v1.Session(config=config)

if __name__ == "__main__":
    NUM_CLASSES = 3                                          #類別數(shù)為3
    input_shape = (600, 600, 3)                              #輸入圖片的大小
    annotation_path = '2007_train.txt'                       #訓(xùn)練所需標(biāo)簽文件(圖片名+目標(biāo)框的信息)的路徑
    inputs = keras.layers.Input(shape=input_shape)           #按方式二(輸入輸出層方式)構(gòu)建模型(好處:可以獲取隱藏層的信息——每一層的輸出都可以取出查看)
    model = retinanet.resnet_retinanet(NUM_CLASSES,inputs)   #resnet_retinanet為Retinanet的整體網(wǎng)絡(luò)結(jié)構(gòu)甥捺,其存放在retinanet文件中
    priors = get_anchors(model)                              #該函數(shù)通過輸入該網(wǎng)絡(luò)來得到先驗(yàn)框
    bbox_util = BBoxUtility(NUM_CLASSES, priors)

#------------------------------------------------------#
#   訓(xùn)練自己的數(shù)據(jù)集時(shí)會提示維度不匹配正常
#   預(yù)測的東西不一樣,自然維度也不匹配
#------------------------------------------------------#

#在訓(xùn)練好的coco數(shù)據(jù)集權(quán)重上繼續(xù)訓(xùn)練镀层,用自己訓(xùn)練出的權(quán)重進(jìn)行訓(xùn)練效果并不太好镰禾,經(jīng)常會出現(xiàn)loss增加的情況
#model.load_weights("model_data/resnet50_coco_best_v2.1.0.h5",by_name=True,skip_mismatch=True)
#使用自己已經(jīng)訓(xùn)練的模型繼續(xù)訓(xùn)練
model.load_weights("logs/2ep038-loss0.800-val_loss0.746.h5", by_name=True, skip_mismatch=True)
#輸入的數(shù)據(jù)集其中0.1用于驗(yàn)證,0.9用于訓(xùn)練
val_split = 0.1
with open(annotation_path) as f:  #根據(jù)annotation的路徑打開標(biāo)簽文件唱逢,并將其賦給f對象
    lines = f.readlines()         #readline函數(shù)讀取標(biāo)簽文件中的每一行的值吴侦,并存放到lines這個(gè)列表中
np.random.seed(10101)
"""
seed( ) 用于指定隨機(jī)數(shù)生成時(shí)所用算法開始的整數(shù)值。
1.如果使用相同的seed( )值坞古,則每次生成的隨即數(shù)都相同备韧;
2.如果不設(shè)置這個(gè)值,則系統(tǒng)根據(jù)時(shí)間來自己選擇這個(gè)值痪枫,此時(shí)每次生成的隨機(jī)數(shù)因時(shí)間差異而不同织堂。
3.設(shè)置的seed()值僅一次有效
"""
np.random.shuffle(lines)          #最好不要讓網(wǎng)絡(luò)通過完全相同的minibatch叠艳,如果框架允許,在每個(gè)epoch都shuffle一次易阳。隨機(jī)打亂順序
np.random.seed(None)              #shuffle的作用:增加隨機(jī)性附较。提高網(wǎng)絡(luò)的泛化性能,避免因?yàn)橛幸?guī)律的數(shù)據(jù)出現(xiàn)潦俺,導(dǎo)致權(quán)重更新時(shí)的梯度過于極端拒课,避免最終模型過擬合或欠擬合。
num_val = int(len(lines)*val_split)#標(biāo)簽文件的行數(shù)即為總訓(xùn)練數(shù)據(jù)數(shù)黑竞,乘以其比例0.1則為訓(xùn)練過程中用來做測試評估當(dāng)前模型好壞的驗(yàn)證集的數(shù)量
num_train = len(lines) - num_val  #總測試數(shù)據(jù)減去驗(yàn)證集即為訓(xùn)練集的數(shù)量

# 訓(xùn)練參數(shù)設(shè)置
logging = TensorBoard(log_dir="logs")#log_dir: 用來保存Tensorboard的日志文件等內(nèi)容的位置捕发。默認(rèn)保存在當(dāng)前文件夾下的logs文件夾之下
#Save the model after every epoch.
# 指定保存的權(quán)重文件的路徑和名稱;監(jiān)視器;需要監(jiān)視的值很魂,此處要監(jiān)視的是驗(yàn)證集上的損失val_loss;save_weights_only=true:保證只有模型的權(quán)重被保存扎酷,而不是整個(gè)模型被保存;
# save_best_only=False:不是只有最佳模型才被保存遏匆。period周期:多少個(gè)epoch執(zhí)行一次checkpoint檢查點(diǎn)法挨,做一次評估,保存一次模型幅聘。此處是一個(gè)epoch就做一次評估.
checkpoint = ModelCheckpoint('logs/3ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5',
    monitor='val_loss', save_weights_only=True, save_best_only=False, period=1)
#verbose:信息展示模式凡纳,0或1。為1表示輸出epoch模型保存信息帝蒿,默認(rèn)為0表示不輸出該信息
#兩個(gè)世代val_loss沒有下降的話荐糜,就將learning_rate減少到原來的一半,防止越過最優(yōu)點(diǎn)
reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2, verbose=1)#學(xué)習(xí)率下降的多少葛超,并且將其輸出顯示出來
#此處使用的防止過擬合的方法(early stopping)
#當(dāng)發(fā)現(xiàn)有6個(gè)patience的val_loss沒有下降暴氏,則會認(rèn)為已經(jīng)達(dá)到過擬合點(diǎn),選擇提前結(jié)束本輪的訓(xùn)練绣张,
early_stopping = EarlyStopping(monitor='val_loss', min_delta=0, patience=6, verbose=1)
"""Early stopping函數(shù):監(jiān)測值不再改善時(shí)答渔,停止訓(xùn)練
   對模型進(jìn)行訓(xùn)練的過程即是對模型的參數(shù)進(jìn)行學(xué)習(xí)更新的過程,這個(gè)參數(shù)學(xué)習(xí)的過程往往會用到一些迭代方法侥涵,如梯度下降(Gradient descent)學(xué)習(xí)算法沼撕。
   Early stopping便是一種迭代次數(shù)截?cái)嗟姆椒▉矸乐惯^擬合的方法,即在模型對訓(xùn)練數(shù)據(jù)集迭代收斂之前停止迭代來防止過擬合芜飘。 
   Early stopping方法的具體做法是务豺,在每一個(gè)Epoch結(jié)束時(shí)(一個(gè)Epoch集為對所有的訓(xùn)練數(shù)據(jù)的一輪遍歷)計(jì)算validation data的accuracy,
   當(dāng)accuracy不再提高時(shí)嗦明,就停止訓(xùn)練冲呢。這種做法很符合直觀感受,因?yàn)閍ccurary都不再提高了,在繼續(xù)訓(xùn)練也是無益的敬拓,只會提高訓(xùn)練的時(shí)間。
   那么該做法的一個(gè)重點(diǎn)便是怎樣才認(rèn)為validation accurary不再提高了呢裙戏?并不是說validation accuracy一降下來便認(rèn)為不再提高了乘凸,
   因?yàn)榭赡芙?jīng)過這個(gè)Epoch后,accuracy降低了累榜,但是隨后的Epoch又讓accuracy又上去了营勤,所以不能根據(jù)一兩次的連續(xù)降低就判斷不再提高。
   一般的做法是壹罚,在訓(xùn)練的過程中葛作,記錄到目前為止最好的validation accuracy,當(dāng)連續(xù)10次Epoch(或者更多次)沒達(dá)到最佳a(bǔ)ccuracy時(shí)猖凛,則可以認(rèn)為accuracy不再提高了赂蠢。
   此時(shí)便可以停止迭代了(Early Stopping)。這種策略也稱為“No-improvement-in-n”辨泳,n即Epoch的次數(shù)虱岂,可以根據(jù)實(shí)際情況取,如10菠红、20第岖、30……
"""

BATCH_SIZE = 4
gen = Generator(bbox_util, BATCH_SIZE, lines[:num_train], lines[num_train:], #列表的表示形式,序號為0~numtrain作為訓(xùn)練集试溯,序號為numtrain~最后作為驗(yàn)證集
                (input_shape[0], input_shape[1]),NUM_CLASSES)
#(input_shape[0], input_shape[1])為輸入圖片的大小蔑滓,shape的第一維和第二維即為長和寬的比例
#------------------------------------------------------#
#   主干特征提取網(wǎng)絡(luò)特征通用,凍結(jié)訓(xùn)練可以減少訓(xùn)練參數(shù)遇绞,加快訓(xùn)練速度
#   也可以在訓(xùn)練初期防止權(quán)值被破壞键袱。
#   提示OOM或者顯存不足調(diào)小Batch_size
#------------------------------------------------------#
for i in range(174): #為什么是174?
    model.layers[i].trainable = False  #凍結(jié)试读,使這些層的權(quán)重不會更新杠纵,只訓(xùn)練底層參數(shù) “遷移學(xué)習(xí)”的思想,對于不同的特征提取網(wǎng)絡(luò)钩骇,淺層的特征是相同的
#為了使之生效比藻,在修改 trainable 屬性之后,需要在模型上調(diào)用 compile()進(jìn)行裝載倘屹,理解為配備好模型訓(xùn)練前的準(zhǔn)備工作银亲,比如優(yōu)化器的選擇,比如損失函數(shù)(回歸的損失函數(shù)和分類的損失函數(shù))的選擇纽匙。
model.compile(loss={
            'regression'    : smooth_l1(),
            'classification': focal()
        },optimizer=keras.optimizers.Adam(lr=1e-4, clipnorm=0.001)  #初始學(xué)習(xí)率為10^-4,clipnorm幫助進(jìn)行梯度裁剪务蝠,防止梯度下降導(dǎo)致越過最低點(diǎn)
)#梯度范數(shù)縮放: 在梯度向量的L2向量范數(shù)(平方和)超過閾值時(shí),將損失函數(shù)的導(dǎo)數(shù)更改為具有給定的向量范數(shù)烛缔。例如馏段,可以將范數(shù)指定為1.0轩拨,這意味著,如果梯度的向量范數(shù)超過1.0院喜,則向量中的值將重新縮放亡蓉,以使向量范數(shù)等于1.0。在Keras中通過在優(yōu)化器上指定 clipnorm 參數(shù)實(shí)現(xiàn):
 #梯度范數(shù)縮放:就算loss的求導(dǎo)值很大喷舀,但為了保證穩(wěn)定砍濒,一次梯度下降不會過多,設(shè)置一次的下降值不會超過clipnorm(0.001)
model.fit_generator(    gen.generate(True), 
        steps_per_epoch=num_train//BATCH_SIZE,  #每個(gè)訓(xùn)練epoch的步長硫麻,訓(xùn)練數(shù)量除以每一個(gè)批處理的數(shù)量
        validation_data=gen.generate(False),
        validation_steps=num_val//BATCH_SIZE,   #每個(gè)驗(yàn)證集的步長爸邢,驗(yàn)證集數(shù)目除以一次批處理的數(shù)目
        epochs=15,
        verbose=1,
        initial_epoch=0, #設(shè)置訓(xùn)練時(shí)候應(yīng)用的回調(diào)函數(shù)(理解為模型優(yōu)化過程遵守的規(guī)則)
        callbacks=[logging, checkpoint, reduce_lr, early_stopping])

#解凍部分層的權(quán)值,和其他參數(shù)一起參與訓(xùn)練更新(此處會極大的增加網(wǎng)絡(luò)中的參數(shù)量)拿愧。使用第一階段以訓(xùn)練完成的網(wǎng)絡(luò)權(quán)重繼續(xù)訓(xùn)練杠河,此時(shí)學(xué)習(xí)率應(yīng)設(shè)置得比上一次要小。
for i in range(174):
    model.layers[i].trainable = True

model.compile(loss={
            'regression'    : smooth_l1(),
            'classification': focal()
        },optimizer=keras.optimizers.Adam(lr=1e-5, clipnorm=0.001) #
)
model.fit_generator(    gen.generate(True), 
        steps_per_epoch=num_train//BATCH_SIZE,
        validation_data=gen.generate(False),
        validation_steps=num_val//BATCH_SIZE,
        epochs=40,
        verbose=1,
        initial_epoch=15,
        callbacks=[logging, checkpoint, reduce_lr, early_stopping])
"""
    對于小型赶掖,簡單化的數(shù)據(jù)集感猛,直接使用Keras的.fit函數(shù),
    實(shí)際項(xiàng)目中奢赂,訓(xùn)練數(shù)據(jù)會很大陪白,簡單地使用model.fit將整個(gè)訓(xùn)練數(shù)據(jù)讀入內(nèi)存將不再適用,所以需要改用model.fit_generator分批次讀取膳灶,并且我們還可以利用其進(jìn)行數(shù)據(jù)增強(qiáng)咱士。
    fit_generator函數(shù)各參量意義:
    generator:生成器函數(shù),輸出應(yīng)該是形為(inputs,target)或者(inputs,targets,sample_weight)的元組轧钓,生成器會在數(shù)據(jù)集上無限循環(huán)
    steps_per_epoch: 顧名思義序厉,每輪的步數(shù),整數(shù)毕箍,當(dāng)生成器返回steps_per_epoch次數(shù)據(jù)時(shí)弛房,進(jìn)入下一輪。
    epochs :整數(shù)而柑,數(shù)據(jù)的迭代次數(shù)
    verbose:日志顯示開關(guān)文捶。0代表不輸出日志,1代表輸出進(jìn)度條記錄媒咳,2代表每輪輸出一行記錄
    validation_data:驗(yàn)證集數(shù)據(jù).
    
    .fit_generator函數(shù)假定存在一個(gè)為其生成數(shù)據(jù)的基礎(chǔ)函數(shù)粹排。
    該函數(shù)本身是一個(gè)Python生成器。Keras在使用.fit_generator訓(xùn)練模型時(shí)的過程:
    (1)Keras調(diào)用提供給.fit_generator的生成器函數(shù)
    (2)生成器函數(shù)為.fit_generator函數(shù)生成一批大小為BS的數(shù)據(jù)
    (3).fit_generator函數(shù)接受批量數(shù)據(jù)涩澡,執(zhí)行反向傳播顽耳,并更新模型中的權(quán)重。重復(fù)該過程直到達(dá)到期望的epoch數(shù)量(設(shè)定的epoch總數(shù))
    需要在調(diào)用.fit_generator時(shí)提供steps_per_epoch參數(shù)(.fit方法沒有這樣的參數(shù))。
    為什么我們需要steps_per_epoch射富?
    請記住膝迎,Keras數(shù)據(jù)生成器意味著無限循環(huán),它永遠(yuǎn)不會返回或退出胰耗。
    由于該函數(shù)旨在無限循環(huán)弄抬,因此Keras無法確定一個(gè)epoch何時(shí)開始的,并且新的epoch何時(shí)開始宪郊。
    因此,我們將訓(xùn)練數(shù)據(jù)的總數(shù)除以批量大小的結(jié)果作為steps_per_epoch的值拖陆。一旦Keras到達(dá)這一步弛槐,它就會知道這是一個(gè)新的epoch。
"""

(2)預(yù)測圖片代碼及流程(predict.py)

1依啰、圖片經(jīng)過ResNet乎串,F(xiàn)PN,classsubnet和boxsubnet后輸出預(yù)測結(jié)果

網(wǎng)絡(luò)的輸出結(jié)果:目標(biāo)框信息和類別信息

但是這個(gè)預(yù)測結(jié)果還需要進(jìn)行解碼才能使用
2速警、預(yù)測結(jié)果的解碼

我們通過對每一個(gè)特征層的處理叹誉,可以獲得三個(gè)內(nèi)容,分別是:
(1)num_priors x 4的卷積用于預(yù)測該特征層上每一個(gè)網(wǎng)格點(diǎn)上每一個(gè)先驗(yàn)框的變化情況闷旧。
(2)num_priors x num_classes的卷積用于預(yù)測該特征層上每一個(gè)網(wǎng)格點(diǎn)上每一個(gè)預(yù)測框?qū)?yīng)的種類长豁。
(3)每一個(gè)有效特征層對應(yīng)的先驗(yàn)框?qū)?yīng)著該特征層上每一個(gè)網(wǎng)格點(diǎn)上預(yù)先設(shè)定好的9個(gè)框。
我們利用 num_priors x 4的卷積與每一個(gè)有效特征層對應(yīng)的先驗(yàn)框獲得框的真實(shí)位置忙灼。
每一個(gè)有效特征層對應(yīng)的先驗(yàn)框就是匠襟,如下圖所示的作用:
每一個(gè)有效特征層將整個(gè)圖片分成與其長寬對應(yīng)的網(wǎng)格,如P3的特征層就是將整個(gè)圖像分成75x75個(gè)網(wǎng)格该园;然后從每個(gè)網(wǎng)格中心建立9個(gè)先驗(yàn)框酸舍,一共75x75x9個(gè),50625個(gè)先驗(yàn)框里初。



先驗(yàn)框雖然可以代表一定的框的位置信息與框的大小信息啃勉,但是其是有限的,無法表示任意情況双妨,因此還需要調(diào)整淮阐,Retinanet利用4次256通道的卷積+num_priors x 4的卷積的結(jié)果對先驗(yàn)框進(jìn)行調(diào)整。
num_priors x 4中的num_priors表示了這個(gè)網(wǎng)格點(diǎn)所包含的先驗(yàn)框數(shù)量斥难,其中的4表示了框的左上角xy軸枝嘶,右下角xy的調(diào)整情況。
Retinanet解碼過程就是將對應(yīng)的先驗(yàn)框的左上角和右下角進(jìn)行位置的調(diào)整哑诊,調(diào)整完的結(jié)果就是預(yù)測框的位置了群扶。
當(dāng)然得到最終的預(yù)測結(jié)構(gòu)后還要進(jìn)行得分排序與非極大抑制篩選(同個(gè)區(qū)域可能有很多目標(biāo)檢測都符合置信度的要求,為了防止框的重疊(重復(fù)),要篩選出同一區(qū)域同一種類得分最大的框竞阐。)這一部分缴饭,這也基本上是所有目標(biāo)檢測通用的部分。
1骆莹、取出每一類得分大于confidence_threshold的框和得分颗搂。
2、利用框的位置和得分進(jìn)行非極大抑制幕垦。

預(yù)測部分修改之后的代碼:

from keras.layers import Input
from retinanet import Retinanet
import tensorflow as tf
from PIL import Image
import os
#import datetime
import time

#config =    tf.compat.v1.ConfigProto(gpu_options=tf.compat.v1.GPUOptions(allow_growth=True))
#sess = tf.compat.v1.Session(config=config)
#程序計(jì)時(shí)器丢氢,啟動計(jì)時(shí)器
start = time.clock()
retinanet = Retinanet()
#i=0
#while i<1:
path = 'F:\\retinanet-keras-master4\\img' 
#print(os.listdir(path))
for picture in os.listdir(path):
    #img = input('Input image filename:')
    img = picture #此處即為圖片名
    #path,imgname = os.path.split(img)
    try:
        image = Image.open("img\\"+img)
    except:
        print('Open Error! Try again!')
        continue
    else:
    # 打開一個(gè)文件,把該圖片的運(yùn)行結(jié)果寫入文件
        f = open(r"F:\retinanet-keras-master4\newfile.txt", "a")
        f.write(img+"\n")
        r_image,rebar,coil,brickwork = retinanet.detect_image(image)
        print(r_image, rebar, coil, brickwork)
        f.write("rebar:"+str(rebar)+"\n")
        f.write("coil:"+str(coil)+"\n")
        f.write("brickwork:"+str(brickwork)+"\n")
        # 關(guān)閉打開的文件
        f.close()
        r_image.show()
retinanet.close_session()
#計(jì)算啟動時(shí)間和結(jié)束時(shí)間的時(shí)間差
end = time.process_time()
print('運(yùn)行時(shí)間 : %s 秒'%(end-start))

(3)數(shù)據(jù)增強(qiáng)部分

似乎在這個(gè)文件里沒有數(shù)據(jù)增強(qiáng)部分,只是在每一輪開始前shuffle了一下改變圖片次序先改。

4疚察、訓(xùn)練過程及預(yù)測過程

訓(xùn)練過程

(1)首先將訓(xùn)練集的圖片和標(biāo)簽分別放在VOC2007文件夾下的JPEGImages文件夾和Annotations文件夾下,這邊要注意的是仇奶,訓(xùn)練圖片的文件名和其對應(yīng)的標(biāo)簽文件的文件名要相同貌嫡。而且命名的時(shí)候不要出現(xiàn)空格,否則第二步運(yùn)行的時(shí)候就會出錯(cuò)该溯。
(2)運(yùn)行同一個(gè)文件夾下的voc2retinanet.py文件岛抄。在運(yùn)行之前需要將標(biāo)簽文件的路徑和生成的文件的保存路徑改成自己對應(yīng)文件夾的路徑,而且生成的文件應(yīng)保存在ImageSets下的Main文件夾中狈茉。運(yùn)行之后會生成test.txt/train.txt/trainval.txt/val.txt夫椭。


(3)接著運(yùn)行voc_annotation.py文件。只需要將類別名改為自己需要的類別名即可论皆。運(yùn)行后會生成2007_test.txt/2007_train.txt/2007_val.txt三個(gè)文件益楼。


(4)在訓(xùn)練前還需要修改model_data里面的voc_classes.txt文件,需要將classes改成自己的classes点晴。如下圖是我的classes文件里的類別感凤。


(5)最后就是運(yùn)行train.py文件。運(yùn)行之前需要把類別數(shù)NUM_CLASSES改為自己的類別數(shù)粒督,下面的圖片輸入尺寸也可以改陪竿,還有再下面的測試數(shù)據(jù)集中驗(yàn)證集的比例也可以改。不過我只改了類別數(shù)屠橄,其他都沒改族跛。


預(yù)測過程

預(yù)測過程就簡單許多了,只需要運(yùn)行predict.py文件即可锐墙。原博主提供的代碼是需要我們預(yù)先在img文件夾中存入要測試的圖片礁哄,之后運(yùn)行預(yù)測文件的時(shí)候要人為輸入圖片的路徑和名稱,就能跳出檢測后的圖片溪北。不過一次只能檢測單張圖片桐绒。但是驗(yàn)收時(shí)需要連續(xù)測試900張圖片夺脾,所以我在predict.py中加入了循環(huán)讀取和將結(jié)果寫入新文件的程序,并把一些圖片顯示的不必要的部分刪去茉继。運(yùn)行前把900張圖片放入img文件夾咧叭,運(yùn)行predict.py文件之后就可以在newfile.txt中看到每張圖片的所有預(yù)測結(jié)果。

5烁竭、網(wǎng)絡(luò)訓(xùn)練調(diào)參過程及結(jié)果

(1)調(diào)整的參數(shù)及對應(yīng)參數(shù)作用

我們在調(diào)參的時(shí)候調(diào)整了如下參數(shù)的值:
batch size菲茬、epoch、learning rate派撕、置信度婉弹、patience、clipnorm终吼。
batch size:訓(xùn)練時(shí)每一批處理的圖片數(shù)马胧。這個(gè)參數(shù)主要受電腦的顯存資源牽制。我用本機(jī)的GPU進(jìn)行訓(xùn)練的時(shí)候衔峰,batchsize不能大過6,而且當(dāng)我把batchsize調(diào)整到2時(shí)蛙粘,訓(xùn)練到參數(shù)解凍的時(shí)候還是會內(nèi)存溢出垫卤。即使是在Colaboratory在線進(jìn)行訓(xùn)練的時(shí)候,batchsize一般也只調(diào)到8出牧,否則還是會在解凍參數(shù)后內(nèi)存溢出穴肘。一般來說,解凍前的batch size都會比解凍后的batch size來得大舔痕。而且batch size不能設(shè)置得過小评抚,否則處理一個(gè)batch,網(wǎng)絡(luò)都學(xué)不到什么東西伯复,容易引起走偏“振蕩”(因?yàn)槭軅€(gè)體影響太明顯慨代,無法確認(rèn)下降的大方向)。此外啸如,有博主說侍匙,將batch size設(shè)置為2的次方有利于GPU進(jìn)行高效運(yùn)算。

batch size設(shè)置大的好處
1)內(nèi)存的利用率提高了叮雳,大矩陣乘法的并行化效率提高想暗。
2)跑完一次epoch(全數(shù)據(jù)集)所需迭代次數(shù)減少,對于相同的數(shù)據(jù)量的處理速度進(jìn)一步加快帘不。
3)一定范圍內(nèi)说莫,batchsize越大,其確定的下降方向就越準(zhǔn)寞焙,引起訓(xùn)練震蕩越小储狭。
壞處:容易內(nèi)存溢出
總結(jié):
1)batch數(shù)太小互婿,而類別又比較多的時(shí)候,真的可能會導(dǎo)致loss函數(shù)震蕩而不收斂晶密,尤其是在你的網(wǎng)絡(luò)比較復(fù)雜的時(shí)候擒悬。
2)隨著batchsize增大,處理相同的數(shù)據(jù)量的速度越快稻艰。
3)隨著batchsize增大懂牧,達(dá)到相同精度所需要的epoch數(shù)量越來越多。
4)由于上述兩種因素的矛盾尊勿, Batch_Size 增大到某個(gè)時(shí)候僧凤,達(dá)到時(shí)間上的最優(yōu)。 5)由于最終收斂精度會陷入不同的局部極值元扔,因此 Batch_Size 增大到某些時(shí)候躯保,達(dá)到最終收斂精度上的最優(yōu)。
6)過大的batchsize的結(jié)果是網(wǎng)絡(luò)很容易收斂到一些不好的局部最優(yōu)點(diǎn)澎语。同樣太小的batch也存在一些問題途事,比如訓(xùn)練速度很慢,訓(xùn)練不容易收斂等擅羞。
【batch size設(shè)置技巧參考資料】https://blog.csdn.net/zqx951102/article/details/88918948

epoch:每次處理完全部測試集圖片就算一個(gè)epoch尸变。一般epoch我都是設(shè)置20 25 50這樣。我們的程序最開始是有部分參數(shù)被凍結(jié)不參與梯度下降更新的减俏,幾個(gè)輪次完之后才解凍召烂,全部參數(shù)一起參與更新。我一般設(shè)置epoch1=epoch2(意思是凍結(jié)訓(xùn)練的總輪數(shù)與解凍訓(xùn)練的總輪數(shù)相等)娃承,不過我們這么多次都沒有一次是跑到最后的奏夫,都在中間某一回合就early stopping了。

learning rate:初始學(xué)習(xí)率調(diào)大一些可以提高收斂速度历筝,且有利于跳出局部最優(yōu)解酗昼。

置信度:置信度關(guān)系到先驗(yàn)框的篩選,我們初始設(shè)置的置信度是0.5梳猪,后面改到0.7發(fā)現(xiàn)效果并沒有更好仔雷,后來嘗試改到0.6,發(fā)現(xiàn)這樣的效果還是比較好的舔示。對于置信度我們就調(diào)整了這兩次碟婆,之后就沒調(diào)整過。

patience:這是為了防止過擬合而設(shè)置的參數(shù)惕稻。當(dāng)發(fā)現(xiàn)有patience個(gè)世代的val_loss沒有下降竖共,則會認(rèn)為已經(jīng)達(dá)到過擬合點(diǎn),選擇提前結(jié)束本輪的訓(xùn)練俺祠,即達(dá)到early stopping公给。

clipnorm:我們設(shè)置的初始學(xué)習(xí)率為10^-4,clipnorm參數(shù)幫助進(jìn)行梯度裁剪借帘,防止梯度下降導(dǎo)致越過最低點(diǎn)
梯度范數(shù)縮放:就算loss的求導(dǎo)值很大,但為了保證穩(wěn)定淌铐,一次梯度下降不會過多肺然,我們設(shè)置一次的下降值不會超過clipnorm(0.001)

有一個(gè)問題一直沒有解決,就是為什么基于自己訓(xùn)練好的權(quán)重繼續(xù)訓(xùn)練效果并不好
我們在訓(xùn)練過程中經(jīng)常發(fā)生內(nèi)存溢出還有early stopping的情況腿准,并且常常覺得訓(xùn)練得并不夠际起,于是就想在目前最好的模型的基礎(chǔ)上進(jìn)行訓(xùn)練,但是效果往往并不好吐葱,常常loss反而增大了街望,而且不收斂了。取出其中的權(quán)值文件進(jìn)行測試弟跑,效果也不是很好灾前。這個(gè)問題一直沒有解決,因此我們這次試用的預(yù)訓(xùn)練權(quán)重都是提供的別的COCO數(shù)據(jù)集上的預(yù)訓(xùn)練權(quán)重孟辑。
訓(xùn)練階段補(bǔ)充對比:
我們后面其實(shí)還實(shí)現(xiàn)了EfficienDet-D4和YOLOv4兩個(gè)網(wǎng)絡(luò)哎甲,在訓(xùn)練的時(shí)候我們發(fā)現(xiàn)這三個(gè)網(wǎng)絡(luò)還是有很大差別的。
EfficienDet-D4收斂速度很快饲嗽,loss下降的速度非成崭快。(4位數(shù)-->2位數(shù)-->1位數(shù)-->小數(shù))喝噪,訓(xùn)練輪次少即可得到很高的精度,loss在0.0幾指么。
YOLOv4初始的loss非常大酝惧,且下降的速度明顯慢于EfficienDet-D4,在loss到50多的時(shí)候伯诬,變化很奇怪晚唇,下降特別慢,幾乎要收斂盗似,如下圖所示哩陕。因?yàn)閅OLOv4并沒有訓(xùn)練出很滿意的結(jié)果,所以最終也并沒有用其去驗(yàn)收赫舒。

YOLOv4訓(xùn)練過程Loss下降奇怪

而對于Retinanet悍及,算是適中,初始loss在10以內(nèi)接癌,第二輪就降到了3左右心赶,之后就平緩下降,等到early stopping的時(shí)候loss大概在0.8缺猛。

(2)過程記錄

我們調(diào)整的參數(shù)主要是上面提到的那幾個(gè)缨叫,但是因?yàn)槭艿絻?nèi)存溢出的影響椭符,所以batch size和epoch動得相對較少,其他的參數(shù)動得比較多耻姥,具體搭配已經(jīng)記不清了销钝,所以下面的記錄就只寫了batch size和epoch。
(1)最開始:使用CPU訓(xùn)練 Batch size = 2 epoch1=10 epoch2=40琐簇,訓(xùn)練速度太慢蒸健,只訓(xùn)練得到第13輪的模型,試了幾張圖片鸽嫂,發(fā)現(xiàn)檢測效果還是挺好的纵装。這個(gè)模型其實(shí)一直保留到最后,因?yàn)榇_實(shí)效果不錯(cuò)据某。
(2)開始使用GPU加速訓(xùn)練橡娄,但因?yàn)轱@存不夠總是訓(xùn)練到第十輪就終止了。(下一大輪是參數(shù)解凍癣籽,解凍后參數(shù)大大增加挽唉,GPU還是無法負(fù)荷) 我調(diào)整Batch size最大只能到6,這些只跑到第10輪的模型效果都很差筷狼。凍結(jié)了的參數(shù)還是需要參與訓(xùn)練才能到達(dá)一個(gè)比較好的地方瓶籽。
(3)學(xué)習(xí)使用Colab 在線用GPU進(jìn)行訓(xùn)練
第一次:Batch size=16 epoch1=20 epoch2=80 跑到第33輪出現(xiàn)early stopping ,ep33loss=0.886
該模型的檢測精度高埂材,但對砌體的檢測比較有問題塑顺。
程序設(shè)定有6個(gè)世代不下降的時(shí)候,會認(rèn)為到了early stopping的地步俏险,停止 訓(xùn)練
第二次:Batch size=10 epoch1=20 epoch2=60
這次的loss的降低速度比上次要快一些严拒,跑到第39輪出現(xiàn)early stopping, ep39loss=0.800竖独,其效果還是很好裤唠,比上一次的更加精確。但是對于砌體的檢測仍然比其他兩類的檢測效果要來的差(主要表現(xiàn)在把兩個(gè)視為一個(gè))

(3)最終模型選擇

我們最終根據(jù)loss以及val_loss的值莹痢,取出loss最小的种蘸,即2ep038-loss0.800-val_loss0.746.h5的權(quán)值文件。這是loss和驗(yàn)證集上val_loss綜合最優(yōu)的竞膳。后面驗(yàn)收的時(shí)候測出來AP值為61航瞭,但是error rate有三十多。還是參數(shù)調(diào)得不夠好坦辟。

二沧奴、擴(kuò)展

1、EfficienDet-D4的實(shí)現(xiàn)

我們在實(shí)現(xiàn)了Retinanet之后长窄,通過網(wǎng)上很多資料都知道它速度慢的毛病滔吠。比如其與YOLOv3的對比:YOLOv3的速度非掣倬快,比起YOLOv3疮绷,Retinanet大約需要3.8倍的時(shí)間來處理一張圖像候味。而且YOLOv3準(zhǔn)確度也不低网梢。有博主測試顯示YOLOv3處理一張圖片50多ms 巷懈,而Retinanet需要花費(fèi)的時(shí)間則為198ms别瞭。既然已經(jīng)入門完畢,那我們就再看看有沒有比較新的只冻,更高效的算法庇麦。后來我們看到了下面這張圖:發(fā)現(xiàn)EfficienDet的精度很高啊,而且從橫坐標(biāo)來看似乎處理速度也很快喜德。我們?nèi)タ戳讼嚓P(guān)的博客還有其論文筆記山橄,發(fā)現(xiàn)這就是一個(gè)追求高效的網(wǎng)絡(luò),這從它的命名EfficientDet其實(shí)就能看出來了舍悯。當(dāng)然我們也知道很多小組都在使用YOLOv5做,甚至使用的同一個(gè)代碼航棱,而YOLOv5算是最新提出的目標(biāo)檢測算法了吧,性能肯定很好萌衬。

這個(gè)時(shí)候的心理就有點(diǎn)奇怪了饮醇,就覺得,既然大家都用這個(gè)秕豫,那我們就不要跟他們用一樣的了..用一樣的網(wǎng)絡(luò)的話豈不是所有時(shí)間都花在調(diào)參上了朴艰,而調(diào)參又是一門“玄學(xué)”,這既不穩(wěn)妥也不符合我們的想法(我們想著是把時(shí)間用到入門檢測算法上 而不是把時(shí)間花在調(diào)參上)大概是有點(diǎn)想出奇制勝的想法混移,又覺得別人用什么就跟著用什么沒意思祠墅,所以我和趙同學(xué)便毅然決然地開始學(xué)習(xí)實(shí)現(xiàn)EfficienDet了。
綜合速度和精度還有參數(shù)數(shù)量沫屡,我們選擇的是EfficienDet-D4


EfficienDet在這張表里性能真的太突出了!

圖片.png

EfficienDet基本結(jié)構(gòu)

我覺得其實(shí)EfficienDet是Retinanet性能的升級版本撮珠。因?yàn)镋fficienDet的整體結(jié)構(gòu)就是以Retinanet為Baseline創(chuàng)建(特征提取+特征加強(qiáng)+預(yù)測部分)沮脖,但對特征提取和特征融合強(qiáng)化部分的進(jìn)行了優(yōu)化。


EfficienDet基本結(jié)構(gòu)

EfficienDet與Retinanet的結(jié)構(gòu)差別:
(1)EfficienDet以EfficientNet作為主干特征提取網(wǎng)絡(luò)
(深度芯急、寬度勺届、分辨率的最優(yōu)解,尋求一個(gè)最好的平衡娶耍,可以提取出最好的特征)
卷積神經(jīng)網(wǎng)絡(luò)精度提升的方法

  1. 網(wǎng)絡(luò)深度的增加免姿,典型的如resnet,就是通過殘差網(wǎng)絡(luò)的堆疊,增加網(wǎng)絡(luò)層數(shù)榕酒,以此來提升精度胚膊。
  2. 網(wǎng)絡(luò)寬度的增加故俐,通過增加每層網(wǎng)絡(luò)的特征層數(shù),提取更多的特征紊婉,以此來提升精度药版。
  3. 圖像分辨率的增加,分辨率越高的圖像喻犁,所能獲取的信息越多槽片,網(wǎng)絡(luò)能夠?qū)W習(xí)到更多的特征,從而提升精度肢础。

EfficientNet特點(diǎn)
【參考資料】https://blog.csdn.net/qq128252/article/details/110953858

歷史的實(shí)驗(yàn)經(jīng)驗(yàn)表明还栓,對于卷積神經(jīng)網(wǎng)絡(luò)的提升,著重點(diǎn)在于網(wǎng)絡(luò)深度传轰,網(wǎng)絡(luò)寬度剩盒,分辨率這三個(gè)維度。因此路召,efficientnet應(yīng)運(yùn)而生勃刨,efficientnet結(jié)合了這三個(gè)優(yōu)點(diǎn),很好的平衡深度股淡、寬度和分辨率這三個(gè)維度身隐,通過一組固定的縮放系數(shù)統(tǒng)一縮放這三個(gè)維度。舉個(gè)栗子唯灵,如果我們需要使用 2 N 2^N 2N的計(jì)算機(jī)資源贾铝,我們可以對網(wǎng)絡(luò)深度放大 α N α^N αN,對網(wǎng)絡(luò)寬度放大 β N β^N βN,對分辨率放大 γ N γ^N γN埠帕。

EfficientNet通過縮放平衡深度垢揩、寬度和分辨率這三個(gè)維度

如上圖,(a)是baseline基準(zhǔn)網(wǎng)絡(luò)敛瓷。(b)是網(wǎng)絡(luò)寬度的縮放叁巨。(c)是網(wǎng)絡(luò)深度的縮放。(d)是分辨率的縮放呐籽。(e)是平衡綜合網(wǎng)絡(luò)深度锋勺,網(wǎng)絡(luò)寬度,分辨率三個(gè)維度狡蝶。
其中庶橱,baseline基準(zhǔn)網(wǎng)絡(luò)是通過網(wǎng)絡(luò)結(jié)構(gòu)搜索得到,基于baseline基準(zhǔn)網(wǎng)絡(luò)進(jìn)行放大得到的一系列網(wǎng)絡(luò)就是EfficientNet網(wǎng)絡(luò)贪惹。

如下圖苏章,是EfficientNet和其他網(wǎng)絡(luò)的比較,我們可以看出EfficientNet相對于其他網(wǎng)絡(luò),有了質(zhì)的突破枫绅。



(2)中間特征加強(qiáng)是BiFPN layer(雙向特征金字塔)簡單來說就是先上采樣融合后下采樣融合泉孩,提取、融合特征撑瞧,可串聯(lián)好多這樣的子模塊棵譬,最后提取到具有高語義信息的特征。經(jīng)過特征強(qiáng)化輸出的每個(gè)特征層后也都連著回歸子網(wǎng)和分類子網(wǎng)预伺。class subnet和box subnet與Retinanet是相似的订咸。并且根據(jù)BiFPN layer單元模塊數(shù)的不同,可以分為B0酬诀,B1脏嚷,B2......B7,對應(yīng)的網(wǎng)絡(luò)就是EfficienDet-D0到EfficienDet-D7瞒御。

訓(xùn)練過程記錄:

(1)預(yù)訓(xùn)練權(quán)重是b4權(quán)重文件(即從EfficientNet的參數(shù)開始訓(xùn)練)父叙,使用全部數(shù)據(jù)集。
epoch1=50 epoch2=25 Batchsize = 4 跑到第47輪的時(shí)候出現(xiàn)early stopping直接進(jìn)入下一輪肴裙,進(jìn)入下一輪后一開始就發(fā)生內(nèi)存溢出了趾唱,取中間的某個(gè)模型進(jìn)行測試,效果還不錯(cuò)蜻懦。但是預(yù)測速度并沒有Retinanet快甜癞。
(2)使用d1預(yù)訓(xùn)練權(quán)重文件,使用全部數(shù)據(jù)集epoch1=25 epoch2=25 Batchsize = 4宛乃,仍然是跑到解凍參數(shù)部分就內(nèi)存溢出了悠咱。取第25輪的模型,效果還可以征炼,略遜于上一次提取的模型析既。

因?yàn)閮?nèi)存溢出好幾次,我們沒有什么更好的辦法谆奥,batch size也不宜再調(diào)醒刍怠(一批太少的話學(xué)不到什么新東西),所以決定把輸入訓(xùn)練的圖片減少酸些。
(3)預(yù)訓(xùn)練權(quán)重是b4權(quán)重文件 只用第二批數(shù)據(jù)集
epoch1=25 epoch2=25 Batchsize = 4
這次終于在25輪之后沒有內(nèi)存溢出宰译,跑到了38輪才early stopping 但預(yù)測效果還是沒有第一次的好(因?yàn)槲覀冊跍y試的時(shí)候選擇的圖片既有第一批數(shù)據(jù)集的也有第二批數(shù)據(jù)集的,而且還有自己拼接的兩批數(shù)據(jù)集的圖片合在一起的測試圖片)尤其對于鋼筋的數(shù)據(jù)來說擂仍,由于第二批的數(shù)據(jù)集和第一批的數(shù)據(jù)集還是有些差別囤屹,所以檢測效果會差別有點(diǎn)明顯熬甚。還是應(yīng)該使用全部數(shù)據(jù)集進(jìn)行訓(xùn)練逢渔。

我們檢查了GPU的分配情況,發(fā)現(xiàn)分配正常乡括,free GPU 將近15G 但是跑epoch1=2 epoch2=2 Batchsize=2的時(shí)候肃廓,輸入全部的圖智厌, 還是在第二輪解凍參數(shù)后內(nèi)存溢出。這是不太理解的地方盲赊,按理說16G的顯存應(yīng)該完全夠我們使用铣鹏。
后來有一次我們查找資料看到了一篇華科的碩士畢業(yè)論文,里面的batchsize也只設(shè)置為4哀蘑,說是圖片很多而且分辨率很高诚卸,所以數(shù)據(jù)量太大,只能設(shè)置為4绘迁。我去查看了一下我們的訓(xùn)練集的圖片合溺,有一些圖片的分辨率確實(shí)很高。但是內(nèi)存溢出這個(gè)問題確實(shí)大大限制了我們的調(diào)參訓(xùn)練缀台。
但是不知道為何棠赛,EfficienDet的檢測精度雖然很高,但是在速度上的優(yōu)勢并沒有看出來膛腐,還不如Retinanet的速度睛约。后面在網(wǎng)上查了一下,發(fā)現(xiàn)很多都是用Pytorch實(shí)現(xiàn)的EfficienDet哲身,檢測速度都很快辩涝。
后來我們開始思考,也查資料發(fā)現(xiàn)用Keras實(shí)現(xiàn)速度會比較慢律罢,如果用Pytorch或者TensorFlow就會運(yùn)行得快一些膀值,但是后面沒有時(shí)間再去從頭好好整了,所以就放棄了使用其他的深度學(xué)習(xí)框架來搭建了误辑。

2沧踏、YOLOv4的實(shí)現(xiàn)

其實(shí)這并沒有什么可以講的,無非是我們在發(fā)現(xiàn)EfficienDet的運(yùn)行速度不符合預(yù)期后巾钉,為了驗(yàn)收不至于太難看而加急實(shí)現(xiàn)的翘狱,連網(wǎng)絡(luò)的結(jié)構(gòu)還沒摸清就開始火急火燎按著步驟去訓(xùn)練了。當(dāng)然還是不太順利砰苍,之前在對比三個(gè)網(wǎng)絡(luò)的訓(xùn)練過程的時(shí)候就提到過它的loss在50左右的時(shí)候一直不太降得下來潦匈,后來時(shí)間不夠,我們也就沒繼續(xù)了赚导。

3茬缩、對于使用第一批提供的標(biāo)簽

還記得之前老師提示過要把注意力放在計(jì)數(shù)上面。其實(shí)目標(biāo)檢測的計(jì)數(shù)思路是實(shí)現(xiàn)是對篩選出的目標(biāo)框進(jìn)行計(jì)數(shù)吼旧,但標(biāo)簽里給了計(jì)數(shù)的數(shù)量凰锡。計(jì)數(shù)這一信息應(yīng)該怎么訓(xùn)練?或者說這個(gè)信息要怎么利用呢?
之后有一天我有了這樣的想法:計(jì)數(shù)其實(shí)可以作為損失評估的一部分掂为。損失函數(shù)或許可以改改裕膀?但是又覺得這改動太大了實(shí)在不確定。我覺得通過訓(xùn)練時(shí)給出目標(biāo)物體的數(shù)量可以反過來把精度較低的框去掉勇哗,這樣就能進(jìn)一步保證計(jì)數(shù)的精度昼扛。相當(dāng)于多一個(gè)維度作為反饋部分,應(yīng)該是有利于計(jì)數(shù)精度的提升的欲诺。但最終我們并沒有去嘗試抄谐,一個(gè)是時(shí)間問題,還有一個(gè)是不太敢去動損失函數(shù)(畢竟Focal Loss相比原來稍微加了個(gè)系數(shù)項(xiàng)效果就差這么多了)

4扰法、一點(diǎn)想法

目標(biāo)檢測算法的改進(jìn)有時(shí)候就是一個(gè)小創(chuàng)新點(diǎn)(一個(gè)小trick)但是能起到很好的改進(jìn)斯稳。一個(gè)網(wǎng)絡(luò)性能好不好,感覺有點(diǎn)‘細(xì)節(jié)決定成敗’的意味迹恐,從focal loss這個(gè)損失函數(shù)的小改進(jìn)就能感受到挣惰。

5、之后想做的

想看看獲獎(jiǎng)同學(xué)的YOLOv5的代碼殴边,對于調(diào)參向他們?nèi)∪〗?jīng)憎茂,畢竟YOLOv5的性能是真的好。雖然自己不喜歡調(diào)參锤岸,因?yàn)椴淮_定性太高竖幔,似乎很不可控,而且硬件資源又捉襟見肘是偷。不過調(diào)參還是相當(dāng)重要的一部分拳氢,還是得面對。感受就不多說了蛋铆,很多都在博客里面啰嗦過了馋评。只能說有點(diǎn)后悔又覺得沒什么好后悔的吧,一方面覺得如果自己一開始也去用YOLOv5刺啦,測試結(jié)果肯定比現(xiàn)在好留特,課題的分?jǐn)?shù)肯定會比現(xiàn)在高;一方面又覺得我們做的工作可能比其他組的都要多(少了一些調(diào)參的時(shí)間玛瘸,我和小趙跑去看代碼蜕青,從這個(gè)函數(shù)調(diào)到另一個(gè)文件的某個(gè)函數(shù),大致疏通整個(gè)流程)糊渊,我覺得可能真實(shí)收獲還更多右核,不知道算不算在自我安慰,不過這三篇博客寫下來我覺得還是成就感滿滿的渺绒!

最后這一篇因?yàn)橐sdeadline所以寫得很匆忙贺喝,要是哪里有不對的磷瘤,希望大家可以指出,一起交流啊~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搜变,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子针炉,更是在濱河造成了極大的恐慌挠他,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篡帕,死亡現(xiàn)場離奇詭異殖侵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)镰烧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門拢军,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怔鳖,你說我怎么就攤上這事茉唉。” “怎么了结执?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵度陆,是天一觀的道長。 經(jīng)常有香客問我献幔,道長懂傀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任蜡感,我火速辦了婚禮蹬蚁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘郑兴。我一直安慰自己犀斋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布情连。 她就那樣靜靜地躺著闪水,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒙具。 梳的紋絲不亂的頭發(fā)上球榆,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音禁筏,去河邊找鬼持钉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛篱昔,可吹牛的內(nèi)容都是我干的每强。 我是一名探鬼主播始腾,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼空执!你這毒婦竟也來了浪箭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤辨绊,失蹤者是張志新(化名)和其女友劉穎奶栖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體门坷,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宣鄙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了默蚌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冻晤。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖绸吸,靈堂內(nèi)的尸體忽然破棺而出鼻弧,到底是詐尸還是另有隱情,我是刑警寧澤锦茁,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布温数,位于F島的核電站,受9級特大地震影響蜻势,放射性物質(zhì)發(fā)生泄漏撑刺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一握玛、第九天 我趴在偏房一處隱蔽的房頂上張望够傍。 院中可真熱鬧,春花似錦挠铲、人聲如沸冕屯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽安聘。三九已至,卻和暖如春瓢棒,著一層夾襖步出監(jiān)牢的瞬間浴韭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工脯宿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留念颈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓连霉,卻偏偏與公主長得像榴芳,于是被迫代替她去往敵國和親嗡靡。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容