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

借助英特尔® 数据分析加速库改进支持向量机

$
0
0

借助英特尔® 数据分析加速库改进支持向量机的性能

简介

在互联网广泛普及的情况下,文本分类已成为处理和管理文本数据的重要方式。文本分类用于对新闻报道分类和查找 Web 上的信息。例如,为搜索 Web 上的照片或区分马和狮子,必须采用相关机制对图片进行识别和分类。对文本或图片分类非常耗时。这种分类是一种出色的机器学习功能。[1]

本文描述了一种名为支持向量机的分类机器学习算法[2],以及英特尔® 数据分析加速库(英特尔® DAAL)[3]可如何帮助优化该算法,使其以更高性能在搭载英特尔® 至强® 处理器的系统上运行。

什么是支持向量机?

支持向量机 (SVM) 是一种监督式机器学习算法。它可用于分类和回归。

在实施分类时,SVM 会查找分离不同类别的一组对象的超平面[4]。超平面选择应最大限度增加两个类别之间的边际,以减少噪音和提高结果的准确性。边际上的向量称为支持向量。支持向量指边际上的数据点。

图 1 显示了 SVM 进行对象分类的过程:

Improving Performance - Fig 1
图 1:借助支持向量机进行对象分类。

类别有两种:绿色和紫色。超平面能够分离这两个类别。位于超平面左侧的对象被归为绿色类别,位于超平面右侧的对象属于紫色类别。

如上所述,我们需要最大限度增加差数 H(两个边际之间的距离),以减少噪音,提高预测的准确性。

Improving Performance - Equation

为最大限度增加差数 H,我们需要最大限度减小 |W|。

我们还需要确保两个边际之间没有数据点。为此,需满足下列条件:

xi •w+b ≥ +1 when yi =+1

xi •w+b ≤ –1 when yi =–1

上述条件可重写至:

yi (xi •w) ≥ 1

到目前为止,我们将超平面作为二维空间中的平面或线条进行了讨论。然而,在现实中,情况并不总是这样。多数情况下,超平面为曲线而非直线状态,如图 2 所示。

Improving Performance - Fig 2
图 2:曲线超平面。

为便于说明,假设我们在二维空间中工作。此时,超平面为曲线。如需将曲线转变为直线,我们可以提高整个空间的维度。通过引入第三维 z 将空间转变为三维空间怎么样?

Improving Performance - Fig 3
图 3:引入第三维 z。

将数据置于更高维度空间以在更高维度下创建直线或平面的技术称为核技巧[5]。

Improving Performance - Fig 4
图 4:使用核技巧在更高维度下创建直线或平面

SVM 的应用

SVM 可用于:

  • 对文本和超文本分类。
  • 对图像分类。
  • 识别手写字符。

SVM 的优势和劣势

使用 SVM 有优势也有劣势:

  • 优势
    • 借助清晰的分离边际高效运行。
    • 高维度空间可提升效率。
    • 将训练点子集用于决策函数(称为支持向量),因此还可节省内存。
  • 劣势
    • 花费大量时间训练大型数据集。
    • 目标类别之间未清晰分离,因此无法高效运行。

英特尔® 数据分析加速库

英特尔 DAAL 是一个包含众多优化的基本构建模块的库,适合数据分析和机器学习。这些基本构建模块经过高度优化,能适应最新版英特尔® 处理器的最新特性。如欲了解关于英特尔 DAAL 的更多信息,请查看 [7]。SVM 分类器是一种由 DAAL 提供的分类算法。在本文中,我们利用英特尔 DAAL 的 Python* API 来构建基本的 SVM 分类器。请按照 [10]中的说明安装它。

在英特尔数据分析加速库中使用支持矢量机算法

本节展示如何使用英特尔 DAAL 在 Python [9] 中调用 SVM 算法 [8]。

执行以下步骤,从英特尔 DAAL 中调用 SVM 算法:

  1. 输入如下命令以导入必要的封装,并导入

    1. 执行如下命令以导入 Numpy:
      import numpy as np
    2. 执行如下命令,导入英特尔 DAAL 数值表:
      from daal.data_management import HomogenNumericTable
    3. 输入如下命令,导入 SVM 算法:
      from daal.algorithms.svm import training, prediction
      from daal.algorithms import classifier, kernel_function
      import daal.algorithms.kernel_function.linear
  2. 创建函数,把输入数据集划分为训练数据、标签和测试数据。

    输入数据集阵列大体上被分为两个阵列。例如,把 100 行的数据集分为 80 行和 20行:对 80% 的数据进行训练,对 20% 的数据进行测试。阵列输入数据集的前 80 行是训练数据,剩下的 20 行是测试数据。

  3. 重构训练和测试数据集,以供英特尔 DAAL 读取。

    输入如下命令以重新格式化数据(我们将 trainLabelstestLabels视为 n-by-1 表,其中 n 是对应数据集的行数:

    trainInput = HomogenNumericTable(trainingData)
    trainLabels = HomogenNumericTable(
    trainGroundTruth.reshape(trainGroundTruth.shape[0],1))

    testInput = HomogenNumericTable(testingData)
    testLabels = HomogenNumericTable(testGroundTruth.reshape(testGroundTruth.shape[0],1))

    其中

    trainInput:训练数据被重新格式化为英特尔 DAAL 数值表。

    trainLabels:训练标签被重新格式化为英特尔 DAAL 数值表。

    testInput:测试数据被重新格式化为英特尔 DAAL 数值表。

    testLabels:测试标签被重新格式化为英特尔 DAAL 数值表。

  4. 创建函数,以训练模型。

    1. 首先创建一个算法对象,以训练模型,请输入如下命令:
      algorithm = training.Batch_Float64DefaultDense(nClasses)

    2. 输入如下命令,将训练数据和标签传输到算法:
      algorithm.input.set(classifier.training.data,trainInput)
      algorithm.input.set(classifier.training.labels,trainLabels)

      其中
      algorithm:在上述步骤 a 中定义的算法对象。
      trainInput:训练数据。
      trainLabels:训练标签。
    3. 输入如下命令以训练模型:
      Model = algorithm.compute()
      其中
      algorithm:在上述步骤 a 中定义的算法对象。
  5. 创建函数,以测试模型。

    1. 首先,输入如下命令,创建一个算法对象,以测试/预测模型:
      algorithm = prediction.Batch_Float64DefaultDense(nClasses)
    2. 输入如下命令,将测试数据和训练模型传输到模型:
      algorithm.input.setTable(classifier.prediction.data, testInput) algorithm.input.setModel(classifier.prediction.model, model.get(classifier.training.model))
      其中
      algorithm:在上述步骤 a 中定义的算法对象。
      testInput:测试数据。
      model:模型对象的名称。

    3. 输入如下命令以测试/预测模型:Prediction = algorithm.compute()
      其中
      algorithm:在上述步骤 a 中定义的算法对象。
      prediction:包含测试数据预测标签的预测结果。

结论

SVM 是一种强大的分类算法。借助清晰的分离边际,它可高效运行。英特尔 DAAL 优化了 SVM 算法。通过使用英特尔 DAAL,开发人员可充分利用未来更多代英特尔® 至强® 处理器中的全新特性,无需费心修改他们的应用。他们只需将其应用链接到英特尔 DAAL 的最新版。

 

参考资料

  1. 维基百科 —— 机器学习

  2. 支持矢量机

  3. 英特尔 DAAL 简介

  4. 超平面简介

  5. 核技巧

  6. 英特尔数据分析加速库简介

  7. 支持矢量机算法

  8. Python 网站

  9. 如何在 Linux* 中安装英特尔 DAAL 的 Python 版本。


英特尔® 数学核心函数库(英特尔® MKL)新添免费选择,依靠自己,免版税

$
0
0

所有用户均可以在 Windows*、Linux* 或 OS X* 上免费获取英特尔® 数学核心函数库(英特尔® MKL),它是一款面向 x86 和 x86-64 的高性能数学库(单击此处注册并下载)。只有在英特尔® Parallel Studio XE中获取英特尔® 卓越支持(英特尔提供的 1:1 直接私人支持)、数学核心函数库的早期版本或其他工具时,需要购买。英特尔将继续积极开发和支持这个强大的库,使每个人都能从中受益!

英特尔® 数学核心函数库(英特尔® MKL)是一款常用的英特尔库产品,显著加快数学处理例程,提高应用的性能。英特尔® MKL 包括高度矢量化和线程化的线性代数、快速傅里叶变换 (FFT)、矢量数学和统计函数。使用精心优化的计算数学库是充分利用所有处理功能的最轻松途径,即使是最佳的编译器也无法与采用人工优化的函数库性能相媲美。如果您的应用已经采用了 BLAS 或 LAPACK 功能,只需重新连接英特尔® MKL,在英特尔和兼容架构上实现更出色的性能。

英特尔® MKL通常通过英特尔® 编译器获得,也可以在各种英特尔产品中通过其他英特尔® 性能库获得。通过购买英特尔® Parallel Studio XE,利用分析、调试和微调工具、面向 MPI 的工具和英特尔® MPI 库获取英特尔® MKL。您知道吗,部分上述工具可以免费获取?

以下指南提供了免费获取 最新版英特尔® 数学核心函数库(英特尔® MKL)的各种方法,无需访问英特尔® 卓越支持(通过在英特尔数学核心函数库论坛上发帖获取支持)。您可以随时在世界各地购买全套工具(英特尔® Parallel Studio XE ),获取在线服务中心的优先支持并访问前代库。

面向哪些人?免费的范围是什么?信息从何处获取?
面向所有人的社区许可

英特尔® 数学核心函数库(英特尔® NKL - Linux*、Windows* 或 OS X* 版)

英特尔® 数据分析加速库
(英特尔® DAAL - Linux*、Windows* 或 OS X* 版)

英特尔® 线程构建模块
(英特尔® TBB - Linux*、Windows* 或 OS X* 版)

英特尔® 集成性能基元(英特尔® IPP - Linux*、Windows* 或 OS X* 版)

英特尔® 性能库社区许可 – 面向所有人免费、需要注册、无版税、对公司或项目规模没有限制、最新版的库、无法访问英特尔卓越支持。

讨论和支持论坛面向所有人开放:

英特尔性能库社区许可
面向所有人的评估版

英特尔® 数学核心函数库(英特尔® MKL)
以及编译器、库和分析工具(覆盖面非常广!

评估版 – 先试后买。

(Linux、Windows 或 OS X 版)

先试后买
学术研究人员使用

Linux, Windows 或 OS X 版

英特尔® 数学核心函数库

英特尔® 数据 分析加速库

英特尔® 线程构建模块

英特尔® 集成性能基元

英特尔® MPI 库(OS X 不可用)

与高等教育机构的学术研究结合使用。

(Linux、Windows 或 OS X 版,在 OS X 上不受支持的英特尔® MPI 库除外)

供学术研究人员使用
学生

英特尔® 数学核心函数库(英特尔® MKL)
以及编译器、库和分析工具(覆盖范围非常广!

面向学位授予机构中的学生

(Linux、Windows 或 OS X 版)

供学生使用
教师

英特尔® 数学核心函数库(英特尔® MKL)
以及编译器、库和分析工具(覆盖范围非常广!

在教学课程中使用。

(Linux、Windows 或 OS X 版)

供教育人员使用
面向
开源工作者

英特尔® 数学核心函数库(英特尔® MKL)
以及
面向 Linux 的全部英特尔® Parallel Studio XE 专业版

如果您是一名积极为开源项目贡献力量的开发人员 – 这款工具是您的绝佳选择。

(Linux 版本)

供开源工作者使用

面向部分用户的免费许可是我们产品的一个重要方面。英特尔出售优质的工具,为购买英特尔工具的软件开发人员提供一流的支持,这是英特尔的独特优势。我们提供多种选择,并希望您能从中 准确找到自己所需的产品。

 

在 Matlab* 上使用英特尔® 数据分析加速库

$
0
0

英特尔®数据分析加速库(英特尔® DAAL)是一种高性能库,它提供了丰富的算法集,从面向数据集的最基本的描述统计,到更高级的数据挖掘和机器学习算法。它可以帮助开发人员轻松地开发高度优化的大数据算法。该加速库支持众多常见的数据平台,例如 Hadoop*、Spark*, R和 Matlab*。

Matlab* 是一种多模式数值计算和交互式软件,广泛用于解决各种设计和科学问题。

本文旨在为 Matlab* 和英特尔 DAAL 开发人员展示如何使用来自 Matlab* 的英特尔 DAAL。

前提条件:

在您的系统上安装英特尔® DAAL

安装 C++ 编译器,例如 Microsoft Visual Studio* 2015 (MSVS 2015);

在系统上安装 Matlab*;

注意:本文使用 MATLAB R2015b 和面向 Windows 的英特尔 DAAL 2017 进行测试

原则:通过 MexFunction 链接 Matlab 和英特尔 DAAL 函数

Matlab 提供了一些机制,支持开发人员连接用其它语言编写的程序,例如 C++、Fortran 等。与 C++ 语言的基本连接是通过一个被称为“mexFunction”的 C++ 函数实现的,该函数由Matlab MEX 库提供。.通过在 *.c 或 *.cppBy 文件中创建“mexFunction”,能够在 Matlab* 平台中编译和调用函数,就像在函数中构建那样。*.c 文件被称为 MEX 文件,函数名称为 Mex 文件名。英特尔 DAAL 具有 C++、Java 和 Python 接口。我们将使用这种机制从 Matlab* 调用英特尔 DAAL C++ 函数。

第一部分:C++ 端

cpp 文件可使用 C++ 编辑器编写,例如 MSVS2015;像记事本这样的任意 C 语言编辑器,或者 Matlab* IDE 中

第一步:在 cpp 中编写基本的 mexFunction

创建“mexFunction

#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]){...;}

在这里,mxArray *prhs[] 能够接受来自 Matlab 工作区的输入。

mxArray *plhs[] 能够将函数计算结果传至 Matlab 工作区。

在函数中,我们可以添加英特尔 DAAL 算法,例如获得数据集的绝对值。

   /* retrieve the input data from data source */
    DataSource<xxx> dataSource(...);
    dataSource.loadDataBlock();
   /* Create an algorithm */
    abs::Batch<float> algorithm;
    /* Set an input object for the algorithm */
    algorithm.input.set(abs::data, dataSource.getNumericTable());
    /* Compute Abs function */
    algorithm.compute();
    /* Print the results of the algorithm */
    services::SharedPtr<abs::Result> res = algorithm.getResult();

请在英特尔 DAAL 开发人员指南中查看完整的c++ 示例

下面是 C 语言中 mexFunction 的基本结构。

第二步:输入并转换矩阵(可选)

正如上面的定义所示,函数 mexFunction 能够简化 MEX 文件与 Matlab 工作区之间的数据传输(通过 mxArray)。在有些情况下,可以直接读取外部输入数据源,并传输至函数中的英特尔 DAAL 算法。但有时候,用户希望将 Matlab 矩阵传输至英特尔 DAAL 函数。这时我们可以考虑转换 mxArray 和 C 中普通阵列之间的矩阵。

函数mxGetpr()能够读取 Matlab* 输入的矩阵。但是这会出现严重的问题,mxGetpr()的矩阵按照以列为主的顺序存储,而 C 阵列默认情况下使用以行为主的顺序。例如在 mexFunction 函数中,prhs[] 指输入矩阵结构 mxArray,输入矩阵按照以列为主的顺序存储,即:通用矩阵

1 2 3

4 5 6

7 8 9

作为 [1 4 7 2 5 8 3 6 9] 存储在 prhs[] 中

但是,C 语言中的默认读取顺序是以行为主,也就是说,将阵列转换为矩阵时,

[1 4 7 2 5 8 3 6 9](尺寸为 3*3)将被读取为:

1 4 7

2 5 8

3 6 9

因此,您需要考虑转置 Matlab* 中的输入矩阵数据,或者在 *.cpp 文件中的 mexFunction 中进行转换,或者通过其他读取方式来支持英特尔 DAAL 算法。

Matlab 中的一段代码示例如下所示:

input1=input1';
[output1,...]=yourfunctionname(input1,...)
output1=output1';

*请记住,每次输入一个新的矩阵时,必须对其进行转换。(mexArray 到 C 阵列)

*如果输入矩阵还需要在 Matlab* 中处理,则必须转换为以列为主的顺序。(C 阵列到 mexArray)

*请记住,每次输出一个矩阵时,必须对其进行转换。(C 阵列到 mexArray)

第三步:定义英特尔® DAAL 函数输入和输出

在英特尔® DAAL 计算中,要求 NumericTable 类作为算法输入。因此,在设置算法输入之前,输入矩阵应转换为 NumericTable,以便被英特尔® DAAL 算法识别。

下面显示了从 inputMatrix(第二步处理的 C++ 矩阵)到 inputData(DAAL 的 NumericTable)的转换:

SharedPtr<NumericTable>inputData = SharedPtr<NumericTable>(new Matrix<double>(number_of_colome, number_of_row, inputMatrixPtr))

以下是针对 Abs 算法设置输入的代码示例。inputMatrix 来自 Matlab 工作区。

#include "daal.h"
#include "mex.h"
#include "matrix.h"

using namespace std;
using namespace daal;
using namespace daal::services;
using namespace daal::data_management;
using namespace daal::algorithms;
using namespace daal::algorithms::math;
#define inputA prhs[0]
#define outputA plhs[0]
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
	/*Input Arguments Check if needed*/
	if (!mxIsDouble(inputA) || mxIsComplex(inputA))
	{	mexErrMsgIdAndTxt("Sample:prhs", "Input matrix must be double");}
    */
	/*Defination*/
	mwSize nrow, ncol;
	double *pxtrain;
	double *poutA;
	/*Define the Size and Pointer of Input Matrix*/
	nrow = mxGetM(inputA);
	ncol = mxGetN(inputA);
	pxtrain = mxGetPr(inputA);

	/*Convert MexArray to C Array (Matlab to C++) if needed*/
	//matrix_cr_conv(pxtrain, ncol, nrow);

	/*Create an Intel DAAL NumericTable*/
	SharedPtr<NumericTable>inputdataA = SharedPtr<NumericTable>(new Matrix<double>(ncol, nrow, pxtrain));

	/* Create an algorithm */
	abs::Batch<double> abs;

	/* Set an input object for the algorithm */
	abs.input.set(abs::data, inputdataA);

	/* Compute Abs function */
	abs.compute();

	/*Define Output Pointer*/
	SharedPtr<Matrix<double>>result = staticPointerCast<Matrix<double>, NumericTable>(abs.getResult()->get(abs::value));

	/*Create Output Matlab Matrix*/
	outputA = mxCreateDoubleMatrix(nrow,ncol, mxREAL);

	/*Define Output Pointer*/
	poutA = mxGetPr(outputA);
	int i;
	for (i = 0; i < nrow*ncol; i++) {
		poutA[i] = (*result)[0][i];
	}
	/*Convert C Array to MexArray (C++ to Matlab)* if needed/
	//matrix_cr_conv(poutA, nrow, ncol);
}

如欲了解关于英特尔® DAAL 编程的更多信息,请参考 daal_ur_guides 和示例:

https://software.intel.com/zh-cn/daal-programming-guide

要获得并输出结果,首先必须从算法(请参考指南)获得结果,然后创建输出矩阵,最后定义算法结果的输出指示器。

此外,不要忘了上文提及的转置输出矩阵。

关于调试

如果要在 C++ 编译器 IDE 中调试 *.cpp 文件,不仅需要设置英特尔® DAAL 环境参数,还需要设置 Matlab* 环境参数。

包括路径:%MATLAB ROOT%\extern\include;

库路径:

%MATLAB ROOT%\lib\win64;%MATLAB ROOT%\extern\lib\win64\microsoft;

路径:%MATLAB ROOT%\bin\win64;

和 libmx.lib; libmex.lib; libmat.lib;

应添加至额外的依赖性(配置属性>>Linker>>输入>> 额外属性)

 

第二部分:Matlab*

创建 *.cpp 文件后,便可通过 Matlab 平台来构建和调用函数。

第一步:在 Matlab 中设置用于英特尔®DAAL 的环境*

设置该环境最简单的方法是通过英特尔 Parallel Studio 编译器命令提示符启动 Matlab*:

  • 从“开始所有”应用打开命令提示符,英特尔 Parallel Studio XE 201X;英特尔编译器 xx 命令提示符;英特尔 64 Visual Studio 201X 环境或 IA-32 Visual Studio 201X 环境)。
  • C:\Program Files (x86)\IntelSWTools>>"%MATLAB ROOT%\bin\matlab.exe"

然后在 Matlab 命令窗口中,getenv() 函数和 setenv() 函数可用于向 Matlab* 的新环境添加包括和库路径。

(或者,如果打开编译器命令提示符时发生编译器或链接错误,您可以手动的方式在 Matlab* 命令窗口中检查和设置环境,如下所示)

英特尔 DAAL 安装在默认路径 (C:\Program Files (x86)\IntelSWTools\)。

要添加至 'include'列表:

C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxxx.x.xxx\windows\compiler\include;

C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxxx.x.xxx\windows\compiler\include\intel64;

C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxxx.x.xxx\windows\daal\include;

要添加至 'lib'列表:

C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxxx.x.xxx\windows\compiler\lib;

C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxxx.x.xxx\windows\compiler\lib\intel64;

C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxxx.x.xxx\windows\compiler\lib\intel64_win;

C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxxx.x.xxx\windows\daal\lib\intel64_win;

下面是一个向 'INCLUDE'列表添加路径的示例

>>setenv('INCLUDE',[getenv('INCLUDE') ';C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxxx.x.xxx\windows\daal\include']);

setenv('LIBPATH',[getenv('LIBPATH') ';C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_xxx\windows\lib\intel64'])

然后,在 Matlab* 命令行设置编译器,使用

>>mex -setup

识别 C/C++ 语言编译,例如 Microsoft Visual Studio*。

第二步:通过 cpp 文件创建 mexfile

首先,将当前的文件夹分配 (cd) 至 cpp 文件的位置,然后使用下面的命令,以静态的方式将 mexfile 链接至英特尔® DAAL 库。

>>C:\Users\xxx\Desktop\Matlab\daal_abs_sample

>>mex -v -largeArrayDims yourfunctionname.cpp daal_core.lib daal_thread.lib

如果您希望在 Matlab* 中使用静态库,则可以使用 daal_core.lib(这将增加 mexfile 的体积);

如果您不需要多线程计算,则可以使用 daal_sequential.lib。

如果 getenv('PATH') 列表中没有路径,则可能不包含路径。

成功构建 mexfile 时,屏幕上将会显示:MEX completed successfully and one yourfunctionname.mexw64 file was produced。

第三步:将 mexfunction 作为 matlab build-in 函数运行

现在,您可以充分利用 "yourfunctionname"函数(名称必须与 cpp 文件的名称相同),可以在 mexfile 所处的路径中调用该函数。

[output1, output2...]=yourfunctionname (input1, input2, input3...);

下面是通过 m_daal_abs.cpp 调用‘m_daal_abs’函数的示例。

附加文件:daal_abs_sample.rar

如欲了解关于 mex 和 mexFunction 的更多信息,请访问:

http://www.mathworks.com/help/matlab/ref/mex.html

http://www.mathworks.com/help/matlab/apiref/mexfunction.html

如欲了解关于英特尔 DAAL 开发人员指南的更多信息,请访问:

https://software.intel.com/zh-cn/daal-programming-guide

技术能替代人眼吗?

$
0
0

数据不仅涉及数字

还是一种实施高效通信的工具,能够帮助我们做出明智的决策。

有史以来,伽利略对数据分析的理解最为透彻。1610 年,他在观察昴星团时发现,用肉眼可以看到 6 颗明亮的星星,在黑夜,视力非常好的人最多能看到 9 颗星星。 伽利略利用望远镜发现 40 多颗微弱的亮点散落在 6 个明亮的星星中间。他在星团图中记录了 36 颗星星的位置,在远古时代已经发现的星星周围标记了圆圈。

本质上,伽利略正在利用数据点数据属性进行数据模式分析,将星星分为明亮的和暗淡的星星,最终将这些明亮的星星划分为星座。

我们现在研究的数据科学和机器学习采用了相同的方法,但是通过机器进行分类,需要采集更多的数据点,具有更复杂的属性。


图 1.伽利略的昴星团绘制图。(图片来源:Octavo 公司。/Warnock 库。)

数据科学热潮

数据科学,顾名思义就是研究数据:信息来源、信息意义和相关性方面的数据,以及对重要信息的潜在价值进行分析。 

通过挖掘大量结构化和非结构化数据点,并确定其模式,企业可以获取较高的业务价值。数据分析和数据科学旨在从经验中学习。数据不仅能够帮助我们理解过去发生的事情,还能帮助我们分析其原因。在许多情况下,以上知识能帮助我们预测未来,有效地管理未来。 

数据科学如果利用得当,将是一项强大的工具,为企业带来巨大的竞争优势,从降低成本、提高效率到为业务扩展和客户终生价值带来新的机会。正因如此,全球的技术和业务组织都对数据科学的兴趣日益浓厚。

但是更好的分析需要更准确的推断,更准确的推断需要更完善的思考,反过来需要更出色的工具、更卓越的模式识别和更好的算法。

本文首先介绍了几个数据科学的核心理念、然后介绍了图像分类器(一种对数据科学原则的应用),还有车辆分类器上专用的放大工具,您可以利用这个工具解决类似复杂度的模式识别问题。

核心理念

在深入了解图像分类器之前,首先需要理解某些数据科学理念和术语。 

分类器

分类器是一款数据挖掘工具,将需要分类的若干数据作为输入数据,尝试预测新数据的类别。

以包含一系列电子邮件的数据集为例,我们知道每封电子邮件都包含特定的属性,如发件人、日期、主题、内容等。根据这些属性,我们能将某人接收的邮件划分为两个或两个以上的类型(类别)。而且,我们能够根据电子邮件的属性预测接收的电子邮件类别。进行上述分类的数据科学算法被称为分类器。

分类器利用上述属性自动对用户的电子邮件进行分类。Gmail* 是自动分类电子邮件系统的绝佳示例,将邮件分为促销邮件、垃圾邮件、社交媒体更新或普通邮件。

监督式学习

监督式学习算法分析训练数据,并生成推断函数,用于映射新的示例。

通过这种方法,利用标记训练数据推断数据模式。

为了训练模型,我们将数据集和相应的正确答案输入模型,模型训练完成后,可以预测未来的值。

利用多个现有的数据点来定义预测未来值的简单规则,又称启发法或基于规则的模型。

无监督学习

数据建模的另一种方法是让数据自行决定,通过这种方法,将许多数据点输入设备,反过来基于复杂分析和模式识别技术,显示了潜在的信息集群。

通过学习来源于大数据的复杂决策规则,以程式化的方式制定明智决策的能力是机器学习的一个主要卖点。

利用机器学习,监督式学习技术(尤其是深度学习)的预测准确性提高了数倍。这种无需人类监督的学习技术目前生成了许多分类器,在许多任务的预测准确性方面优于人类。

这种无监督模型现在被用于各行各业,支持预测个人对电影的评分 (Netflix*)、对图像进行分类(如.com* 和 Facebook *)、进行语音识别 (Siri*),还有更多其他功能!

神经网络

神经网络是一种编程模式,受了人脑和神经系统(神经元)的结构和功能的启发,支持电脑根据观察性数据学习。

神经网络的目标是制定决策,然后像人脑一样地解决问题。现代神经网络项目通常处理数千至数百万个神经单元以及数百万个连接,但是其复杂性仍比人脑低出许多数量级,接近于一条蠕虫的计算能力。

Robert Hecht-Nielsen 教授是第一代神经计算机的发明人,他在《神经网络初级教程:第一部分》(Maureen Caudill,一名人工智能专家,1989 年 2 月)中将神经网络描述为“……一种由许多高度互联的简单处理单元构成的计算系统,处理外部输入的动态响应信息。”

神经网络划分为许多层,每一层都由大量的互联节点构成,每个节点都有激活功能

  • 通过输入层将模式传输至网络
  • 输入层与一个或多个隐藏层通信,在隐藏层中,通过加权连接系统进行实际的处理。
  • 然后将隐藏层连接至输出层,后者负责输出结果。

 

神经元

神经元是人脑的一个基本工作单元,它是一种特殊的细胞,专门负责将信息传输至其他神经、肌肉或腺细胞。

在神经网络的编程世界中,在的互联、交互的组件(称为节点或人工神经元)中对生物 神经元 进行模拟。节点接收输入数据,对数据执行简单的 操作,并有选择地将结果传输至其他神经元或节点。

根据模型类型的不同,人工神经元的类型会有所不同。感知和 sigmoid 神经元是两种常用的神经元。

深度学习

根据层数的不同,神经网络的深浅会有所差异。上图显示了一个输入层、两个隐藏层和一个输出层。神经网络也可以包含若干个隐藏层。和布尔电路的操作一样,利用中间层可以创建多个抽象层。

当我们创建多个抽象层时,深度学习是进行神经网络学习的一系列强大技术。

例如,如果您想针对视觉形状识别构建一个网络,第一层的神经元能够学习识别边缘,第二层的神经元能够学习识别角度,第三层将进一步识别复杂形状,如三角形、圆形或矩形,第四层将利用上述信息识别最终的物体,如滑板或圆形。

直观来讲,深层网络有更多的隐藏层,在性能上优于浅层网络, 然而,由于每个隐藏层的学习速度各不相同,训练深层网络的难度非常大。例如,在被称为梯度消失问题的现象中,前层神经元的学习速度比后层神经元的学习速度慢很多。因此,区分三角形、正方形或圆形相对简单,但是学习边缘和角度之间的差异会更难。

案例研究:图像分类器

图像识别

人脑非常强大,人眼同样强大!我们的视觉系统也许是世界上最不受重视的奇迹了。

观察以下数字: 

大多数人会轻松地认出数字 8754308,但是这种毫不费力具有一定的欺骗性。人类大脑的每一个半球均包含一个被称为 V1 的主要视皮层,包含 1.4 亿个神经元,神经元之间有数百亿个连接。此外,人类视觉不只包含一个视皮层,而是一系列视皮层,负责执行越来越复杂的图像处理。而且,人类视觉系统中的超级电脑经过了数亿年的训练(进化),能够适应我们的视觉世界。

这个被称为人眼的强大图像处理器能够分辨苹果和熊、狮子和美洲豹,阅读大量的标志,感知颜色以及进行各种模式识别。对大脑来说,这些任务看似简单;但是对计算机来说,实际上非常难以解决。

数据分析、数据科学和机器学习的进步是通过复杂的处理模型而实现的。有一种模型被称为深度 卷积神经网络,能够在较难的视觉识别任务中实现合理的性能。

卷积神经网络

卷积神经网络是一种神经网络,利用相同神经元的多个相同副本,支持网络严格控制实际参数的数量,同时利用大量的神经元处理大型模型。

由于这种架构显著提高了神经网络的训练速度,尤其适合图像分类。更快的训练速度有助于训练深层(多层)网络,这也是进行图像识别和分类所必需的。

一款应用:车辆分类器

利用 TensorFlow* 创建模型,TensorFlow* 是一款利用数据流图进行数值计算的开源软件库。

训练集

创建图像分类器的第一步是创建训练数据集。创建训练数据时需要考虑以下方面:

  • 图像的数量越多,经过训练的模型的准确性越高。每个对象类型应包含至少 100 张图像。
  • 注意训练偏差。如果某张图像中一棵树位于花园中,另一张图像中一棵树位于路边,最终的训练预测将基于背景,而不是基于更为关键的特定对象特性,这是因为训练收集图像共同的特性。因此,收集的场景种类要尽量广泛,如不同的位置、时间、来自不同的设备等。
  • 对训练数据进行精细分类,保持类别间的视觉差异,而不是分为包含许多不同外形的大类。否则,您可能最终得到无意义的抽象对象。
  • 最后,请确保所有的图像均得以正确标记。

以下示例来自我们的数据集:


图 2:公共汽车的随机图像。


图 3:汽车的随机图像。


图 4:自行车的随机图像。


图 5:摩托车的随机图像。

为了创建车辆分类器,我们从 ImageNet* 为每个类别下载了大约 500 张图像,共有 4 种不同的车辆类别 - 汽车、自行车、公共汽车和摩托车。

训练模型

下一步,我们指定脚本,随机选择用于模型训练的图像。

由于对象识别模型具有数千个参数,有时训练会持续数周。传输学习技术通过获取一个经过完全训练的模型(针对一系列类别),如 ImageNet,然后利用现有的权重针对新类别进行重新训练,极大地缩短了训练时间。

我们采用这个技术训练模型。

在本篇案例研究中,我们只对最后一层进行重新训练,其他层保持不变。我们删除了原始的顶层,在车辆照片上训练了一个新的顶层,照片均不位于用于训练整个网络的原始 ImageNet 类别中。现在,利用这些传输技术训练下层,可以完成其他识别任务,几乎不需要对技术进行修改,而以前只能区分一系列对象。

验证模型

由于我们正训练模型,使它适合示例图像,还需要测试模型能否处理示例集以外的图像。毕竟,识别示例集以外的图像才是车辆分类器的全部目标!

因此,我们把图像分为 3 个不同的子集。

  1. 训练集—用于训练网络的图像,其结果用于更新模型的权重。通常情况下,指派 80% 的图像用于训练。
  2. 验证集—在训练模型时,利用这些图像定期验证模型。通常,10% 的数据用于验证模型。
  3. 测试集—作为测试集,预测分类器实际性能的次数相对较少。

TensorFlow 命令

我们通过以下命令实现 TensorFlow 的传输学习功能。

前提条件

- Install bazel ( check tensorflow's github for more info )
    Ubuntu 14.04:
        - Requirements:
            sudo add-apt-repository ppa:webupd8team/java
            sudo apt-get update
            sudo apt-get install oracle-java8-installer
        - Download bazel, ( https://github.com/bazelbuild/bazel/releases )
tested on: https://github.com/bazelbuild/bazel/releases/download/0.2.0/bazel-0.2.0-jdk7-installer-linux-x86_64.sh
        - chmod +x PATH_TO_INSTALL.SH
        - ./PATH_TO_INSTALL.SH --user
        - Place bazel onto path ( exact path to store shown in the output)

注: Bazel 帮助我们通过命令行运行 TensorFlow。

训练模型

文件夹结构如下所示:

- root_folder_name
        - class 1
            - file1
            - file2
        - class 2
            - file1
            - file2
- Clone tensorflow
- Go to root of tensorflow
- bazel build tensorflow/examples/image_retraining:retrain
- bazel-bin/tensorflow/examples/image_retraining/retrain --image_dir /path/to/root_folder_name  --output_graph /path/output_graph.pb -- output_labels /path/output_labels.txt -- bottleneck_dir /path/bottleneck

每一个类别(1 类和 2 类)代表一个车辆类别,如汽车、公共汽车等。每一个文件包含该类别的图像。此处,将 TensorFlow 模型指向图像文件夹。

通过 Bazel 进行测试

bazel build tensorflow/examples/label_image:label_image && \
bazel-bin/tensorflow/examples/label_image/label_image \
--graph=/path/output_graph.pb --labels=/path/output_labels.txt \
--output_layer=final_result \
--image=/path/to/test/image

为了进行测试,我们使用了另一个称为 Label* 的 TensorFlow 程序,它是一个位于 TensorFlow 目录中的 C++ 程序。

结果

通过几个图像对模型进行了测试。随附每个类别的示例图像和模型得出的结果。


I tensorflow/examples/label_image/main.cc:205] 汽车 (0):0.729847
I tensorflow/examples/label_image/main.cc:205] 摩托车 (1):0.140029
I tensorflow/examples/label_image/main.cc:205] 自行车 (2):0.0864567
I tensorflow/examples/label_image/main.cc:205] 公共汽车 (3):0.0436665

结果显示模型预测图像为汽车的可信度为 72%。


I tensorflow/examples/label_image/main.cc:205] 公共汽车 (3):0.933695
I tensorflow/examples/label_image/main.cc:205] 汽车 (0):0.0317426
I tensorflow/examples/label_image/main.cc:205] 摩托车 (1):0.0192131
I tensorflow/examples/label_image/main.cc:205] 自行车 (2):0.0153493

从上图我们可以看出,模型预测图像为公共汽车的可信度为 93%;预测为摩托车的可信度只有 1.9%。


I tensorflow/examples/label_image/main.cc:205] 自行车 (2):0.999912
I tensorflow/examples/label_image/main.cc:205] 汽车 (0):4.71345e-05
I tensorflow/examples/label_image/main.cc:205] 公共汽车 (3):2.30646e-05
I tensorflow/examples/label_image/main.cc:205] 摩托车 (1):1.80958e-05


I tensorflow/examples/label_image/main.cc:205] 摩托车 (1):0.979943
I tensorflow/examples/label_image/main.cc:205] 自行车 (2):0.019588
I tensorflow/examples/label_image/main.cc:205] 公共汽车 (3):0.000264289
I tensorflow/examples/label_image/main.cc:205] 汽车 (0):0.000204627

新的可能

互联网的发展使每个人都能接触到知识,社交媒体让我们能够随时随地保持彼此之间的联系。现在,随着机器学习的发展,新一轮的创新大潮在许多领域日益凸显。 

我们来看一下车辆图像识别的应用!

收费桥

面向汽车的自动化收费已经不足为奇了。但是,您是否注意到收费桥上的一列列卡车长队?

汽车、SUV、卡车和其他车辆类型的收费各不相同。技术使利用传感器自动检测收费成为了可能。为何不对收费的金额进行分类,进一步提高便利性?汽车收费 5 美元,卡车收费 25 美元……全部实现自动化。

自动驾驶汽车

Google* 和 Uber* 正积极研发自动驾驶汽车。想象一下,未来的道路上全是自动驾驶汽车,道路上有一些人,至少我们可以依靠我们的视觉系统来谨慎驾驶,穿梭于无人驾驶汽车和鲁莽的驾驶者之间。

但是当具有超级计算能力的人眼不再关注其它车辆时,我们需要一台车辆分类器,帮助自动驾驶汽车识别不同的道路交通类型!

我们的安全

如今,FBI、警察局和许多安全公司保护我们和国家的安全。

即使在日常生活中,我们看到安珀警戒,显示被绑架的小孩在高速公路上的汽车内,以及其他情况。

想象一下在科幻电影里,以完全自动化的形式进行搜索,现在通过 google 地图的聚合、基于数据科学的图像识别和车辆分类算法,这一切都有了现实的可能性。

停车

停车是一个日益严峻的问题,尤其对城市而言。借助强大的车辆分类器,我们将建造完全自动化的高效停车方式,特定的区域和楼层用于停放特定的车辆类型。跑车需要较低的高度,而运动型卡车身型庞大。每层车库的大门上需要安装车辆检测传感器,只允许特定类型的车辆进入停车区。高度上节省了许多空间,确保在有限的空间内停放更多的汽车!

结论

这些示例只是一个开始,我们非常期待收到您的反馈,了解您所作的改进和处理的复杂问题,释放隐藏在数据中的巨大潜力。

无论如何,基于本文涵盖的话题和引用的示例,希望您能和我们一样相信,技术(尤其是模拟人脑和人眼的技术)正在快速地向前发展,可能在不久的将来会替代人眼。自动化将替代人工任务,尤其是涉及可视化和分类的重复性任务。 

尽管如此,人脑结构和复杂的视觉系统仍有许多谜团尚未解开,随着研究的深入,我们将掀开它们的神秘面纱。

引用

"第 1 章" Nielsen、Michael,“神经网络和深度学习”,2016 年 1 月,2016 年 10 月 23 日访问。

直观了解卷积网络”Zeiler、Matthew 和 Fergus、Rob,2013 年 11 月,2016 年 10 月 23 日访问。

"图像识别" TensorFlow 教程,2016 年 12 月,2016 年 10 月 23 日访问。

"人工神经元" 维基百科,Wikimedia Foundation,2016 年 12 月,2016 年 10 月 23 日访问。

"Neural Network Structures" 面向射频和微波设计的神经网络,IEEE.CZ。

"如何针对新类别重新训练 Inception 的最后一层" TensorFlow 教程,2016 年 12 月,2016 年 10 月 23 日访问。

利用用户体验设计对抗虚拟现实眩晕

$
0
0

作者:Matt Rebong

简介

虚拟现实 (VR) 游戏成为了游戏开发的一个新趋势,唤起了每位创新型开发人员的热情。 在这个全新的领域中,独一无二的游戏机制具有无限的可能。 然而,随着越来越多的游戏开发人员由传统游戏开发迁移至虚拟现实,有一个问题正日益限制创新的自由。 恶心,被社区称为“虚拟现实眩晕” (VRS),是目前虚拟现实游戏开发人员面临的最大障碍,这一点并不奇怪:它是技术新手掌握该技术的最大障碍之一。

作为独立开发人员,我们在 Well Told Entertainment* 公司时发现,用户体验设计是创建令人满意的虚拟现实体验的重中之重。 不管是需要保持稳定的帧速率,还是提高用户在虚拟世界中的舒适度,我们的目标是确保所有人—从虚拟现实领域的新手到经验丰富的玩家—都有极致的体验。 但是,该领域缺少专家。 在虚拟现实游戏设计公司工作一年后,我们开始逐渐了解如何帮助各种用户掌握虚拟现实技术基础,我们想和整个社区分享一些代码、着色技术和设计原则,让更多的人体验制作精良的虚拟现实技术。

Well Told Entertainment* 和虚拟现实学习

我们对虚拟现实的探索源于对远距传动中的交替运动方案非常好奇。 除了严格的空间定位体验,虚拟现实用户过去和现在都在追求通过独特的智能方式逃离空间定位,不必摔倒或感到茫然。 在 2016 年年初,我们有幸见证了新一轮的虚拟现实游戏浪潮,但是,当在查看了 VRS 报告后,我们和其他虚拟现实新手一样充满了怀疑。 当我们通过论坛得知远距传动可以解决这个问题时,非常期待能够试玩 The Lab*、Rec Room* 和 Budget Cuts*等游戏。 尽管这些游戏本身提供出色的虚拟现实体验,而且远距传动对 VRS 比较有效,但是我们仍旧觉得有什么不对。 最后,问题可以归结为虚拟现实中的远距传动破坏了浸入式体验(尤其和故事情节不相关时)。 当我们了解了虚拟现实最卓越的品质就是真实的浸入式体验后,我们开始研究如何为动态解决方案添加动作。

和其他有强烈求知欲的开发人员一样,我们通过详细了解 Reddit* 论坛开始入门。 (r/viver/virtualrealityr/oculus中有许多卓越开发人员分享他们在独立开发领域取得的进步。 在我们看来,这是调查、寻找创新型解决方案的最佳起点。) 2016 年夏天,动作方案成为了最热门的话题,我们注意到许多开发人员开始在线分享他们的意见和演示。 我阅读了一些技术,如攀登方案,该方案通过控制器移动角色,挥动手臂时角色前进,甚至可以将控制器放置于腰带环上,当您跳跃时,人物也会跟着前进(相当于一台计步器)。 基于调查,我们找到了解决问题的独特方法,并形成了自己的假设。 经过一个月的随机测试后(测试由 Vincent Wing 执行,他是一名可靠的开发人员),我们开始取得进展。 基于研究,我们找到了在不影响动态移动和交互式游戏体验的同时,对抗 VRS 的几个最佳实践,如下所示。

注重虚拟现实中的用户体验

六月底,我和我的联合创始人 Sam Warner赶往 Playa Vista 参加了由交互设计协会举办的虚拟现实活动。 尽管活动的主题是虚拟现实,但是在众多参会者中,我们是唯一的非专业 UX 设计师,这对我们之间的交流造成了一点阻碍。 但是,我们非常高兴能在 Andrew Cochrane的演示中发现极大的价值,Andrew Cochrane 是一名演讲嘉宾,也是一名新媒体总监。 Cochrane 专注于分享他在虚拟现实内容创新方面的见解,当晚,我们根据他的见解强化了我们的虚拟现实设计方法。

大多数研究虚拟现实的开发人员都有游戏开发或电影制作背景,或来自主要围绕故事叙述的媒体。 Cochrane 详细虚拟现实技术不需要叙述故事, 相反,虚拟现实是一个体验性媒体,身处其中的每个人都是体验设计师。 在游戏或电影中,设计师能够根据用户的需求更改故事, 他们遵循故事表演的结构,使用剪辑和特效吸引用户的注意,并能激起他们的情绪响应。 但是,虚拟现实的目标是为观众提供完全的浸入式体验,以激起他们的情绪响应, 因此,必须确保用户在虚拟空间中有较强的临场感,这点非常重要。 和传统介质相比,开发虚拟现实要求浸入式体验效果越强越好,这个要求使开发人员承担了更大的责任。 根据我们的移动设计经验,如果您想减少浸入式体验中断、避免虚拟现实眩晕,我们的建议是为玩家提供强大的可视参考。

空间定向

为了在虚拟现实中创建出色的临场感,第一步需要让玩家熟悉周围的环境。 尽管移动被认为是引起 VRS 的主要原因,但是戴上头盔后,便会产生眩晕,因为玩家实际戴上了一个眼罩。 遮住双眼对人的平衡性会产生很大的影响。 请您试试这个:站起来并闭上眼睛。 尽管最初看起来非常简单,但是许多人在短时间内便开始站不稳, 有人甚至摔倒了。 这是因为平衡极度依赖视觉参考和深度感知。 视觉消失后,身体便开始通过主体感觉维持平衡,每个人主体感觉的能力各不相同。 人们在虚拟世界中开启奇妙的游戏体验前,较强的空间感知是必不可少的。

开发 Vapor Riders ‘99*(Vapor Riders ‘99 是一款滑雪/ 滑行竞技游戏)的过程中,我们发现用户以脚下的地面作为参考时,将有效减轻 VRS。 玩家第一次进入游戏时,确保他们能看清脚下的道路、前的方距离和周围的环境。 然而,鉴于游戏的竞技性质,我们需要进一步对 Vapor Riders ‘99的地面参考进行完善。 我们在 2016 年洛杉矶虚拟现实展会上首次公开测试该游戏时,有些用户表示不适应距离地面的高度,尤其是高个子玩家。 为了解决这个问题,在开始游戏前,我们启动了一个简单的高度校准系统,效果还不错。 以下是面向 Unity* 软件的脚本,您可以在自己的游戏中运行这个脚本。

Vapor Riders‘99*演示视频:https://vimeo.com/175480598

我们利用 UpdateTPoseInfo() 和 ScaleAvatar() 函数计算并设置玩家身高和臂展。 第一个函数检测玩家头部与地面的位置,还检查两个控制器之间的距离。 计算距离前,玩家需呈 T 型姿态, 否则,将很难获得关于玩家体型的准确信息。 

基于 UpdateTPoseInfo() 收集的尺寸信息,利用 ScaleAvatar() 制作实际玩家的预制件。 我们根据收集的尺寸对玩家的默认尺寸进行分类,然后在 new Vector2 预制件的 X 和Y 轴上设置局部比例。 本游戏无需设置玩家的 z 轴的比例。

该系统支持我们确定“默认”玩家位置和当前身体位置的差异。 Vapor Riders 根据头部和手的位置来确定速度和移动方向,如果您想为不同尺寸和体型的玩家提供一致的游戏体验,这是非常重要的一步。

void UpdateTPoseInfo()
    {
        if (!leftWrist)
            leftWrist = manager.leftController.transform.Find("LControllerWrist");
        if(!rightWrist)
            rightWrist = manager.rightController.transform.Find("RControllerWrist");

        Manager.Instance.playerHeight = eyes.position.y - playspaceFloor.position.y;
        Manager.Instance.playerWingspan = Vector3.Distance(leftWrist.position, rightWrist.position);
    }

    void ScaleAvatar()
    {
        float avatarHeight = eyes.position.y - avatarFoot.position.y;
        float avatarWidth = Vector3.Distance(avatarLeftWrist.position, avatarRightWrist.position);
        Vector2 scaleFactor = new Vector2(Manager.Instance.playerHeight / avatarHeight, Manager.Instance.playerWingspan / avatarWidth);
        avatar.localScale = new Vector3(initAvatarScale.x * scaleFactor.x, initAvatarScale.y * scaleFactor.y, initAvatarScale.z);
    }

我们不完全明白为什么这个游戏中,玩家的身高对眩晕产生这么大的影响, 最终我们认为这取决于与非交通工具移动和平视显示器 (HUD) 相关的几个因素。

在 10 月份,我们开始开发一个简单的恐怖密室逃脱游戏,并将其命名为Escape Bloody Mary*。 在该游戏中,您可以扮演一个被锁在浴室里的儿童,尽力逃离恶魔。 这个游戏的目标之一就是让玩家体验儿童的视角,并且无需处理我们在 Vapor Riders ‘99中面临的身高问题。 我们保持了玩家的身高比,然后根据头盔到地面的距离确定房间内全部物品的比例,无需缩小玩家,让他们在浴室里感到渺小。 通过这种方式,玩家看到的都是经过设置的物品,在不影响到地面的参考点的同时产生变小了的感觉。

级别设计

玩家移动的空间和移动方案本身一样重要,尤其在虚拟现实中。 测试 Vapor Riders ‘99时,我们了解玩家和不同测试道路的交互方式。 设计道路时,我们想确定最佳的道路路线,使玩家不会厌烦。 玩家开始游戏时,会沿着 x 轴不断地左转、右转,一开始转弯较为平缓,逐渐变为急转弯, 然后,玩家主要沿着 z 轴上坡、下坡。 用户反馈显示用户更习惯道路的后半部分,而不是道路的前半部分。 排除故障后,我们开始明白其中的原因, 这不是因为相比左转和右转,用户更倾向于上坡、下坡运动(尽管飞行会带来无限的乐趣), 造成不适的原因是我们无法使用户适应该级别的中断因素。 为了避免加速运动时的眩晕,玩家必须一直保持参考点,知道自己的前进方向和到达时间。 刚开始测试道路时,由于玩家快速通过逐渐急转的转弯,无法注意前方的道路, 看不到前方的环境使他们措手不及,无法在前进的道路上保持平衡感—这是快速游戏中需要重视的问题。

常见的高效运动方案

除了远距传动,还有几个运动方案能够解决虚拟现实中的移动和眩晕问题, 最受欢迎的一种方案是 dashing(猛冲)。 Dashing 和远距传动非常相似,除了后者使玩家从一个地点到达下一个地点,而前者使玩家快速飞奔至指定的目标。 这种运动类型在 Raw Data* 游戏中得到了良好的运用。 相比远距传动,dashing 的最大优势在于用户永远不会失去参考点,他们不需要重新调整自己,就能到达新的地方,大家都非常喜欢这个改进。

例如,在 Vapor Riders ‘99中,以车辆作为交通设备,进行快速移动,dashing 在最受玩家欢迎的几款游戏中发挥了理想的效果。 Vapor Riders ‘99的目标是实现非交通工具式的快速运动,然而,交通工具的引入为虚拟现实游戏设计带来了独特的优势。 首先,它为玩家提供了保持平衡的视觉参考点。 尽管周围的事物都在移动,但是玩家可以不断调整自己,适应车辆,跟开车没有太大区别。 Hover Junkers* 等游戏将这个方案运用得非常娴熟,特别是因为 Hover Junkers中的车辆还拥有一个视野开阔的大平台。

优化

很早就有人提出,保持稳定的 60fps/120hz 是避免虚拟现实内容眩晕的必要基准。 低于这个基准,会使用户感到帧到头部的位置偏差,引起明显的卡滞。 开发 Escape Bloody Mary的过程中,我们很快发现空间定位游戏也很难满足整个场景的特定引擎特性。

在项目的初始阶段,我们知道如果我们想要实现技术或艺术方面的目标,需要避免特性蠕动轴的剧烈偏移。 我们想在墙上安装一面大镜子,反射全部的灯光、角色、道具和静态对象。 我们需要随时随地使用灯光,以管理环境和紧张气氛。 我们还计划为 Bloody Mary 4设计几片布料,让她看起来更遮掩、更漂浮。 当她从镜子中走出时,我们需要她与玩家一对一的距离越近越好,以增强浸入式体验,这意味着在同一个场景中始终需要保持两个相同的人物。

为了在游戏中尽可能流畅地展现这些构想,我们需要设置几个小系统。 对于灯光管理,我们创建了初始的签入、签出系统,在场景中可以根据需要移动灯光,更改它们的设置。 我们发现由于灯光被镜子反射后变成之前的两倍,切换灯光的开关状态对效果造成了巨大破坏,因此,我们需要移动灯光,移动的数量占通常情况下浴室场景可渲染灯光的一半。

最终,我们还降低了许多远程纹理的分辨率,使镜子能够轻松处理整个场景副本。 复制灯光管理系统后,将获得稳定的环境,能够调整灯光、点燃四颗蜡烛、控制动态手电筒的开关、发射子弹以及保留几盏室内环境灯,用来管理游戏效果。

两个 Bloody Mary 总共运行四片活跃布料模拟。 当 Mary 开始在镜子中走出时,关闭旧 Mary 的模拟,激活从镜子中走出的 Mary 身上的布料。 利用有限状态机同步两个 Mary 的动画,确保她们始终保持动作一致。 这两个系统支持我们随时对她进行远距传动和生成,无论她在哪里,都会保持相当稳定的帧速率。

声效将一切融合在一起

任何游戏的收尾步骤都是声效,声效使全部因素变得逼真。 在电影屏幕上移动的任何事物,不管该事物多么微不足道,都需要各自的声效。 游戏进一步增强了声效,这是因为玩家的动作也带有声效,如脚步声和呼吸。 但是由于不明原因,这个原则通常在虚拟现实游戏开发中被忽视。

自适应配音可以高效地将声音融入缺少配音的动作中,音效由用户输入引发,与声道的节奏相匹配。 Thumper* 是一个绝佳示例。 我们的长期目标之一就是为 Vapor Riders ’99添加自适应配音,为用户提供音频参考,帮助用户了解他们如何影响基于控制器位置的定向移动。

总结清单

  • 基于为用户创建更好的浸入式体验的理念,制定决策。
  • 在玩家体验游戏前,确保他们获取了对地面的准确参考点。
  • 使用户放松地体验游戏。
  • 在设计级别时,确保玩家在快速响应时(尤其在运动时),能够了解将要出现的环境。
  • 在快速游戏中,车辆有助于提供参考点。
  • 随时进行优化,避免帧延迟。
  • 为您的动作和交互添加音效。 在适当的情况下考虑适应性配音。

现在,把我们的建议都置之脑后吧

好吧,您不必这么做。 . . 但是,我们想鼓励游戏开发人员打破思维定式,寻找最适合游戏风格的移动方案。 Vapor Riders ‘99是经过反复的随机实验而创建的副产品。 我们开始测试动作方案,无意中发现了有趣的事情,以及解决眩晕的方法,并基于此开发了一款游戏。 这就是虚拟现实的魅力,引领我们进入未知的领域,我们期待成为更出色的开发人员。 只有勇于承担风险,才能创建更富有创新性的解决方案和工具,现在正是虚拟现实发展的初期,我们迫不及待地看到更多的突破!

参考资料

 

英特尔 Developer Mesh – https://devmesh.intel.com/
Sam - https://devmesh.intel.com/users/14426
Vapor Riders ‘99 - https://devmesh.intel.com/projects/vapor-riders-99
https://motherboard.vice.com/read/how-vapor-riders-99-is-helping-cure-motion-sickness-in-vr-gaming
Escape Bloody Mary - http://store.steampowered.com/app/544530
The Lab - http://store.steampowered.com/app/450390/
Budget Cuts - http://store.steampowered.com/app/400940/
Thumper - http://store.steampowered.com/app/356400/
Rec Room - http://store.steampowered.com/app/471710/
Raw Data - http://store.steampowered.com/app/436320/
Hover Junkers - http://store.steampowered.com/app/380220/
Reddit - https://www.reddit.com/r/Vive/
https://www.reddit.com/r/virtualreality/
https://www.reddit.com/r/oculus/
IxDA event - http://www.andrew-cochrane.com/biography/
https://www.meetup.com/UXPALA/events/231750013/
http://ixda.org/
http://www.virtualrealityla.com/

2016 第九届“英特尔杯”全国大学生软件创新大赛获奖作品

$
0
0

回到英特尔学术社区首页 >>

第九届“英特尔杯”全国大学生软件创新大赛暨移动应用开发大赛获奖作品

第九届"英特尔杯"全国大学生软件创新大赛总决赛合影(下载高清版本

获奖作品展示

• 第九届英特尔杯全国大学生软件创新大赛 — 特等奖(1名)

参赛学校:哈尔滨工业大学
团队名称:Ilife
项目名称:
Eyelife弱视群体生活伴侣

项目文档

•  第九届英特尔杯全国大学生软件创新大赛 — 一等奖(3名)

参赛学校:天津大学
团队名称:E_LIFE
项目名称:E-LIFE智能温室

同时荣获:最具创业潜力奖
项目文档

参赛学校:厦门大学
团队名称:WonderFour
项目名称:Magic Builder


同时荣获:最具创业潜力奖
项目文档

参赛学校:山东大学
团队名称:Smarlley
项目名称:E-CART


项目文档

 

•  第九届英特尔杯全国大学生软件创新大赛 — 二等奖(6名)

参赛学校:天津大学
团队名称:TheA
项目名称:Training4Fun




同时荣获:最具创业潜力奖
项目文档

参赛学校:北京航空航天大学
团队名称:Anonymous
项目名称:DronePlus

同时荣获:最具创业潜力奖
项目文档

参赛学校:中南大学
团队名称:榴莲牛奶
项目名称:
Flash-Buy超市智能购物

项目文档

参赛学校:北京邮电大学
团队名称:UniqueSwitch
项目名称:微保姆


项目文档

参赛学校:东南大学
团队名称:Only
项目名称:Smart Cube

项目文档

参赛学校:中南大学
团队名称:IdeaERs_开拓
项目名称:
智能停车场定位系统

项目文档

 

•  第九届英特尔杯全国大学生软件创新大赛 — 三等奖(10名)

参赛学校:中南大学
团队名称:拿摩兔团队
项目名称:i-系统



项目文档

参赛学校:集美大学
团队名称:鱼来了
项目名称:智鱼管家



项目文档

参赛学校:北京邮电大学
团队名称:芝士就是力量
项目名称:
Star智能鞋的坐姿监测及提醒系统

项目文档

参赛学校:东北大学
团队名称:ThePillow团队
项目名称:睡眠管家




项目文档

参赛学校:天津大学
团队名称:MonkeyGuard
项目名称:安全家居 Camera

项目文档

参赛学校:华南理工大学
团队名称:灯_等灯等灯
项目名称:盲人助手


项目文档

参赛学校:北京工业大学
团队名称:冬眠
项目名称:SmartNest





项目文档

参赛学校:北京邮电大学
团队名称:Aloha
项目名称:
Intelligent Cultivation  购物导航



项目文档

参赛学校:湖南城市学院
团队名称:橙子队
项目名称:iFitDiet


项目文档

参赛学校:大连理工大学
团队名称:Tornado
项目名称:书吧

同时荣获:最具极客人气奖
项目文档

  

 

借助英特尔集成显卡,优化提升PC版 Halo War*2 性能

$
0
0

下载文档 PDF 1.35MB

任务

当来自英国的顶级工作室 Creative Assembly* 开始开发 Halo Wars* 2时,他们的目标很宏伟,他们希望游戏在 DirectX* 12 支持的各种设置上运行,在各个硬件层面上都具有较强的可玩性——包括高级台式机 PC 配置和笔记本电脑。 尽管通过一系列的优化,配有独立显卡的高端系统的游戏体验在不断增强,他们团队仍将进一步探索针对英特尔集成显卡和多核处理功能如何进行游戏优化。

扩展特许经营

第一人称射击游戏 Halo*是 PC 游戏历史上最受欢迎的特许经营之一,于 2001 年推出,初代游戏名称为 Halo: Combat Evolved,也是 Xbox* 最初发布的游戏应用。 到 2015 年年底,Halo在生命周期游戏和硬件销售中创造了超过 50 亿美元。 Halo Wars 2的开发工作由 Creative Assembly负责,该工作室之前开发了 Alien: Isolation*Total War: Warhammer*。 他们拥有面向多个平台开发游戏的丰富经验,但是随着 DirectX 12 的不断成熟,该团队面临引擎、DX 12 驱动程序、多核效率等各方面的挑战。


图 1. Halo Wars* 2 是 Halo* 世界的新品。

将游戏转换为 RTS(实时战略)游戏意味着在频繁更新“迷你地图”的同时,跟踪更多的单元、为界面输入更多关键统计数据。 Halo Wars 2还推出了 “Blitz” 模式—它是一种极具创意的、紧张刺激的全新 RTS 游戏体验,将基于显卡的策略与炸弹横飞的战斗相结合。 新模式简化了大部分传统 RTS 系统,如基地建设、技能训练和资源管理。

扩展用户基础

Michael Bailey 是 Creative Assembly 的首席引擎编程员,也是该游戏的主要开发人员之一,他说:

“考虑到 RTS 游戏的性质,重点始终都是单元数量和带有大量 VFX 的大规模战斗。” “以上重点和 RTS 游戏中常见的确定性网络模式相结合,限制了我们的扩展性,因此,我们需要从头开始创建一个良好的基础,以利用多个 CPU 内核。 我们的主要目标是确保游戏能够在各种硬件上运行,力求在 Microsoft Surface* Pro 4 笔记本电脑上达到 30 fps 的帧速率,以后还会进一步提高。”


图 2.身临其境的粒子效果要求屏幕显示器持续工作

在多个硬件上实现最佳性能最为关键。 一开始,计划开发游戏的早期版本,初步适应高端台式机系统的规格,充分利用 10 teraflops 的潜在 GPU 计算能力。 但是,由于早期设计可能限制优化选项,这个计划会为游戏在 Xbox One* 和超极本™ 设备上的扩展带来一定的困难。 高端超极本和笔记本电脑在功耗和性能上接近 Xbox One,目前,这些设备被视为主流。 这意味着开发人员不能只关注 CPU 或 GPU 优化—两者都非常重要。


图 3.在超极本™ 设备和笔记本电脑上运行游戏是一个关键任务。

Bailey 没有面向英特尔® 高清显卡专门优化前代游戏,但是通过与英特尔直接合作,他实现了大幅的速度提升。 幸运的是,Creative Assembly 使用的显卡系统专为扩展而设计,而且不会破坏总体外观或感觉。 为了诊断新添特性的问题并测量它们的性能,英特尔工程师和 Bailey 团队现场讨论了他们的工具。 随着 DX 12 版本的不断成熟,提升的空间越来越小, 包括在多台设备上测试游戏性能,以实现可接受的出色扩展。

游戏即将开启

在项目的早期阶段,开发团队遇到了以下几个问题:

  • 目标系统不稳定
  • 独立 GPU 在低水平设置时出现的速度问题
  • 严重的 I/O 延迟
  • 在通用 Windows 平台 (UWP) 和 DirectX 12 上出现了工具故障
  • 驱动程序问题
  • 完成前重新使用缓冲而引起的损坏

这些问题都没有发展成障碍,但是早期的截屏显示有许多需要改进的地方。


图 4.早期截屏显示大范围损坏,影响地形、单元和“迷你地图”。

多核优化

  • 该团队通过研究几个不同的领域,优化了游戏的多核支持:
  • 改善算法
  • 减少内存分配
  • 简化资产
  • 执行低级别优化
  • 增强并行性

在多个玩家参与的战斗中,保持分散在多个线程中的模拟的确定性,是一个充满挑战的工作。 在 CPU 内部,团队专注于分解模拟,同时确保确定性。 此外,他们专门面向 DirectX 12 尽力减少资源障碍和重复工作。

该团队发现,改变顺序意味着有可能使玩家产生差异,在 Halo Wars 2世界中有不同的表达。 如果两个或更多玩家不同意校验和,例如,结果显示为“异步”,导致一位玩家被踢出游戏。 原因之一是在竞争条件下,输出取决于其他不可控事件的序列或时间。 很快团队便知道应该将哪些计算应用于其他线程,他们运行的时间,以及安全的实施时间。

他们最终实现了 CPU 在多个线程间高效运行,渲染线程除外。 他们在多核优化方面所付出的努力将在 2017 年游戏开发人员大会上展示(查看 链接和下表)。

GPU 优化和诊断

Creative Assembly 团队利用英特尔® 图形性能分析器(英特尔® GPA)执行了多个 GPU 优化。 英特尔® GPA 包含许多强大、灵活的工具,支持开发人员充分利用游戏平台的全部潜能,包括英特尔® 酷睿™ 处理器、英特尔® 高清显卡以及在 Android* 操作系统上运行的基于英特尔® 架构的平板电脑。 英特尔图形性能分析器工具直观显示来自应用的性能数据,向开发人员提供关于系统级别和单帧性能问题的详细信息,还支持实施“假设”实验,以评估优化带来的潜在性能。

Creative Assembly 团队利用英特尔 GPA 监控器的新特性启动和分析现行的游戏。 他们还利用英特尔 GPA 帧分析器检查了有问题的帧,以确定特定场景中的显卡“热点”。

英特尔 GPA 工具还有助于检查地砖损坏,团队利用描述符表跟踪地砖的错误。 此外,他们调查了性能瓶颈、对控制地形镶嵌的代码进行性能与质量方面的权衡,并判断了纹理分辨带宽瓶颈。

Bailey 和他的团队还利用 Microsoft GPUView 和其他英特尔游戏分析器。 团队在以下方面缩减了图形选项:

  • 当异步计算为了保护多个命令列表而使用过多的同步基元时,禁用异步计算。 (当异步计算不受原生支持时,会降低硬件速度。)
  • 为了保持质量与性能的可扩展性,降低地形镶嵌的数量;在较小的屏幕上,镶嵌所提供的额外信息不值得付出这样的成本。
  • 降低地形复合平铺资源的地图尺寸,使其与游戏运行的分辨率相匹配。当显示器分辨率过高时,这样做有助于避免复合地形纹理。
  • 纠正像素着色系统。 团队假设像素着色不在阴影渲染管线状态对象 (PSO) 中产生任何输出,它和顶点着色器中的插入器被一同删除了。 英特尔 GPA 显示事实并未如此。
  • 调整小物体的遮挡因素,使其适应分辨率(在主场景和阴影分辨率更低的阴影中调整)。
  • 更换深阴影 PCF 过滤器内核,通过 GatherCmp 大幅降低成本的同时,不会引起视觉差异。
  • 去除冗余地形复合层(这个错误使它们导出全部的层,包括不必要的层)。
  • 实施动态粒子降低系统,该系统对战斗视觉效果进行优先排序,减少较为次要的“偶然”环境效果或繁重的“高端”效果,如粒子光。
  • 玩家通过观察的视觉效果或他们地形中的模样确定单元。 Bailey 和他的团队实施的优化需要确保显示器可读,不能大量削减帧,也不可以禁用特定的效果。 “如果场景太复杂,我们会改进粒子效果,”他说。

他们还确保战斗效果看起来了非常出色。 当接连产生 20 场爆炸时,他们开始关闭环境效果和一些不重要的视觉效果。 “这样做是基于当前粒子系统的负载,”他解释说, “相比更高端的系统,Microsoft Surface Pro 4 上的效果损失会更明显一些,这是因为高端系统能处理更多的负载。 您可以继续保持环境效果,但是实际上,它们不是战斗的重要组成部分。”


图 5.大型战斗包含多个单元、若干例子效果和许多需要处理的信息。

DirectX 12 面临的挑战

Creative Assembly 和 Total War 的技术基础是分开的: Warhammer引擎和显卡层是从 Alien: Isolation中借鉴的。 他们在现有的 DX 11 支持上创建了 DX 12 显卡层,以利用全新的特性,确保 DX 12 比 DX 11 更优化。

在他们开始部署时,显卡层专门针对面向 PC 的 DX 11,因此,只面向 DX 12 进行规划并迁移,为他们提供了实现最佳性能的大量机会。 Bailey 发现在开发阶段,为了确保正确性和稳定性,他们对栅栏和描述符表失效“过于谨慎”。 他们研究了 2016 年游戏开发人员大会上的几场 DX 12 发言,并且通过在线资源收集了更多的信息,才开始了解将其应用于引擎的最佳方式。

随着系统渐趋成熟与稳定,他们开始进行优化,删除了冗余的描述设置和不必要的资源障碍与栅栏。 以上优化和改变渲染通道(为了更好地跟踪资源相关性)的操作意味着他们可以转移某些命令列表构建通道(如阴影),实现与其他区域的并行运行,更充分地利用多核。 迁移以及并行运行后,由于存在较多的绘制调用,命令列表在 CPU 内部造成巨大压力。 这些通道增加了屏幕上战斗单元的成本,因此,在紧张的战斗进程中,它们极大地受益于本次优化。 相比于 DX 11,DX 12 版在 CPU 内部渲染(甚至单线程)的速度很快超越了前者。


图 6.在本截屏中,右下角的“迷你地图”功能一切正常。

由于 DX 12 环境非常新,通过 DX 12 验证层获取诊断警告和错误的流程还不成熟,这点需要权衡考量。 文件可能不完整,或不准确。 当出现错误时,Bailey 的团队不确定错误由他们造成的,还是驱动程序导致的。

最终,他们只在 DX 12 上开发,低级别的单个图形 API 增强了他们对性能权衡的控制, 使他们将目标锁定为带有单个代码路径的大量 Windows* 10 设备。 2017 年 1 月发布的测试版支持 DirectX 12(低至特性级别 11.0);游戏的地形纹理复合代码需要“一级平铺资源”支持。

不断发展的软件所面临的挑战

由于团队想在游戏中利用众多新特性,需要持续更新驱动程序、编译器和 Windows 10 版。 幸运的是,他们和各种硬件厂商保持直接联系,有机会获取配有特定修复的测试版驱动程序以及测试版内部工具。

随着时间的推移,驱动程序、工具和验证层越来越成熟,出现的错误通常来自游戏代码,比较容易跟踪。 图形测试平台有助于调试各种 DX 12 API 功能,从开发新特性到在更精简的环境中运行(控制全部变量)。


图 7.用于测试新特性的精简环境。

为开发人员提供几条建议

联系专家

Bailey 建议开发人员联系独立硬件厂商 (IHV),以便获取解决问题的重要见解。 “他们显然希望您的游戏在他们的硬件上运行良好,因此,他们愿意提供帮助和工具,”他说, “如果他们对您的游戏产生了兴趣,或您对他们的硬件产生了兴趣,这是一种互利的体验。” 部分收益体现在访问早期修复和改进,还有 Bailey 团队广泛采用的优化反馈。

Bailey 说:“我们从未得到相互矛盾的反馈。” 厂商有助于确定已知问题,在某些地方,当游戏识别英特尔、AMD* 或 NVIDIA* 硬件时,运行方式会轻微改变。 Bailey 说:“他们之间有着细微差别,但是通过和他们聊天,您能了解他们擅长的领域。” 总体来说,游戏速度得到大幅提升(尤其在低端硬件上)。

创建强大的测试实验室

Creative Assembly 引擎团队共有六个人负责解决问题、以及通过各种方式测试解决方案。 他们经常根据需要去借机器,极度依赖在第二台机器或 Surface Pro 4 桌面上进行远程调试,触屏设备上的操作因此变得简单多了。 “我不会坐下来尝试设置触屏,使游戏能够适应屏幕,”Bailey 说, “我可以轻松地使用大屏台式机进行远程调试。”

Microsoft 拥有 Halo的特许经营权,因此,Creative Assembly 通过这种关系获取了 Microsoft 紧凑型测试实验室。 它是一款面向各种硬件的网关,有些硬件低于最低规格。 定期测试构件扩大了他们的范围,确保 Creative Assembly 的优化经过全面检查。

共享知识

Bailey 鼓励开发人员相互沟通,积极加入社区,尤其鼓励开发人员参加各种活动,如游戏开发人员大会 (GDC)。 如果无法参加活动,Bailey 建议“下载发言视频,稍后观看。”

以前,DirectX 12 发言的数量很少, 今年,经验丰富的团队将分析更多的方法和技巧。 “您也希望分享自己的成功,”Bailey 说, “如果您将成功经验分享给他人,他们也会跟你聊起他们做过的一些不同的事情,互相探讨经验。”

和多线程的解决方法一样,这里是人在互动,而不是处理器。 “您的分享会吸引他人与您对话,”Bailey 解释道, “邀请他人参与讨论可能为您带来更好的方法。 您的方法或许已经相当出色了,通过分享会变得更好。”

更多资源

英特尔图形性能分析器
https://software.intel.com/zh-cn/gpa

英特尔多核常见问题和解答:
https://software.intel.com/zh-cn/articles/frequently-asked-questions-intel-multi-core-processor-architecture

通用 Windows 平台简介:
https://docs.microsoft.com/zh-cn/windows/uwp/get-started/universal-application-platform-guide

DirectX 12 安装程序:
https://www.microsoft.com/zh-cn/download/details.aspx?id=35

Microsoft Surface Pro 4:
https://www.microsoftstore.com/store/msusa/en_US/pdp/productID.5072641000

2017 年游戏开发者大会中的 CPU 和 GPU 优化发言:
http://schedule.gdconf.com/session/threading-your-way-through-the-tricks-and-traps-of-making-a-dx12-sequel-to-halo-wars-presented-by-intel

从家用游戏机到PC虚拟现实游戏:《不语者》给我们的启示

$
0
0

下载文档 PDF 1.06 MB

1. 简介

Unspoken* 是一款由 Insomniac Games* 公司开发的第一人称施咒虚拟现实游戏,采用了 Oculus Touch* 控制器。 Insomniac Games 是一家经验丰富的控制台开发公司,他们的内部引擎经过扩展后可以支持虚拟现实。 从帧速率为 30 fps 的控制台体验,到高端虚拟现实设备上达到 90 fps,帧速率的大幅提升需要采用性能感知设计,以及改善引擎,以最大程度地利用系统资源。 本文介绍了如何检测系统级别的引擎瓶颈,找出其原因并且消除这些瓶颈,以提升性能。

2. 引擎架构

详细讨论之前,首先向您介绍 Insomniac Games 引擎架构, 主要由以下部分构成:

i) 主线程 (MT)
- MT 负责模拟游戏对象,以及准备下一帧的渲染工作缓冲。 前者包括发布蒙皮、物理和 vfx 工作,后者包括视锥和遮挡剔除。

ii) 渲染提交线程 (RS)
- RS 浏览渲染工作缓冲,并将其转化为渲染命令 (DirectX* 11), 包括更新各种资源,当前使用立即执行的环境,而非延迟环境。 当 RS 没有向 GPU 提交工作时,可以用作工作线程。

iii) 工作线程
- 分别有 4 个优先级较高的工作线程和 4 个优先级较低的工作线程,运行蒙皮、vfx 和物理等。

MT 的运行快一个帧,意味着它在 N+1 帧上工作;RS 将 N 帧的工作提交 GPU。 虽然导致了一帧的输入延迟,但是并行性和松散耦合有助于提升性能。

2.1 控制台对比 PC

在控制台上,GPU 几乎和 RS 同步运行。 MT 和 RS 之间保持严格的同步,当 GPU 进入后处理工作时,RS 唤醒 MT。 这使 MT 在提交蒙皮工作、更新顶点缓冲方面领先一步,有助于帧 RS 随后提交,在保持向 GPU 不间断馈送(这样做有可能产生饥饿风险)的同时,减少输入延迟。

在 PC 上,根据自变量和最大帧延迟,DirectX Present 调用发挥着遏制的作用,防止游戏 (CPU) 过度领先 GPU。 没有引起 RS 停顿的介入性 GPU 查询,便无法及时唤醒 MT,因此,引擎中的 PC 路径在 Present 返回后唤醒 MT。

3. 虚拟现实中的系统级性能分析

在 Oculus Connect* 3 发布后,我们一起看一下游戏,帧开始时,在 GPU 队列中出现了气泡(闲置时间)。 在虚拟现实中,游戏的 GPU 预算是在 10 毫秒内达到 90 fps,1 毫秒左右的 GPU 闲置时间意味着预算的 10% 未被利用! 下图中的 GPUView* 和 RAD 遥测* 时间线显示了这个问题:


之前: 帧开始时的 GPU 气泡(大约 1 毫秒)。 帧中 GTX 980* 使用 7 毫秒左右的时间渲染,包含一个游戏中的低工作负载。


之前: 基于任务的 RAD 遥测时间线视图显示了这个问题。

3.1 了解引擎独立性

气泡由 MT 和 RS 之间的几个独立性引起。

i) MT 等待 RS 在 post-Present 唤醒自己
开发虚拟现实的过程中,在 mirror-Present 前(在非 HMD 显示器上显示游戏),需要调用 Oculus* API ovr_submitFrame,后者防止(阻挡)应用提交比 HMD 更新率(在 Rift* 上达到了 90Hz)更快的帧。
在英特尔® 酷睿™ i7 6700K 上,CPU 帧像周期(RS 提交时间)约为 5 至 6 毫秒,这意味着 ovr_submitFrame 受阻了大约 5 毫秒,在这段时间内,引擎线程几乎完全闲置。

ii) RS 等待 MT 提交并同步蒙皮工作
如果 MT 不再率先运行,RS 最终会等待蒙皮定点缓冲数据的生成,然后才能提交图形缓冲区通道中的蒙皮对象。

iii) MT 等待 RS 复制面向遮挡剔除查询的下采样深度数据(每只眼睛)
引擎使用室内遮挡剔除系统,在这个系统内,MT 等待 RS 对临时缓冲进行映射、复制和取消映射,该缓冲保存每只眼睛的下采样深度数据。 使用的数据有两帧长:如果 MT 在 N 帧上工作,RS 复制 N-2 帧的深度数据后,MT 会重新投影、填补孔洞并剔除遮挡对象。

在低端的虚拟现实设备(如英特尔® 酷睿™ i5 4500 + GTX 970*)上,GPU 处理每个帧工作负载的平均时间为 9 到 10 毫秒,1 毫米的气泡使它超出了 10 毫秒的 GPU 预算,必须开始运行异步空间弯曲* (ASW),将游戏的更新速度提升至 45Hz。 总体而言,虚拟现实最终用户体验具有主观性,观察力较为敏锐的玩家在快速移动手臂或 HMD 时,能够察觉 ASW 的延迟。

3.2 消除不必要的引擎独立性

我们重构引擎,通过 RS 唤醒 MT,并调用 ovr_submitFrame 和 mirror-Present。 为 MT 提供了充足的时间来处理蒙皮和模拟工作,处理完成后,等待 RS 读回前一个帧的深度缓存(详见第 3.3 节)。 我们添加了一个开关,支持动态切换两个模式,以验证是否提升,在低端设备上运行繁重的帧工作负载时,能避免 ASW 频繁延迟。

在修复消除了独立性 (i) 和 (ii) 后,GPUView 和 RAD 遥测时间表如下所示:


之后: RS 受阻之前 MT 已经启动,减少了开始时的 GPU 气泡。


之后: 开始时的气泡减少了,深度缓冲读回的等待更为明显。

帧开始时,GPU 队列中的气泡数量有所下降,但是可以看出,遮挡读回系统(在 N 帧上工作的主线程)正在渲染线程(为 N-1 帧提交工作)上等待处理分期资源(来自 N-2 帧的深度缓冲)的 CPU 副本。 右眼的深度缓冲流程和左眼一致。

3.3 处理遮挡读回系统

左右眼睛的遮挡剔除可以共享大量的逻辑,但是我们不希望引擎为虚拟现实偏离太多。 更为重要的是,MT 算不上一个瓶颈。 游戏仍旧受 GPU 的限制,通过消除 GPU 气泡,能够满足 GPU 预算的 10 毫秒时间限制。

开始时,RS 不读回深度缓冲的原因是需要减少 GPU 饥饿的风险。 在 RS 占用 CPU 时间复制深度缓冲前,需要确保 GPU 不间断馈送,在这段时间内,不提交任何 GPU 工作。 左眼图形缓冲提交完成后,可以连续复制,无需以交错的方式复制。

进行了简单的重构后,在布满帧的区域内对最低规格设备进行测试时发现,节省了整整 1 毫秒的 GPU 时间(降低至 9–9.3 毫秒,支持 Oculus 的后期处理工作能够及时完成),还带来了 90 fps 的体验!


改变遮挡读回后: 遥测时间线显示 GPU 很少闲置。

3.4 相信您的工具

遥测 GPU 时间线(依赖于 GPU 计时器查询)显示在帧中几乎从不闲置,配有面向 Windows* DirectX 事件的事件跟踪的 GPUView 提供了更准确的时间线,显示帧开始时的小气泡。 了解各种分析和监测工具的工作原理,避免被数据误导,这点非常重要。


改变遮挡读回后: 还能继续减少部分闲置 GPU 时间。 此外,请注意各种线程上存在大量闲置时间。

此时,RS 不依靠 MT,这些气泡是由于资源更新引起的,更新需要在分配 DirectX 命令前完成。 资源更新目前通过 RS 以串行的方式进行,不使用延迟环境。 通过映射方法更新的资源需要在立即执行的环境(也就是 RS)中运行,通过 UpdateSubresource方法更新的资源可以在延迟环境中执行,节约了宝贵的驱动程序复制时间(如果 RS 被 ovr_submitFrame 阻挡时,需要在工作线程上处理)。 未来的补丁将优先考虑这个改变。

4. CPU 闲置

在虚拟现实中,由于严格的 10 毫秒 GPU 预算,针对物理、粒子更新和蒙皮等子系统创建一条 CPU 路径意义重大。 Insomniac 引擎已经利用 CPU 模拟子系统,由于 GPU 上扩展空间有限,我们探索了其他的选择,如利用高品质的预设置,通过使全部物品栩栩如生,提升浸入式体验。

Oculus 降低虚拟现实最低规格的决定非常适合采纳,但是高端设备会增加玩家的成本,而且根据最低规格定制他们的体验。 在 Unspoken 中,将预设置自动设置为基于底层系统硬件。 尽管为了增强游戏体验,开启 ASW 可能会消耗 Oculus 的运行时,但是,玩家可以自由修改质量设置,随意感受高端体验。

动态调整各种渲染参数的适应性质量系统是理想的解决方案,支持预设置质量是为玩家提供最佳体验的第一步。

超级预设置包括以下部分:

  1. 可破坏的对象: 赛场中有更多的对象,对玩家的咒语产生物理反应。
  2. 预先录制好的动画: 基于物理的破坏非常棒,但是计算成本过高, 预先录制好的动画能在利用少量运行时成本的同时,提供相似的体验。
  3. 粒子效果(环境和基于 curl 的湍流): 添加了基于 curl 的湍流系统,粒子可以从无政府主义者的手中发出,冬季幽灵的移动方式也更生动。
  4. 更高分辨率的渲染
  5. 临时抗锯齿处理。
  6. 地面上的雾和远方模型的细节。

5. 结论

过去的十年来,许多游戏引擎将物理、蒙皮、遮挡等子系统从 CPU 迁移至 GPU。帧速率为 90 fps 的虚拟现实体验生成了低于 10 毫秒的 GPU 帧预算,这个预算充满挑战。 多数游戏引擎的各种线程存在大量的 CPU 闲置时间,可以用于卸载 GPU 上的工作。 由于支持虚拟现实的设备只是 PC 生态系统中的一个子集,通过测量驱动虚拟现实游戏开发的方法有助于为玩家提供最佳的体验。

6. 扩展阅读:

  1. Oculus 公开的统计数据(通过 API 和 HUD)
    https://developer3.oculus.com/documentation/pcsdk/latest/concepts/dg-hud/
  2. 异步空间弯曲 (ASW)
    https://developer.oculus.com/blog/asynchronous-spacewarp/
  3. 通过 SDK 控制 ASW
    https://developer3.oculus.com/documentation/pcsdk/latest/concepts/asynchronous-spacewarp/

如果您参加了 2017 年游戏开发者大会,可能对 3 月 3 日(周五)举办的 GDC 演讲感兴趣,该演讲以更详细的方式介绍了本文的内容。

致谢

作者特别感谢 Insomniac Games 公司的 Bob Sprentall、Yancy Young、Abdul Bezrati 和 Shaun Mccabe,以及英特尔公司的 Cristiano Ferreira、Chris Kirkpatrick、Brian Bruning 和 Dave Astle。

关于作者

Raja Bala 是英特尔公司的一名游戏开发人员关系工程师。 他喜欢和工作室合作,提升引擎和渲染性能,向开发人员普及英特尔® 显卡架构和游戏性能分析。


Android Things* Developer Preview Now Available on the Intel® Edison board

$
0
0

Today Google released their developer preview of Android Things*, previously known as Project Brillo*. Android Things is Google’s open-source, Android*-based, Internet of Things (IoT) operating system. This first iteration provides developers an opportunity to test key features of the platform, which include:

  • Android developer framework that includes Android Studio and other familiar Android tools to develop, run and debug code
  • Android framework APIs to simplify access to peripheral interfaces, support libraries for common hardware peripherals, and Android extensions to support devices with zero or more displays 
  • Ability to use Google Play* Services on the device, giving access to many popular Google APIs for authentication, cloud and voice services

According to Google, future iterations will incorporate Google’s Weave platform, Google Cloud Platform*, over-the-air updates from Google and the Google IoT Developer Console.

Intel has been involved with Google’s team from the beginning. Intel delivered the first Brillo-compliant starter board, the Intel® Edison Kit for Arduino*, which was featured in Google’s IoT Tech Awards, a global research grant spanning 83 projects selected to experiment with Google and partner IoT technologies. Dr. Max Senges, Google Research Program lead says: “We received very positive feedback from academic researchers, and an overwhelming majority said the experiments laid the foundation for their R&D efforts and they plan to continue to use our products in their research. We want to thank Intel for their contribution and are looking forward to continuing this partnership.”

Deploying Android Things on Intel® architecture combines the power of Android with the performance of Intel architecture to scale IoT projects from proof-of-concept to product reality. Because the firmware is Android-based, developers benefit from working with familiar Android frameworks, languages and tools. Access to Google Play services and over-the-air security updates further simplify and accelerate development and increase security, allowing developers to focus on creating products and the user experience.

Google used the Intel Edison system-on-module (SOM) as a reference device for further development of Android Things. With the launch of Android Things, we added Android Things support for Intel Edison on different expansion boards such as the Intel Edison Kit with Breakout Board  and the SparkFun blocks for Intel Edison. Support for the Intel® Joule™ Compute Module is coming soon. Update: Intel Joule is now supported with the release of Android Things Developer Preview 2.

Intel Edison for Android Things is a compute module that works with a variety of expansion boards, which can be tailored to different application domains. The Intel Edison Breakout Board is a small form factor board that’s just slightly larger than the module itself and provides a minimal set of features and easy access to the GPIO. The Intel Edison Kit for Arduino includes a larger expansion board that allows the Intel Edison module to interface and access the open source Arduino shields, libraries and resources. The module can also be used with the SparkFun’s Blocks series for further customization. These expansion options enable quick adoption, ease of use, and are well suited for developers interested in quick prototyping and making fast time-to-market IoT solutions.

The Intel Joule compute module is an advanced, high performance technology that offers high-end computing, large memory and 4K video capture and display. Once validated, developers will be able to use the Intel Joule platform to build out an embedded system or prototype in the areas of robotics, drones, industrial machine vision, augmented reality and more.

“We are only beginning to see the technological revolution made possible by the Internet of Things,” says Sameer Sharma, GM of Intel’s New Market Developments, Internet of Things Group. “I believe Android Things will be a catalyst in the widespread adoption of IoT in both consumer and enterprise applications. The OS will allow companies to quickly bring new, life-changing products to the market.” Excited at the possibilities presented by Android Things, Sharma notes that while “Android changed the way we use and think about our mobile devices, Android Things has the potential to do the same with the billions of connected IoT devices that will come to market, including products for home, building, and industrial settings.”

Visit our page to get started with Google’s Android Things on Intel architecture. Still not sure about Android Things? Check out a developer’s perspective on why choose Android Things

Installing Android Things* on the Intel® Joule™ Module

$
0
0

This guide describes how to setup your Intel® Joule Module with Android Things*.

Android Things is an open-source operating system from Google that can run on a wide variety of development boards, including the Intel Edison device. Android Things is based on Android* and the Linux* kernel. For more information about Android Things, see https://developer.android.com/things.
In order to get started with your board, you can either watch the Android Things* Bring-Up for Intel® Joule™ Module video or read the guide below.
 

使用英特尔® MKL 安装和构建 MXNet

$
0
0

概要:

MXNet 是一种开源深度学习框架,支持您在各种设备上定义、训练和部署深度神经网络,从云基础设施到移动设备。它具有高度可扩展性,可用于实施快速模型训练,支持灵活的编程模型和多种语言。MXNet 支持您混合使用符号和命令式编程,以实现最高的效率和生产力。MXNet 构建于动态依赖性调度程序之上,后者可即时对符号和命令式操作自动进行并行化处理。它上面的图表优化层可确保符号执行既快速又节省内存。 最新版 MXNet 包括对于英特尔® 数学核心函数库(英特尔® MKL)2017 的内置支持。最新版英特尔 MKL 包括针对 英特尔® 高级矢量扩展指令集 2(英特尔® AVX2) 和 AVX-512 指令的优化,英特尔® 至强® 处理器和英特尔® 至强融核® 处理器均支持这些指令。

前提条件:

请按照此处说明操作

使用 MKL 构建/安装:

MXNet 可以在各种平台上安装使用,支持不同开发工具和库的组合。关于如何利用英特尔 MKL 2017 在基于 CentOS* 和 Ubuntu* 的系统上构建和安装 MXNet,本教程会提供详细步骤。

1.     克隆 mxnet 树,获得其子模块依赖性:

git submodule update --init --recursive

git clone https://github.com/dmlc/mxnet.git

2.     将 make/config.mk 中的下列行命令编辑为“1”,以启用 MKL 支持。 

在进行构建时启用该项目可帮助您获取最新的 MKL 软件包并将其安装在系统上。

USE_MKL2017 = 1

USE_MKL2017_EXPERIMENTAL = 1

3.     构建 mxnet 库

NUM_THREADS=$(($(grep 'core id' /proc/cpuinfo | sort -u | wc -l)*2))

make -j $NUM_THREADS

4.     安装 python 模块

cd python

python setup.py install

性能指标评测:

examples/image-classification 下面提供了各种标准映像分类性能指标评测。  我们将重点介绍运行一种专为跨拓扑结构测试推断性能的性能指标评测。

运行推断性能指标评测:

所提供的 benchmark_score.py 将以不同批量大小运行各种标准拓扑结构(AlexNet、Inception、ResNet 等),并报告 img/sec 结果。.  运行前设置以下环境变量以实现最佳性能:

导出 OMP_NUM_THREADS=$(($(grep 'core id' /proc/cpuinfo | sort -u | wc -l)*2))

导出 KMP_AFFINITY=granularity=fine,compact,1,0

然后实施下列操作以运行性能指标评测:

python benchmark_score.py

如果一切安装妥当,您会看到各种拓扑结构及批量大小的 img/sec #’s 输出。例如:

INFO:root:network: alexnet

INFO:root:device: cpu(0)

INFO:root:batch size  1, image/sec:XXX

INFO:root:batch size  2, image/sec:XXX

INFO:root:batch size 32, image/sec:XXX

INFO:root:network: vgg

INFO:root:device: cpu(0)

INFO:root:batch size  1, image/sec:XXX

自动矢量化失败后应该怎么办?

$
0
0

简介

本文是一篇后续文章,详细分析了英特尔® 开发人员专区(英特尔® DZ)论坛1上报告的英特尔® C++ 编译器出现的问题2

一位英特尔开发人员专区用户在代码现代化研讨会上实施了一个简单的程序,检测到了一个内层 for-loop 问题。以下是与问题相关的代码段:

	...
	for (std::size_t i = 0; i < nb_cluster; ++i) {
	float x = point[k].red - centroid[i].red;
	float y = point[k].green - centroid[i].green;
	float z = point[k].blue - centroid[i].blue;
	float distance = std::pow(x, 2) + std::pow(y, 2) + std::pow(z, 2);
	if (distance < best_distance) {
	best_distance = distance;
	best_centroid = i;
	}
	...

:它不是来自 KmcTestAppV1.cpp的自动矢量化内层 for-loop。

这位英特尔开发人员专区用户认为无法进行内层 for-loop 自动矢量化的原因是变量“i”被声明为“std::size_t”数据类型,即为“无符号整数”。

附上未改动的源代码 6。请查阅 KmcTestAppV1.cpp以获取更多详细信息。

需要指出的是,本文不是一篇关于矢量化或并行技术的教程,但是,本文下一部分对这些技术进行了简要概述。

矢量化和并行技术简介

现代软件非常复杂,为了实现峰值性能,尤其在数据密集型处理过程中实现峰值性能,需要充分利用现代 CPU 的矢量化和并行功能,现代 CPU 具有多个内核,每个内核上有若干个逻辑处理单元 (LPU) 和矢量处理单元 (VPU)。

VPU 支持在多个数据集值中同时执行不同的操作,这项技术被称为矢量化,与标量或顺序方式相比,利用矢量化部署相同的处理将提升处理的性能。

并行化是另一种技术,支持不同的 LPU 同时处理数据集的不同部分。

将矢量化和并行化结合后,处理性能将显著提升。

通用矢量化规则

您需要考虑以下源代码矢量化通用规则:

  • 需要使用配有矢量化支持的现代 C/C++ 编译器。
  • 可以使用两种矢量化技术:自动矢量化 (AV) 和显式矢量化 (EV)。
  • 只有相对简单的内层 for-loop 才能实现矢量化。
  • 某些内层 for-loop 因为使用了复杂的 C 或 C++ 结构(如标准模板库类别或 C++ 操作符),不能通过 AV 或 EV 技术实现矢量化。
  • 当现代 C/C++ 编译器无法矢量化内层 for-loop 时,建议审核和分析全部示例。

如何对内层 for-loop 计数器变量实施声明

因为不需要修改代码,所以 AV 技术被认为是实施简单的内层 for-loop 最有效的方法,在使用优化选项“O2”或“O3”时,默认启用现代 C/C++ 编译器的 AV。

在更复杂的示例中,可以使用 EV 的内置函数或矢量化#pragma指令强制执行矢量化,但是需要修改内层 for-loop。

您可能会有这样的疑问:内层 for-loop 计数器变量怎样得以声明?

有两种声明方法供您选择:

案例 A - 变量“i”被声明为“整数”

	...
	for( int i = 0; i < n; i += 1 )
	{
		A[i] = A[i] + B[i];
	}
	...

案例 B - 变量“i”被声明为“无符号整数”

	...
	for( unsigned int i = 0; i < n; i += 1 )
	{
		A[i] = A[i] + B[i];
	}
	...

案例 A中,变量“i”被声明为带符号数据类型“整数”。

案例 B中,变量“i”被声明为无符号数据类型“无符号整数”。

案例 AB结合到简单的测试程序 3后,可以对 C/C++ 编译器的矢量化功能进行评估:

////////////////////////////////////////////////////////////////////////////////////////////////////
// TestApp.cpp - 为了生成汇编器列表,需要使用选项“-S”。
// Linux:
//		icpc -O3 -xAVX -qopt-report=1 TestApp.cpp -o TestApp.out
//		g++ -O3 -mavx -ftree-vectorizer-verbose=1 TestApp.cpp -o TestApp.out
// Windows:
//		icl   -O3 /QxAVX /Qvec-report=1 TestApp.cpp TestApp.exe
//		g++ -O3 -mavx -ftree-vectorizer-verbose=1 TestApp.cpp -o TestApp.exe

#include <stdio.h>
#include <stdlib.h>
//

////////////////////////////////////////////////////////////////////////////////////////////////////

	typedef float			RTfnumber;

	typedef int				RTiterator;			// Uncomment for Test A
	typedef int				RTinumber;
//	typedef unsigned int	RTiterator;			// Uncomment for Test B
//	typedef unsigned int	RTinumber;

////////////////////////////////////////////////////////////////////////////////////////////////////

	const RTinumber iDsSize = 1024;

////////////////////////////////////////////////////////////////////////////////////////////////////

int main( void )
{
	RTfnumber fDsA[ iDsSize ];
	RTfnumber fDsB[ iDsSize ];

	RTiterator i;

	for( i = 0; i < iDsSize; i += 1 )
		fDsA[i] = ( RTfnumber )( i );
	for( i = 0; i < iDsSize; i += 1 )
		fDsB[i] = ( RTfnumber )( i );

	for( i = 0; i < 16; i += 1 )
		printf( "%4.1f ", fDsA[i] );
	printf( "\n" );
	for( i = 0; i < 16; i += 1 )
		printf( "%4.1f ", fDsB[i] );
	printf( "\n" );

	for( i = 0; i < iDsSize; i += 1 )
		fDsA[i] = fDsA[i] + fDsB[i];			// Line 49

	for( i = 0; i < 16; i += 1 )
		printf( "%4.1f ", fDsA[i] );
	printf( "\n" );

	return ( int )1;
}

结果表明,这两种 for-loop(详见上述代码示例的第 49 行)能够轻松进行矢量化 4(使用了带有前缀“v”的指令,如 vmovupsvaddps等),无论怎样对变量“i”进行声明,C++ 编译器生成相同的矢量化报告:

案例 A 案例 B 的矢量化报告

...
	Begin optimization report for: main()
	Report from:Interprocedural optimizations [ipo]
	INLINE REPORT:(main())
	Report from:Loop nest, Vector & Auto-parallelization optimizations [loop, vec, par]
	LOOP BEGIN at TestApp.cpp(37,2)
		remark #25045:Fused Loops:( 37 39 )
		remark #15301:FUSED LOOP WAS VECTORIZED
	LOOP END
	LOOP BEGIN at TestApp.cpp(39,2)
	LOOP END
	LOOP BEGIN at TestApp.cpp(42,2)
		remark #25460:No loop optimizations reported
	LOOP END
	LOOP BEGIN at TestApp.cpp(45,2)
		remark #25460:No loop optimizations reported
	LOOP END
	LOOP BEGIN at TestApp.cpp(49,2)
		remark #15300:LOOP WAS VECTORIZED
	LOOP END
	LOOP BEGIN at TestApp.cpp(52,2)
		remark #25460:No loop optimizations reported
	LOOP END
...

矢量化报告4显示第 49 行3 for-loop 实现了矢量化:

	...
	LOOP BEGIN at TestApp.cpp(49,2)
		remark #15300:LOOP WAS VECTORIZED
	LOOP END
	...

但是,英特尔 C++ 编译器将两个 for-loop 视为不同的 C 语言结构,因此生成不同的矢量化二进制代码。

以下是汇编器列表的两个核心代码段,与两个案例的第 49 行3 for-loop 相关:

案例 A - 汇编器列表(编译 TestApp.cpp 时,需要使用选项“-S”)

...
..B1.12:								# Preds ..B1.12 ..B1.11
	vmovups		(%rsp,%rax,4), %ymm0					#50.13
	vmovups		32(%rsp,%rax,4), %ymm2					#50.13
	vmovups		64(%rsp,%rax,4), %ymm4					#50.13
	vmovups		96(%rsp,%rax,4), %ymm6					#50.13
	vaddps		4128(%rsp,%rax,4), %ymm2, %ymm3			#50.23
	vaddps		4096(%rsp,%rax,4), %ymm0, %ymm1			#50.23
	vaddps		4160(%rsp,%rax,4), %ymm4, %ymm5			#50.23
	vaddps		4192(%rsp,%rax,4), %ymm6, %ymm7			#50.23
	vmovups		%ymm1, (%rsp,%rax,4)					#50.3
	vmovups		%ymm3, 32(%rsp,%rax,4)					#50.3
	vmovups		%ymm5, 64(%rsp,%rax,4)					#50.3
	vmovups		%ymm7, 96(%rsp,%rax,4)					#50.3
	addq		$32, %rax#49.2
	cmpq		$1024, %rax								#49.2
	jb			..B1.12						# Prob 99%	#49.2
...

:请参阅 TestApp.icc.itype.s5.1以获取完整的汇编器列表。

案例 B - 汇编器列表(编译 TestApp.cpp 时,需要使用选项“-S”)

...
..B1.12:								# Preds ..B1.12 ..B1.11
	lea			8(%rax), %edx							#50.13
	lea			16(%rax), %ecx							#50.13
	lea			24(%rax), %esi							#50.13
	vmovups		(%rsp,%rax,4), %ymm0					#50.13
	vaddps		4096(%rsp,%rax,4), %ymm0, %ymm1			#50.23
	vmovups		%ymm1, (%rsp,%rax,4)					#50.3
	addl		$32, %eax								#49.2
	vmovups		(%rsp,%rdx,4), %ymm2					#50.13
	cmpl		$1024, %eax								#49.2
	vaddps		4096(%rsp,%rdx,4), %ymm2, %ymm3			#50.23
	vmovups		%ymm3, (%rsp,%rdx,4)					#50.3
	vmovups		(%rsp,%rcx,4), %ymm4					#50.13
	vaddps		4096(%rsp,%rcx,4), %ymm4, %ymm5			#50.23
	vmovups		%ymm5, (%rsp,%rcx,4)					#50.3
	vmovups		(%rsp,%rsi,4), %ymm6					#50.13
	vaddps		4096(%rsp,%rsi,4), %ymm6, %ymm7			#50.23
	vmovups		%ymm7, (%rsp,%rsi,4)					#50.3
	jb			..B1.12						# Prob 99%	#49.2
...

:请参阅 TestApp.icc.utype.s5.2以获取完整的汇编器列表。

最后明确的一点是,内层 for-loop 无法自动矢量化的问题(详见论坛贴子 1的开头)和变量“i”的声明方式无关,其他因素 影响了英特尔 C++ 编译器的矢量化引擎。

为了查明矢量化问题产生的根源,需要提出一个问题:无法应用 AV 或 EV 技术时,会生成什么样的编译器消息?

AV 或 EV 技术无法应用时,英特尔 C++ 编译器将生成一系列“循环未进行矢量化”信息,信息列表如下所示:

...loop was not vectorized: not inner loop.
...loop was not vectorized: existence of vector dependence.
...loop was not vectorized: statement cannot be vectorized.
...loop was not vectorized: unsupported reduction.
...loop was not vectorized: unsupported loop structure.
...loop was not vectorized: vectorization possible but seems inefficient.
...loop was not vectorized: statement cannot be vectorized.
...loop was not vectorized: nonstandard loop is not a vectorization candidate.
...loop was not vectorized: dereference too complex.
...loop was not vectorized: statement cannot be vectorized.
...loop was not vectorized: conditional assignment to a scalar.
...warning #13379: loop was not vectorized with "simd".
...loop skipped: multiversioned.

一个信息需要特别注意:

...loop was not vectorized: unsupported loop structure.

KmcTestAppV1.cpp6中可以看到,内层 for-loop 由 3 个部分组成:

第 1 部分 - 初始化 x、y 和 z 变量

...
float x = point[k].red - centroid[i].red;
float y = point[k].green - centroid[i].green;
float z = point[k].blue - centroid[i].blue;
...

第 2 部分 - 计算点 x、y 和 z 的距离

...
float distance = std::pow(x, 2) + std::pow(y, 2) + std::pow(z, 2);
...

第 3 部分 - 更新“best_distance”变量

...
if (distance < best_distance) {
best_distance = distance;
best_centroid = i;
}
...

由于这些部分全部位于同一个内层 for-loop,英特尔 C++ 编译器的结构无法匹配预定义矢量化模版。然而,带有条件 if 语句的第 3 部分是矢量化问题的根本原因。

解决矢量化问题一个可行方法是将内层 for-loop 划分为以下 3 个部分:

...														// Calculate Distance
for( i = 0; i < nb_cluster; i += 1 )
{
	float x = point[k].red - centroid[i].red;
	float y = point[k].green - centroid[i].green;
	float z = point[k].blue - centroid[i].blue;			// Performance improvement:( x * x ) is
	distance[i] = ( x * x ) + ( y * y ) + ( z * z );	// used instead of std::pow(x, 2), etc
}
														// Best Distance
for( i = 0; i < nb_cluster; i += 1 )
{
	best_distance = ( distance[i] < best_distance ) ?( float )distance[i] : best_distance;
}
														// Best Centroid
for( i = 0; i < nb_cluster; i += 1 )
{
	cluster[k] = ( distance[i] < best_distance ) ?( float )i : best_centroid;
}
...

最重要的两个修改和 for-loop 中的条件 if 语句相关。从通用形式:

...
if( A < B )
{
	D = val1
	C = val3
}
...

修改为利用两个条件运算符的形式 ( ?:):

...
D = ( A < B ) ?( val1 ) :( val2 )
...
C = ( A < B ) ?( val3 ) :( val4 )
...

又称 三元运算符。现在,当代 C/C++ 编译器能使这个 C 语言结构与预定义矢量化模版匹配。

对未修改和经过修改的源代码进行性能评估

通过 1,000,000 个浮点、1,000 个集群和 10 次迭代完成了两版程序的性能评估,结果如下所示:

...>KmcTestAppV1.exe
		Time:111.50

:原始版本6

...>KmcTestAppV2.exe
	Time:20.48

:优化和矢量化版本7

与原始版本相比,经过优化和矢量化后,程序7的速度提升了约 5.5倍,(详见 16),节省了数秒时间。

结论

如果当代 C/C++ 编译器无法对 for-loop 进行矢量化,非常有必要评估它的复杂性。在英特尔 C++ 编译器中,需要使用“opt-report=n”选项(n大于 3)。

多数情况下,由于 C/C++ 编译器结构不能与预定义矢量化模板相匹配,C/C++ 编译器无法矢量化 for-loop。例如,以英特尔 C++ 编译器为例,将报告以下矢量化信息:

...loop was not vectorized: unsupported reduction.

...loop was not vectorized: unsupported loop structure.

如果出现这种情况,您需要修改 for-loop,以简化其结构,通过 #pragma指令(如 #pragma simd)使用 EV 技术,或通过内置函数重新实施所需的功能。

关于作者

Sergey Kostrov 是一名经验丰富的 C/C++ 软件工程师,也是一名英特尔® 黑带头衔获得者。他是面向嵌入式和台式机平台的高度便携 C/C++ 软件设计和实施领域的专家,也是大数据集科学算法和高性能计算领域的专家。

资料下载

WhatToDoWhenAVFails.zip

全部资料列表(来源、汇编列表和矢量化报告):

KmcTestAppV1.cpp
KmcTestAppV2.cpp
TestApp.cpp
TestApp.icc.itype.rpt
TestApp.icc.utype.rpt
TestApp.icc.itype.s
TestApp.icc.utype.s

另请参阅

1.无符号整数导致矢量化失败?

https://software.intel.com/zh-cn/forums/intel-c-compiler/topic/698664

2.英特尔开发人员专区中的英特尔 C++ 编译器论坛:

https://software.intel.com/zh-cn/forums/intel-c-compiler

3.演示简单的 for-loop 矢量化过程的测试程序:

TestApp.cpp

4.TestApp.cpp 程序的英特尔 C++ 编译器矢量化报告:

TestApp.icc.itype.rpt

TestApp.icc.utype.rpt

5.1.TestApp.cpp程序案例 A的完整汇编器列表:

TestApp.icc.itype.s

5.2.TestApp.cpp程序案例 B的完整汇编器列表:

TestApp.icc.utype.s

6.未经修改的源代码(原始版 KmcTestAppV1.cpp

7.经过修改的源代码(经过优化和矢量化的 KmcTestAppV2.cpp

Unreal Engine 4 优化教程 第一部分

$
0
0

本教程旨在帮助开发人员提升基于虚幻引擎(Unreal Engine*4 (UE4))开发的游戏性能。在教程中,我们对引擎内部及外部使用的一系列工具,以及面向编辑器的最佳实践加以概述,还提供了有助于提高帧速率和项目稳定性的脚本。

本文的目的是找出游戏性能问题之所在,并提供解决这些问题的若干模式。

撰写本文时使用的是 4.14 版本。

 

单元

测量优化对性能的提升作用时,需要考虑每秒帧数 (fps) 和每帧的持续时间(以毫秒 (ms) 计算)。

该表显示了平均 fps 和 ms之间的关系。

为了计算任意 fps 的 ms,只需将 fps 的倒数乘以 1000。

利用毫秒描述性能改善,有助于更好地量化需要达到一定 fps 目标的优化级别。

例如,某个场景提升了 20 fps:

  • 从 100 fps 到 120 fps 提升了 1.66 毫秒
  • 从 10 fps 到 30 fps 提升了 66.67 毫秒

工具

现在向大家介绍三种工具,便于您了解引擎内部的情况: UE4 CPU 分析器、UE4 GPU 可视化工具和英特尔® 图形性能分析器(英特尔® GPA)。

分析器

UE4 CPU 分析器工具是一款引擎内监控器,支持您实时或从捕获的片段中查看游戏的性能。

分析器位于 Window > Developer Tools > Session Frontend 中。


图 1:  找到 Session Frontend 窗口。

在 Session Frontend 中选择 Profiler 选项卡。


图 2:Unreal Engine 中 Profiler 选项卡

打开 Profiler 窗口后,选择 Play-In-Editor (PIE),然后选择 Data Preview 和 Live Preview,以查看从游戏中收集的数据。 选择 Data Capture 选项开始从游戏中捕获数据,然后取消选择并保存数据,以便稍后查看。


图 3:在 Profiler 中查看进程

Profiler 中每个动作和调用的时间以毫秒表示。 对每个区域进行检查,以查看对项目内帧速率的影响。

如欲深入了解 Profiler,请查看 Epic 文档

GPU 可视化工具

UE4 GPU 可视化工具确定渲染通道的成本,还提供显示场景快照内部情况的高级视图。

在游戏内开发人员控制台中输入 ProfileGPU便可获取 GPU 可视化工具。


图 4ProfileGPU 控制台命令。

输入命令后,弹出 GPU 可视化工具窗口, 该窗口显示快照内每个渲染通道的时间,您还可以大致了解场景内通道产生的位置。


图 5:在 GPU 可视化工具中查看进程。

Profiler 能够显示哪些项目的处理时间最长,可以依据它来确定如何优化。

如欲深入了解 GPU 可视化工具,请查看 Epic 文档

英特尔® 图形性能分析器(英特尔® GPA)

英特尔® 图形性能分析器(英特尔® GPA) 是一套图形分析和优化工具,可帮助开发人员提升图形应用的性能。

本文将重点介绍该套件中的两个工具:实时分析应用和帧分析器。 首先需要从英特尔® 开发人员专区下载 GPA。 安装完成后,利用所选的开发创建配置创建 Unreal 项目。

创建完成后,进入图像监控器的分析应用,在命令行中选择可执行文件的位置并运行这个文件。

游戏将正常启动,但是在屏幕的左上角出现了一个统计指南。 为了扩大显示范围,单次按下 CRTL+F1 获取实时的指标信息,再次按下 CRTL+F1 获取试验的键盘快捷方式列表,运行游戏时可以应用这些试验。


图 6:游戏中英特尔® GPA 的叠加。

为了在帧分析器中获取用于分析的帧,还需要在游戏内执行两个步骤。

第一步,打开 Toggle Draw Events, 在游戏控制台中输入 ToggleDrawEvents,便可打开 Toggle Draw Events。


图 7:  ToggleDrawEvents 控制台命令

打开后,Toggle Draw Events 为引擎创建的绘制调用附上名称,以后在帧分析器中查看捕获的帧时便有了环境。

最后,利用键盘快捷方式 CTRL+SHIFT+C 捕获帧。

保存帧后,从图像监控器中打开帧分析器并选择需要加载的帧。 对捕获的帧进行处理后,可以获得帧内部的所有图形信息。


图 8:英特尔® GPA。

如欲深入了解英特尔® GPA,请查看英特尔 GPA 文档

英特尔® 图形性能分析器(英特尔® GPA)使用示例

在英特尔 GPA 中查看全部数据看起来比较复杂,我们从查看较大的信息块入手。 在窗口的右上角,在 X 轴和 Y 轴上设置 GPU 持续时间图表,我们可以借助这张图标找出帧内消耗时间最多的绘制调用。

本示例捕获了一张沙漠景观场景,可以看到在基础通道中有一个大型绘制调用。 选择大型绘制调用后,“Render Target Preview(渲染目标预览)”区域的“Highlighted(突出显示)”部分被选中,我们可以发现峰值由场景内的景观引起(以粉色突出显示)。 如果我们在流程树列表(在预览区域的上方)中查找选中的绘制调用,将发现景观包含 520200 个基元,GPU 持续时间为 1,318.5(1.3185 毫秒)。


图 9:在场景中找出最长的持续时间

确定引起峰值的原因后,开始进行优化。

作为第一次测量,借助面向景观工具的 Manage Mode对景观进行重新采样,将基元数量降至 129032 个。 GPU 持续时间也随之降至 860.5,为场景带来了 5% 的性能提升。


图 10:持续时间下降。

为了继续降低景观的成本,也可以从材料着手。 景观的层混合材料使用 13 个 4096 x 4096 (4k) 纹理,纹理传输总计达 212.5 MB。


图 11:在英特尔® GPA 中查看渲染纹理

将所有景观纹理压缩至 2048 x 2048 (2k) 后,GPU 持续时间降至 801.0,性能又提升了 6%。

通过将景观纹理传输降至 53.1 MB,并减少场景中三角形的总数,显著提高了项目在英特尔显卡上的运行速度。 项目只是损失了少量的景观视觉逼真度。


图 12:持续时间随纹理减少而下降

总体而言,通过重新采样和调整纹理改变场景中的景观,我们实现了以下优化:

  • 景观流程的 GPU 持续时间下降了 40%(从 1318.5 到 801)
  • fps 提升了 18 帧(从 143 到 161)
  • 每帧减少了 0.70毫秒。

下一节

利用英特尔® Nervana™ 技术、neon* 和 Pachyderm* 实施 Docker* 化的分布式深度学习

$
0
0

机器学习和人工智能领域的最新进步令人惊叹!几乎每天都会出现新的突破性进展,从自动驾驶汽车到人工智能学习复杂的游戏。为了给公司带来真正的价值,数据科学家必须在公司的数据管线和基础设施上部署模型,不能仅限于在电脑上展示模型。

此外,数据科学家应该在改善机器学习应用上投入更多的精力,他们不需要花大量的时间手动更新应用,以应对不断变化的生产数据;也不需要在追溯和跟踪反常的过往行为中浪费时间。

Docker*Pachyderm*有助于数据科学家在生产集群上创建、部署和更新机器学习应用,在大数据集之间分配处理以及在数据管线中跟踪输入和输出数据。本文将展示如何利用英特尔® Nervana™ 技术、neon*和 Pachyderm设置生产就绪型机器学习工作流程。

英特尔® Nervana™ 技术和 neon*

包含 neon 的英特尔 Nervana 技术是“世界上速度最快的深度学习框架。”它是基于 Python* 的开源代码(请查看 GitHub*),包含一套用于深度学习模型开发的实用工具。

借助neon 文档本地安装 neon 教程立即开发:

git clone https://github.com/NervanaSystems/neon.git
cd neon; make

然后尝试创建 一些示例

Pachyderm*

Pachyderm 是一个开源框架,提供基于容器(尤其是 Docker 容器)的数据版本和数据管线。Pachyderm 支持创建与语言无关的数据管线,Pachyderm 还控制每个管线阶段的输入和输出版本;类似于面向数据的 Git。您可以查看数据的 diff,利用 Pachyderm 的查询量和分支与团队成员开展合作。此外,如果数据管线生成了意外的结果,可以调试或验证结果(甚至完全复制结果),前提是了解结果产生的历史处理步骤。

仅仅通过向构成 Pachyderm 管线的每个容器展示一个数据子集,Pachyderm 便可以轻松地并行化计算。单个节点能看到每个文件的片段(map 任务)或整个文件(reduce 任务)。数据被放置于选中的对象存储中(如 S3),Pachyderm 将需要处理的各个数据片段智能地分配到不同的容器中。

Pachyderm 集群可以运行于任何云,您也可以尝试在本地运行 Pachyderm。安装了 Pachyderm CLI 工具(Pachctl)以及安装并运行了 Minikube*以后,可以利用单行命令在本地安装 Pachyderm:

pachctl deploy local

使用 Docker 的 neon

为了在生产 Pachyderm 集群中利用 Neon(或仅仅为了轻松部署),需要 Docker Neon。Docker 支持将机器学习应用打包成便携式映像,可以在安装了 Docker 的系统上以容器的形式运行该映像。因此,机器学习应用具有了便携性。

值得庆幸的是,可以公开获取面向 neon 的 Docker 映像,我们看一下映像是怎么运行的。假设您已经安装了 Docker并且只运行 CPU,通过调取以下映像(得到 neon 团队的认可)获得 Docker 的 neon:

docker pull kaixhin/neon

为了在 Docker 内部交互地试验 neon,运行以下命令:

docker run -it kaixhin/neon /bin/bash

该命令将在 neon 映像(或容器)的运行例程中打开一个 bash shell,您可以在 bash shell 中创建并运行利用 neon 的 Python 程序。您也可以转到 Neon `示例` (examples) 目录,在 Docker 内部运行 neon 示例模型。

如果我们已经有了一个利用 neon 的 Python 程序 `mymodel.py`,并且想在 Docker 内部运行这个程序,需要创建一个包含本程序的定制 Docker 映像。如欲创建映像,只需在 `mymodel.py` 脚本中创建一个名为 `Dockerfile` 的文件:

my_project_directory
├── mymodel.py
└── Dockerfile

`Dockerfile` 将告知 Docker 如何创建一个的定制映像,该映像包含 neon 和基于 neon 的定制脚本。由于本示例中 `mymodel.py` 只利用 Neon 和 Python 标准库,`Dockerfile` 非常简单:

FROM kaixhin/neon
ADD mymodel.py /

运行以下命令,创建定制 Docker 映像:

docker build -t mycustomimage .

在项目的根目录下运行。创建完成后,利用以下命令,在 Docker 运行的任意设备上运行 neon 模型:

docker run -it mycustomimage python /mymodel.py

在生产集群上分配 Docker neon

按照上述步骤运行 Docker neon 有助于提升便携性,但是没有必要为了部署单个模型例程,手动登录生产设备。我们需要将 neon 模型集成至在生成集群上运行的分布式数据管线上,并确保模型能在大型数据集上扩展。Pachyderm 能帮助我们实现上述目标。

下面将在可持续的生产就绪型数据管线上实施模型训练和推断。利用示例 LSTM 模型展示这个过程,该模型基于来自 IMDB 的训练数据集预测电影评论中的情感。获取该模型的更多信息

训练

利用 Pachyderm,我们能够在处理阶段利用 Docker 容器创建数据管线。这些容器化的处理阶段将数据存储库中的版本化数据作为输入,输出至相应的数据存储库。因此,在 Pachyderm 中对每个处理阶段的输入/输出数据实行版本化。

为了训练 LSTM 模型,需要设置一个管线阶段。这个模型管线阶段将以带有标签的训练数据集 `labeledTrainData.tsv` 作为输入,并持续输出(或保存于磁盘)经过训练的模型版本,输出的形式为一系列模型参数 imdb.p` 和一个模型 vocab `imdb.vocab`。利用 python 脚本 `train.py` 处理这个阶段,该脚本已经包含在 公用 kaixhin/neon Docker 映像中。

Figure 1

为了创建上述管线,首先需要创建一个训练数据存储库,该存储库将输入模型管线阶段:

pachctl create-repo training

利用以下命令确认存储库已成功创建:

pachctl list-repo

为了处理训练存储库中的数据,需要创建模型管线阶段。为了创建模型管线阶段,向 Pachyderm 提供 JSON 管线规范,用于告知 Pachyderm 如何处理数据。本示例的 JSON 规范如下所示:

{"pipeline":{"name":"model"
  },"transform":{"image":"kaixhin/neon","cmd":["python","examples/imdb/train.py","-f","/pfs/training/labeledTrainData.tsv","-e","2","-eval","1","-s","/pfs/out/imdb.p","--vocab_file","/pfs/out/imdb.vocab"
    ]
  },"inputs":[
    {"repo":{"name":"training"
      },"glob":"/"
    }
  ]
}

规范看起来很复杂,实际上只包含几项说明。(1)创建名为模型 (model) 的管线阶段,(2)在管线阶段中利用 kaixhin/neon Docker 映像,(3)运行给出的 Python cmd以处理数据,(4)处理输入存储库训练 (training) 中的任何数据。目前,我们忽略了其他选择,但是在 Pachyderm 文档中全部涵盖。

获得 JSON 文件(保存于 `train.json`)后,在生产就绪型 Pachyderm 集群上创建管线变得非常简单,命令如下所示:

pachctl create-pipeline -f train.json

现在,Pachyderm 将执行上述处理,在本示例中,Pachyderm 将利用训练存储库中的训练数据训练 neon 模型。事实上,由于 Pachyderm 进行数据版本化并且能够辨别新的数据,Pachyderm 将保持模型输出(在 Pachyderm 创建的相应的模型存储库中实行版本化)与训练数据的最新更新之间的同步。新的训练数据被提交至训练存储库后,Pachyderm 将在模型存储库中自动更新经过训练的永久性模型。

但是,我们还没有在训练存储库中添加任何数据,需要先完成这个操作。尤其需要将文件 `labeledTrainData.tsv` 放入训练存储库的 master分支(再次利用类似 Git 的语义):

pachctl put-file training master labeledTrainData.tsv -c -f labeledTrainData.tsv

运行 pachctl list-repo` 时,您将发现数据已经添加至训练存储库。此外,运行 `pachctl list-job` 时,您会看到 Pachyderm 开始处理训练数据,并将永久性模型输出至模型存储库。这个任务结束后,模型训练完毕。通过向训练存储库输入新的训练数据或在模型管线阶段利用新映像更新管线,可以重新训练生产模型。

如果 Pachyderm 面向管线阶段在输出数据存储库中创建了 `imdb.p` 和 `imdb.vocab`,可以确认模型已被训练:

pachctl list-file model master

需要指出的是,将文件保存在 Pachyderm 的 `/pfs/out` 目录(该目录指定与处理阶段相应的输出存储库)中,可以告知 Pachyderm 在存储库中编写的内容。

预测/推断

然后,将推断阶段添加至利用版本化、永久性模型的生产管线。推断阶段将在模型输入之前收集新的电影评论,推测出这些电影评论中的情感,并利用持久性模型输出。推断再次运行利用 neon 的 Python 脚本 `auto_inference.py`。

Figure 2

为了创建推断管线阶段,首先需要创建对输入评论进行存储和版本化的 Pachyderm 存储库:

pachctl create-repo reviews

然后创建另一个 JSON 日志,该日志将告知 Pachyderm 如何执行推断阶段的处理:

{"pipeline":{"name":"inference"
  },"transform":{"image":"dwhitena/neon-inference","cmd":["python","examples/imdb/auto_inference.py","--model_weights","/pfs/model/imdb.p","--vocab_file","/pfs/model/imdb.vocab","--review_files","/pfs/reviews","--output_dir","/pfs/out"
    ]
  },"parallelism_spec":{"strategy":"CONSTANT","constant":"1"
  },"inputs":[
    {"repo":{"name":"reviews"
      },"glob":"/*"
    },
    {"repo":{"name":"model"
      },"glob":"/"
    }
  ]
}

它和上一个 JSON 规范相似,只是本示例包含两个输入存储库(评论模型),我们使用了包含 `auto_inference.py` 的另一种 Docker 映像。访问此处获取面向本映像的 Dockerfile。

只需运行以下命令,即可创建推断阶段:

pachctl create-pipeline -f infer.json

将新评论添加至评论 (reviews) 存储库后,Pachyderm 将看到新评论,执行推断并将推断结果输出至相应的推断 (inference) 目录。

事实上,我们已经向评论目录添加了 100 万条评论。如果在存储库中添加一条评论,Pachyderm 知道这是一条新评论后,只面向新评论更新结果。因为 Pachyderm 能够感知 diff ,保持处理与最新数据变化的同步,没有必要重新处理全部数据。

事实上,推断管线发挥批量和流推断管线的作用。您可以定期将许多评论添加至评论目录,在多个批次的评论中执行批量推断。您也可以创建个人评论后直接添加至存储库。无论使用哪种方式,Pachyderm 在新评论中自动执行推断并输出结果。

影响

将训练和推断结合到处理版本化数据的数据管线中,有助于我们利用价值极高的功能。数据科学家或工程师可以随时更新模型使用的训练数据集,在模型目录中创建永久性、版本化的全新模型。更新模型后,可以利用该模型处理进入评论存储库的任何新评论。

此外,利用更新模型可以重新计算之前的预测,或利用先前的版本化输入测试新模型。无需手动更新历史结果,更无需担心在生产中更换模型!

此外,尽管我们跳过了这个步骤,可以借助 并行规范对 Pachyderm 管线中的每个管线阶段进行单独扩展。如果一次性收到数万份评论,可以调整推断管线阶段的并行性,具体设置方法如下所示:

"parallelism_spec":{"strategy":"CONSTANT","constant":"10"
  }

或者将常量设置为大于 1 的任何数字。上述设置将告知 Pachyderm 启动多个 worker,以执行推断(上例需要 10 个 worker),Pachyderm 将在并行处理的 worker 之间自动划分评论数据。

Figure 3

我们可以重点关注开发和改善模型,Pachyderm 负责分配生产集群上的推断,还有助于确保实施的简单与可读性。可以将本地设备上开发的 Python/neon 脚本扩展为生产规模数据,无需考虑数据分片,利用 Dask* 等框架增加代码的复杂性,甚至面向生产使用将模型转换成其他语言或框架。

优化

尽管 Pachyderm 处理数据分片、并行性和协调分段,该管线为用户提供了几个出色的优化。

首先,训练和推断阶段都运行了导入 Neon 的 Python。我们可以利用 英特尔® Python 分发包* 进一步优化处理,无需修改任何代码。Python 分发包将强大的英特尔® 数学核心函数库(英特尔® MKL)、英特尔® 数据分析加速卡(英特尔® DAAL)与 pyDAAL、英特尔® MPI 库以及英特尔® 线程构建模块(英特尔® TBB)自动集成至内核 Python 分发包,包括 NumPy*、SciPy* 和 pandas*。

为了在 Pachyderm 中利用英特尔优化的 Python,只需将现有的 Neon 映像替换为基于某个公用英特尔 Python 映像的定制 Docker 映像。该映像的示例可以 Dockerfile创建。您只需将您选取的 Python 脚本添加至映像(如 此处所示),将映像上传至 DockerHub(或另一个注册表),并在 Pachyderm 管线规范中修改参考映像的名称。

此外,我们可以将管线部署于 英特尔® 至强融核™ 处理器架构上,该架构自动提升机器学习工作流程的速度。面向深度学习训练和推断中的各类处理对芯片进一步优化。

结论/资源

我们能够在 neon 中方便快捷地实施深度学习,并将它部署到生产集群上,还能在集群上分配处理,无需担心分片数据或代码并行性。生产管线将跟踪模型的版本,使处理与数据更新保持同步。

上述数据管线的全部代码和配置可在此处获取。

Pachyderm 资源:

包含 Neon 的英特尔 Nervana 技术资源:

 
关于作者

Daniel (@dwhitena) 是一名拥有博士学位的数据科学家,目前就职于 Pachyderm (@pachydermIO)。Daniel 开发了创新型、分布式数据管线,包含预测模型、数据可视化、统计分析等。他在全球各大会议上发表演讲(ODSC、Spark 峰会、Datapalooza、DevFest Siberia、GopherCon等),在 Ardan 研究院 (@ardanlabs) 教授数据科学/工程,面向 Jupyter 维护 Go 内核,并积极支持各种开源数据科学项目的筹办。

面向大数据的 Go* 语言

$
0
0

利用配有 Go* 编程语言的英特尔® 数据分析加速库 (英特尔® DAAL)支持批量、在线和分布式处理

最热门的现代基础设施项目均Go*,包括 Kubernetes*、Docker*、Consul*、etcd* 等。Go 成为了广泛应用于开发运营、web 服务器和微服务的 go to语言。它简单易学、便于快速部署,还为开发人员提供了一套出色的工具。

由于业务越来越受数据驱动,需要在公司基础设施的不同层次集成计算密集型算法,包括应用 Go 的层级。因此,有必要关注如何将机器学习、分布式数据转换和在线数据分析集成至蓬勃发展的基于 Go 的系统中。

为了在 Go 中提供高性能、可扩展的强大数据处理,可以在 Go 程序中使用英特尔® 数据分析加速库(英特尔® DAAL)。该库为一系列实用任务提供现成的批量、在线和分布式算法:

由于 Go 实现了与 C/C++ 的良好交互,我们可以将这个功能轻松地添加至 Go 程序,通过利用面向即购即用架构而优化的英特尔库来实现。如此处所示,进行某些操作时(如主组件分析),英特尔 DAAL 的速度比 Spark* 和 MLlib* 的速度高出 7 倍。哇!我们要抓紧时间探索如何利用上述性能升级 Go 应用。

安装英特尔® DAAL

英特尔 DAAL 以开源方式提供,可以通过以下指令安装。Linux* 设备上的指令非常简单:

  1. 下载开源代码。
  2. 运行安装脚本。
  3. 设置必要的环境变量(可以通过给出的 shell 脚本设置)。

将英特尔 DAAL 集成至 Go 程序前,请确保一切正常运行。根据英特尔 DAAL 文档中的各种入门指南进行操作。具体而言,这些入门指南提供面向 Cholesky 分解的示例英特尔 DAAL 应用,随后我们将利用 Go 重新创建 Cholesky 分解。Cholesky 分解的原始 C++ 示例如下所示:

/*******************************************************************************
!版权所有 © 2014-2017,英特尔公司。保留所有权利。
!
!本文所含的源代码、信息和材料
!归英特尔公司及其供应商或许可方所有,
!材料的所有权仍归英特尔及其供应商或许可方所有。材料
!包含了英特尔及其供应商或许可方的
!专有信息。这些材料受全球版权法律和条约条款
!的保护。不经英特尔事先书面许可,不得以任何形式使用、复制、翻制、
!修改、出版、上传、张贴、传输、发布或公开
!这些材料。本材料
!未通过明确、隐含、诱导、禁止反言或其它任何方式,
!授予您任何专利、版权或其它知识产权
!的许可。任何知识产权
!的授权必须得到英特尔
!的书面批准。
!
!*其他的名称和品牌可能是其他所有者的资产。
!
!除非经过英特尔的书面同意认可,不得以任何方式移除或更改
!本声明或英特尔及其供应商或许可方材料中的
!其他声明。
!
!*******************************************************************************
!内容:
!    Cholesky 分解示例程序。
!******************************************************************************/

#include "daal.h"
#include <iostream>

using namespace daal;
using namespace daal::algorithms;
using namespace daal::data_management;
using namespace daal::services;

const size_t dimension = 3;
double inputArray[dimension *dimension] =
{
    1.0, 2.0, 4.0,
    2.0, 13.0, 23.0,
    4.0, 23.0, 77.0
};

int main(int argc, char *argv[])
{
    /* Create input numeric table from array */
    SharedPtr<NumericTable> inputData = SharedPtr<NumericTable>(new Matrix<double>(dimension, dimension, inputArray));

    /* Create the algorithm object for computation of the Cholesky decomposition using the default method */
    cholesky::Batch<> algorithm;

    /* Set input for the algorithm */
    algorithm.input.set(cholesky::data, inputData);

    /* Compute Cholesky decomposition */
    algorithm.compute();

    /* Get pointer to Cholesky factor */
    SharedPtr<Matrix<double> > factor =
        staticPointerCast<Matrix<double>, NumericTable>(algorithm.getResult()->get(cholesky::choleskyFactor));

    /* Print the first element of the Cholesky factor */
    std::cout << "The first element of the Cholesky factor:"<< (*factor)[0][0];

    return 0;
}

尝试进行编译和运行,确保成功安装了英特尔 DAAL,还让您提前体验了一次 Go 中的操作。如果对英特尔 DAAL 安装有任何问题,可以在英特尔 DAAL 论坛中讨论,对我而言,它是宝贵的资源,有助于熟练使用英特尔 DAAL。 

在 Go 中使用英特尔 DAAL

可以利用以下几种方式在 Go 中使用英特尔 DAAL:

  1. 利用包装函数从 Go 程序中直接调用英特尔 DAAL。
  2. 创建一个包装特定英特尔 DAAl 功能的可复用库。

下面将展示两种方法,访问此处查看本文使用的全部代码。这只是一个示例,建议最终为存储库添加更多的 Go 和 英特尔 DAAL 示例。试验完毕后,提交您的 Pull 请求。我非常期待看到您的成果!

如果您刚刚接触 Go,可以在阅读本教程前,花点时间熟悉 Go。事实上,本地安装 Go 不是学习 Go 的必要条件。您可以参加在线 Go 概览课程,并使用< a href="https://play.golang.org/" target="_blank">Go Playground,一切就绪后,本地安装 Go

从 Go 中直接调用英特尔 DAAL

Go 实际提供一款称为 cgo的工具,生成调用 C 代码的 Go 程序包。在本例中,利用 cgo 实现 Go 程序与英特尔 DAAL 的互操作。

请注意:互联网上深入讨论了在 Go 程序中使用 cgo 的利弊(特别建议您查看 Dave Cheney 的讨论和 Cockroach 实验室* 撰写的一篇文章)。选择 cgo 之前,您应该考虑这些成本,至少知道成本的存在。在本示例中,为了利用高度优化的分布式英特尔 DAAL 库,我们乐意寻找均衡利用 cgo 的方法,极有可能改善特定的数据密集型或计算密集型使用案例。

为了在示例 Go 程序中集成英特尔 DAAL Cholesky 分解功能,需要创建以下目录结构(在 $GOPATH中):

cholesky`
├── cholesky.go`
├── cholesky.hxx`
└── cholesky.cxx`
 

cholesky.go文件就是利用英特尔 DAAL Cholesky 分解功能的 Go 程序。cholesky.cxxcholesky.hxx文件是包含英特尔 DAAL 的 C++ 定义/声明文件,将等待包装的英特尔 DAAL 功能的信号传输给 cgo。让我们逐一查看。

首先查看 *.cxx文件:

#include "cholesky.hxx"
#include "daal.h"
#include <iostream>

using namespace daal;
using namespace daal::algorithms;
using namespace daal::data_management;
using namespace daal::services;

int choleskyDecompose(int dimension, double inputArray[]) {

    /* Create input numeric table from array */
    SharedPtr<NumericTable> inputData = SharedPtr<NumericTable>(new Matrix<double>(dimension, dimension, inputArray));

    /* Create the algorithm object for computation of the Cholesky decomposition using the default method */
    cholesky::Batch<> algorithm;

    /* Set input for the algorithm */
    algorithm.input.set(cholesky::data, inputData);

    /* Compute Cholesky decomposition */
    algorithm.compute();

    /* Get pointer to Cholesky factor */
    SharedPtr<Matrix<double> > factor =
        staticPointerCast<Matrix<double>, NumericTable>(algorithm.getResult()->get(cholesky::choleskyFactor));

    /* Return the first element of the Cholesky factor */
    return (*factor)[0][0];
}

然后查看 *.hxx文件:

#ifndef CHOLESKY_H
#define CHOLESKY_H

// __cplusplus gets defined when a C++ compiler processes the file.
// extern "C" is needed so the C++ compiler exports the symbols w/out name issues.
#ifdef __cplusplus
extern "C" {
#endif

int choleskyDecompose(int dimension, double inputArray[]);

#ifdef __cplusplus
}
#endif

#endif

这些文件定义了一个以 C++ 编写的 choleskyDecompose 包装程序函数,该函数利用英特尔 DAAL Cholesky 分解功能计算输入矩阵的 Cholesky 分解,并输出 Cholesky 因数的第一个因素(和英特尔 DAAL 入门指南的内容相似)。请注意,在本示例中,输入阵列的长度等于矩阵的长度(即 3 x 3 矩阵与长度为 9 的输入阵列对应)。需要将 extern “C”置于 *.hxx文件中。这样做将使 cgo 调用的 C++ 编译器知道我们需要导出 C++ 文件中定义的相关名称。

*.cxx*.hxx文件中定义 Cholesky 分解包装程序函数后,可以从 Go 中直接调用该函数。cholesky.go如下所示:

package main

// #cgo CXXFLAGS:-I$DAALINCLUDE
// #cgo LDFLAGS:-L$DAALLIB -ldaal_core -ldaal_sequential -lpthread -lm
// #include "cholesky.hxx"
import "C"

import (
	"fmt""unsafe"
)

func main() {

	// Define the input matrix as an array.
	inputArray := [9]float64{
		1.0, 2.0, 4.0,
		2.0, 13.0, 23.0,
		4.0, 23.0, 77.0,
	}

	// Get the first Cholesky decomposition factor.
	data := (*C.double)(unsafe.Pointer(&inputArray[0]))
	factor := C.choleskyDecompose(3, data)

	// Output the first Cholesky dcomposition factor to stdout.
	fmt.Printf("The first Cholesky decomp. factor is:%d\n", factor)
}

下面将概述各个步骤,介绍其中的原理。首先告知 Go 需要在编译程序时使用 cgo 以及利用特定的标记编译:

// #cgo CXXFLAGS:-I$DAALINCLUDE
// #cgo LDFLAGS:-L$DAALLIB -ldaal_core -ldaal_sequential -lpthread -lm
// #include "cholesky.hxx"
import "C"

为了使用 cgo,需要导入“C”,C 是一个伪程序包,利用 C 通知 Go 我们正在使用cgo。如果导入“C”后立即出现了一条注释(被称为引言),编译程序包的 C++ 部件时将注释用作标头。

利用 CXXFLAGS 和 LDFLAGS 指定编译过程中 cgo 使用的编译和链接标记,可以通过 // #include "cholesky.hxx”添加 C++ 函数。我使用安装 gcc 的 Linux 编译本示例,因此,使用上文中给出的标签。您也可以依据 本指南决定如何将应用连接至英特尔 DAAL。

然后,编写 Go 代码(和其他程序的编写方式一样),利用 C.choleskyDecompose()访问包装函数:

// Define the input matrix as an array.
inputArray := [9]float64{
	1.0, 2.0, 4.0,
	2.0, 13.0, 23.0,
	4.0, 23.0, 77.0,
}

// Get the first Cholesky decomposition factor.
data := (*C.double)(unsafe.Pointer(&inputArray[0]))
factor := C.choleskyDecompose(3, data)

// Output the first Cholesky dcomposition factor to stdout.
fmt.Printf("The first Cholesky decomp. factor is:%d\n", factor)

使用 cgo 的一个独有特性是需要将 float64 片段的第一个要素指示器转换为不安全指示器,继而面向 choleskyDecompose函数明确转换为 *C.double(兼容 C++)指示器。不安全包,顾名思义,帮助我们绕过 Go 程序的类型安全。

非常好!现在,我们有了一个调用英特尔 DAAL Cholesky 分解的 Go 程序。现在我们开始创建并运行这个程序。像往常一样,利用 go build创建:

$ ls
cholesky.cxx  cholesky.go  cholesky.hxx
$ go build
$ ls
cholesky  cholesky.cxx  cholesky.go  cholesky.hxx
$ ./cholesky
The first Cholesky decomp. factor is:1
$

我们获得了预期的输出!事实上,第一个 Cholesky 分解因数为 1。我们成功了,可以从 Go 中直接利用英特尔 DAAL 的功能!但是,由于不安全包和 C 位,这个 Go 程序有点与众不同。而且,它属于一次性解决方案。现在,为了使我们的 Go 程序包和其他 Go 程序包一样能够导入,需要将它转换为可复用 Go 程序包。

利用英特尔 DAAL 创建可复用 Go 程序包

为了创建包装英特尔 DAAL 功能的 Go 程序包,需要使用一种名为 SWIG*的工具。除 cgo 以外,Go 具有在构建时调用 SWIG 的功能,编译包装 C/C++ 功能的 Go 程序包。为了启用这种构建,需要创建以下目录结构:

choleskylib
├── cholesky.go
├── cholesky.hxx
├── cholesky.cxx
└── cholesky.swigcxx
 

*.cxx*.hxx包装程序文件可以同时存在,现在需要添加一个 *.swigcxx文件,如下所示:

%{
#include "cholesky.hxx"
%}

%include "cholesky.hxx"

该文件指示 SWIG 工具生成面向 Cholesky 函数的包装代码,可以将代码用作 Go 程序包。

由于现在正在创建可复用 Go 程序包(而非独立的 Go 应用),不需要将 package main 或 function main 纳入 *.go文件。只需要定义程序包名称。本示例称其为 cholesky,意味着 cholesky.go如下所示:

package cholesky

// #cgo CXXFLAGS:-I$DAALINCLUDE
// #cgo LDFLAGS:-L$DAALLIB -ldaal_core -ldaal_sequential -lpthread -lm
import "C"

(再次提供标头标记。)

现在,可以在本地创建并安装程序包:

$ ls
cholesky.cxx  cholesky.go  cholesky.hxx  cholesky.swigcxx
$ go install
$

通过上述操作,创建了所需的全部二进制和库,当 Go 程序使用这个程序包时,可以调用这些二进制和库。Go 会发现目录中存在一个 *.swigcxx文件,随后它会自动利用 SWIG 创建程序包。

太棒了;我们创建了使用英特尔 DAAL 的 Go 程序包。我们看一下应该如何导入并使用这个程序包:

package main

import (
	"fmt""github.com/dwhitena/daal-go/choleskylib"
)

func main() {

	// Define the input matrix as an array.
	inputArray := [9]float64{
		1.0, 2.0, 4.0,
		2.0, 13.0, 23.0,
		4.0, 23.0, 77.0,
	}

	// Get the first Cholesky decomposition factor.
	factor := cholesky.CholeskyDecompose(3, &inputArray[0])

	// Output the first Cholesky dcomposition factor to stdout.
	fmt.Printf("The first Cholesky decomp. factor is:%d\n", factor)
}

好极了!和直接包装英特尔 DAAL 相比,这样看起来整洁多了。按照导入其他 Go 程序包的方式导入 Cholesky 程序包,并将包装函数命名为 cholesky.CholeskyDecompose(...)。此外,SWIG 帮助我们处理了所有的不安全因素。现在我们可以将原始 float64 片段的第一个元素的地址传输给 cholesky.CholeskyDecompose(...)

和其他 Go 程序类似,利用 go build编译与运行:

$ ls
main.go
$ go build
$ ls
example  main.go
$ ./example
The first Cholesky decomp. factor is:1
$

哇!答案正确。现在可以在另一个需要 Cholesky 分解的 Go 程序中使用这个程序包。

结论/资源

利用英特尔 DAAL、cgo 和 SWIG,能够将经过优化的 Cholesky 分解集成至 Go 程序中。但是,这项技术不仅限于 Cholesky 分解,您可以创建 Go 程序和程序包,使它们以同样的方式利用英特尔 DAAL 中的任何算法。这意味着您可以在 Go 应用中实施批量、在线和分布式神经网络、集群、加速、协同过滤等各种操作。

如欲获取本文使用的全部代码,请访问此处

Go 数据资源:

英特尔 DAAL 资源:

关于作者

Daniel (@dwhitena) 是一名拥有博士学位的数据科学家,目前就职于 Pachyderm (@pachydermIO)。Daniel 开发了创新型、分布式数据管线,包含预测模型、数据可视化、统计分析等。他在全球各大会议上发表演讲(ODSC、Spark 峰会、Datapalooza、DevFest Siberia、GopherCon等),在 Ardan 研究院 (@ardanlabs) 教授数据科学/工程,面向 Jupyter 维护 Go 内核,并积极支持各种开源数据科学项目的筹办。


英特尔® MKL-DNN:第一部分 – 库的概述和安装

$
0
0

简介

目前,在大型数据集、高度并行化的处理能力和增强设备智能性的需求的推动下,深度学习成为了计算机科学领域最受关注的热点话题之一。根据维基百科的描述,深度学习是机器学习 (ML) 的一个子集,由模拟高级别数据抽象的算法构成。如图 1 所示,机器学习是人工智能 (AI) 的一个子集,人工智能的研究范围非常广泛,其目标是开发模拟人类智能的计算机系统。


图 1.深度学习和人工智能的关系。

英特尔积极致力于深度学习领域的研究,为了充分利用英特尔® 架构 (IA) 而优化了常用的框架(如 Caffe* 和 Theano*),创建了高级别工具(如面向数据科学家的英特尔® 深度学习 SDK),还为开发人员社区提供了强大的软件库,如英特尔® 数据分析加速库(英特尔® DAAL)面向深度神经网络的英特尔® 数学核心函数库(英特尔® MKL-DNN)

英特尔 MKL-DNN 是一个开源的性能增强库,能够提高在英特尔架构上运行的深度学习框架的速度。对深度学习感兴趣的软件开发人员可能听说过英特尔 MKL-DNN,但是有可能未曾使用过它。

英特尔 MKL-DNN 教程系列的开发人员简介从开发人员的角度介绍了英特尔 MKL-DNN。第一部分提供了丰富的资源,详细介绍了如何安装和构建库组件。教程系列的第二部分介绍了如何配置 Eclipse* 集成开发环境,以创建 C++ 代码示例,还提供了源代码演示。

英特尔® MKL-DNN 概述

如图 2 所示,英特尔 MKL-DNN 专为在英特尔架构上加快深度学习框架的速度而设计,包含了高度矢量化和线程化的构建模块,支持利用 C 和 C++ 接口实施卷积神经网络。


图 2.英特尔架构上的深度学习框架。

英特尔 MKL-DNN 主要在以下对象上运行:基元、引擎和流。库文档对这些对象的定义如下所示:

  • 基元 - 任何操作,包括卷积、数据格式重新排序和内存。基元可以以其他基元为输入,但是智能输出内存基元。
  • 引擎 - 一种执行设备,如 CPU。每个基元都映射为特定的引擎。
  • - 一种执行环境,将基元提交至流后等待完成。提交至流的基元可能有不同的引擎。流对象也可以跟踪基元间的相关性。

典型的工作流程创建一系列基元,将基元传输至流进行处理,并等待处理的完成。如欲获取关于编程模式的更多信息,请查看英特尔 MKL-DNN 文档

资源

您可以通过互联网获取关于英特尔 MKL-DNN 的实用资源,了解英特尔 MKL-DNN 的功能和限制以及将它集成至深度学习项目所带来的预期效果。

GitHub 存储库

英特尔 MKL-DNN 是一款面向深度学习应用的开源性能库,库中包含一些构建模块,用于实施带 C 和 C++ 接口的卷积神经网络 (CNN),可以在 GitHub* 上免费下载。

关于 GitHub 网站需要注意的是,尽管英特尔 MKL-DNN 和英特尔® 数学核心函数库(英特尔® MKL)2017的功能相似,但是前者不兼容 API。在撰写本文时,英特尔 MKL-DNN 发布了技术预览版,添加了加快图像识别拓扑(如 AlexNet* 和 VGG*)速度所需的功能。

英特尔开源技术中心

MKL-DNN|01.org项目微型网站是英特尔开源技术中心的成员,该技术中心的名称为 01.org,参加过各种开源项目的英特尔工程师都参与到该社区当中来。该网站包含英特尔 MKL-DNN 项目概述,如何参加项目与实施项目的信息,还提供一篇内容丰富的博文“面向神经网络的英特尔® 数学核心函数库(英特尔® MKL-DNN)简介”(作者:Kent Moffat)。

安装英特尔 MKL-DNN

本章节详细介绍了安装和构建英特尔 MKL-DNN 库组件的逐步说明,进一步解释了 GitHub 存储库网站上提供的安装信息。您的电脑必须配备支持英特尔® 高级矢量扩展指令集 2(英特尔® AVX2)的英特尔® 处理器。英特尔 MKL-DNN 专门面向英特尔® 至强® 处理器和英特尔® 至强融核™ 处理器优化。

GitHub 指出软件在 RedHat* Enterprise Linux* 7 上验证,但是本教程提供的信息是关于在运行 Ubuntu* 16.04的系统上进行开发。

安装关联组件

英特尔 MKL-DNN 包含以下关联组件:

  • CMake* – 一款用于创建、测试和打包软件的跨平台工具。
  • Doxygen* – 一款从自动源代码中生成文档的工具。

如果您的电脑没有安装这些软件工具,可以输入以下命令进行安装:

sudo apt install cmake

sudo apt install doxygen

下载并创建源代码

打开终端并输入以下命令,便可从 GitHub 存储库复制英特尔 MKL-DNN 库:

git clone https://github.com/01org/mkl-dnn.git

注:如果您的电脑没有安装 Git*,可以输入以下命令进行安装:

sudo apt install git

安装完成后,您会在 Home 目录下发现一个 mkl-dnn目录。输入以下命令转至目录:

cd mkl-dnn

GitHub 存储库网站是这样说明的:英特尔 MKL-DNN 使用了英特尔 MKL 中的经过优化的一般矩阵乘法 (GEMM) 函数。存储库还包含支持这个功能的库,运行 scripts目录中的 prepare_mkl.sh脚本进行下载:

cd scripts && ./prepare_mkl.sh && cd ..

这个脚本创建了一个名为 external的目录,然后下载并提取库文件至 mkl-dnn/external/mklml_lnx*目录。

mkl-dnn目录中执行下一条命令,通过命令创建了一个 build子目录,并运行 CMakeMake,以生成构建系统:

mkdir -p build && cd build && cmake ..&& make

验证构建

为了验证构建,从 mkl-dnn/build目录中执行以下命令:

make test

这个步骤执行了一系列验证构建的单元测试。全部测试均应显示通过 (Passed),处理时间如图 3 所示。


图 3.测试结果。

库文档

可以在线获取英特尔 MKL-DNN 文档。从 mkl-dnn/build目录中执行以下命令,也可以在系统中本地生成这个文档:

make doc

完成安装

执行 mkl-dnn/build目录中的以下命令,完成英特尔 MKL-DNN 的安装:

sudo make install

以下命令将在 /usr/local目录中安装库和其他组件,这些组件是开发面向英特尔 MKL-DNN 的应用所必需的:

共享库 (/usr/local/lib):

  • libiomp5.so
  • libmkldnn.so
  • libmklml_intel.so

标头文件 (/usr/local/include):

  • mkldnn.h
  • mkldnn.hpp
  • mkldnn_types.h

文档 (/usr/local/share/doc/mkldnn):

  • 英特尔许可和版权声明
  • 构成 HTML 文档的各种文件(在 /reference/html之下)

在命令行上创建代码示例

GitHub 存储库包含 C 和 C++ 代码示例,展示了如何创建包含卷积、修正线性单元、本地响应标准化和池化的神经网络拓扑模块。以下章节介绍了如何在 Linux 中利用命令行创建代码示例。教程系列的第二部分展示了如何面向创建和扩展 C++ 代码示例而配置 Eclipse 集成开发环境。

C++ 示例命令行构建 (G++)

为了创建包含在英特尔 MKL-DNN 存储库中的 C++ 示例程序 (simple_net.cpp),首先转至 examples目录:

cd ~/mkl-dnn/examples

然后,为可执行代码创建一个目标目录:

mkdir –p bin

通过连接英特尔 MKL-DNN 共享库和指定以下输出目录,创建 simple_net.cpp 示例:

g++ -std=c++11 simple_net.cpp –lmkldnn –o bin/simple_net_cpp


图 4.使用 G++ 的 C++ 命令行构建。

转至 bin目录并运行可执行代码:

cd bin

./simple_net_cpp

使用 GCC 的 C 示例命令行构建

为了创建包含在英特尔 MKL-DNN 存储库中的 C 示例应用 (simple_net.c),首先转至 examples目录:

cd ~/mkl-dnn/examples

然后,为可执行代码创建一个目标目录:

mkdir –p bin

通过连接英特尔 MKL-DNN 共享库和指定以下输出目录,创建 simple_net.c 示例:

gcc –Wall –o bin/simple_net_c simple_net.c -lmkldnn


图 5.利用 GCC 的 C 命令行构建。

转至 bin目录并运行可执行代码:

cd bin

./simple_net_c

完成后,C 应用将向终端输出通过 (passed) 或失败 (failed)。

后续步骤

此时您已经成功安装了英特尔 MKL-DNN 库,执行了单元测试,并且创建了存储库所提供的示例程序。英特尔 MKL-DNN 开发人员简介的第二部分介绍了如何配置 Eclipse 集成开发环境,以创建 C++ 代码示例,还提供了代码演示。

Unreal Engine 4 优化教程第三部分

$
0
0

此为教程第三部分,旨在帮助开发人员利用 Unreal Engine* 4 (UE4) 提升游戏性能。本教程对引擎内部和外部使用的一系列工具以及面向编辑器的最佳实践加以概述,还提供了有助于提高帧速率和项目稳定性的脚本。

脚本优化

禁用完全透明对象

即使完全透明的游戏对象也会用到渲染绘制调用。为避免这些调用浪费,可设置引擎停止对它们的渲染。

如要使用蓝图完成该操作,UE4 需有多个系统共同协作。

材质参数集合

首先,创建一个材质参数集合 (MPC)。这些资源储存了可被游戏中任何材质引用的标量参数和向量参数,可用于在游戏中修改这些材质以产生动态效果。

可通过在 Create Advanced Asset > Materials & Textures 菜单中选中它来创建 MPC。


图 32:创建一个材质参数集合。

在 MPC 中,可创建、命名和设置默认的标量参数和向量参数值。对于该优化,我们使用 Opacity(透明度)标量参数并用其控制材质的透明度。


图 33:设置 Opacity 标量参数

材质

接下去,我们需要可利用 MPC 的材质。在该材质中,创建一个名为 Collection Parameter 的 节点。通过该节点选择 MPC 以及其将使用的参数。


图 34:在材质中获取 Collection Parameter 节点。

创建节点后,拖曳其 Return Pin(回位梢)以使用该参数的值。


图 35:在材质中设置 Collection Parameter。

蓝图脚本部分

在创建 MPC 和材质后,我们可通过蓝图设置和获取 MPC 的值。可通过 Get/Set Scalar Parameter Value 和 Get/Set Vector Parameter Value 调用和更改这些值。在这些节点中,选择要使用的集合 (MPC) 以及该集合内的参数名称。

在本示例中,我们将 Opacity 标量值设置为游戏时间的正弦值,介于 1 到 -1 之间。


图 36:设置和使用标量参数并在函数中使用它的值。

如要设置是否渲染对象,我们可创建名为 Set Visible Opacity 的新函数,以 MPC 的 Opacity 参数值和一个静态网格组件为输入,输出则为对象是否可见的 Boolean 值。

然后我们进行是否大于近似零值的检测,在本示例中为 0.05。检测 0 值也可以,但接近零时玩家无法再看到对象,因此我们可在值接近零时将其关闭。不将标量参数准确设置为零有助于在出现浮点错误时提供缓冲,确保其被关闭(例如,将其设置为 0.0001)。

之后,运行分支,其中 True 条件设置对象可见度为 true,False 条件设置为 false。


图 37: 设置 Visible Opacity 函数。

Tick、剔除和时间

如果场景中的蓝图使用 Event Tick,则即使对象不再出现在屏幕中,脚本也会运行。正常情况下这没有问题,但场景中每帧调用的蓝图越少,其运行得越快。

可使用本优化的部分示例如下:

  • 在玩家不看时无需进行的东西
  • 根据游戏时间运行的进程
  • 在玩家不在时无需做任何事情的非玩家角色 (NPC)

作为一种简单的解决方案,我们可在 Event Tick 开始时添加“是否最近进行渲染”(Was Recently Rendered) 检查。这样,我们无需再关联自定义时间和侦听器来进行“tick”的开关,系统仍可独立于场景中的其他 Actor。


图 38:利用剔除系统控制 Event Tick 的内容。

按该方法,如果我们有根据游戏时间运行的进程,如每秒钟变暗和变亮的按钮发光材质,我们可使用下文中的方法。


图 39:在进行渲染时,材质集合的 Emissive Value 设置为时间的绝对正弦值。 

图中所示是通过绝对正弦值加 1 (产生介于 1 和 2 之间的正弦波)来检测经过的游戏时间。

此举的优势是不管玩家什么时候看这个按钮,即使是他们旋转或长期注视时,因为值设置为游戏时间的正弦值,按钮始终看起来按该曲线准确对时。

这在模组 (modulo) 中也能发挥作用,虽然图形看上去有点不同。

之后可在 Event Tick 中调用该检测。如果 Actor 在每帧中需要完成多项重要任务,其可在渲染检测前进行。蓝图中 tick 所调用节点数量的任何减少都能实现一定程度的优化。


图 40:使用剔除控制蓝图的视觉组成部分。

限制蓝图消耗的另一方法是减慢其速度并只允许每一时间间隔只“tick”一次。这可通过 Set Actor Tick Interval 节点实现,由此可通过脚本设置所需时间。


图 41: Tick 时间间隔切换。

此外还可在蓝图的 Details 选项卡设置 Tick Interval。这使蓝图按秒数时间进行 tick 时也能进行设置。


图 42: Details 选项卡中的 Tick Interval。

例如,这在计秒数应用中很有用。


图 43:设置秒计蓝图仅每秒 tick 一次。

以下示例减少了平均毫秒数,可帮助我们了解该优化的作用。


图 44:“不作为”可导致高效的示例

这里我们有进行 0 到 10000 计数的 ForLoop,我们将整数计数设置到当前的 ForLoop 计数中。这一蓝图会消耗很多,效率非常低,导致我们的场景运行时间长达 53.49 毫秒。


图 45:使用 Stat Unit 查看以上示例的消耗。

如果我们进入分析器 (Profiler) 就知道为什么了。这一简单但消耗众多资源的蓝图每次 tick 需耗费 43 毫秒。


图 46:分析器中可看到的示例每帧 tick 所需的消耗。

但,如果我们仅每秒钟 tick 该蓝图一次,则大部分时间它只需 0 毫秒。如果查看蓝图三个 tick 周期的平均时间(单击并拖曳 Graph View 的一块区域),我们会发现其所使用的平均时间是 0.716 毫秒。


图 47:分析器中可看到的示例每秒 tick 一次所需的消耗。

可再看一个更为常见的示例,如果在以 60 fps 运行的场景中有一个运行时间为 1.4 毫秒的蓝图,其需要 84 毫秒的处理时间。但是,如果我们可减少其 tick 时间,它就能减少蓝图的总处理时间。

大量移动、ForLoop 和多线程

让多个网格同时移动,这是个非常棒的想法,可成为游戏的真正视觉风格卖点。但所需的处理资源会给 CPU 造成巨大压力,并反过来影响 FPS 。幸运的是,借助多线程功能和 UE4 的 Worker 线程处理,我们能够将这一大量的处理分散到多个蓝图,从而实现优化。

在本章节,我们将使用以下蓝图脚本,随修改后的正弦曲线上下移动由 1600 个实例化球面网格组成的集合。

以下是构建栅格的简单构造脚本。仅需将实例化静态网格组件添加到 Actor 中,在 Details 选项卡选择其使用的网格,而后将这些节点添加到其构造中。


图 48:构建简单栅格的构造脚本。

创建栅格后,将这一蓝图脚本添加到 Event Graph 中。

关于 Update Instance Transform 节点,需要注意以下问题。在修改任何示例的变换时,除非将 Mark Render State Dirty 标记为 True,否则无法看到变化。但是,这是一个耗费甚多的操作,因为它会进入实例的每一网格并将之标记为“dirty”。为节省操作消耗,特别是当节点在单一 tick 中运行多次时,在该蓝图结束时再更新网格。在以下脚本中,我们仅在处于 ForLoop 的 Last Index 中,且当索引值等于 Grid Size 减去 1 时,才将 Mark Render State Dirty 标记为“true”。


图 49:实例化静态网格的动态移动蓝图。

利用我们的 Actor 蓝图和栅格创建构造以及动态移动事件,我们可放置多个不同的变体,目标都是一次性显示 1600 个网格。


图 50:将 1600 个网格的栅格分解成不同变体的示意图。

在运行场景时,我们会看到栅格各片上下移动。


图 51:动态移动的实例化静态网格栅格(1600 个网格)。

但是,栅格的分解会影响场景的运行速度。

从以上图表可知,分为 1600 片的实例化静态网格格栅(每片 1 个网格)(甚至是抵消了使用实例的意义)和单一一片 1600 网格的栅格运行最慢,其余的性能则在 19 和 20 毫秒之间。

单片运行最慢的原因在于运行 1600 个蓝图的时间是 16.86 毫秒,每个蓝图平均仅 0.0105 毫秒。但是,虽然每一蓝图所需时间很短,如此多数量累加会拖累整个系统。可进行优化的唯一地方是减少每 tick 所运行的蓝图数量。其他变慢原因包括大量单个网格所导致绘制调用以及网格变换命令数量的增加。

图的另一极是第二慢的单一一片 1600 网格格栅。这一网格在绘制调用上非常高效,因为整个栅格只有一次绘制调用,但运行蓝图时必须在每次 tick 更新所有 1600 个网格,导致需 19.63 毫秒的时间进行处理。

比较其它三个集合的处理时间,我们可发现分解这些大量移动 Actor 的好处,因为这样可实现更短的脚本时间并利用引擎内的多线程功能。因为 UE4 利用多线程,它将蓝图分散到多个 Worker 线程中,可通过有效利用所有 CPU 内核来加速求值。

如果查看蓝图运行时间的简单分解以及它们在 Worker 线程间的分配,我们可得到下图。

数据结构

对于任何程序而言,使用正确的数据类型都是重中之重,而游戏开发与任何其他软件开发一样,也是如此。在 UE4 中利用蓝图进行编程时,对于作为主要容器的模板数组并未给出数据结构。它们可利用 UE4 提供的函数和节点手动创建。

典型用途

关于在游戏开发中为何以及如何使用数据结构,可考虑射击 (Shmup) 风格的游戏。射击游戏的主要机制之一就是在屏幕上向接近的敌人射出成千上万的子弹。虽然可以生成每一颗子弹、然后销毁它们,这会要求引擎进行大量的碎片收集,并导致帧率变慢或丢失。为应付该问题,开发人员可考虑子弹的孵化池(对象集合均被放置到在游戏开始时将处理的数组和 List 中),按需启用和禁用它们,使引擎仅需一次创建所有子弹。

使用这些孵化池的一种常见方法是抓取未启用数组/List 中的第一颗子弹,使之进入出发位置、启用子弹、然后在子弹飞出屏幕或射入敌人后再禁用它。该方法的问题是脚本的运行时间,或者说Big O问题。因为是在对象集合中迭代寻找下一被禁用对象,如果集合有 5000 个对象(举例说明),找到一个对象需要多次迭代。这种类型的函数会出现 O(n) 时间,其中 n 是集合中对象的数量。

当 O(n) 未糟糕到算法无法进行时,我们越能够接近 O(1),即无关集合大小的固定消耗,脚本和游戏的效率越高。如孵化池要实现这一目标,我们可利用 Queue(队列)数据结构。和现实生活中的队列一样,这一数据结构获取集合中的第一个对象、使用它并在之后移除它,而后继续队列直至每一对象从前端出队。

通过对孵化池使用队列,我们可获取我们集合的前端对象、启用它、而后在集合中弹出(移除)它并立即将之推到集合后端;从而在我们的脚本中实现高效的循环并将运行时间减少到 O(1)。我们还可在该循环中添加启用的检查。如果本将弹出的对象已启用,脚本会取代该对象,转而孵化新的对象、启用它、而后将之推到队列后端,由此增加集合的大小而不会降低运行时间的效率。

队列

以下各图说明了如何在蓝图中执行队列、使用函数帮助维持代码的简洁和重用性。

弹出


图 52:蓝图中执行的列队弹出,包含返回操作。

后推


图 53:蓝图中执行的队列后推。

空缺


图 54:蓝图中执行的队列空缺。

大小


图 55:蓝图中执行的队列大小。

前端


图 56:蓝图中执行的队列前端。

后端


图 57:蓝图中执行的队列后端。

插入


图 58:蓝图中执行的队列插入,包含位置检查。

交换


图 59:蓝图中执行的队列交换,包含位置检查

堆栈

以下各图说明了如何在蓝图中执行堆栈、使用函数帮助维持代码的简洁和重用性。

弹出


图 60:蓝图中执行的堆栈弹出,包含返回操作。

后推


图 61: 蓝图中执行的堆栈后推。

空缺


图 62:蓝图中执行的堆栈空缺。

大小


图 63:蓝图中执行的堆栈大小。

后端


图 64:蓝图中执行的堆栈后端。

插入


图 65:蓝图中执行的堆栈插入,包含位置检查。

回到第 2 部分

Unreal Engine 4 优化教程第二部分

$
0
0

这是教程的第 2 部分,旨在帮助开发人员提升 Unreal Engine* 4 (UE4) 的游戏性能。本教程对引擎内部和外部使用的一系列工具以及面向编辑器的最佳实践加以了概述,还提供了有助于提高帧速率和项目稳定性的脚本。

编辑器优化

正向与延迟渲染

延迟渲染是 UE4 使用的标准渲染方式。虽然这种方式似乎是最佳的,但我们需要理解一些它对性能的重要影响,尤其是对于 VR 游戏和低端硬件的影响。在这些情况下使用正向渲染可能会更好。

如欲了解更多关于正向渲染的效果,请参阅 Epic 文档

如果查看 Epic’s Marketplace 中的反射场景,我们可以看见延迟渲染和正向渲染之间一些明显的区别。


图 13:延迟渲染的反射场景


图 14:正向渲染的反射场景

虽然正向渲染会因为反射、照明和阴影而导致丧失视觉保真,但剩余的场景从视觉上看没有改变,而性能的提高也许弥补了这种折衷。

如果我们在英特尔 GPA 帧分析器工具内,使用延迟渲染查看场景的帧捕获,那么我们会看到该场景正在以 103.6 毫秒 (9 fps) 运行,照明和反射占据了大部分持续时间。


图 15:使用英特尔® 高清显卡 530 的延迟渲染捕获反射场景

通过查看正向渲染捕获,我们发现场景运行时间已经从 103.6 毫秒降低到 44.0 毫秒,或者说优化了 259%,其中大部分时间由基础通道和后处理占据,两者均可以进一步优化。


图 16:使用英特尔® 高清显卡 530 的正向渲染捕获反射场景

细节级别

UE4 内部静态网格拥有成千甚至上万的三角形,可以将 3D 艺术家想要加入他们作品的全部最小的细节展示出来。但是,当玩家远离这种模型时,即使引擎仍然在渲染全部三角形,他们也看不到任何细节。为了解决这个问题并优化游戏,我们可以使用细节级别 (LOD) 将该细节拉近,同时仍显示远处较弱的模型。

LOD 生成

在标准管道下,LOD 由 3D 建模人员在创建该模型时创建。由于这种方法可以对最终外观实现最大控制,UE4 现在加入了可生成 LOD 的极佳工具。

自动 LOD 生成

前往该模型的细节选项卡,可自动生成静态网格 LOD。在LOD Settings 面板选择需要创建的 LOD 数量。


图 17:创建自动生成的细节级别。

点击 Apply Changes 向引擎发出信号生成 LOD 并编号,使用 LOD0 作为原始模型。从下例中,我们可以看到第 5 个生成的 LOD 将静态网格三角形的数量从 568 降到了 28,这是 GPU 的一个重大优化。


图 18:三角形和顶点数,以及针对每个细节级别的屏幕尺寸设置。

将 LOD 网格置入场景后,我们会看网格会随着离开镜头而变化。


图 19:基于屏幕尺寸的细节级别的视觉演示。

LOD 资料

LOD 的另一个特点是每个 LOD 拥有自己的资料,这样我们可以进一步减少静态网格的成本。


图 20:资料实例适用于所有细节级别。

例如,法线贴图已在业内成为标准。然而,对于 VR 来说却存在一个问题;法线贴图在拉近时并不理想,因为玩家看到的仅是一个平面。

解决该问题的一种方法就是使用 LOD。将 LOD0 静态网格细致化处理到螺栓和螺钉的建模点上,玩家就会在拉近观看时获得更沉浸的体验。因为已对全部细节建模,所以可以免去这一级别应用法线贴图的成本。当玩家远离网格和转换 LOD 时,便会交换法线贴图,同时也减少模型细节。当玩家离得更远时,网格会变得更小,这时可以再一次去除法线贴图,因为它已经小得看不到了。

实例化静态网格

每次在场景中添加任何事物时,都相当于图形硬件的额外绘制调用。如果此物是某一级别的静态网格,则它会应用于该网格的每个副本。如果在某一级别上反复出现几次相同的静态网格,那么进行优化的一种方法是将该静态网格实例化,以减少绘制调用的数量。

例如,下图是由 200 个八面体网格组成的两个球体;一组呈绿色,另一组呈蓝色。


图 21:静态球体和实例化的静态网格。

绿色组的网格均是标准静态网格,这表示每个网格拥有自己的绘制调用集合。


图 22:场景中 200 个静态网格球体的绘制调用(最多 569 个)。

蓝色组的网格均是单实例静态网格,这表示这些网格共用一个绘制调用集合。


图 23:场景中 200 个实例化静态网格球体的绘制调用(最多 143 个)。

查看两组的 GPU 可视化工具,可看到绿色(静态)球体的基础通道持续时长是 4.30 毫秒,而蓝色(实例化)球体的渲染时间是 3.11 毫秒;在本场景中持续时长优化了大约 27%。

关于实例化静态网格须知的一点是,如果该网格的任何部分被渲染,那么整个集合都会被渲染。如果任何部分离开镜头,那么这会浪费潜在的吞吐量。建议在更小的区域内使用单一实例化网格集;例如,一堆石头或垃圾袋、一堆盒子和拉远的模块化建筑。


图 24:即使看不到大部分实例化网格球体,但它们仍在渲染。

分层实例化静态网格

如果使用了有 LOD 的静态网格集合,则考虑使用分层实例化静态网格。


图 25:有细节级别的分层实例化网格球体。

像标准实例化网格一样,分层实例减少了网格进行的绘制调用,但分层实体也使用其网格的 LOD 信息。


图 26:拉近观察有细节级别的分层实例化网格球体。

遮挡

在 UE4 中,遮挡剔除指不渲染玩家看不到的对象的系统。这样会帮助减少对游戏的性能要求,因为不必绘制每一帧每一级的每个对象。


图 27:八面体分布。

想要看到有绿色边界框的被遮挡对象,可以在编辑器的控制命令台输入 r.VisualizeOccludedPrimitives 1(输入 0 则关闭)。


图 28:使用 r.VisualizeOccludedPrimitives 1 查看被遮挡网格的边界

是否绘制网格的控制因素与其边界框有关。鉴于此,玩家可能看不到一些绘制的对象,但镜头却能看到边界框。


图 29:在网格细节窗口查看边界。

如果一个网格需要在被玩家看到前渲染,例如为了获得更多的流时间或让空闲的动画在被看到前渲染,则可以依次进入网络设置窗口中 Static Mesh Settings > Positive Bounds Extension 和 Negative Bounds Extension 增加边界框的大小。


图 30:设置网格边界范围。

由于复杂网格和形状的边界框总是延展至网格边缘,所以产生空白会导致网格渲染更加频繁。务必要考虑网格边界框将怎样影响场景性能。

我们可以对 3D 模型设计和导入至 UE4 进行思考实验,考虑怎样制作立体布景(罗马圆形大剧场风格的竞技场)。

想象一下,一位玩家正站在竞技场的中央,环顾巨大罗马竞技场,即将降服他的对手。当玩家旋转镜头时,镜头方向和角度会确定游戏引擎正在渲染哪些内容。由于此区域是我们游戏的立体布景,所以该场景布置得十分细致,但为了节省绘制调用,我们需要使用固体碎片制作。首先,我们要抛弃使用一块固体碎片制作竞技场的想法。在本例中,必须绘制的三角形数量等于整个竞技场,因为我们是以单一对象绘制的场景(无论是否在视野范围内)。如何改善模型将其应用到游戏当中呢?

看情况而定。我们的决定会受到一些因素的影响。首先,如何切割切片;第二,这些切片对边界框的遮挡剔除有何影响。例如,玩家为了提高可视度,将镜头角度调到 90 度。

如果我们使用披萨饼型切片,我们可以切出八份完全一样的切片,围绕基准点旋转形成整个竞技场。这种方法比较简单,但远没有达到遮挡的效果,因为存在很多重叠的边界框。如果玩家站在中间环顾四周,他们的镜头始终横跨三个或四个边界,结果导致经常绘制出半个竞技场。最糟糕的情况是,当玩家退回内墙遥望竞技场的时候,八个切片都会渲染而无法优化。

接下来,我们采用井字画法,分成九个切片。这种方法并不是非常正规,但却有一点优势,就是没有重叠边界框。与切披萨的方法相同,当玩家站在竞技场中间时,他们的镜头始终横跨三个或四个边界。然而,在玩家站在内墙时的最糟糕情况下,此时会渲染九个切片当中的六片,相比披萨切法略有优化。

最后一例,我们采用取苹果核的切法(即一个单独的中心块和八块壁切片)。这种方法是思考实验最常见的方法,它造成的重叠少,是建模的好方法。玩家站在中央时会横跨五个或六个边界,但是与其他两种切法不同,这种切法最差的情况是九个切片当中有五个或者六个会被渲染。

图 31:展示如何切割大型模型,以及如何影响边界框和重叠部分的思考实验。

级联阴影贴图

动态阴影级联可以让游戏的细节级别很高,但同时它也很昂贵,并且需要强大的游戏电脑来运行才不会丢失帧速率。

幸运的是,从其名称中可以看出,这些阴影是以动态形式一帧一帧地创建的,所以玩家可以在游戏中按偏好优化设置。

使用英特尔® 高清显卡 350 动态阴影级联的成本

动态阴影级联的级别可以通过若干方式进行动态控制:

  • 在 Engine Scalability Settings 下进行阴影质量设置
  • 在 BaseScalability.ini 文件下编辑 r.Shadow.CSM.MaxCascades的整数值(0 至 4),之后更改 sg.ShadowQuality(在 0 至 3 之间设置低、中、高,以及 Epic)
  • 当手动设置 r.Shadow.CSM.MaxCascades时,在游戏内的蓝图中添加执行控制命令台

返回第一部分   下一节

英特尔® 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 生产发行版。

BigDL – Apache Spark* 集群上的横向扩展深度学习

$
0
0

要点综述

BigDL 是一种面向 Apache Spark* 的分布式深度学习库。用户可以通过 BigDL 将深度学习应用编写为标准的 Spark 程序,可以直接在现有的 Spark 或 Hadoop* 集群上运行。这有助于在现有的 Hadoop/Spark 集群上提供深度学习功能,并分析 HDFS*、HBase* 和 Hive* 中已经存在的数据。BigDL 的其他常见特性包括:

  • 丰富的深度学习支持。 Torch* 模仿, BigDL 为深度学习提供综合支持,包括数值计算(通过 Tensor)和高级神经网络;此外,用户可以利用 BigDL 将预训练 Caffe* 或 Torch 模型加载至 Spark 程序。
  • 极高的性能。为了获得出色的性能,BigDL 将英特尔® 数学内核函数库(英特尔® MKL)和多线程编程应用到每个 Spark 任务中。因此,相比现成的开源Caffe、Torch 或 TensorFlow,BigDL 在单节点英特尔® 至强® 处理器上的运行速度高出多个数量级(即与主流 GPU 相当)。
  • 高效的横向扩展。 BigDL 通过 Apache Spark(一种极速分布式数据处理框架)实现高效横向扩展,执行大数据规模数据分析,在 Spark 上有效实施同步 SGD 和 all-reduce 通信。

图 1 显示了 BigDL 程序在现有 Spark 集群上的执行情况。在集群管理器和应用主进程或驱动程序的帮助下,Spark 任务可在 Spark 工作节点或容器(执行程序)间分布。英特尔 MKL 有助于 BigDL 更快地执行 Spark 任务。

图 1.BigDL 程序在 Spark* 集群上的执行情况。

试验设置

虚拟 Hadoop 集群

您可以参考面向 Apache Hadoop 的 Cloudera* 管理员培训指南,了解如何设置用于试验的四节点虚拟 Hadoop 集群,其中 YARN* 用作资源管理器。该集群上安装了独立 Spark 和 Spark on YARN。

Virtual Machine

Node_1

Node_2

Node_3

Node_4

Services

NameNode

Secondary NameNode

ResourceManager

JobHistoryServer

NodeManager

NodeManager

NodeManager

NodeManager

DataNode

DataNode

DataNode

DataNode

Spark Master

Spark Worker

Spark Worker

Spark Worker

Spark Worker

 

 

 

物理机(主机)– 系统配置

系统/主机处理器

英特尔® 至强® 处理器 E7-8890 v4 @ 2.20 GHz ;(4 插槽)

物理内核总数

96

主机内存

512 GB DDR-1600 MHz

Host OS

Linux*; 版本 3.10.0-327.el7.x86_64

Virtual Guests

4

虚拟机客户机 - 系统配置

系统/客户机处理器

英特尔® 至强® 处理器 E7-8890 v4 @ 2.20 GHz

物理内核

18

主机内存

96 GB DDR-1600 MHz

Host OS

Linux*; 版本 2.6.32-642.13.1.el6.x86_64

Java 版本

1.8.0_121

Spark 版本

1.6

Scala 版本

2.10.5

CDH 版本

5.10

BigDL 安装

前提条件

构建 BigDL 需要安装 Java* Java。最新版的 Java 可从Oracle 网站下载。运行 Spark 2.0 时,强烈建议使用 Java 8,否则可能会出现性能问题。

导出 JAVA_HOME=/usr/java/jdk1.8.0_121/

下载和构建 BigDL 需要安装 Maven* Apache Maven 软件管理工具。最新版的 Maven 可从Maven 网站下载和安装

wget http://mirrors.ibiblio.org/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz
export M2_HOME=/home/training/Downloads/apache-maven-3.3.9
export PATH=${M2_HOME}/bin:$PATH
export MAVEN_OPTS="-Xmx2g -XX:ReservedCodeCacheSize=512m"

当使用 Java 7 进行编译时,你需要添加 “-XX:MaxPermSize=1G”,以避免出现 OutOfMemoryError。

export MAVEN_OPTS="-Xmx2g -XX:ReservedCodeCacheSize=512m -XX:MaxPermSize=1G"

Building BigDL

下载 BigDL。BigDL 源代码位于GitHub*。

git clone https://github.com/intel-analytics/BigDL.git

强烈 建议您使用  make-dist.sh 脚本来构建 BigDL

bash make-dist.sh

这将借助实用程序脚本 (${BigDL_HOME}/dist/bin/bigdl.sh)创建目录 dist 以便设置 BigDL 环境,以及使用所需的 Spark 依赖性、Python* 和其它支持工具与库来创建打包的 JAR* 文件。

默认情况下,make-dist.sh 使用 Scala* 2.10 处理 Spark 1.5.x 或 1.6.x;使用 Scala 2.11 处理 Spark 2.0。有关构建 BigDL 的其他方法,请参考 BigDL 构建页面

BigDL 和 Spark 环境

BigDL 可与多种本地和集群环境结合使用,比如 Java、独立 Spark、Spark with Hadoop YARN 或 Amazon EC2 云。在这里,我们以 LeNet* 为例来介绍每种模式的不同之处。下文将向您介绍关于 LeNet 模型和使用方法的详细信息。

  • 本地 Java 应用 - 在该模式中,BigDL 可使用本地 Java 环境来启动应用。
    ${BigDL_HOME}/dist/bin/bigdl.sh -- java \
      -cp ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies-and-spark.jar \
       com.intel.analytics.bigdl.models.lenet.Train \
       -f $MNIST_DIR \
       --core 8 --node 1 \
       --env local -b 512 -e 1
  • 独立 Spark - 在该模式中,使用 Spark 自带的集群管理器,在运行 BigDL 的应用之间分配资源。
    • 本地环境 - 在该模式中,可使用 –master=local[$NUM-OF_THREADS] 和 --env local标记来本地启动 BigDL 应用。例如,可在本地节点上启动 LeNet 模型训练:
      ${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master local[16] \
        --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
        --class com.intel.analytics.bigdl.models.lenet.Train \
        ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
        -f $MNIST_DIR \
        --core 16 --node 1 \
        -b 512 -e 1 --env local
  • Spark 集群环境 - 在该模式中,可在集群环境中启动 BigDL 应用。在 Spark 集群中可以通过两种方式使用 BigDL,具体取决于驱动程序所部署的位置。
    • 客户端部署模式中的 Spark 独立集群—在该模式中,驱动程序作为外部客户端从本地启动。这是默认模式,用户可在客户端上查看应用的运行情况。
      ${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master spark://Node_1:7077 \
         --deploy-mode client --executor-cores 8 --executor-memory 4g --total-executor-cores 32 \
         --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
         --class com.intel.analytics.bigdl.models.lenet.Train \
         ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
         -f $MNIST_DIR \
         --core 8 --node 4 \
         -b 512 -e 1 --env spark
    • 集群部署模式中的 Spark 独立集群—在该模式中,驱动程序在其中一个工作节点上启动。您可以使用 webUI* 或 Spark 日志文件来追踪应用的进度。
      ${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master spark://Node_1:7077 \
        --deploy-mode cluster --executor-cores 8 --executor-memory 4g \
        --driver-cores 1 --driver-memory 4g --total-executor-cores 33 \
        --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
        --class com.intel.analytics.bigdl.models.lenet.Train \
         ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
         -f $MNIST_DIR \
         --core 8 --node 4 \
         -b 512 -e 1 --env spark
  • Spark(YARN 作为集群管理器) - 在该模式中,使用 Hadoop 的 YARN 集群管理器,在运行 BigDL 的应用之间分配资源。
    • 客户端部署模式 - 在该模式中,Spark 驱动程序在主机上运行,相关的任务也在该主机上提交。
      ${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master yarn \
        --deploy-mode client --executor-cores 16 --executor-memory 64g \
        --driver-cores 1 --driver-memory 4g --num-executors 4 \
        --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
        --class com.intel.analytics.bigdl.models.lenet.Train \
        ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
        -f $MNIST_DIR \
        --core 16 --node 4 \
        -b 512 -e 1 --env spark
    • 集群部署模式 - 在该模式中,Spark 驱动程序在 YARN 选择的集群主机上运行。
      ${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master yarn --deploy-mode cluster \
        --executor-cores 16 --executor-memory 64g \
        --driver-cores 1 --driver-memory 4g --num-executors 4 \
        --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
        --class com.intel.analytics.bigdl.models.lenet.Train \
        ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
        -f $MNIST_DIR \
        --core 16 --node 4 \
        -b 512 -e 1 --env spark
  • 在 Amazon EC2 上运行 - BigDL 团队提供了一个公用的 Amazon Machine Image* (AMI*) 文件,以便在 EC2 上试验 BigDL(在 Spark 上运行)。有关在 Amazon EC2 环境中设置在 Spark 上运行 BigDL 的详细信息,请参考GitHub

BigDL 示例模式

本教程展示了针对两个示例模式(LeNet 和 VGG*)的训练和测试,以便介绍如何将 BigDL 用于 Apache Spark 上的分布式深度学习。

LeNet

LeNet 5 是用于数值分类的典型 CNN 模式。如欲了解详情,请参考 http://yann.lecun.com/exdb/lenet/

MNIST* 数据库可从 http://yann.lecun.com/exdb/mnist/下载。下载图像和标签,以训练并验证数据。 

安装 BigDL 时会创建用于训练和测试示例 LeNet 模式的 JAR 文件。如果还未创建,请参考构建 BigDL章节的相关内容。

训练 LeNet 模型

下面是一个示例命令,展示了在 YARN 上使用 BigDL with Spark 来训练 LeNet 模型的情形:

${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master yarn \
  --deploy-mode cluster --executor-cores 16 --executor-memory 64g \
  --driver-cores 1 --driver-memory 4g --num-executors 4 \
  --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
  --class com.intel.analytics.bigdl.models.lenet.Train \
  ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
  -f $MNIST_DIR \
  --core 16 --node 4 \
  -b 512 -e 5 --env spark --checkpoint ~/models

用法:
LeNet 参数
  -f <value> | --folder <value>
        where you put the MNIST data
  -b <value> | --batchSize <value>
        batch size
  --model <value>
        model snapshot location
  --state <value>
        state snapshot location
  --checkpoint <value>
        where to cache the model
  -r <value> | --learningRate <value>
        learning rate
  -e <value> | --maxEpoch <value>
        epoch numbers
  -c <value> | --core <value>
        cores number on each node
  -n <value> | --node <value>
        node number to train the model
  -b <value> | --batchSize <value>
        batch size (currently this value should be multiple of (–-core * –-node)
  --overWrite
        overwrite checkpoint files
  --env <value>执行环境
YARN 参数
                --master yarn --deploy-mode cluster :在集群部署模式中使用 spark with YARN 集群管理器
       --executor-cores 16 --num-executors 4:这将设置执行器的数量以及每个执行器的内核数(面向 YARN),并使其与 LeNet 训练的 --core 和 –-node 参数相匹配。 这是一个确认存在的问题,因此需要使用 Spark 成功完成对 BigDL 的集群训练

测试 LeNet 模型

下面是一个示例命令,展示了在 YARN 上使用 BigDL with Spark 来测试 LeNet 模型的情形:

${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master yarn --deploy-mode cluster \
  --executor-cores 16 --executor-memory 64g \
  --driver-cores 1 --driver-memory 4g --num-executors 4 \
  --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
  --class com.intel.analytics.bigdl.models.lenet.Test \
  ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
  -f $MNIST_DIR \
  --core 16 --nodeNumber 4 \
  -b 512 --env spark --model ~/models/model.591

用法:
    -f <value> | --folder <value>
       where you put the MNIST data
  --model <value>
        model snapshot location (model.iteration#)
  -c <value> | --core <value>
        cores number on each node
  -n <value> | --nodeNumber <value>
        nodes number to train the model
  -b <value> | --batchSize <value>
        batch size
  --env <value>执行环境

请按照下面的操作快速检查模型准确性结果:

yarn logs -applicationId application_id | grep accuracy

请参考 Hadoop 集群 WebUI,了解关于此训练的更多信息。

CIFAR-10* 上的 VGG 模型

该实例展示了如何使用 BigDL 在 CIFAR-10* 数据集上训练和测试 VGG 这样的模型。有关该模式的详细信息,请参考这里

如欲下载 CIFAR-10 数据集的二进制版本,请点击这里

安装 BigDL 时会创建用于训练和测试示例 VGG 模式的 JAR 文件。如果还未创建,请参考构建 BigDL章节的相关内容。

训练 VGG 模型

下面是一个示例命令,展示了在 YARN 上使用 BigDL with Spark 来训练 CIFAR-10 数据集上的 VGG 模型的情形:

${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master yarn --deploy-mode cluster \
  --executor-cores 16 --executor-memory 64g \
  --driver-cores 1 --driver-memory 16g --num-executors 4 \
  --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
  --class com.intel.analytics.bigdl.models.vgg.Train \
  ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
  -f $VGG_DIR \
  --core 16 --node 4 \
  -b 512 -e 5 --env spark --checkpoint ~/models

用法:
  -f <value> | --folder <value>
        where you put the Cifar10 data
  --model <value>
        model snapshot location
  --state <value>
        state snapshot location
  --checkpoint <value>
        where to cache the model and state
  -c <value> | --core <value>
        cores number on each node
  -n <value> | --node <value>
        node number to train the model
  -e <value> | --maxEpoch <value>
        epoch numbers
  -b <value> | --batchSize <value>
        batch size
  --overWrite
        overwrite checkpoint files
  --env <value>执行环境

测试 VGG 模型

下面是一个示例命令,展示了在 YARN 上使用 BigDL with Spark 来测试 CIFAR-10 数据集上的 VGG 模型的情形:

${BigDL_HOME}/dist/bin/bigdl.sh -- spark-submit --master yarn \
  --deploy-mode cluster --executor-cores 16 --executor-memory 64g \
  --driver-cores 1 --driver-memory 16g --num-executors 4 \
  --driver-class-path ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
  --class com.intel.analytics.bigdl.models.vgg.Test \
  ${BigDL_HOME}/dist/lib/bigdl-0.1.0-SNAPSHOT-jar-with-dependencies.jar \
  -f $VGG_DIR \
  --core 16 --nodeNumber 4 \
  -b 512 --env spark --model ~/models/model.491

用法:
  -f <value> | --folder <value>
        where you put the Cifar10 data
  --model <value>
        model snapshot location
  -c <value> | --core <value>
        cores number on each node
  -n <value> | --nodeNumber <value>
        nodes number to train the model
  -b <value> | --batchSize <value>
        batch size
  --env <value>执行环境

关于其他使用 BigDL 的示例模型的训练和测试的详细步骤,如循环神经网络 (RNN)、残差网络 (ResNet)、Inception*、Autoencoder* 等,请访问BigDL GitHub网站。

BigDL 还可用于将预训练 Torch 和 Caffe 模型加载至 Spark 程序,以进行分类和预测。请访问BigDL GitHub网站,查看相关的示例。

性能扩展

图 2 展示了通过增加内核和节点数量(根据当前设置的虚拟节点),使用 BigDL on Spark 实现的 VGG 和 ResNet 模型训练性能的扩展。在此处,我们在 5 个时间点上比较了在 CIFAR-10 数据集上训练这两种模型的时间。

图 2:在 YARN 上运行时使用 BigDL on Spark 实现的 VGG 和 ResNet 性能扩展。

结论

在本文中,我们讨论了如何安装 BigDL,以及如何在四节点虚拟 Hadoop 集群上使用 BigDL 来训练部分常用的深度神经网络模型(在 Apache Spark 上运行)。我们看到了 BigDL 能够轻松地在现有 Hadoop/Spark 集群上支持深度学习功能。 借助每个 Spark 任务中的英特尔 MKL 和多线程编程,以及将 Spark 任务分配至 Hadoop/Spark 集群上的多个节点,训练模型所需的总时间大幅缩短。

参考

BigDL GitHub

Apache Spark

Spark on YARN – Cloudera Enterprise 5.10.x

LeNet/MNIST

Torch 中的 VGG on CIFAR-10

面向图像识别的深度残差学习

CIFAR-10 数据集

BigDL:Apache Spark 上的分布式深度学习

BigDL:已知问题

面向 Apache Hadoop 的 Cloudera 管理员培训

Cloudera Archive – CDH 5.10

Java SE 下载套件

VirtualBox

Viewing all 583 articles
Browse latest View live


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