この問題を解決する方法を既に理解しているかどうかはわかりませんが、最近見つけた方法を次に示します。
私は Caffe を初めて使用し、Caffe のアーキテクチャ全体を理解するのが難しいと感じています。Caffe の CNN から特徴を抽出し、後で操作する簡単な方法が欲しかっただけです。さらに、私は OSX で作業しており、ソースから Caffe をインストールしていませんでした。「ポート」からインストールしましたが、インストールが不完全なようです。そこで、Caffe が適切にインストールされている別のマシンで Caffe の「feature_extractor」を実行し、出力ファイルを自分のマシンにコピーしてさらに処理しました。
そのためには、LMDB と Google の Protobuf をマシンにインストールする必要があります。C/C++ プログラムを liblmdb および libprotobuf とリンクする必要があります。
Caffe のチュートリアルに従って、AlexNet のレイヤー「fc7」からの出力を LMDB 形式のファイルに保存しました。次に、それを読み取るための簡単な C/C++ プログラムを作成しました。これは、次のコードを使用して実行できます。
#include <fstream>
#include <iostream>
#include <lmdb.h>
using namespace std;
int main( int argc, char *argv[] )
{
if( argc!=2 )
{
cerr<< "Error"<< endl
<< "Usage : "<< argv[0]<< " mdb_dirname"<< endl;
return 0;
}
char *mdb_dirname = argv[1];
int rc;
MDB_env *env;
MDB_dbi dbi;
MDB_val key, data;
MDB_txn *txn;
MDB_cursor *cursor;
char sval[32];
rc = mdb_env_create(&env);
rc = mdb_env_open(env, mdb_dirname, 0, 0664);
rc = mdb_txn_begin(env, NULL, 0, &txn);
rc = mdb_open(txn, NULL, 0, &dbi);
rc = mdb_cursor_open(txn, dbi, &cursor);
key.mv_size = sizeof(int);
key.mv_data = sval;
data.mv_size = sizeof(sval);
data.mv_data = sval;
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0)
{
printf("key: %p %d %.*s, data: %p %d %.*s\n",
key.mv_data, (int) key.mv_size, (int) key.mv_size, (char *) key.mv_data,
data.mv_data, (int) data.mv_size, (int) data.mv_size, (char *) data.mv_data);
}
mdb_cursor_close(cursor);
mdb_txn_abort(txn);
mdb_close(env, dbi);
mdb_env_close(env);
return 0;
}
「mdb_dirname」は、「feature_extractor」によって作成されたディレクトリです。「data.mdb」と「lock.mdb」が含まれています。
私もLMDBが初めてであることに注意してください。上記のコードのすべての行がよくわかりません。しかし、私は働きます:)
LMDB ファイルを処理すると、「key.mv_data」が実際にサンプルのインデックスであることがわかります。したがって、「data.mv_data」には、この例の特徴ベクトルが含まれている必要があります。Caffe の feature_extractor のソース コードを調べたところ、文字列「data.mv_data」が「Datum」オブジェクトのシリアル化から取得されていることがわかりました。このデータムは、実際には Google の Protocol Buffer または Protobuf を使用して構築されています。Caffe ディレクトリのどこかに「caffe.proto」があるかもしれません。この .proto ファイルは、コンパイラ「protoc」によって処理され、プロジェクトに含める必要がある「caffe.pb.h」と「caffe.pb.cc」を生成します。見つからない場合はこんな感じ
syntax = "proto2";
package caffe;
message Datum {
optional int32 channels = 1;
optional int32 height = 2;
optional int32 width = 3;
// the actual image data, in bytes
optional bytes data = 4;
optional int32 label = 5;
// Optionally, the datum could also hold float data.
repeated float float_data = 6;
// If true data contains an encoded image that need to be decoded
optional bool encoded = 7 [default = false];
}
次に、「data.mv_data」を特徴ベクトルに変換できます
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0)
{
string str( (char*)data.mv_data, (int)data.mv_size );
datum.ParseFromString( str );
if( datum.float_data_size()>0 )
{
// datum.float_data_size() is the dimension of the feature vectors
for( int i = 0; i < datum.float_data_size(); i++ )
{
float f = datum.float_data(i);
// do something
}
}
}
上記のコードをビルドすると、未定義の参照など、多くのリンク エラーが発生し、Protobuf に関連するその他のエラーが発生します。同じ問題が発生した場合、私が見つけた解決策は、単純に -llmdb (動的リンケージではなく静的リンケージ) ではなく、libprotobuf.a に対してプログラムをリンクすることです。
もう 1 つの小さな問題は、'feature_extractor' によって処理されたファイル内の各サンプルに割り当てたラベルが失われていることです。どうしてか分かりません。そのため、これらのラベルを別のファイルに入れ、LMDB ファイルに沿って処理しました。たとえば、LIBSVM ファイルを出力する場合:
int c = 0;
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0)
{
string str( (char*)data.mv_data, (int)data.mv_size );
datum.ParseFromString( str );
if( datum.float_data_size()>0 )
{
cout<< label[c]<< " ";
for( int i = 0; i < datum.float_data_size(); i++ )
{
float f = datum.float_data(i);
cout<< (i+1)<< ":"<< f<< " ";
}
cout<< endl;
c++;
}
}
幸運を。