FCN代碼解析


http://blog.csdn.net/zzzzzzz0407/article/details/69831388

 

網上有許多FCN網絡的安裝和訓練教程,但卻沒有代碼解讀的詳細教程,這讓我這種剛剛入門深度學習的萌新不知所措;為了弄清楚FCN,不知走了多少彎路,想把它記錄下來,給自己看看,也希望能幫助到那些和我一樣剛剛入門的人。 
以下只是小弟的一些拙見,若有錯誤與不足歡迎指出~ 
話不多說,上代碼:


首先是solve.py文件,fcn沒有用sh腳本去寫訓練文件,應該是和他自己寫的surgery.py和score.py有關;里面有兩個問題我希望有大牛可以幫忙解決: 
1. for _ in range(50): 
solver.step(2000) 
發現設置完這個以后solver.prototxt中設置的 max_iter失效; 
2. score.seg_tests(solver, False, test, layer=’score_sem’, gt=’sem’) 
score.seg_tests(solver, False, test, layer=’score_geo’, gt=’geo’) 
語義分割和幾何分割的解讀

#solve.py import caffe import surgery, score import numpy as np import os #os模塊封裝了操作系統的目錄和文件操作 import sys try: import setproctitle setproctitle.setproctitle(os.path.basename(os.getcwd()#獲得當前路徑)#返回最后的文件名) #比如os.getcwd()獲得的當前路徑為/home/zhangrf/fcn,則os.path.basename()為fcn; #setproctitle是用來修改進程入口名稱,如C++中入口為main()函數 except: pass weights = '../ilsvrc-nets/vgg16-fcn.caffemodel' #用來fine-tune的FCN參數 vgg_weights = '../ilsvrc-nets/vgg16-fcn.caffemodel' #訓練好的VGGNet參數 vgg_proto = '../ilsvrc-nets/VGG_ILSVRC_16_layers_deploy.prototxt' #VGGNet模型 # init #caffe.set_device(int(sys.argv[1])) #獲取命令行參數,其中sys.argv[0]為文件名,argv[1]為緊隨其后的那個參數 caffe.set_device(0) #GPU型號 caffe.set_mode_gpu() #用GPU模式運行 #solver.net.copy_from(weights) solver = caffe.SGDSolver('solver.prototxt') #調用SGD(隨即梯度下降)Solver方法,solver.prototxt為所需參數 vgg_net = caffe.Net(vgg_proto,vgg_weights,caffe.TRAIN) #vgg_net是原來的VGGNet模型(包括訓練好的參數) surgery.transplant(solver.net,vgg_net) #FCN模型(參數)與原來的VGGNet模型之間的轉化 del vgg_net #刪除VGGNet模型 # surgeries interp_layers = [k for k in solver.net.params.keys() if 'up' in k] #interp_layers為upscore層 surgery.interp(solver.net, interp_layers) #將upscore層中每層的權重初始化為雙線性內核插值。 # scoring test = np.loadtxt('../data/sift-flow/test.txt', dtype=str) #載入測試圖片信息 for _ in range(50): solver.step(2000) #每2000次訓練迭代執行后面的函數 # N.B. metrics on the semantic labels are off b.c. of missing classes; # score manually from the histogram instead for proper evaluation score.seg_tests(solver, False, test, layer='score_sem', gt='sem') #測試圖片語義特征 score.seg_tests(solver, False, test, layer='score_geo', gt='geo') #測試圖片幾何特征 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

surgery.py是精髓,全連接層參數到全卷積層的參數轉化是靠它完成的,每一步都很巧妙,如沐春風~

#surgery.py from __future__ import division #導入python未來支持的語言特征division(精確除法) import caffe #導入caffe import numpy as np #導入模塊numpy並以np作為別名 def transplant(new_net#FCN, net#VGGNet, suffix=''): #用於將VGGNet的參數轉化給FCN(包括全連接層的參數) """ Transfer weights by copying matching parameters, coercing parameters of incompatible shape, and dropping unmatched parameters. 通過復制匹配的參數,強制轉換不兼容形狀的參數和丟棄不匹配的參數來達到傳輸(轉化)權重的目的; The coercion is useful to convert fully connected layers to their equivalent convolutional layers, since the weights are the same and only the shapes are different. 因為權重的個數是一樣的僅僅是Blob的形狀不一樣,所以強制轉換對於將全連接層轉換為等效的卷積層是有用的; In particular, equivalent fully connected and convolution layers have shapes O x I and O x I x H x W respectively for O outputs channels, I input channels, H kernel height, and W kernel width. 參數數量為O*I*H*W Both `net` to `new_net` arguments must be instantiated `caffe.Net`s. 參數一對一 """ for p in net.params: #net.params是字典形式,存放了所有的key-value,p為key p_new = p + suffix #將p賦給p_new if p_new not in new_net.params: #用來丟棄fc8(因為FCN中沒有fc8) print 'dropping', p continue for i in range(len(net.params[p])): if i > (len(new_net.params[p_new]) - 1): #感覺沒啥用? print 'dropping', p, i break if net.params[p][i].data.shape!= new_net.params[p_new][i].data.shape: #Blob不一樣轉換(這邊就是全連接層和卷積層的轉換,很精髓!!!) print 'coercing', p, i, 'from', net.params[p][i].data.shape, 'to', new_net.params[p_new][i].data.shape else: #形狀一樣則直接copy print 'copying', p, ' -> ', p_new, i new_net.params[p_new][i].data.flat = net.params[p][i].data.flat #將參數按順序賦值(flat函數只要保證參數個數相同,不用保證數組形狀完全一樣) def upsample_filt(size): """ Make a 2D bilinear kernel suitable for upsampling of the given (h, w) size. 上采樣卷積核的制作 """ factor = (size + 1) // 2 if size % 2 == 1: center = factor - 1 else: center = factor - 0.5 og = np.ogrid[:size, :size] #生成一列向量和一行向量 return (1 - abs(og[0] - center) / factor) * \ #(64*1)的列向量和(1*64)行向量相乘則得到一個64*64的數組 (1 - abs(og[1] - center) / factor) def interp(net, layers): """ Set weights of each layer in layers to bilinear kernels for interpolation. 將每一層的權重設置為雙線性內核插值。 """ for l in layers: m, k, h, w = net.params[l][0].data.shape if m != k and k != 1: print 'input + output channels need to be the same or |output| == 1' raise if h != w: print 'filters need to be square' raise filt = upsample_filt(h) #初始化卷積核的參數(64*64*1) net.params[l][0].data[range(m), range(k), :, :] = filt #這邊很關鍵!!!只有對於對應層的那層filter有參數,其余都為0,而且有filter參數的那層還都是相等的~ #因為前一層已經是個分類器了,對分類器進行特征組合沒有任何意義!所以這一層的上采樣效果上而言只是對應的上采樣(屬於猴子還是屬於猴子) def expand_score(new_net, new_layer, net, layer):#這個函數干啥用的沒看懂- -貌似solve.py里沒有這個函數的調用 """ Transplant an old score layer's parameters, with k < k' classes, into a new score layer with k classes s.t. the first k' are the old classes. """ old_cl = net.params[layer][0].num new_net.params[new_layer][0].data[:old_cl][...] = net.params[layer][0].data new_net.params[new_layer][1].data[0,0,0,:old_cl][...] = net.params[layer][1].data
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

score.py是評估文件,fcn不用caffe框架自帶的test測試,而是自己寫了個評估文件,里面有許多評估指標。不過小弟才疏學淺,對於很多評估指標的概念完全沒接觸過,比方說IU的概念,overall accuracy和mean accuracy的區別,希望有大牛可以科普,這部分由於對概念的不知以及數據量的巨大,沒有很好的解讀~實在有愧

#score.py from __future__ import division import caffe import numpy as np import os import sys from datetime import datetime from PIL import Image def fast_hist(a, b, n): k = (a >= 0) & (a < n) return np.bincount(n * a[k].astype(int) + b[k], minlength=n**2).reshape(n, n) def compute_hist(net, save_dir#False, dataset, layer='score', gt='label'): n_cl = net.blobs[layer].channels #3通道的圖 if save_dir: os.mkdir(save_dir) hist = np.zeros((n_cl, n_cl)) #創建一個二維數組hist[3][3],元素都為0 loss = 0 for idx in dataset: net.forward() hist += fast_hist(net.blobs[gt].data[0, 0].flatten() #將數據拉為1列, net.blobs[layer].data[0].argmax(0).flatten(), n_cl) if save_dir: #是否需要保存圖片 im = Image.fromarray(net.blobs[layer].data[0].argmax(0).astype(np.uint8), mode='P') im.save(os.path.join(save_dir, idx + '.png')) # compute the loss as well loss += net.blobs['loss'].data.flat[0] return hist, loss / len(dataset) def seg_tests(solver#配置文件, save_format#False, dataset#test文件, layer='score'#實驗輸出, gt='label'#真實輸出): print '>>>', datetime.now(), 'Begin seg tests' solver.test_nets[0].share_with(solver.net) #將solver.net復制給solver.test_net[0] do_seg_tests(solver.test_nets[0], solver.iter, save_format, dataset, layer, gt) def do_seg_tests(net, iter#累計迭代次數, save_format, dataset, layer='score', gt='label'): n_cl = net.blobs[layer].channels if save_format: save_format = save_format.format(iter) #format函數用來格式化數據;如果save_format為TRUE,則為1 hist, loss = compute_hist(net, save_format, dataset, layer, gt) # mean loss print '>>>', datetime.now(), 'Iteration', iter, 'loss', loss #平均誤差 # overall accuracy acc = np.diag(hist).sum() / hist.sum() print '>>>', datetime.now(), 'Iteration', iter, 'overall accuracy', acc # per-class accuracy acc = np.diag(hist) / hist.sum(1) print '>>>', datetime.now(), 'Iteration', iter, 'mean accuracy', np.nanmean(acc) # per-class IU iu = np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist)) print '>>>', datetime.now(), 'Iteration', iter, 'mean IU', np.nanmean(iu) freq = hist.sum(1) / hist.sum() print '>>>', datetime.now(), 'Iteration', iter, 'fwavacc', \ (freq[freq > 0] * iu[freq > 0]).sum() return hist
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

以上是我對fcn代碼的一點拙見,這次經歷也讓我明白,未接觸過的代碼並不可怕,只要沉下心來,一條一條的進行調試,你也可以知其所以然,發現其中奧秘。在研究生生涯開始之際寫下我的第一篇博客,希望自己能在今后的學習生涯中保持這份初心,送給自己,也送給看過這篇博客的有緣人。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM