Linux MXNet编译安装 C++

一、介绍

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章