caffe
カスタムPythonレイヤー
サーチ…
前書き
このチュートリアルでは、Pythonを使用してCaffeのシンプルなカスタムレイヤーを作成する手順を説明します。最後にカスタムレイヤーの例がいくつかあります。通常、Caffeでは利用できない機能を実装するカスタムレイヤーを作成し、要件に合わせて調整します。
Pythonカスタムレイヤーを作成すると、ネットワークにオーバーヘッドが加わり、おそらくC ++カスタムレイヤーほど効率的ではありません。ただし、この方法では、新しいレイヤーでcaffe全体をコンパイルする必要はありません。
パラメーター
| パラメータ | 詳細 |
|---|---|
| 上 | あなたのレイヤの上の塊を持つ配列。 top [i] .dataを使用して渡されたデータにアクセスします。ここで、 iは特定のBLOBのインデックスです |
| 底 | あなたのレイヤの下のブロブを持つ配列。 bottom [i] .dataを使用して渡されたデータにアクセスします。ここで、 iは特定のBLOBのインデックスです |
備考
- Python層を使ったCaffeビルド
CaffeはWITH_PYTHON_LAYERオプションでコンパイルする必要があります:
WITH_PYTHON_LAYER=1 make && make pycaffe
- クラスファイルはどこに保存しますか?
あなたは2つの選択肢があります(私が知っている以上に)。カスタムレイヤーファイルをcaffeコマンドを実行するのと同じフォルダに保存することもできます(おそらくprototxtファイルの場所に保存することもできます)。私のお気に入りのもう一つの方法は、すべてのカスタムレイヤーをフォルダに保存し、このフォルダをPYTHONPATHに追加することです。
参考文献
レイヤーテンプレート
import caffe
class My_Custom_Layer(caffe.Layer):
def setup(self, bottom, top):
pass
def forward(self, bottom, top):
pass
def reshape(self, bottom, top):
pass
def backward(self, bottom, top):
pass
それで覚えておくべき重要なこと:
- あなたのカスタムレイヤーはcaffe.Layerから継承する必要があります (したがって、 caffeをインポートすることを忘れないでください)。
- 以下の4つの方法を定義する必要があります: セットアップ 、 フォワード 、 リサーブ 、 バックワード 。
- すべてのメソッドには、レイヤーに渡された入力と出力を格納するブロブである上部および下部のパラメータがあります。あなたは私はあなたが複数の上部または下部のブロブを持っている場合にはブロブの指標である[I] .dataのか、 下の[i]は.dataの トップを 、使用してアクセスすることができます。
- セットアップ方法
Caffeがすべてのレイヤをインスタンス化しているとき、Setupメソッドは実行のライフタイム中に一度呼び出されます。ここでは、パラメータを読み込み、固定サイズのバッファをインスタンス化します。
- 変形方法
下のblob(レイヤ入力)サイズに依存する初期化/設定には、再形成メソッドを使用します。ネットワークがインスタンス化されると、一度呼び出されます。
- フォワード方式
Forwardメソッドは、入力バッチごとに呼び出され、ロジックのほとんどが使用されます。
- 後方への方法
Backwardメソッドは、ネットワークの逆方向パス中に呼び出されます。たとえば、畳み込み状のレイヤーでは、グラデーションを計算する場所になります。これはオプションです(レイヤーは転送のみ可能)。
Prototxtテンプレート
さて、あなたのレイヤーがデザインされました!これは、 .prototxtファイルで定義する方法です。
layer {
name: "LayerName"
type: "Python"
top: "TopBlobName"
bottom: "BottomBlobName"
python_param {
module: "My_Custom_Layer_File"
layer: "My_Custom_Layer_Class"
param_str: '{"param1": 1,"param2":True, "param3":"some string"}'
}
include{
phase: TRAIN
}
}
重要な発言:
- 型はPythonでなければなりません。
- 少なくともモジュールとレイヤのパラメータを持つpython_paramディクショナリが必要です。
- モジュールはあなたのレイヤーを実装したファイルを指します( .pyなし)。
- layerはあなたのクラスの名前を指します。
- param_strを使用してレイヤにパラメータを渡すことができます(詳細は以下を参照してください)。
- 他のレイヤと同様に、どのフェーズでアクティブにするかを定義できます(現在のフェーズの確認方法の例を参照)。
レイヤーにパラメータを渡す
あなたはPARAM_STRを使用してprototxtにおける層のパラメータを定義することができます。それを済ませたら、レイヤークラス内のこれらのパラメータをどのようにアクセスするかの例を以下に示します。
def setup(self, bottom, top):
params = eval(self.param_str)
param1 = params["param1"]
param2 = params.get('param2', False) #I usually use this when fetching a bool
param3 = params["param3"]
#Continue with the setup
# ...
レイヤーを測定する
この例では、訓練中のバイナリ問題の精度と混同行列と、テスト/検証中の偽陽性率と偽陰性率を出力する「メジャー」層を設計します。 CaffeにはすでにAccuracyレイヤーがありますが、時にはF-measureのようなものが必要です。
これは私のクラス定義のmeasureLayer.pyです:
#Remark: This class is designed for a binary problem, where the first class would be the 'negative'
# and the second class would be 'positive'
import caffe
TRAIN = 0
TEST = 1
class Measure_Layer(caffe.Layer):
#Setup method
def setup(self, bottom, top):
#We want two bottom blobs, the labels and the predictions
if len(bottom) != 2:
raise Exception("Wrong number of bottom blobs (prediction and label)")
#And some top blobs, depending on the phase
if self.phase = TEST and len(top) != 3:
raise Exception("Wrong number of top blobs (acc, FPR, FNR)")
if self.phase = TRAIN and len(top) != 5:
raise Exception("Wrong number of top blobs (acc, tp, tn, fp and fn)")
#Initialize some attributes
self.TPs = 0.0
self.TNs = 0.0
self.FPs = 0.0
self.FNs = 0.0
self.totalImgs = 0
#Forward method
def forward(self, bottom, top):
#The order of these depends on the prototxt definition
predictions = bottom[0].data
labels = bottom[1].data
self.totalImgs += len(labels)
for i in range(len(labels)): #len(labels) is equal to the batch size
pred = predictions[i] #pred is a tuple with the normalized probability
#of a sample i.r.t. two classes
lab = labels[i]
if pred[0] > pred[1]:
if lab == 1.0:
self.FNs += 1.0
else:
self.TNs += 1.0
else:
if lab == 1.0:
self.TPs += 1.0
else:
self.FPs += 1.0
acc = (self.TPs + self.TNs) / self.totalImgs
try: #just assuring we don't divide by 0
fpr = self.FPs / (self.FPs + self.TNs)
except:
fpr = -1.0
try: #just assuring we don't divide by 0
fnr = self.FNs / (self.FNs + self.TPs)
except:
fnr = -1.0
#output data to top blob
top[0].data = acc
if self.phase == TRAIN:
top[1].data = self.TPs
top[2].data = self.TNs
top[3].data = self.FPs
top[4].data = self.FNs
elif self.phase == TEST:
top[1].data = fpr
top[2].data = fnr
def reshape(self, bottom, top):
"""
We don't need to reshape or instantiate anything that is input-size sensitive
"""
pass
def backward(self, bottom, top):
"""
This layer does not back propagate
"""
pass
これはprototxtの例です:
layer {
name: "metrics"
type: "Python"
top: "Acc"
top: "TPs"
top: "TNs"
top: "FPs"
top: "FNs"
bottom: "prediction" #let's supose we have these two bottom blobs
bottom: "label"
python_param {
module: "measureLayer"
layer: "Measure_Layer"
}
include {
phase: TRAIN
}
}
layer {
name: "metrics"
type: "Python"
top: "Acc"
top: "FPR"
top: "FNR"
bottom: "prediction" #let's supose we have these two bottom blobs
bottom: "label"
python_param {
module: "measureLayer"
layer: "Measure_Layer"
}
include {
phase: TEST
}
}
データレイヤー
この例は、イメージパスを含むテキストファイルを受け取り、バッチのイメージを読み込んでそれらを前処理するカスタムデータレイヤーです。ちょっとしたヒント、Caffeにはすでに大きなデータレイヤーがあり、単純なものをほしいと思っているのであれば、おそらくカスタムレイヤーは最も効率的な方法ではありません。
私のdataLayer.pyは次のようなものです:
import caffe
class Custom_Data_Layer(caffe.Layer):
def setup(self, bottom, top):
# Check top shape
if len(top) != 2:
raise Exception("Need to define tops (data and label)")
#Check bottom shape
if len(bottom) != 0:
raise Exception("Do not define a bottom.")
#Read parameters
params = eval(self.param_str)
src_file = params["src_file"]
self.batch_size = params["batch_size"]
self.im_shape = params["im_shape"]
self.crop_size = params.get("crop_size", False)
#Reshape top
if self.crop_size:
top[0].reshape(self.batch_size, 3, self.crop_size, self.crop_size)
else:
top[0].reshape(self.batch_size, 3, self.im_shape, self.im_shape)
top[1].reshape(self.batch_size)
#Read source file
#I'm just assuming we have this method that reads the source file
#and returns a list of tuples in the form of (img, label)
self.imgTuples = readSrcFile(src_file)
self._cur = 0 #use this to check if we need to restart the list of imgs
def forward(self, bottom, top):
for itt in range(self.batch_size):
# Use the batch loader to load the next image.
im, label = self.load_next_image()
#Here we could preprocess the image
# ...
# Add directly to the top blob
top[0].data[itt, ...] = im
top[1].data[itt, ...] = label
def load_next_img(self):
#If we have finished forwarding all images, then an epoch has finished
#and it is time to start a new one
if self._cur == len(self.imgTuples):
self._cur = 0
shuffle(self.imgTuples)
im, label = self.imgTuples[self._cur]
self._cur += 1
return im, label
def reshape(self, bottom, top):
"""
There is no need to reshape the data, since the input is of fixed size
(img shape and batch size)
"""
pass
def backward(self, bottom, top):
"""
This layer does not back propagate
"""
pass
そしてprototxtは次のようになります:
layer {
name: "Data"
type: "Python"
top: "data"
top: "label"
python_param {
module: "dataLayer"
layer: "Custom_Data_Layer"
param_str: '{"batch_size": 126,"im_shape":256, "crop_size":224, "src_file": "path_to_TRAIN_file.txt"}'
}
}