65.9K
CodeProject 正在变化。 阅读更多。
Home

Intel® MKL-DNN:第二部分 – 示例代码构建和实践

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2017 年 4 月 19 日

CPOL

7分钟阅读

viewsIcon

8472

在第 2 部分中,我们将探讨如何配置集成开发环境 (IDE) 来构建 C++ 代码示例,并基于 AlexNet 深度学习拓扑提供代码演练。

引言

第 1 部分 中,我们介绍了 Intel® Math Kernel Library for Deep Neural Networks (Intel® MKL-DNN),这是一个用于深度学习应用程序的开源性能库。我们提供了在支持 Intel® Advanced Vector Extensions 2 (Intel® AVX2) 的 Intel® 处理器上运行 Ubuntu* 操作系统的计算机上安装库组件的详细步骤。第 1 部分还涵盖了如何从命令行构建 C 和 C++ 代码示例的详细信息。

在第 2 部分中,我们将探讨如何配置集成开发环境 (IDE) 来构建 C++ 代码示例,并基于 AlexNet* 深度学习拓扑提供代码演练。在本教程中,我们将使用 Eclipse Neon* IDE 和 C/C++ Development Tools (CDT)。(如果您的系统尚未安装 Eclipse*,可以按照 Ubuntu Handbook 网站上的说明进行操作,并选择 Oracle Java* 8 和 Eclipse IDE for C/C++ Developers 选项。)

在 Eclipse IDE 中构建 C++ 示例

本节介绍如何在 Eclipse 中创建新项目并导入 Intel MKL-DNN C++ 示例代码。

在 Eclipse 中创建新项目

  • 启动 Eclipse。
  • 单击屏幕左上角的 **New**。
  • 在“Select a wizard”屏幕中,选择 **C++ Project**,然后单击 **Next**(图 1)。

图 1. 在 Eclipse 中创建新的 C++ 项目。
  • 输入 simple_net 作为项目名称。项目类型选择 **Executable, Empty Project**。工具链选择 **Linux GCC**。单击 **Next**。
  • 在“Select Configurations”屏幕中,单击 **Advanced Settings**。

为项目启用 C++11

  • 在“Properties”屏幕中,展开菜单树中的 **C/C++ Build** 选项,然后选择 **Settings**。
  • 在“Tool Settings”选项卡中,选择 **GCC C++ Compiler**,然后选择 **Miscellaneous**。
  • 在“Other flags”框中,在现有字符串末尾用空格分隔添加 **-std=c++11**(图 2)。

图 2 为项目启用 C++11(第 1 部分)。
  • 在“Properties”屏幕中,展开 **C/C++ General**,然后选择 **Preprocessor Include Paths, Macros etc.**。
  • 选择 **Providers** 选项卡,然后选择您正在使用的编译器(例如,CDT GCC Built-in Compiler Settings)。
  • 找到名为“Command to get compiler specs:”的字段,并添加 **-std=c++11**。完成后,该命令应与以下内容类似:
    "${COMMAND} ${FLAGS} -E -P -v -dD "${INPUTS}" -std=c++11"。
  • 单击 **Apply**,然后单击 **OK**(图 3)。

图 3 为项目启用 C++11(第 2 部分)。

向链接器设置添加库

  • 在“Properties”屏幕中,展开菜单树中的 **C/C++ Build** 选项,然后选择 **Settings**。
  • 在“Tool Settings”选项卡中,选择 **GCC C++ Linker**,然后选择 **Libraries**。
  • 在“Libraries (l)”部分下单击 **Add**。
  • 输入 **mkldnn**,然后单击 **OK**(图 4)。

图 4 向链接器设置添加库。

完成项目创建

  • 在“Properties”屏幕底部单击 **OK**。
  • 在“C++ Project”屏幕底部单击 **Finish**。

添加 C++ 源文件(注意:此时“simple_net”项目应出现在 Project Explorer 中)

  • 在 Project Explorer 中右键单击项目名称,选择 **New**、**Source Folder**。输入 src 作为文件夹名称,然后单击 **Finish**。
  • 在 Project Explorer 中右键单击“src”文件夹,选择 **Import…**
  • 在“Import”屏幕中,展开 **General** 文件夹,然后高亮显示 **File System**。单击 **Next**。
  • 在“File System”屏幕中,单击“From directory”字段旁边的 **Browse** 按钮。导航到包含 Intel MKL-DNN 示例文件的位置,在本例中为 /mkl-dnn/examples。单击屏幕底部的 **OK**。
  • 回到“File System”屏幕,选中 **simple_net.cpp** 复选框,然后单击 **Finish**。

构建 Simple_Net 项目

  • Project Explorer 中右键单击项目名称 **simple_net**。
  • 单击 **Build Project**,并验证是否出现任何错误。

Simple_Net 代码示例

虽然 Simple_Net 不是一个功能齐全的深度学习框架,但它提供了构建包含卷积、ReLU(整流线性单元)、LRN(局部响应归一化)和池化操作的神经网络拓扑块的基本功能,所有这些都在一个可执行项目中。Intel MKL-DNN C++ API 的简要分步说明在 文档 中提供;然而,Simple_Net 代码示例基于 AlexNet 拓扑提供了更完整的演练。因此,我们将首先简要概述 AlexNet 架构。

AlexNet 架构

正如论文 ImageNet Classification with Deep Convolutional Neural Networks 中所述,AlexNet 架构包含一个输入图像 (L0) 和八个学习层(L1 至 L8)—五个卷积层和三个全连接层。此拓扑在图 5 中以图形方式显示。

图 5 AlexNet 拓扑(来源:MIT*)。

表 1 提供了 AlexNet 架构的更多详细信息

Layer

类型

描述

L0

输入图像

尺寸:227 x 227 x 3(图示为 227 x 227 x 3)

L1

卷积

尺寸:55* x 55 x 96

  • 96 个卷积核,尺寸 11 × 11
  • 步长 4
  • 填充 0

*尺寸 = (N - F)/S + 1 = (227 - 11)/4 + 1 = 55

-

最大池化

尺寸:27* x 27 x 96

  • 96 个卷积核,尺寸 3 × 3
  • 步长 2

*尺寸 = (N - F)/S + 1 = (55 – 3)/2 + 1 = 27

L2

卷积

尺寸:27 x 27 x 256

  • 256 个卷积核,尺寸 5 x 5
  • 步长 1
  • 填充 2

-

最大池化

尺寸:13* x 13 x 256

  • 256 个卷积核,尺寸 3 × 3
  • 步长 2

*尺寸 = (N - F)/S + 1 = (27 - 3)/2 + 1 = 13

L3

卷积

尺寸:13 x 13 x 384

  • 384 个卷积核,尺寸 3 × 3
  • 步长 1
  • 填充 1

L4

卷积

尺寸:13 x 13 x 384

  • 384 个卷积核,尺寸 3 × 3
  • 步长 1
  • 填充 1

L5

卷积

尺寸:13 x 13 x 256

  • 256 个卷积核,尺寸 3 × 3
  • 步长 1
  • 填充 1

-

最大池化

尺寸:6* x 6 x 256

  • 256 个卷积核,尺寸 3 × 3
  • 步长 2

*尺寸 = (N - F)/S + 1 = (13 - 3)/2 + 1 = 6

L6

全连接

4096 个神经元

L7

全连接

4096 个神经元

L8

全连接

1000 个神经元

表 1. AlexNet 层描述。

卷积神经网络和 AlexNet 拓扑的详细描述超出了本教程的范围,但如果需要更多信息,读者可以参考以下链接。

Simple_Net 代码演练

下面提供的源代码本质上与存储库中包含的 Simple_Net 示例相同,只是进行了重构以使用完全限定的 Intel MKL-DNN 类型来增强可读性。此代码实现了拓扑的第一层(L1)。

  1. 添加库头文件的包含指令
    	#include "mkldnn.hpp"
  2. 初始化 CPU 引擎,索引为 0
    	auto cpu_engine = mkldnn::engine(mkldnn::engine::cpu, 0);
  3. 分配数据并创建张量结构
    	const uint32_t batch = 256;
    	std::vector<float> net_src(batch * 3 * 227 * 227);
    	std::vector<float> net_dst(batch * 96 * 27 * 27);
    
    	/* AlexNet: conv
    	 * {batch, 3, 227, 227} (x) {96, 3, 11, 11} -> {batch, 96, 55, 55}
    	 * strides: {4, 4}
    	 */
    	mkldnn::memory::dims conv_src_tz = {batch, 3, 227, 227};
    	mkldnn::memory::dims conv_weights_tz = {96, 3, 11, 11};
    	mkldnn::memory::dims conv_bias_tz = {96};
    	mkldnn::memory::dims conv_dst_tz = {batch, 96, 55, 55};
    	mkldnn::memory::dims conv_strides = {4, 4};
    	auto conv_padding = {0, 0};
    
    	std::vector<float> conv_weights(std::accumulate(conv_weights_tz.begin(),
    		conv_weights_tz.end(), 1, std::multiplies<uint32_t>()));
    
    	std::vector<float> conv_bias(std::accumulate(conv_bias_tz.begin(),
    		conv_bias_tz.end(), 1, std::multiplies<uint32_t>()));
  4. 为用户数据创建内存
    	auto conv_user_src_memory = mkldnn::memory({{{conv_src_tz},
    		mkldnn::memory::data_type::f32,
    		mkldnn::memory::format::nchw}, cpu_engine}, net_src.data());
    
    	auto conv_user_weights_memory = mkldnn::memory({{{conv_weights_tz},
    		mkldnn::memory::data_type::f32, mkldnn::memory::format::oihw},
    		cpu_engine}, conv_weights.data());
    
    	auto conv_user_bias_memory = mkldnn::memory({{{conv_bias_tz},
    		mkldnn::memory::data_type::f32, mkldnn::memory::format::x}, cpu_engine},
    	    conv_bias.data());
  5. 使用卷积数据格式的通配符 any 创建卷积数据内存描述符(这使得卷积原始操作能够选择最适合其输入参数的数据格式—卷积核大小、步长、填充等)
    	auto conv_src_md = mkldnn::memory::desc({conv_src_tz}, 
    		mkldnn::memory::data_type::f32,
    		mkldnn::memory::format::any);
    
    	auto conv_bias_md = mkldnn::memory::desc({conv_bias_tz},
    		mkldnn::memory::data_type::f32,
    		mkldnn::memory::format::any);
    
    	auto conv_weights_md = mkldnn::memory::desc({conv_weights_tz},
    		mkldnn::memory::data_type::f32, mkldnn::memory::format::any);
    
    	auto conv_dst_md = mkldnn::memory::desc({conv_dst_tz}, 
    		mkldnn::memory::data_type::f32,
    		mkldnn::memory::format::any);
  6. 通过指定算法、传播类型、输入、权重、偏置、输出的形状以及卷积步长、填充和填充类型来创建卷积描述符
    	auto conv_desc = mkldnn::convolution_forward::desc(mkldnn::prop_kind::forward,
    		mkldnn::convolution_direct, conv_src_md, conv_weights_md, conv_bias_md,
    		conv_dst_md, conv_strides, conv_padding, conv_padding,
    		mkldnn::padding_kind::zero);
  7. 创建卷积原始操作的描述符。创建后,此描述符具有特定格式,而不是卷积描述符中指定的任何通配符格式
    	auto conv_prim_desc =
    		mkldnn::convolution_forward::primitive_desc(conv_desc, cpu_engine);
  8. 创建表示网络的原始操作向量
    	std::vector<mkldnn::primitive> net;
  9. 如果需要,在用户和数据之间创建重排序,并在卷积之前将其添加到网络
    	auto conv_src_memory = conv_user_src_memory;
    	if (mkldnn::memory::primitive_desc(conv_prim_desc.src_primitive_desc()) !=
    	conv_user_src_memory.get_primitive_desc()) {
    
    		conv_src_memory = mkldnn::memory(conv_prim_desc.src_primitive_desc());
    
    		net.push_back(mkldnn::reorder(conv_user_src_memory, conv_src_memory));
    	}
    
    	auto conv_weights_memory = conv_user_weights_memory;
    	if (mkldnn::memory::primitive_desc(conv_prim_desc.weights_primitive_desc()) !=
    			conv_user_weights_memory.get_primitive_desc()) {
    
    		conv_weights_memory = 
    			mkldnn::memory(conv_prim_desc.weights_primitive_desc());
    
    		net.push_back(mkldnn::reorder(conv_user_weights_memory, 
    			conv_weights_memory));
    	}
    
    	auto conv_dst_memory = mkldnn::memory(conv_prim_desc.dst_primitive_desc());
  10. 创建卷积原始操作并将其添加到网络
    	net.push_back(mkldnn::convolution_forward(conv_prim_desc, conv_src_memory,
    		conv_weights_memory, conv_user_bias_memory, conv_dst_memory));
  11. 创建 ReLU 原始操作并将其添加到网络
    	/* AlexNet: relu
    	 * {batch, 96, 55, 55} -> {batch, 96, 55, 55}
    	 */
    	const double negative_slope = 1.0;
    	auto relu_dst_memory = mkldnn::memory(conv_prim_desc.dst_primitive_desc());
    
    	auto relu_desc = mkldnn::relu_forward::desc(mkldnn::prop_kind::forward,
    	conv_prim_desc.dst_primitive_desc().desc(), negative_slope);
    
    	auto relu_prim_desc = mkldnn::relu_forward::primitive_desc(relu_desc, cpu_engine);
    
    	net.push_back(mkldnn::relu_forward(relu_prim_desc, conv_dst_memory,
    	relu_dst_memory));
  12. 创建 AlexNet LRN 原始操作
    	/* AlexNet: lrn
    	 * {batch, 96, 55, 55} -> {batch, 96, 55, 55}
    	 * local size: 5
    	 * alpha: 0.0001
    	 * beta: 0.75
    	 */
    	const uint32_t local_size = 5;
    	const double alpha = 0.0001;
    	const double beta = 0.75;
    
    	auto lrn_dst_memory = mkldnn::memory(relu_dst_memory.get_primitive_desc());
    
    	/* create lrn scratch memory from lrn src */
    	auto lrn_scratch_memory = mkldnn::memory(lrn_dst_memory.get_primitive_desc());
    
    	/* create lrn primitive and add it to net */
    	auto lrn_desc = mkldnn::lrn_forward::desc(mkldnn::prop_kind::forward, 
    		mkldnn::lrn_across_channels,
    	conv_prim_desc.dst_primitive_desc().desc(), local_size,
    		alpha, beta);
    
    	auto lrn_prim_desc = mkldnn::lrn_forward::primitive_desc(lrn_desc, cpu_engine);
    
    	net.push_back(mkldnn::lrn_forward(lrn_prim_desc, relu_dst_memory,
    	lrn_scratch_memory, lrn_dst_memory));
  13. 创建 AlexNet 池化原始操作
    	/* AlexNet: pool
    	* {batch, 96, 55, 55} -> {batch, 96, 27, 27}
    	* kernel: {3, 3}
    	* strides: {2, 2}
    	*/
    	mkldnn::memory::dims pool_dst_tz = {batch, 96, 27, 27};
    	mkldnn::memory::dims pool_kernel = {3, 3};
    	mkldnn::memory::dims pool_strides = {2, 2};
    	auto pool_padding = {0, 0};
    
    	auto pool_user_dst_memory = mkldnn::memory({{{pool_dst_tz},
    		mkldnn::memory::data_type::f32,
    		mkldnn::memory::format::nchw}, cpu_engine}, net_dst.data());
    
    	auto pool_dst_md = mkldnn::memory::desc({pool_dst_tz},
    			mkldnn::memory::data_type::f32,
    		mkldnn::memory::format::any);
    
    	auto pool_desc = mkldnn::pooling_forward::desc(mkldnn::prop_kind::forward,
    		mkldnn::pooling_max, lrn_dst_memory.get_primitive_desc().desc(), pool_dst_md, pool_strides, pool_kernel, pool_padding, pool_padding,mkldnn::padding_kind::zero);
    
    	auto pool_pd = mkldnn::pooling_forward::primitive_desc(pool_desc, cpu_engine);
    	auto pool_dst_memory = pool_user_dst_memory;
    
    	if (mkldnn::memory::primitive_desc(pool_pd.dst_primitive_desc()) !=
    			pool_user_dst_memory.get_primitive_desc()) {
    		pool_dst_memory = mkldnn::memory(pool_pd.dst_primitive_desc());
    	}
  14. 从池化目标创建池化索引内存
    	auto pool_indices_memory = 
    		mkldnn::memory(pool_dst_memory.get_primitive_desc());
  15. 创建池化原始操作并将其添加到网络
    	net.push_back(mkldnn::pooling_forward(pool_pd, lrn_dst_memory,
    		pool_indices_memory, pool_dst_memory));
  16. 如果需要,在内部数据和用户数据之间创建重排序,并在池化之后将其添加到网络
    	if (pool_dst_memory != pool_user_dst_memory) {
        	net.push_back(mkldnn::reorder(pool_dst_memory, pool_user_dst_memory));
    	}
  17. 创建流,提交所有原始操作,并等待完成
    	mkldnn::stream(mkldnn::stream::kind::eager).submit(net).wait();
  18. 上述代码包含在 simple_net() 函数中,该函数在 main 中带有异常处理被调用
    	int main(int argc, char **argv) {
    	    try {
    	        simple_net();
    	    }
    	    catch(mkldnn::error& e) {
    	        std::cerr << "status: " << e.status << std::endl;
    	        std::cerr << "message: " << e.message << std::endl;
    	    }
    	    return 0;
    	}

结论

本教程系列的第 1 部分 确定了学习 Intel MKL-DNN 技术预览版的几个资源。还提供了安装和构建库组件的详细说明。在本篇(本教程系列的第 2 部分)中,提供了关于如何配置 Eclipse 集成开发环境以构建 C++ 代码示例的信息,以及基于 AlexNet 深度学习拓扑的代码演练。敬请关注 Intel MKL-DNN 即将发布的生产版本。

© . All rights reserved.