Quantcast
Channel: 英特尔开发人员专区文章
Viewing all articles
Browse latest Browse all 583

英特尔® MKL-DNN:第二部分 – 代码示例创建与详解

$
0
0

简介

第一部分,我们介绍了面向深度神经网络的英特尔® 数学核心函数库(英特尔® MKL-DNN),MKL-DNN 是一款面向深度学习应用的开源性能库。提供了在配有英特尔处理器的电脑上安装库组件的具体步骤,要求处理器支持英特尔® 高级矢量扩展指令集 2(英特尔® AVX2)并运行 Ubuntu* 操作系统。第一部分还包括从命令行中创建 C 和 C++ 代码示例的详细信息。

第二部分将介绍如何配置集成开发环境 (IDE),以创建 C++ 代码示例,并提供基于 AlexNet* 深度学习拓扑的代码详解。本教程使用的是安装了 C/C++ 开发工具 (CDT)Eclipse Neon* IDE。(如果您的系统没有安装 Eclipse*,可以按照 Ubuntu 手册网站上的说明指定 Oracle Java* 8 和 Eclipse IDE,以便 C/C++ 开发人员选择。)

在 Eclipse IDE 中创建 C++ 示例

本章节介绍了如何在 Eclipse 中创建一个新项目,以及如何导入英特尔 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(二中取一)。

  • Properties屏幕上展开 C/C++ General并选择 Preprocessor Include Paths, Macros etc。
  • 选择 Providers选项卡,然后选择您正在使用的编译器(如 CDT GCC 内置编译器设置)。
  • 找到 Command to get compiler specs:字段并添加 -std=c++11。完成后,命令与以下部分相似:
    “${COMMAND} ${FLAGS} -E -P -v -dD “${INPUTS}” -std=c++11”.
  • 单击Apply后单击 OK(图 3)。


图 3.为项目启用 C++11(二中取二)。

将库添加至链接器设置:

  • 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项目应该出现在项目资源管理器中):

  • 在项目资源管理器中右键单击项目名称,并选择 New, Source Folder。输入文件夹名称 src并单击 Finish
  • 在项目资源管理器中右键单击 src文件夹并选择 Import…
  • Import屏幕中展开 General文件夹并突出显示 File System。单击 Next
  • File System屏幕中单击 From directory字段旁的 Browse按钮。转至包含英特尔 MKL-DNN 示例文件的地址,在本例中,地址为 /mkl-dnn/examples。单击屏幕底部的 OK
  • 返回 File System屏幕,查看 simple_net.cpp框并单击 Finish

创建 Simple_Net 项目:

  • 项目资源管理器中右键单击 simple_net项目名称。
  • 单击 Build Project并验证未出现错误。

Simple_Net 代码示例

虽然 Simple_Net 不是功能齐全的深度学习框架,但是它为神经网络拓扑模块的创建提供了基础,将模块所包含的卷积、修正线性单元 (ReLU)、本地响应标准化 (LRN) 和池化集成至单个可执行项目中。虽然此文档逐步概述了英特尔 MKL-DNN C++ API,但是 Simple_Net 代码示例提供了基于 AlexNet 拓扑的更完整的详解。因此,首先向您简要介绍 AlexNet 架构。

AlexNet 架构

“利用深度卷积神经网络进行 ImageNet 分类”文章中这样描述,AlexNet 架构包含一张输入图像 (L0) 和 8 个经过学习的层(L1 到 L8)—5 个卷积层和 3 个完全连接层。图 5 以图形方式描述了该拓扑。


图 5.AlexNet 拓扑(来源:麻省理工学院*)。

表 1 提供了关于 AlexNet 架构的其他详细信息:

类型

描述

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 示例相同,但是前者进行了重构,能够使用完全合格的英特尔 MKL-DNN 类别来增强可读性。本代码实施了拓扑的第一层 (L1)。

  1. 将 include 指令添加至库标头文件:
    	#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. 从池化 dst 中创建池化指数内存:
    	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;
    	}

结论

本教程系列的第一部分提供了几个资源,您可以利用这些资源了解英特尔 MKL-DNN 的技术预览版。还详细说明了如何安装和构建库组件。本文(教程系列的第二部分)介绍了如何配置 Eclipse 集成开发环境,以创建 C++ 代码示例,还提供了基于 AlexNet 深度学习拓扑的代码详解。敬请关注即将上市的英特尔 MKL-DNN 生产发行版。


Viewing all articles
Browse latest Browse all 583

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>