1. 程式人生 > >Windows10上使用VS2017編譯MXNet原始碼操作步驟(C++)

Windows10上使用VS2017編譯MXNet原始碼操作步驟(C++)

MXNet是一種開源的深度學習框架,核心程式碼是由C++實現。MXNet官網推薦使用VS2015或VS2017編譯,因為原始碼中使用了一些C++14的特性,VS2013是不支援的。這裡通過VS2017編譯,步驟如下:

1. 編譯OpenCV,版本為3.4.2,可參考 https://blog.csdn.net/fengbingchun/article/details/78163217 ,注意加入opencv_contrib模組;

2. 編譯OpenBLAS,版本為0.3.3,可參考: https://blog.csdn.net/fengbingchun/article/details/55509764

  ;

3. 編譯dmlc-core,版本為0.3:

4. 下載mshadow,注意不能是1.0或1.1版本,只能是master,因為它們的檔名不一致,裡面僅有標頭檔案;

5. 編譯tvm,版本為0.4,在編譯MXNet時,目前僅需要nnvm/src下的c_api, core, pass三個目錄的檔案參與:

6. 編譯dlpack,版本為最新master,commit為bee4d1d,

7. 編譯MXNet,版本為1.3.0:

8. 使用mxnet/cpp-package/scripts/OpWrapperGenerator.py生成mxnet/cpp-package/include/mxnet-cpp目錄下的op.h檔案操作步驟:

(1). 將lib/rel/x64目錄下的libmxnet.dll和libopenblas.dll兩個動態庫拷貝到mxnet/cpp-package/scripts/目錄下;

(2). 在mxnet/cpp-package/scripts/目錄下開啟命令列提示符,執行:

python OpWrapperGenerator.py libmxnet.dll

(3). 修改生成的op.h檔案中的兩處UpSampling函式:將引數scale,修改為int scale;將引數num_filter = 0,修改為int num_filter = 0; 注:直接通過以下命令生成op.h檔案時不用作任何修改,後面查詢下原因

git clone --recursive https://github.com/apache/incubator-mxnet

注意:

(1). 關於MXNet中的依賴庫介紹和使用可參考:https://blog.csdn.net/fengbingchun/article/details/84981969

(2). 為了正常編譯整個工程,部分原始碼作了微小的調整;

(3). 所有專案依賴的版本如下:

1. OpenBLAS:
	commit: fd8d186
	version: 0.3.3
	date: 2018.08.31
	url: https://github.com/xianyi/OpenBLAS/releases
2. dlpack:
	commit: bee4d1d
	version: master
	date: 2018.08.24
	url: https://github.com/dmlc/dlpack
3. mshadow:
	commit: 2e3a895
	version: master
	date: 2018.11.08
	url: https://github.com/dmlc/mshadow
4. dmlc-core:
	commit: 85c1180
	version: 0.3
	date: 2018.07.18
	url: https://github.com/dmlc/dmlc-core/releases
5. tvm:
	commit: 60769b7
	version: 0.4
	date: 2018.09.04
	url: https://github.com/dmlc/tvm/releases
6. HalideIR:
	commit: a08e26e
	version: master
	date: 2018.11.28
	url: https://github.com/dmlc/HalideIR
7. mxnet:
	commit: b3be92f
	version: 1.3.0
	date: 2018.09.12
	url: https://github.com/apache/incubator-mxnet/releases

(4). 整個專案可以從https://github.com/fengbingchun/MXNet_Test clone到E:/GitCode目錄下直接編譯即可。

下面測試程式碼是用生成的MXNet.dll動態庫訓練MNIST:

#include "funset.hpp"
#include <chrono>
#include <string>
#include <fstream>
#include <vector>
#include "mxnet-cpp/MxNetCpp.h"

namespace {

bool isFileExists(const std::string &filename)
{
	std::ifstream fhandle(filename.c_str());
	return fhandle.good();
}

bool check_datafiles(const std::vector<std::string> &data_files)
{
	for (size_t index = 0; index < data_files.size(); index++) {
		if (!(isFileExists(data_files[index]))) {
			LG << "Error: File does not exist: " << data_files[index];
			return false;
		}
	}
	return true;
}

bool setDataIter(mxnet::cpp::MXDataIter *iter, std::string useType, const std::vector<std::string> &data_files, int batch_size)
{
	if (!check_datafiles(data_files))
		return false;

	iter->SetParam("batch_size", batch_size);
	iter->SetParam("shuffle", 1);
	iter->SetParam("flat", 1);

	if (useType == "Train") {
		iter->SetParam("image", data_files[0]);
		iter->SetParam("label", data_files[1]);
	} else if (useType == "Label") {
		iter->SetParam("image", data_files[2]);
		iter->SetParam("label", data_files[3]);
	}

	iter->CreateDataIter();
	return true;
}

} // namespace

////////////////////////////// mnist ////////////////////////
/* reference: 
	https://mxnet.incubator.apache.org/tutorials/c%2B%2B/basics.html
	mxnet_source/cpp-package/example/mlp_cpu.cpp
*/
namespace {

mxnet::cpp::Symbol mlp(const std::vector<int> &layers)
{
	auto x = mxnet::cpp::Symbol::Variable("X");
	auto label = mxnet::cpp::Symbol::Variable("label");

	std::vector<mxnet::cpp::Symbol> weights(layers.size());
	std::vector<mxnet::cpp::Symbol> biases(layers.size());
	std::vector<mxnet::cpp::Symbol> outputs(layers.size());

	for (size_t i = 0; i < layers.size(); ++i) {
		weights[i] = mxnet::cpp::Symbol::Variable("w" + std::to_string(i));
		biases[i] = mxnet::cpp::Symbol::Variable("b" + std::to_string(i));
		mxnet::cpp::Symbol fc = mxnet::cpp::FullyConnected(i == 0 ? x : outputs[i - 1], weights[i], biases[i], layers[i]);
		outputs[i] = i == layers.size() - 1 ? fc : mxnet::cpp::Activation(fc, mxnet::cpp::ActivationActType::kRelu);
	}

	return mxnet::cpp::SoftmaxOutput(outputs.back(), label);
}

} // namespace

int test_mnist_train()
{
	const int image_size = 28;
	const std::vector<int> layers{ 128, 64, 10 };
	const int batch_size = 100;
	const int max_epoch = 20;
	const float learning_rate = 0.1;
	const float weight_decay = 1e-2;

	std::vector<std::string> data_files = { "E:/GitCode/MXNet_Test/data/mnist/train-images.idx3-ubyte",
											"E:/GitCode/MXNet_Test/data/mnist/train-labels.idx1-ubyte",
											"E:/GitCode/MXNet_Test/data/mnist/t10k-images.idx3-ubyte",
											"E:/GitCode/MXNet_Test/data/mnist/t10k-labels.idx1-ubyte"};

	auto train_iter = mxnet::cpp::MXDataIter("MNISTIter");
	setDataIter(&train_iter, "Train", data_files, batch_size);

	auto val_iter = mxnet::cpp::MXDataIter("MNISTIter");
	setDataIter(&val_iter, "Label", data_files, batch_size);

	auto net = mlp(layers);

	mxnet::cpp::Context ctx = mxnet::cpp::Context::cpu();  // Use CPU for training

	std::map<std::string, mxnet::cpp::NDArray> args;
	args["X"] = mxnet::cpp::NDArray(mxnet::cpp::Shape(batch_size, image_size*image_size), ctx);
	args["label"] = mxnet::cpp::NDArray(mxnet::cpp::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 = mxnet::cpp::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
	mxnet::cpp::Optimizer* opt = mxnet::cpp::OptimizerRegistry::Find("sgd");
	opt->SetParam("rescale_grad", 1.0 / batch_size)->SetParam("lr", learning_rate)->SetParam("wd", weight_decay);

	// Create executor by binding parameters to the model
	auto *exec = net.SimpleBind(ctx, args);
	auto arg_names = net.ListArguments();

	// Start training
	for (int iter = 0; iter < max_epoch; ++iter) {
		int samples = 0;
		train_iter.Reset();

		auto tic = std::chrono::system_clock::now();
		while (train_iter.Next()) {
			samples += batch_size;
			auto data_batch = train_iter.GetDataBatch();
			// Set data and label
			data_batch.data.CopyTo(&args["X"]);
			data_batch.label.CopyTo(&args["label"]);

			// Compute gradients
			exec->Forward(true);
			exec->Backward();
			// Update parameters
			for (size_t i = 0; i < arg_names.size(); ++i) {
				if (arg_names[i] == "X" || arg_names[i] == "label") continue;
				opt->Update(i, exec->arg_arrays[i], exec->grad_arrays[i]);
			}
		}
		auto toc = std::chrono::system_clock::now();

		mxnet::cpp::Accuracy acc;
		val_iter.Reset();
		while (val_iter.Next()) {
			auto data_batch = val_iter.GetDataBatch();
			data_batch.data.CopyTo(&args["X"]);
			data_batch.label.CopyTo(&args["label"]);
			// Forward pass is enough as no gradient is needed when evaluating
			exec->Forward(false);
			acc.Update(data_batch.label, exec->outputs[0]);
		}
		float duration = std::chrono::duration_cast<std::chrono::milliseconds>
			(toc - tic).count() / 1000.0;
		LG << "Epoch: " << iter << " " << samples / duration << " samples/sec Accuracy: " << acc.Get();
	}

	std::string json_file{ "E:/GitCode/MXNet_Test/data/mnist.json" };
	std::string param_file{"E:/GitCode/MXNet_Test/data/mnist.params"};
	net.Save(json_file);
	mxnet::cpp::NDArray::Save(param_file, exec->arg_arrays);

	delete exec;
	MXNotifyShutdown();

	return 0;
}

執行結果如下:

GitHub: https://github.com/fengbingchun/MXNet_Test