Caffe官方教程翻译(7):Fine-tuning for Style Recognition
前言
最近打算重新跟著官方教程學習一下caffe,順便也自己翻譯了一下官方的文檔。自己也做了一些標注,都用斜體標記出來了。中間可能額外還加了自己遇到的問題或是運行結果之類的。歡迎交流指正,拒絕噴子!
官方教程的原文鏈接:http://nbviewer.jupyter.org/github/BVLC/caffe/blob/master/examples/02-fine-tuning.ipynb
Fine-tuning a Pretrained Network for Style Recognition
在這個例子中,我們會一起探索一種在現實世界的應用中比較常用的方法:使用一個預訓練模型,并使用用戶自定義的數據集來微調網絡的參數。
這個方法的優點就是,既然預訓練的網絡已經事先使用很大的數據集訓練好了,那么網絡的中間層可以獲取大多數視覺上的語義(“semantics”)信息。有關這里說到的“語義”(“semantics”)這個詞,我們把它當一個黑盒子來看,就將它想象成一個非常強大且通用的視覺特征就行。更重要的是,我們只需要一個相對較小的數據集就足夠在目標任務上取得不錯的結果。
首先,我們需要準備好數據集。包括以下幾個步驟:(1)通過提供的shell腳本來獲取ImageNet ilsvrc的預訓練模型。(2)從整個Flickr style數據集中下載一個子數據集。(3)編譯下載好的Flickr數據集成Caffe可以使用的格式。
1.準備數據集
下載此例子需要的數據集。
- get_ilsvrc_aux.sh:下載ImageNet數據集的均值文件,標簽文件等。
- download_model_binary.py:下載預訓練好的模型。
- finetune_flickr_style/assemble_data.py:下載用于圖像風格檢測的訓練和測試的數據集,后面就簡稱style數據集。
我們在下面的練習中,會從整個數據集中下載一個較小的子數據集:8萬張圖片中只下載2000張,從20個風格類別中只下載5種類別。(如果想要下載完整數據集,修改下面對應的代碼成full_dataset=True即可。)
# Download just a small subset of the data for this exercise. # (2000 of 80K images, 5 of 20 labels.) # To download the entire dataset, set `full_dataset = True`. full_dataset = False if full_dataset:NUM_STYLE_IMAGES = NUM_STYLE_LABELS = -1 else:NUM_STYLE_IMAGES = 2000NUM_STYLE_LABELS = 5# This downloads the ilsvrc auxiliary data (mean file, etc), # and a subset of 2000 images for the style recognition task. # 備注:下面這段代碼我運行時將其注釋了,因為我事先在命令行下運行過腳本,事先下載好了要用到的幾個文件 ''' import os os.chdir(caffe_root) # run scripts from caffe root !data/ilsvrc12/get_ilsvrc_aux.sh !scripts/download_model_binary.py models/bvlc_reference_caffenet !python examples/finetune_flickr_style/assemble_data.py \--workers=-1 --seed=1701 \--images=$NUM_STYLE_IMAGES --label=$NUM_STYLE_LABELS # back to examples os.chdir('examples') ''' "\nimport os\nos.chdir(caffe_root) # run scripts from caffe root\n!data/ilsvrc12/get_ilsvrc_aux.sh\n!scripts/download_model_binary.py models/bvlc_reference_caffenet\n!python examples/finetune_flickr_style/assemble_data.py --workers=-1 --seed=1701 --images=$NUM_STYLE_IMAGES --label=$NUM_STYLE_LABELS\n# back to examples\nos.chdir('examples')\n"定義weights,路徑指向我之前下載的使用ImageNet數據集預訓練好的權重,請確保這個文件要存在。
import os weights = os.path.join(caffe_root, 'models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel') assert os.path.exists(weights)從ilsvrc12/synset_words.txt導入1000個ImageNet的標簽,并從finetune_flickr_style/style_names.txt導入5個style數據集的標簽。
# Load ImageNet labels to imagenet_labels imagenet_label_file = caffe_root + 'data/ilsvrc12/synset_words.txt' imagenet_labels = list(np.loadtxt(imagenet_label_file, str, delimiter='\t')) assert len(imagenet_labels) == 1000 print 'Loaded ImageNet labels:\n', '\n'.join(imagenet_labels[:10] + ['...'])# Load style labels to style_labels style_label_file = caffe_root + 'examples/finetune_flickr_style/style_names.txt' style_labels = list(np.loadtxt(style_label_file, str, delimiter='\n')) if NUM_STYLE_LABELS > 0:style_labels = style_labels[:NUM_STYLE_LABELS] print '\nLoaded style labels:\n', ', '.join(style_labels) Loaded ImageNet labels: n01440764 tench, Tinca tinca n01443537 goldfish, Carassius auratus n01484850 great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias n01491361 tiger shark, Galeocerdo cuvieri n01494475 hammerhead, hammerhead shark n01496331 electric ray, crampfish, numbfish, torpedo n01498041 stingray n01514668 cock n01514859 hen n01518878 ostrich, Struthio camelus ...Loaded style labels: Detailed, Pastel, Melancholy, Noir, HDR2.定義網絡并運行
我們一開始先定義caffenet()函數,用來初始化CaffeNet結構(AlexNet的一個小的變種網絡)。該函數使用參數來指定數據和輸出類別的數量。
from caffe import layers as L from caffe import params as Pweight_param = dict(lr_mult=1, decay_mult=1) bias_param = dict(lr_mult=2, decay_mult=0) learned_param = [weight_param, bias_param]frozen_param = [dict(lr_mult=0)] * 2# 卷積層加ReLU單元 def conv_relu(bottom, ks, nout, stride=1, pad=0, group=1,param=learned_param,weight_filler=dict(type='gaussian', std=0.01),bias_filler=dict(type='constant', value=0.1)):conv = L.Convolution(bottom, kernel_size=ks, stride=stride,num_output=nout, pad=pad, group=group,param=param, weight_filler=weight_filler,bias_filler=bias_filler)return conv, L.ReLU(conv, in_place=True)# 全連接層加ReLU單元 def fc_relu(bottom, nout, param=learned_param,weight_filler=dict(type='gaussian', std=0.005),bias_filler=dict(type='constant', value=0.1)):fc = L.InnerProduct(bottom, num_output=nout, param=param,weight_filler=weight_filler,bias_filler=bias_filler)return fc, L.ReLU(fc, in_place=True)# 最大池化 def max_pool(bottom, ks, stride=1):return L.Pooling(bottom, pool=P.Pooling.MAX, kernel_size=ks, stride=stride)# caffenet網絡 def caffenet(data, label=None, train=True, num_classes=1000,classifier_name='fc8', learn_all=False):"""Returns a NetSpec specifying CaffeNet, following the original proto textspecification (./models/bvlc_reference_caffenet/train_val.prototxt)."""n = caffe.NetSpec()# 按照套路來,一層一層接下去n.data = dataparam = learned_param if learn_all else frozen_paramn.conv1, n.relu1 = conv_relu(n.data, 11, 96, stride=4, param=param)n.pool1 = max_pool(n.relu1, 3, stride=2)n.norm1 = L.LRN(n.pool1, local_size=5, alpha=1e-4, beta=0.75)n.conv2, n.relu2 = conv_relu(n.norm1, 5, 256, pad=2, group=2, param=param)n.pool2 = max_pool(n.relu2, 3, stride=2)n.norm2 = L.LRN(n.pool2, local_size=5, alpha=1e-4, beta=0.75)n.conv3, n.relu3 = conv_relu(n.norm2, 3, 384, pad=1, param=param)n.conv4, n.relu4 = conv_relu(n.relu3, 3, 384, pad=1, group=2, param=param)n.conv5, n.relu5 = conv_relu(n.relu4, 3, 256, pad=1, group=2, param=param)n.pool5 = max_pool(n.relu5, 3, stride=2)n.fc6, n.relu6 = fc_relu(n.pool5, 4096, param=param)# 訓練集還要加上一個Dropout,測試集就不需要;加上Dropout,以防止過擬合if train:n.drop6 = fc7input = L.Dropout(n.relu6, in_place=True)else:fc7input = n.relu6n.fc7, n.relu7 = fc_relu(fc7input, 4096, param=param)# 訓練集還要加上一個Dropout,測試集就不需要;加上Dropout,以防止過擬合if train:n.drop7 = fc8input = L.Dropout(n.relu7, in_place=True)else:fc8input = n.relu7# always learn fc8 (param=learned_param)fc8 = L.InnerProduct(fc8input, num_output=num_classes, param=learned_param)# give fc8 the name specified by argument `classifier_name`n.__setattr__(classifier_name, fc8)# 如果不是訓練模式,即測試模式,fc8接上一個softmax,輸出置信率if not train:n.probs = L.Softmax(fc8)# 如果給了label,建立loss和acc層,loss為損失函數,acc計算準確率if label is not None:n.label = labeln.loss = L.SoftmaxWithLoss(fc8, n.label)n.acc = L.Accuracy(fc8, n.label)# write the net to a temporary file and return its filenamewith tempfile.NamedTemporaryFile(delete=False) as f:f.write(str(n.to_proto()))return f.name現在,我們來建立一個CaffeNet,輸入為沒打標簽的”dummy data”。這樣子,我們可以從外部設置它的輸入數據,也能看看它預測的ImageNet類別是哪個。
dummy_data = L.DummyData(shape=dict(dim=[1, 3, 227, 227])) imagenet_net_filename = caffenet(data=dummy_data, train=False) imagenet_net = caffe.Net(imagenet_net_filename, weights, caffe.TEST)定義一個style_net函數,調用前面的caffenet函數,輸入的數據為Flickr style數據集。
這個新的網絡也有CaffeNet的結構,但是區別在輸入和輸出上:
- 輸入是我們下載好的Flickr style數據集,使用ImageData層將其讀入
- 輸出是一個超過20個類的分布,而不是原始的ImageNet類別的1000個類
- 分類層由fc8被重命名或fc8_flickr,以告訴Caffe不要從ImageNet預訓練模型中導入原始的fc8層
使用style_net函數來初始化untrained_style_net,結構也是CaffeNet,但是輸入圖像是來自style數據集,權重是來自ImageNet預訓練模型。
在untrained_style_net調用forward函數來從style數據集獲取一個batch。
從一個batch的50張圖像中選取一整圖像輸入style net(前面使用style_net()函數定義的網絡,后面都簡稱style net)。這里我們任意選一張圖片,就選取一個batch中的第8張圖片。將圖片顯示出來,然后跑一邊ImageNet預訓練模型imagenet_net,接著顯示從1000個ImageNet類中預測的前5個結果。
下面,我們選了一張圖片,這張圖片中是有關海灘的,由于”sandbar”和”seashore”都是ImageNet-1000中的類別,所以網絡在預測這張圖片時預測結果還算合理。然而對于其他圖片,預測結果就不怎么好了,有時由于網絡沒能檢測到圖片中的物體,也可能不是所有圖片都包含ImageNet的100個類別中的物體。修改batch_index變量,它的默認值是8,也可改成0-49中的任意數值(因為一個batch就只有50個樣本),來看看預測結果。(如果不想使用這個batch的50張圖像,可以運行上面的cell重新導入一個新的batch到style_net)
因為這兩個模型在conv1到fc7層之間使用的是相同的預訓練權重,所以我們也可以在分類層變成與ImageNet預訓練模型一樣之前,驗證在fc7上的激勵函數輸出。
diff = untrained_style_net.blobs['fc7'].data[0] - imagenet_net.blobs['fc7'].data[0] error = (diff ** 2).sum() assert error < 1e-8刪除untrained_style_net以節約內存。imagenet_net先放一放,后面還會用到。
del untrained_style_net3.訓練風格分類器
現在我們要創建一個函數solver來創建caffe的solver,我們可以用這個solver來訓練網絡。在這個函數中,我們將為各種用于訓練網絡、顯示、”snapshotting”的各種參數設置初值。請參考注釋理解各個參數的作用。你也可以試著自己修改一些參數,看看能不能取得更好的效果!
from caffe.proto import caffe_pb2def solver(train_net_path, test_net_path=None, base_lr=0.001):s = caffe_pb2.SolverParameter()# Specify locations of the train and (maybe) test networks.s.train_net = train_net_path# 設置了與測試相關的參數:test_interval:每訓練1000次測試一次;test_iter:測試中,每次迭代送入100個batch;if test_net_path is not None:s.test_net.append(test_net_path)s.test_interval = 1000 # Test after every 1000 training iterations.s.test_iter.append(100) # Test on 100 batches each time we test.# The number of iterations over which to average the gradient.# Effectively boosts the training batch size by the given factor, without# affecting memory utilization.s.iter_size = 1# 最大迭代次數s.max_iter = 100000 # # of times to update the net (training iterations)# Solve using the stochastic gradient descent (SGD) algorithm.# Other choices include 'Adam' and 'RMSProp'.s.type = 'SGD' # 迭代使用的優化算法:SGD——隨機梯度下降法,也可以試試其他的算法比如:Adam、RMSProp# Set the initial learning rate for SGD.s.base_lr = base_lr # SGD的初始學習率# Set `lr_policy` to define how the learning rate changes during training.# Here, we 'step' the learning rate by multiplying it by a factor `gamma`# every `stepsize` iterations.s.lr_policy = 'step's.gamma = 0.1s.stepsize = 20000# Set other SGD hyperparameters. Setting a non-zero `momentum` takes a# weighted average of the current gradient and previous gradients to make# learning more stable. L2 weight decay regularizes learning, to help prevent# the model from overfitting.s.momentum = 0.9s.weight_decay = 5e-4# Display the current training loss and accuracy every 1000 iterations.s.display = 1000 # 每迭代1000次,會在終端打印信息,包括訓練的loss值和準確率# Snapshots are files used to store networks we've trained. Here, we'll# snapshot every 10K iterations -- ten times during training.s.snapshot = 10000 # 每過10000次迭代,保存一次當前網絡s.snapshot_prefix = caffe_root + 'models/finetune_flickr_style/finetune_flickr_style' # 保存網絡的路徑# Train on the GPU. Using the CPU to train large networks is very slow. # s.solver_mode = caffe_pb2.SolverParameter.GPUs.solver_mode = caffe_pb2.SolverParameter.CPU # 原本這里應該是GPU模式,我在筆記本上跑,所以換成了CPU模式# Write the solver to a temporary file and return its filename.# 寫入臨時文件with tempfile.NamedTemporaryFile(delete=False) as f:f.write(str(s))return f.name現在我們要調用上面定義好的solver來訓練style網絡的分類層。
不過,如果想要在命令行下調用solver來訓練網絡,也是可以的。指令如下:
build/tools/caffe train \ -solver models/finetune_flickr_style/solver.prototxt \ -weights models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel \ -gpu 0
補充:如果不適用gpu模式不加-gpu 0就可以了。
但是我們在這個例子中使用python來訓練網絡。
首先,定義一個run_solvers函數,這個函數會在循環中獲取solvers列表的每個元素,并一步一步迭代訓練網絡,同時記錄每次迭代的損失值和準確率。最后會將訓練好的權重保存到一個文件中。
接下來,運行創建好的solver來訓練那個用于風格識別的網絡。我們接下來會創建兩個網絡——一個(style_solver)的使用ImageNet預訓練網絡的權重初始化,另一個(scratch_style_solver)使用隨機初始化的權重。
在訓練過程中,我們會看到使用ImageNet預訓練好的權重的網絡,相比隨機初始化權重的網絡訓練得更快,準確率也更高。
對比兩個網絡訓練的loss和acc看看。可以看出使用ImageNet預訓練網絡的loss的下降速度非常快,而隨機初始化權重的網絡的訓練速度則慢很多。
plot(np.vstack([train_loss, scratch_train_loss]).T) xlabel('Iteration #') ylabel('Loss') Text(0,0.5,u'Loss') plot(np.vstack([train_acc, scratch_train_acc]).T) xlabel('Iteration #') ylabel('Accuracy') Text(0,0.5,u'Accuracy')再來看看迭代200次后的測試準確率。要預測的只有5個類,那么平均下來的準確率應該是20%左右。我們肯定期望,兩個網絡取得的結果都超過隨機狀況下的20%的準確率,另外,我們還期望使用ImageNet預訓練網絡的結果遠比隨機初始化網絡的結果好。讓我們拭目以待吧!
def eval_style_net(weights, test_iters=10):test_net = caffe.Net(style_net(train=False), weights, caffe.TEST)accuracy = 0for it in xrange(test_iters):accuracy += test_net.forward()['acc']accuracy /= test_itersreturn test_net, accuracy test_net, accuracy = eval_style_net(style_weights) print 'Accuracy, trained from ImageNet initialization: %3.1f%%' % (100*accuracy, ) scratch_test_net, scratch_accuracy = eval_style_net(scratch_style_weights) print 'Accuracy, trained from random initialization: %3.1f%%' % (100*scratch_accuracy, ) Accuracy, trained from ImageNet initialization: 51.4% Accuracy, trained from random initialization: 23.6%端到端微調風格網絡
最后,我們再次訓練前面的兩個網絡,就從剛才訓練學習到的參數開始繼續訓練。這次唯一的區別是我們會以“端到端”的方式訓練參數,即訓練網絡中所有的層,從起始的conv1層直接送入圖像。我們將learn_all=True傳遞給前面定義的style_net函數,如此一來,在網絡中會給所有的參數都乘上一個非0的lr_mult參數。在默認情況下,是learn_all=False,所有預訓練層(conv1到fc7)的參數都被凍結了(lr_mult=0),我們訓練的只有分類層fc8_flickr。
請注意,這兩個網絡開始訓練時的準確率大致相當于之前訓練結束時的準確率。為了更科學一些,我們還要使用與之相同的訓練步驟,但結構不是端到端的,來確認我們的結果并不是因為訓練了兩倍的時間長度才取得更好的結果的。
讓我們現在測試一下端到端微調模型。由于網絡中所有的層都參與到了訓練當中,所以我們期望這次的結果會比之前只讓分類層參與到訓練中的網絡取得更好的效果。
test_net, accuracy = eval_style_net(style_weights_ft) print 'Accuracy, finetuned from ImageNet initialization: %3.1f%%' % (100*accuracy, ) scratch_test_net, scratch_accuracy = eval_style_net(scratch_style_weights_ft) print 'Accuracy, finetuned from random initialization: %3.1f%%' % (100*scratch_accuracy, ) Accuracy, finetuned from ImageNet initialization: 54.4% Accuracy, finetuned from random initialization: 40.2%先看看輸入的圖片,和它在端到端模型中的預測結果。
plt.imshow(deprocess_net_image(image)) disp_style_preds(test_net, image) top 5 predicted style labels =(1) 87.82% Melancholy(2) 6.10% Pastel(3) 5.66% HDR(4) 0.41% Detailed(5) 0.01% Noir喔!預測結果相比之前好了不少。但是請注意,這個圖片是來自數據集的,所以網絡在訓練時就看過它的標簽了。
接下來,我們從測試集中取出一張圖片,看看端到端模型的預測結果如何。
我們也可以看看這張圖片在scratch網絡中的預測結果。它也輸出了正確的結果,盡管置信率較另一個(使用預訓練權重的網絡)更低。
disp_style_preds(scratch_test_net, image) top 5 predicted style labels =(1) 46.02% Pastel(2) 23.50% Melancholy(3) 16.43% Detailed(4) 11.64% HDR(5) 2.40% Noir當然我們還可以看看在ImageNet模型上的預測結果:
disp_imagenet_preds(imagenet_net, image) top 5 predicted ImageNet labels =(1) 34.90% n07579787 plate(2) 21.63% n04263257 soup bowl(3) 17.75% n07875152 potpie(4) 5.72% n07711569 mashed potato(5) 5.27% n07584110 consomme總結
以上是生活随笔為你收集整理的Caffe官方教程翻译(7):Fine-tuning for Style Recognition的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Caffe官方教程翻译(6):Learn
- 下一篇: Caffe官方教程翻译(8):Brewi