随机梯度下降的实现细节
http://www.miaoerduo.com/deep-learning/%E5%9F%BA%E4%BA%8Ecaffe%E7%9A%84deepid2%E5%AE%9E%E7%8E%B0%EF%BC%88%E4%B8%8A%EF%BC%89.html
最近看了一篇文章,詳細說明了隨機梯度下降中隨機是在create_imagenet.sh中shuffle實現的。
相關資源:
- DeepID:http://mmlab.ie.cuhk.edu.hk/pdf/YiSun_CVPR14.pdf
- DeepID2:http://papers.nips.cc/paper/5416-analog-memories-in-a-balanced-rate-based-network-of-e-i-neurons
- Caffe:http://caffe.berkeleyvision.org/
由于篇幅較大,這里會分成幾個部分,依次講解。
一、設計我們獨特的Data層
在DeepID2中,有兩種監督信號。一是Identity signal,這和DeepID中的實現方法一樣,用給定label的人臉數據,進行分類的訓練,這里使用softmax_with_loss層來實現(softmax+cross-entropy loss)。這里不再介紹。
另一種就是verification signal,也就是人臉比對的監督。這里要求,輸入的數據時成對存在,每一對都有一個公共的label,是否是同一個類別。如果是同一個identity,則要求他們的特征更接近,如果是不同的identity,則要求他們的特征盡可能遠離。
不論最終怎么實現,我們的第一步是確定的,構造合適的數據。
使用Caffe訓練的時候,第一步是打Batch,將訓練數據寫入LMDB或者LevelDB數據庫中,訓練的時候Caffe會從數據庫中讀取圖片,因此一個簡單的實現方法就是構造許多的pair,然后打Batch的時候就能保證每對圖片都是相連的,然后在訓練的時候做一些小Trick就可以實現。
但是就如上面所說,打Batch的同時,圖片的順序就已經是確定的了,因此網絡輸入的圖片pair也是固定的,這樣似乎就缺乏了一些靈活性。
那么如何動態的構造我們的訓練數據呢?
設計我們獨特的data層。
這里為了方便,使用Python來拓展Caffe的功能。Python是一門簡潔的語言,非常適合做這種工作。不過Caffe中如果使用了Python的層,那么就不能使用多GPU了,這點需要注意(希望以后能增加這個支持)。
1)讓你的Caffe支持Python拓展。
在Caffe根目錄的Makefile.config中,有這么一句話。
我們需要使用Python層,因此需要取消這個注釋。
之后Make一下你的Caffe和pycaffe。
這樣Caffe就支持Python層了。
2)編寫data層
基于Python的data層的編寫,Caffe是給了一個簡單的例子的,在/caffe_home/examples/pycaffe/layers/中。
我們簡單的照著這個例子來寫。
首先,我們定義自己需要的參數。
這里,我們需要:
- batch_size: batch的大小,沒什么好解釋的,要求這個數是大于0的偶數
- mean_file:圖像的均值文件的路徑
- scale:圖像減均值后變換的尺度
- image_root_dir:訓練數據的根目錄
- source:訓練數據的list路徑
- crop_size:圖像crop的大小
- ratio:正樣本所占的比例(0~1)
caffe在train.prototxt中定義網絡結構的時候,可以傳入這些參數。我們目前只需要知道,這些參數一定可以獲取到,就可以了。另外,source表示訓練數據的list的文件地址,這里用到的訓練數據的格式和Caffe打batch的數據一樣。
file_path1??? label1
file_path2??? label2
這樣的格式。
Data層的具體實現,首先需要繼承caffe.Layer這個類,之后實現setup, forward, backward和reshape,不過data層并不需要backward和reshape。setup主要是為了初始化各種參數,并且設置top的大小。對于Data層來說,forward則是生成數據和label。
閑話少說,代碼來見。
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 | #-*- encoding: utf-8 -*_import sysimport caffeimport numpy as npimport osimport os.path as ospimport randomimport cv2class id2_data_layer(caffe.Layer): """??? 這個python的data layer用于動態的構造訓練deepID2的數據??? 每次forward會產生多對數據,每對數據可能是相同的label或者不同的label??? """ def setup(self, bottom, top): self.top_names = ['data', 'label'] # 讀取輸入的參數 params = eval(self.param_str) print "init data layer" print params self.batch_size = params['batch_size']? # batch_size????????self.ratio = float(params['ratio']) self.scale = float(params['scale']) assert self.batch_size > 0 and self.batch_size % 2 == 0, "batch size must be 2X" assert self.ratio > 0 and self.ratio < 1, "ratio must be in (0, 1)" self.image_root_dir = params['image_root_dir'] self.mean_file = params['mean_file']?????? self.source = params['source'] self.crop_size = params['crop_size'] top[0].reshape(self.batch_size, 3, params['crop_size'], params['crop_size']) top[1].reshape(self.batch_size, 1) self.batch_loader = BatchLoader(self.image_root_dir, self.mean_file, self.scale, self.source, self.batch_size, self.ratio) def forward(self, bottom, top): blob, label_list = self.batch_loader.get_mini_batch() top[0].data[...] = blob top[1].data[...] = label_list def backward(self, bottom, top): pass def reshape(self, bottom, top): passclass BatchLoader(object): def __init__(self, root_dir, mean_file, scale, image_list_path, batch_size, ratio): print "init batch loader" self.batch_size = batch_size self.ratio = ratio # true pair / false pair self.image2label = {}?? # key:image_name??? value:label self.label2images = {}? # key:label???????? value: image_name array self.images = []??????? # store all image_name self.mean = np.load(mean_file) self.scale = scale self.root_dir = root_dir with open(image_list_path) as fp: for line in fp: data = line.strip().split() image_name = data[0] label = data[-1] self.images.append(image_name) self.image2label[image_name] = label if label not in self.label2images: self.label2images[label] = [] self.label2images[label].append(image_name) self.labels = self.label2images.keys() self.label_num = len(self.labels) self.image_num = len(self.image2label) print "init batch loader over" def get_mini_batch(self): image_list, label_list = self._get_batch(self.batch_size / 2) cv_image_list = map(lambda image_name: (self.scale * (cv2.imread(os.path.join(self.root_dir, image_name)).astype(np.float32, copy=False).transpose((2, 0, 1)) - self.mean)), image_list) blob = np.require(cv_image_list) label_blob = np.require(label_list, dtype=np.float32).reshape((self.batch_size, 1)) return blob, label_blob def _get_batch(self, pair_num): image_list = [] label_list = [] for pair_idx in xrange(pair_num): if random.random() < self.ratio: # true pair while True: label_idx = random.randint(0, self.label_num - 1) label = self.labels[label_idx] if len(self.label2images[label]) > 5: break first_idx = random.randint(0, len(self.label2images[label]) - 1) second_id = random.randint(0, len(self.label2images[label]) - 2) if second_id >= first_idx: second_id += 1 image_list.append(self.label2images[label][first_idx]) image_list.append(self.label2images[label][second_id]) label_list.append(int(label)) label_list.append(int(label)) else:?????????????????? # false pair for i in xrange(2): image_id = random.randint(0, self.image_num - 1) image_name = self.images[image_id] label = self.image2label[image_name] image_list.append(image_name) label_list.append(int(label)) return image_list, label_list |
上述的代碼可以根據給定的list,batch size,ratio等參數生成符合要求的data和label。這里還有一些問題需要注意:
至此,我們就完成了一個簡單的Data層了。
那么在么調用自己的data層呢?
這里有一個十分簡單的寫法。在我們用來訓練的prototxt中,將Data層的定義改成如下的方式:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | layer { ????name: "data" ????type: "Python" ????top: "data" ????top: "label" ????include { ????????phase: TRAIN ????}???? ????python_param { ????????module: "id2_data_layer" ????????layer: "id2_data_layer" ????????param_str: "{'crop_size' : 128, 'batch_size' : 96, 'mean_file': '/your/data/root/mean.npy', 'scale': 0.0078125, 'source': '/path/to/your/train_list', 'image_root_dir': '/path/to/your/image_root/'}"????} } |
python_param中的這三個參數需要注意:
module:模塊名,我們先前編寫的data層,本身就是一個文件,也就是一個模塊,因此模塊名就是文件名。
layer:層的名字,我們在文件中定義的類的名字。這里比較巧合,module和layer的名字相同。
param_str:所有的需要傳給data層的參數都通過這個參數來傳遞。這里簡單的使用了Python字典的格式的字符串,在data層中使用eval來執行(o(╯□╰)o ?這其實并不是一個好習慣),從而獲取參數,當然也可以使用別的方式來傳遞,比如json或者xml等。
最后,你在訓練的時候可能會報錯,說找不到你剛剛的層,或者找不到caffe,只需要把這個層的代碼所在的文件夾的路徑加入到PYTHONPATH中即可。
| 1 | export PYTHONPATH=PYTHONPATH:/path/to/your/layer/:/path/to/caffe/python |
這樣就完成了我們的Data層的編寫,是不是非常簡單?
重要更新:1,作者最近發現直接在image_data_layer.cpp中進行修改,可以更好的實現這個目標,而且支持多GPU。
2,訓練的數據可以只用正樣本對,因為identity signal已經十分強調不同identity的feature之間的距離,因此verification signal只需要強調相同的identity的feature相近就好。
3,作者新的訓練數據,構造pair的方式也做了修改。每次使用所有的數據構造pair,然后用來訓練,每個epoch后都重新生成一次list。這樣可以保證identity signal能夠每次訓練所有的圖片,而verification signal也能每次訓練不同的樣本對。
總結
以上是生活随笔為你收集整理的随机梯度下降的实现细节的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 附睾头和尾是怎么区分的
- 下一篇: 心得安缓解紧张真的吗