概要
ncnnのWikiに記載されている、よくある問題とその対処を翻訳して紹介します。
ncnnはなぜ開発されたできたのか?
ディープラーニングアルゴリズムを携帯電話に実装する必要があり、caffeは依存関係が多すぎるし、携帯電話にはcudaがないので、高速で小さなフォワードネットワークの実装が必要でした。
ncnnの名前の由来は?
cnnはconvolutional neural network(畳み込みニューラルネットワーク)の略称で、冒頭のnはワンライナーのようなものです。 例えば、new/next(新しい実装)、naive(ncnnは素朴な実装)、neon(ncnnはもともと携帯電話向けに最適化された)。
対応プラットフォームは?
クロスプラットフォーム、android/ios/linux/windows/macosに対応、ベアメタルにも対応している。
A 計算精度はどうですか?
armv7 neon floatはieee754標準に準拠していないため、高速な実装(exp sinなど)を使用しているものもあり、高速ですが精度は十分高いです。
ncnn の作者は?
nihui氏です。
ソースコードのダウンロード方法は_
git clone --recursive https://github.com/Tencent/ncnn/
クロスコンパイルの方法と、cmakeツールチェインの設定方法は?
The submodules were not downloaded! Please update submodules with "git submodule update --init" and try again とでた場合は?
上記のように、完全なソースコードをGitからrecursiveでダウンロードします。 または、プロンプトに従ってください
git submodule update --init
Could NOT find CUDA (missing: CUDA_TOOLKIT_ROOT_DIR CUDA_INCLUDE_DIRS CUDA_CUDART_LIBRARY)と表示される
Could not find a package configuration file provided by "OpenCV" with any of the following names: OpenCVConfig.cmake opencv-config.cmakeと表示される
sudo apt-get install libopencv-dev
または自分でコンパイルしてインストールする。
set(OpenCV_DIR {OpenCVConfig.cmakeがあるディレクトリ})
Could not find a package configuration file provided by "ncnn" with any of the following names: ncnnConfig.cmake ncnn-config.cmakeと表示される
set(ncnn_DIR {ncnnConfig.cmakeがあるディレクトリ})
Vulkanが見つからない。
cmakeのバージョンは3.10以上です。そうでない場合はFindVulkan.cmakeが付属しません。
android-api >= 24
macosの場合、最初にインストールスクリプトを実行する必要があります。
undefined reference to typeinfo for ncnn::Layerと表示される
opencv rtti -> opencv-mobileへ変更する
undefined reference to __cpu_modelと表示される
コンパイラ( libgcc_s libgcc)のアップグレードが必要
unrecognized command line option "-mavx2"と表示される
gccのアップグレードが必要
私がコンパイルしたncnnアンドロイド・ライブラリが特に大きいのはなぜですか?
ncnnoptimizeとカスタムレイヤー
ncnnoptimizeがカスタムレイヤーの保存を処理できなくなるのを防ぐため、カスタムレイヤーを追加する前にncnnoptimizeを実行してください。
rtti/EXCEPTIONの設定
ncnnはデフォルトでONになっているので、ncnnを再コンパイルする際に以下の2つのパラメーターを追加する
OFF:-DNCNN_DISABLE_RTTI=OFF -DNCNN_DISABLE_EXCEPTION=OFF
ON:-DNCNN_DISABLE_RTTI=ON -DNCNN_DISABLE_EXCEPTION=ON
error: undefined symbol: ncnn::Extractor::extract(char const*, ncnn::Mat&)と表示される
Android StudioのNDKバージョンをアップグレードしてみる
CMake 3.14.0 or higher is required. You are running version 2.8.12.2と表示される
wget https://github.com/Kitware/CMake/releases/download/v3.18.2/cmake-3.18.2-Linux-x86_64.tar.gz
tar zxvf cmake-3.18.2-Linux-x86_64.tar.gz
mv cmake-3.18.2-Linux-x86_64 /opt/cmake-3.18.2
ln -sf /opt/cmake-3.18.2/bin/* /usr/bin/
プロジェクトにncnnライブラリを追加するには?
linux/windowsでは,make installを実行。
cmakeのncnn_DIR の設定で、インストールディレクトリの ncnnConfig.cmake を含むディレクトリを設定します。
学習モデリング変換の問題
- caffe
./caffe2ncnn caffe.prototxt caffe.caffemodel ncnn.param ncnn.bin
- mxnet
./mxnet2ncnn mxnet-symbol.json mxnet.params ncnn.param ncnn.bin
- darknet
https://github.com/xiangweizeng/darknet2ncnn
- pytorch - onnx
- tensorflow 1.x/2.x - keras
https://github.com/MarsTechHAN/keras2ncnn @MarsTechHAN
-
tensorflow 2.x - mlir
-
MLIR経由でtensorflow2のモデルをncnnに変換する
https://zhuanlan.zhihu.com/p/152535430 -
Shape not supported yet! Gather not supported yet! Cast not supported yet!
onnx-simplifier でshapeを整える -
convertmodel
Shape情報を固定してモデルを生成するには?
Input 0=w 1=h 2=c
Could NOT find Protobuf (missing: Protobuf_INCLUDE_DIR)と表示される
sudo apt-get install libprotobuf-dev protobuf-compiler
モデルを暗号化する方法は?
後処理を削除してonnxでエクスポートするには?
https://zhuanlan.zhihu.com/p/128974102 の記事の中でステップ3は、後処理を削除し、onnxをエクスポートすることができます。プロジェクト内でテストする際に、後続のステップを削除することになります。
pytorchはいくつかのレイヤーからonnxを導出することができません。
まずエクスポートできる、ONNX_ATEN_FALLBACK 完全にカスタマイズされたopで、(例:スライスの連結)に変更し、ncnnに移動した後にparamを変更する。
もしくは、PNNXで出力する。PNNXの大まかな説明は以下の記事を参照されたい。
https://zhuanlan.zhihu.com/p/431833958
https://zhuanlan.zhihu.com/p/427512763
Matlab が出力するcaffemodel が row-major になっていない
caffe2ncnnツールは、caffemodelがrow-major(pycaffe/c++のcaffe trainコマンドで生成)であることを前提としています。
例えば、カーネル3x3の重みは以下のように、格納されます。
a b c
d e f
g h i
しかし、matlab が出力するcaffeはcol-major caffemodelを生成します。
a d g
b e h
c f i
全てのカーネル重みを自分で転置するか、c++ caffe trainコマンドを使って再トレーニングする必要があります。また、 matcaffe model (column major)からpycaffe / c++ caffe (row major) modelへ変換するツールがあります。
input画像が RGBか BGRかを確認する
もしあなたのモデルがcaffemodelがc++ caffeとopencvを使ってトレーニングされたものであれば、入力画像はBGRオーダーになるはずです。もしあなたのモデルがmatlab caffeやpytorch、mxnet、tensorflowを使って学習されたものであれば、入力画像はおそらくRGB順になります。
チャンネル順序は、ncnnの処理によって、以下のように適切なタイプに変更できます。
// construct RGB blob from rgb image
ncnn::Mat in_rgb = ncnn::Mat::from_pixels(rgb_data, ncnn::Mat::PIXEL_RGB, w, h);
// construct BGR blob from bgr image
ncnn::Mat in_bgr = ncnn::Mat::from_pixels(bgr_data, ncnn::Mat::PIXEL_BGR, w, h);
// construct BGR blob from rgb image
ncnn::Mat in_bgr = ncnn::Mat::from_pixels(rgb_data, ncnn::Mat::PIXEL_RGB2BGR, w, h);
// construct RGB blob from bgr image
ncnn::Mat in_rgb = ncnn::Mat::from_pixels(bgr_data, ncnn::Mat::PIXEL_BGR2RGB, w, h);
imageデコード
JPEG(.jpg、.jpeg)は圧縮率が低いため、同じ位置の同じ画像でも画素値が異なる場合があります。代わりに.bmp画像をお勧めします。
補間/リサイズ
画像のリサイズにはいくつかの方法があり、同じ入力画像でも異なる結果を生成することがあります。
同じ補間方法を指定しても、フレームワーク/ライブラリやそのバージョンが異なれば、違いが生じる可能性があります。例えば、入力レイヤーが224x224のサイズを必要とする場合、224x244のbmp画像を読み込むようにします。
Mat::from_pixels/from_pixels_resizeは、連続したピクセルバッファを仮定
連続したピクセルバッファを from_pixels ファミリに渡す必要があります。
OpenCVで,画像 がroiから得られた切り取られた画像である場合は, clone() を呼び出して,連続したピクセルバッファ画像を取得します。
cv::Mat image;// the image
cv::Rect facerect;// the face rectangle
cv::Mat faceimage = image(facerect).clone();// get a continuous sub image
ncnn::Mat in = ncnn::Mat::from_pixels(faceimage.data, ncnn::Mat::PIXEL_BGR, faceimage.cols, faceimage.rows);
事前処理
トレーニングの設定に従って前処理を適用します。
モデルによってプリプロセスの設定は異なりますが、データレイヤーセクションに以下のような変換のパラメータ設定があります。
transform_param {
mean_value: 103.94
mean_value: 116.78
mean_value: 123.68
scale: 0.017
}
すると、ncnnの前処理に対応するコードは次のようになります。
const float mean_vals[3] = { 103.94f, 116.78f, 123.68f };
const float norm_vals[3] = { 0.017f, 0.017f, 0.017f };
in.substract_mean_normalize(mean_vals, norm_vals);
pytorchまたはmxnet-gluonの場合は以下のように、パラメータを設定します。
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
このときには、ncnnの前処理に対応するコードは、0~1の範囲のデータを0~255の範囲のデータになるように変換を行う必要があるため、次のようになります。
const float mean_vals[3] = {0.485f*255.f, 0.456f*255.f, 0.406f*255.f};
const float norm_vals[3] = {1/0.229f/255.f, 1/0.224f/255.f, 1/0.225f/255.f};
in.substract_mean_normalize(mean_vals, norm_vals);
// R' = (R / 255 - 0.485) / 0.229 = (R - 0.485 * 255) / 0.229 / 255
// G' = (G / 255 - 0.456) / 0.224 = (G - 0.456 * 255) / 0.224 / 255
// B' = (B / 255 - 0.406) / 0.225 = (B - 0.406 * 255) / 0.225 / 255
ncnn::Extractorのbolb名をモデルのbolb名とあわせる
入力と出力のblob名はモデルによって異なります。
例えば、squeezenet v1.1では入力ブロブとして "data"、出力ブロブとして "prob "を使用しますが、mobilenet-ssdでは入力ブロブとして "data"、出力ブロブとして "detection_out "を使用します。
モデルによっては、複数の入力が必要な場合や、複数の出力を生成する場合があります。
ncnn::Extractor ex = net.create_extractor();
ex.input("data", in);// change "data" to yours
ex.input("mask", mask);// change "mask" to yours
ex.extract("output1", out1);// change "output1" to yours
ex.extract("output2", out2);// change "output2" to yours
blob が channel gapを持つ場合
各チャンネルのポインタは,ncnn Mat 構造体内で 128bit ずつずらして保持しています。
(width x height) が 4 で正確に割り切れない場合,チャンネル間にギャップが生じる可能性があります。
画像データから入力 blob を作成するには, ncnn::Mat::from_pixels または、ncnn::Mat::from_pixels_resize を利用することを推奨します.
連続した blob バッファが必要な場合は,出力の形状を変更します
// out is the output blob extracted
ncnn::Mat flattened_out = out.reshape(out.w * out.h * out.c);
// plain array, C-H-W
const float* outptr = flattened_out;
画像ごとに新しい Extractor を作成する
ncnn::Extractor オブジェクトは処理状態を保持しています。異なる入力データに対して再利用すると、常に内部にキャッシュされた全く同じ結果が得られます。
Extractor がどのように動作するかを把握している場合を除き、画像をループで処理するには常に新しい Extractor を作成します。
for (int i=0; i<count; i++){
// always create Extractor
// it's cheap and almost instantly !
ncnn::Extractor ex = net.create_extractor();
// use
ex.input(your_data[i]);
}
適切なLoad 関数を使う
メモリ上に配置したパラメータファイルバッファをロードしたい場合は、Net::load_param の代わりに Net::load_param_mem を使用しなければなりません。ncnn モデルのロード API については ncnn-load-model を参照してください。
ncnn::Net net;
// param_buffer is the content buffe of XYZ.param file
net.load_param_mem(param_buffer);
fp16を無効にする
モデルによってはfp16がオーバーフローし、nanの結果になることがあります。
そこで、fp16の低精度最適化をオフにしてみると、精度がfp32に改善され、これに起因するオーバーフロー問題を調査・解決することができます。
ncnn::Net net;
net.opt.use_fp16_packed = false;
net.opt.use_fp16_storage = false;
net.opt.use_fp16_arithmetic = false;
参考
この記事は以下のページを参考に作成しました。
FAQ-ncnn-produce-wrong-result
[FAQ]https://github.com/Tencent/ncnn/wiki/faq)