一、介紹
https://mxnet.incubator.apache.org/versions/master/faq/why_mxnet.html
1.爲何選擇MXNet?
也許,如果你偶然發現了這個頁面,你已經聽說過深度學習。深度學習意味着神經網絡的現代化,它是最近在自動駕駛汽車,機器翻譯,語音識別等方面取得突破的技術。雖然深度學習的廣泛興趣在2012年起飛,但深度學習已成爲無數行業不可或缺的工具。
比例和計算
推動深度學習創新的兩個最大因素是數據和計算。通過跨GPU核心的分佈式雲計算和並行性,我們可以比20世紀80年代的研究人員更快地訓練模型數百萬倍。大型高質量數據集的可用性是推動該領域向前發展的另一個因素。在20世紀90年代,計算機視覺中最好的數據集有數千個低分辨率圖像和少數類的地面實況分配。今天,研究人員在ImageNet上大肆宣傳,這是一個龐大的數據集,包含來自千個不同類別的數百萬個高分辨率圖像。存儲價格的下降和高網絡帶寬使得可以隨意使用大數據。
在這個新的世界中,隨着更大的數據集和豐富的計算,神經網絡在大多數模式識別問題上占主導地位。在過去的五年中,神經網絡已經成爲計算機視覺中幾乎所有問題的主導,取代了經典模型和手工設計的特徵。同樣,幾乎每個生產語音識別系統現在都依賴於神經網絡,在那裏取代以前佔據主導地位的隱馬爾可夫模型。
雖然GPU和集羣爲加速神經網絡培訓提供了巨大的機會,但是調整傳統機器學習代碼以利用這些資源可能具有挑戰性。熟悉的科學計算堆棧(Matlab,R或NumPy和SciPy)沒有提供利用這些分佈式資源的直接方式。
像MXNet這樣的加速庫提供了強大的工具來幫助開發人員利用GPU和雲計算的全部功能。雖然這些工具通常適用於任何數學計算,但MXNet特別強調加速大規模深度神經網絡的開發和部署。特別是,我們提供以下功能:
- 設備放置:使用MXNet,可以輕鬆指定每個數據結構的生存位置。
- 多GPU培訓:MXNet可以通過可用GPU的數量輕鬆擴展計算。
- 自動區分:MXNet自動執行曾經陷入神經網絡研究的衍生計算。
- 優化的預定義圖層:雖然您可以在MXNet中編寫自己的圖層,但預定義的圖層會針對速度進行優化,優於競爭庫。
快速計算機上的深網
雖然MXNet可以加速任何數值計算,但我們開發了具有神經網絡的庫。無論您計劃使用MXNet,神經網絡都是展示MXNet功能的強大動力示例。
當然,數十或數百個矩陣乘法可能在計算上很費力。通常,這些線性操作是計算瓶頸。幸運的是,線性運算符可以在GPU上的數千個核心上平行並行化。但低級GPU編程需要專業技能,即使在ML社區的領先研究人員中也不常見。此外,即使對於CUDA專家來說,實現新的神經網絡架構也不需要數週的編程來實現低級線性代數運算。這就是MXNet的用武之地。
- MXNet從Python和R等高級環境中爲GPU和分佈式生態系統提供優化的數值計算
- MXNet可以自動執行常見的工作流程,因此只需幾行代碼即可簡潔地表達標準神經網絡
編程風格
MXNet支持兩種編程風格:命令式編程(由NDArray API 支持)和符號編程(由Symbol API 支持)。簡而言之,命令式編程是您可能最熟悉的風格。這裏,如果A和B是表示矩陣的變量,那麼它是一段代碼,在執行時對和引用的值求和,並將它們的和存儲在一個新變量中。另一方面,符號編程允許通過計算圖抽象地定義函數。在符號風格中,我們首先用佔位符值表達複雜函數。然後,我們可以執行這些功能C = A + BABC將它們綁定到實際值。
結論
MXNet結合了高性能,乾淨的代碼,高級API訪問和低級控制,是深度學習框架中獨一無二的選擇。
二、MXNet安裝
1.源碼
官網:http://mxnet.incubator.apache.org/versions/master/install/download.html
下載1.4.1:http://mirror.bit.edu.cn/apache/incubator/mxnet/1.4.1/apache-mxnet-src-1.4.1-incubating.tar.gz
我是git下來的:
#$ git clone --recursive https://github.com/apache/incubator-mxnet mxnet
$ git clone --recursive -b v1.3.x https://github.com/apache/incubator-mxnet.git mxnet-1.3.x
2.編譯
參考:http://mxnet.incubator.apache.org/versions/master/install/c_plus_plus.html
構建MXNet與C ++(推薦用於配備NVIDIA GPU和Intel CPU的系統):
$ cmake -DUSE_CUDA=1 -DUSE_CUDA_PATH=/usr/local/cuda -DUSE_CUDNN=1 -DUSE_MKLDNN=1 -DUSE_CPP_PACKAGE=1 -GNinja .
$ ninja -v
#會等很久,然後根據提示:
[1/21] cd /home/toson/soft/mxnet/cpp-package/scripts && echo Running:\ OpWrapperGenerator.py && python OpWrapperGenerator.py /home/toson/soft/mxnet/libmxnet.so
Running: OpWrapperGenerator.py
$ cd /home/toson/soft/mxnet/cpp-package/scripts
$ python OpWrapperGenerator.py /home/toson/soft/mxnet/libmxnet.so
#沒看到任何輸出,一切正常。
您還可以將MXNet共享庫添加到LD_LIBRARY_PATH:
$ export LD_LIBRARY_PATH=~/soft/mxnet/include/
3.庫調用
對於API調用,可以在CMakeLists.txt中使用include_directories()
寫入指定調用路徑。
我是把這些文件拷貝到了另一個文件夾下:
新建shell腳本auto_include_mxnet.sh
文件,寫入以下內容,然後根據自己的目錄運行腳本:
#!/bin/bash
if [ "$1" == "-h" ];then
echo "執行的文件名:$0";
echo "功能:拷貝MXNet目錄中的鏈接庫文件,到目標目錄中。"
echo "用法:$ bash 文件名 [源目錄] [目標目錄]"
echo "例子:$ bash auto_include_mxnet.sh /home/toson/soft/mxnet /home/toson/download_libs/mxnet_1_4_1"
exit 0;
fi]
if [ "$1" == "" ];then
echo "請輸入源目錄!"
exit 0;
fi
if [ "$2" == "" ];then
echo "請輸入目標目錄!"
exit 0;
fi
mkdir -p "$2"
cp -r "$1""/libmxnet.so" "$2"
cp -r "$1""/3rdparty/mkldnn/src/libmkldnn.so.0" "$2"
cp -r "$1""/3rdparty/openmp/runtime/src/libomp.so" "$2"
cp -r "$1""/libmklml_intel.so" "$2"
cp -r "$1""/libiomp5.so" "$2"
cp -r "$1""/include" "$2"
mkdir -p "$2""/cpp-package"
cp -r "$1""/cpp-package/include" "$2""/cpp-package"
mkdir -p "$2""/nnvm"
cp -r "$1""/3rdparty/tvm/nnvm/include" "$2""/nnvm"
mkdir -p "$2""/ps-lite"
cp -r "$1""/3rdparty/ps-lite/include" "$2""/ps-lite"
mkdir -p "$2""/dmlc-core"
cp -r "$1""/3rdparty/dmlc-core/include" "$2""/dmlc-core"
echo "end."
這樣在CMakeLists.txt中這樣進行鏈接:
set(CMAKE_CXX_STANDARD 11 )
find_package(OpenCV REQUIRED)
include_directories(/home/toson/download_libs/mxnet_1_4_1/include
/home/toson/download_libs/mxnet_1_4_1/cpp-package/include
/home/toson/download_libs/mxnet_1_4_1/nnvm/include
/home/toson/download_libs/mxnet_1_4_1/ps-lite/include
/home/toson/download_libs/mxnet_1_4_1/dmlc-core/include)
target_link_libraries(demo /home/toson/download_libs/mxnet_1_4_1/libmxnet.so
${OpenCV_LIBS})
4.模型文件
- det3-0001.params
保存的是預訓練好的模型 - det3-symbol.json
二、官方示例代碼C++
可以在MXNet項目的文件夾中找到C ++代碼示例cpp-package/example。有關構建示例的說明,請參閱cpp-package的自述文件:https://github.com/apache/incubator-mxnet/tree/master/cpp-package
1.教程(MXNet C ++ API基礎知識)
本教程通過經典手寫數字識別數據庫-MNIST提供C ++包的基本用法。
以下內容假定工作目錄爲/path/to/mxnet/cpp-package/example
。
加載數據
在進入代碼之前,我們需要獲取MNIST數據。您可以使用該腳本/path/to/mxnet/cpp-package/example/get_data.sh
,也可以自己從Lecun的網站下載mnist數據 並將其解壓縮到data/mnist_data文件夾中。
除了鏈接MXNet共享庫之外,C ++包本身是一個僅包含頭的包,這意味着您需要做的就是包含頭文件。在頭文件中, op.h
它是特殊的,因爲它是動態生成的。在構建C ++包時應該完成生成 。請務必注意,您需要將共享庫(libmxnet.so在Linux和MacOS中, libmxnet.dll在Windows中)從/path/to/mxnet/lib
工作目錄複製到工作目錄。我們不建議您使用預先構建的二進制文件,因爲MXNet正在大量開發中,操作員定義op.h
可能與預構建版本不兼容。
爲了使用C ++包提供的功能,首先我們包括通用頭文件MxNetCpp.h
並指定命名空間。
#include "mxnet-cpp/MxNetCpp.h"
using namespace std;
using namespace mxnet::cpp;
接下來,我們可以使用數據iter來加載MNIST數據(分爲訓練集和驗證集)。MNIST中的數字是二維數組,因此我們應該設置flat
爲true以展平數據。
auto train_iter = MXDataIter("MNISTIter")
.SetParam("image", "./data/mnist_data/train-images-idx3-ubyte")
.SetParam("label", "./data/mnist_data/train-labels-idx1-ubyte")
.SetParam("batch_size", batch_size)
.SetParam("flat", 1)
.CreateDataIter();
auto val_iter = MXDataIter("MNISTIter")
.SetParam("image", "./data/mnist_data/t10k-images-idx3-ubyte")
.SetParam("label", "./data/mnist_data/t10k-labels-idx1-ubyte")
.SetParam("batch_size", batch_size)
.SetParam("flat", 1)
.CreateDataIter();
數據已成功加載。我們現在可以在C ++包的幫助下輕鬆構建各種模型來識別數字。
多層感知器
如果您不熟悉多層感知器,可以在( https://mxnet.incubator.apache.org/versions/master/tutorials/python/mnist.html#multilayer-perceptron )獲得一些基本信息 。我們只關注本教程中的實現。
構建多層感知器模型很簡單,假設我們存儲每個層的隱藏大小layers
,並且每個層使用 ReLu函數作爲激活。
Symbol mlp(const vector<int> &layers) {
auto x = Symbol::Variable("X");
auto label = Symbol::Variable("label");
vector<Symbol> weights(layers.size());
vector<Symbol> biases(layers.size());
vector<Symbol> outputs(layers.size());
for (int i=0; i<layers.size(); ++i) {
weights[i] = Symbol::Variable("w" + to_string(i));
biases[i] = Symbol::Variable("b" + to_string(i));
Symbol fc = FullyConnected(
i == 0? x : outputs[i-1]
weights[i],
biases[i],
layers[i]
);
outputs[i] = i == layers.size()-1 ? fc : Activation(fc, ActivationActType::relu);
}
return SoftmaxOutput(outputs.back(), label);
}
上述函數定義了多層感知器模型,其中隱藏的大小由指定layers
。
我們現在在構造模型之後創建並初始化參數。MXNet可以幫助您推斷大多數參數的形狀。基本上只需要數據和標籤的形狀。
std::map<string, NDArray> args;
args["X"] = NDArray(Shape(batch_size, image_size*image_size), ctx);
args["label"] = NDArray(Shape(batch_size), ctx);
// Let MXNet infer shapes other parameters such as weights
net.InferArgsMap(ctx, &args, args);
// Initialize all parameters with uniform distribution U(-0.01, 0.01)
auto initializer = Uniform(0.01);
for (auto& arg : args) {
// arg.first is parameter name, and arg.second is the value
initializer(arg.first, &arg.second);
}
剩下的就是用優化器訓練模型。
// Create sgd optimizer
Optimizer* opt = OptimizerRegistry::Find("sgd");
opt->SetParam("rescale_grad", 1.0/batch_size);
// Start training
for (int iter = 0; iter < max_epoch; ++iter) {
train_iter.Reset();
while (train_iter.Next()) {
auto data_batch = train_iter.GetDataBatch();
// Set data and label
args["X"] = data_batch.data;
args["label"] = data_batch.label;
// Create executor by binding parameters to the model
auto *exec = net.SimpleBind(ctx, args);
// Compute gradients
exec->Forward(true);
exec->Backward();
// Update parameters
exec->UpdateAll(opt, learning_rate, weight_decay);
// Remember to free the memory
delete exec;
}
}
我們還想看看我們的模型是如何運作的。C ++包提供了便於評估的API。這裏我們使用精度作爲指標。除了我們不需要漸變之外,推斷與訓練幾乎相同。
Accuracy acc;
val_iter.Reset();
while (val_iter.Next()) {
auto data_batch = val_iter.GetDataBatch();
args["X"] = data_batch.data;
args["label"] = data_batch.label;
auto *exec = net.SimpleBind(ctx, args);
// Forward pass is enough as no gradient is needed when evaluating
exec->Forward(false);
acc.Update(data_batch.label, exec->outputs[0]);
delete exec;
}
您可以在中找到完整的代碼mlp_cpu.cpp
。使用make mlp_cpu
編譯它,並./mlp_cpu
運行它。如果在鍵入後找不到libmxnet.so
共享庫,則需要在Linux和 MacOS 中的環境變量LD_LIBRARY_PATH
中指定共享庫的路徑。它基本上告訴系統在當前目錄下找到共享庫,因爲我們剛剛在這裏複製了它。LD_LIBRARY_PATH+=. ./mlp_cpu
GPU支持
值得一提的是,從不斷變化的環境Context::cpu()來Context::gpu()是不夠的,因爲ITER存儲在內存中的數據被讀取的數據,我們不能直接分配給它的參數。爲了彌補這一差距,NDArray在GPU和CPU之間提供數據同步功能。我們將通過在GPU上運行mlp代碼來說明它。
在前面的代碼中,數據使用如下
args["X"] = data_batch.data;
args["label"] = data_batch.label;
如果在GPU的上下文中創建其他參數將會有問題。我們可以 NDArray::CopyTo
用來解決這個問題。
// Data provided by DataIter are stored in memory, should be copied to GPU first.
data_batch.data.CopyTo(&args["X"]);
data_batch.label.CopyTo(&args["label"]);
// CopyTo is imperative, need to wait for it to complete.
NDArray::WaitAll();
通過將前一代碼替換爲後一代碼,我們成功將代碼移植到GPU。您可以在中找到完整的代碼mlp_gpu.cpp
。編譯類似於cpu版本。請注意,必須在啓用GPU支持的情況下構建共享庫。
2.相關主題
使用MXNet的C Predict API進行圖像分類:
https://github.com/apache/incubator-mxnet/tree/master/example/image-classification/predict-cpp
三、問題
實際測試了MXNet前向傳播耗:1 batch:35ms。
1.速度
花了很長時間纔開始在GPU上運行
嘗試禁用opencv以使用GPU:在禁用GPU模塊的情況下從源構建opencv。
單個GPU上的速度很慢
檢查以下內容:
- 確保您的CUDA /驅動程序版本不太舊。
- 用
USE_CUDNN=1
。構建。這通常會使速度提高50+%。嘗試使用最新版本。 - 運行前設置
export MXNET_CUDNN_AUTOTUNE_DEFAULT=1
。這通常會使速度提高10%-15%。 - 如果您使用的是
nvidia-smi -e 0
的Tesla GPU,請禁用ECC。 您可能需要root權限並且必須重新啓動。 - 對於
nvidia-smi -ac ??
的Tesla卡,設置爲最大時鐘。 - 無節流原因
nvidia-smi -q -d PERFORMANCE
通常由溫度引起。
使用多個GPU或計算機時速度沒有增加
檢查以下內容:
- 您的神經網絡是否已經快速運行,例如>1000 example/sec 或者 >10 batches/sec?如果是,由於通信開銷,通過添加更多資源不太可能進一步加速。
- 您使用的是小批量嗎?嘗試增加它。
- 您使用的GPU超過4個嗎?嘗試使用
--kv-store=device
。