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

闪电般的 R 机器学习算法

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2016 年 11 月 3 日

CPOL

15分钟阅读

viewsIcon

24316

使用 Intel® Data Analytics Acceleration Library 和最新的 Intel® Xeon Phi™ 处理器获得成果

点击此处注册并下载免费的 Intel® Parallel Studio XE 30天试用版.

现在,数据科学家可以在最新的 Intel® Xeon Phi™ 处理器 x200 系列(代号为 Knights Landing)上运行 R* 应用程序时获得显著的加速。Intel® 软件工具,如 Intel® Data Analytics Acceleration Library (Intel® DAAL),正在帮助实现这一点——开发者的工作量极少。

最新的 Intel Xeon Phi 处理器是专为苛刻计算应用程序设计的专用平台。它带来了几项新技术突破,包括:

  • 插槽式外形(自引导 CPU),以及协处理器版本
  • 除了 DDR4 内存之外,还有一个高带宽、封装内置内存,称为多通道 DRAM (MCDRAM)
  • 最新、最先进的 向量化 技术:Intel® Advanced Vector Extensions (Intel® AVX-512) 指令集
  • 大规模并行处理,每个芯片最多 72 个核心,每个核心 4 个线程
  • 3+ TFLOPS 的双精度浮点计算能力
  • 6+ TFLOPS 的单精度浮点计算能力

凭借这些技术,Intel 在 Intel® Parallel Studio XE 套件中提供了一套软件工具,帮助开发人员充分利用 Intel Xeon Phi 处理器。其中一个工具是 Intel DAAL,它提供了预优化的机器学习和数据分析算法。现实世界的机器学习问题通常在 CPU 和内存需求方面排名最高。Intel Xeon Phi 是解决这类问题的理想平台。Intel DAAL 提供了一种快速构建针对 Intel Xeon Phi 处理器优化的机器学习应用程序的方法。

R 是一个开源项目和软件环境,它提供了最全面的统计分析、数据挖掘和机器学习算法集1。R 在数据科学从业者中非常受欢迎。然而,没有经过特殊调优,R 及其所有包都无法利用 Intel Xeon Phi 处理器提供的高性能特性。事实上,R 在 Intel Xeon Phi 处理器上的开箱即用性能预计会很差。这是因为在许多情况下,标准的 R 只会在单个 Intel Xeon Phi 处理器核心上使用单个线程。这意味着它只能利用 Intel Xeon Phi 处理器上可用计算资源的 1/288(假设有 72 个核心,每个核心 4 个线程)。单个 Intel Xeon Phi 处理器大致相当于一个 Intel® Atom™ 处理器,其功能不如单个 Intel® Xeon® 处理器。标准的 R 也无法利用其他性能特性,例如基于 Intel AVX-512 指令集的 高级向量化 和高带宽内存 MCDRAM。

但是,R 程序员有一个简单的方法可以利用 Intel Xeon Phi 处理器强大的数据处理能力:将预优化的库集成到 R 环境中。在这里,我们选择了一个经典的机器学习算法,朴素贝叶斯分类器。我们将逐步展示如何使用 Intel DAAL 在 Intel Xeon Phi 处理器自引导系统上构建和运行 R 中的朴素贝叶斯分类器。然后,我们将我们的解决方案与来自经典的 R e1071 包的本地实现进行比较2

朴素贝叶斯分类器

朴素贝叶斯算法是一种基于贝叶斯定理的分类方法3。它假设所有特征都是相互独立的。尽管其简单,但它通常可以胜过更复杂的分类方法。它已广泛用于文档分类、电子邮件垃圾邮件检测等。

给定一个特征向量 \(X_{i}=(x_{i1},...,x_{ip}),i=1,...,n,\) 其中 \(x_{ik}\) 是第 \(k\) 个特征在第 \(i\) 个观测值中观察到的缩放频率,\(p\) 是特征的数量。此外,给定 \(C=(C_{1},...,C_{d})\) 一组可能的标签,朴素贝叶斯算法基于

即,对于标签 \(C_{k1}\)

后验概率 与(先验概率 × 似然度)成正比

在训练阶段,使用具有已知标签的训练数据集来学习一个模型,该模型包含参数,例如每个类(标签)的先验概率以及所有特征的似然度。然后在预测阶段,对于每个新观测值,算法找到该观测值的最大后验概率,并为其分配相应的标签。

R 中的朴素贝叶斯

R 中一个著名的朴素贝叶斯分类器由 e1071 包提供:统计系、概率论组的杂项函数2

它具有一个简单的界面,包含两个函数:一个用于训练模型,另一个用于应用模型。

1 library(e1071)
2
3 model <- naiveBayes(training_data, ground_truths)
4 result <- predict(model, new_data)

当我们在 Intel Xeon Phi 处理器上运行此程序时,对于中等大小的数据集(100K 个观测值、200 个特征和 100 个类别),模型训练步骤需要超过 200 秒。而相同大小数据集的预测步骤需要 6,272 秒。这几乎是 1 小时 45 分钟。

这种性能阻止了 e1071 朴素贝叶斯在 Intel Xeon Phi 处理器上的任何实际应用。现在我们将展示如何使用 Intel DAAL 的朴素贝叶斯快速解决此问题。

Intel® DAAL 的朴素贝叶斯

多项式朴素贝叶斯分类器是 Intel DAAL 提供的分类算法之一4。除了卓越的性能外,Intel DAAL 中的实现还提供了一些 e1071 包不具备的功能和灵活性。特别是,Intel DAAL 中的朴素贝叶斯在模型训练中支持三种处理模式:批量处理、在线处理和分布式处理。批量模式与 e1071 包支持的模式相同:整个数据集一次性处理。在线模式支持一种使用模型,即内存无法一次性容纳的大型数据集可以分块处理,并在所有块处理完成后学习模型。分布式模式支持在集群上进行分布式模型训练。为简单起见,此处我们仅使用批量处理模式。但是,此方法也可以应用于将其他两种处理模式集成到 R 中。

在完成下面描述的集成步骤后,我们期望像这样使用我们新的、启用了 Intel DAAL 的朴素贝叶斯分类器:

1 # DAAL Naive Bayes
2 test <- loadData("traindata.csv", nfeatures)
3 model.daal <- nbTrain(test$data, test$labels, nclasses)
4 labels.daal <- nbPredict(model.daal, test$data, nclasses)

将 Intel DAAL 的朴素贝叶斯分类器集成到 R 中

Intel DAAL 具有 C++ 编程接口(除了 Java* 和 Python* API)。要将 Intel DAAL 的朴素贝叶斯算法与 R 一起使用,我们必须将训练和预测步骤封装到 C++ 函数中,然后将这些函数导出到 R。Rcpp 包可用于此目的5,6

工具设置:Rcpp 和相关包

Rcpp 包是使用 C++ 代码扩展 R 的事实上的标准。数百个其他 R 包使用此包通过 C++ 实现加速计算并连接到其他 C++ 项目。Rcpp 提供无缝的 R 和 C++ 集成。它允许 R 对象在 R 和 C++ 之间直接交换。

Rcpp 可以从 CRAN 安装。我们还需要安装 inline 包7。此包允许直接从 R 代码编译、链接和加载 C++ 代码。在 Rcpp 支持的多种编译、链接和加载 C++ 函数以供 R 使用的方法中,我们发现将 inline 包与 Rcpp 结合使用是在易用性和灵活性之间取得了良好的平衡。最后,我们需要一个 C++ 编译器。通常,Intel® C++ 编译器是构建和优化 Intel Xeon Phi 处理器代码的首选编译器。但在此案例中,我们没有构建大量的 C++ 源代码。我们正在将预编译的二进制文件(来自 Intel DAAL)链接到一个小的动态库中,供 R 加载。因此,任何编译器都可以。如果您系统上没有默认的 C++ 编译器,您可以执行以下操作之一:

  • 在 Windows* 上,安装 Rtools
  • 在 Mac OS* 上,从 App Store 安装 Xcode
  • 在 Linux* 上,运行 sudo apt-get install r-base-dev 或类似命令

连接 R 和 C++ 函数的粘合代码

inline 包提供了一个简单的 cxxfunction 函数,它接受 C++ 函数的签名、C++ 函数的定义和一个插件对象。插件对象用于指定我们项目依赖的其他 C++ 头文件和其他链接行。这通过一个示例(图 1)会更清楚。

图 1 中的第 5-9 行使用 Rcpp 插件生成器工具创建并向 R 注册了一个 Rcpp 插件。该插件允许我们指定对外部库的依赖。在这种情况下,依赖是 Intel DAAL。代码段的其余部分创建了三个 R 函数 `loadData`、`nbTrain` 和 `nbPredict`,分别用于从 CSV 文件读取数据、训练朴素贝叶斯模型和预测新数据。

接下来,我们使用 Intel DAAL 数据结构和算法来实现这些 C++ 函数。

1 library(Rcpp)
2 library(inline)
3
4 # Create and register a Rcpp plugin
5 plug <- Rcpp:::Rcpp.plugin.maker(
6     include.before = "#include <daal.h> ",
7     libs = paste("-L$DAALROOT/lib/ -ldaal_core -ldaal_thread ",
8         "-ltbb -lpthread -lm", sep=""))
9 registerPlugin("daalNB", plug)
10
11 # R function for loading data and labels
12 loadData <- cxxfunction(signature(file="character", ncols="integer"),
13     readCSV, plugin="daalNB")
14
15 # R function for training a model
16 nbTrain <- cxxfunction(signature(X="raw", y="raw", nclasses="integer"),
17     train, plugin="daalNB")
18
19 # R function for scoring
20 nbPredict <- cxxfunction(signature(model="raw", X="raw", nclasses="integer"),
21     predict, plugin="daalNB")
22
图 1 - 用于从内嵌 C++ 代码创建 R 函数的粘合代码

从 CSV 文件中读取数据

我们可以使用 `read.csv()`、`read.table()` 或 `scan()` 等 R 函数轻松地从 CSV 文件中读取数据。但是,然后我们必须将数据转换为 Intel DAAL 可识别的表示形式。

Intel DAAL 使用 NumericTables(C++ 类的层次结构)进行内存数据表示。相反,我们希望使用 Intel DAAL 的数据源设施直接从它加载数据并构建 NumericTables。图 2 中的第 13-16 行定义了一个数据源,该数据源连接到包含训练数据和训练数据真实值的 CSV 文件。我们假设 CSV 表的最后一列显示了真实值(标签)。第 19-24 行创建 Intel DAAL NumericTables 来存储要读取的数据和标签。第 27 行将整个数据集加载到 NumericTables 中。在返回到 R 空间之前,我们将数据 NumericTable 和标签 NumericTable 序列化为两块原始字节,然后将它们在一个列表中返回。稍后,C++ 模型训练函数将获取原始字节并将其恢复为 NumericTables。

1 # load data
2 readCSV <- '
3     using namespace daal;
4     using namespace daal::data_management;
5
6     // Inputs:
7     // file - file name
8     // ncols - number of columns in file
9     std::string fname = Rcpp::as<std::string>(file);
10     int k = Rcpp::as<int>(ncols);
11
12     // Data source
13     FileDataSource<CSVFeatureManager> dataSource(
14         fname,
15         DataSource::notAllocateNumericTable,
16         DataSource::doDictionaryFromContext);
17
18     // DAAL NumericTables for data and labels
19     services::SharedPtr<NumericTable> data(
20         new HomogenNumericTable<double>(k-1, 0, NumericTable::notAllocate));
21     services::SharedPtr<NumericTable> labels(
22         new HomogenNumericTable<int>(1, 0, NumericTable::notAllocate));
23     services::SharedPtr<NumericTable> merged(
24         new MergedNumericTable(data, labels));
25
26     // Load data
27     dataSource.loadDataBlock(merged.get());
28
29     // Serialize NumericTables
30     InputDataArchive dataArch, labelsArch;
31     data->serialize(dataArch);
32     labels->serialize(labelsArch);
33     Rcpp::RawVector dataBytes(dataArch.getSizeOfArchive());
34     dataArch.copyArchiveToArray(&dataBytes[0], dataArch.getSizeOfArchive());
35     Rcpp::RawVector labelsBytes(labelsArch.getSizeOfArchive());
36     labelsArch.copyArchiveToArray(&labelsBytes[0], labelsArch.getSizeOfArchive());
37
38     // Return a list of RawVectors
39     return Rcpp::List::create(
40     _["data"] = dataBytes,
41     _["labels"] = labelsBytes);
42 '
图 2 - 从 CSV 文件读取数据并构建 Intel® Data Analytics Acceleration Library NumericTables 的 C++ 函数

训练朴素贝叶斯模型

在使用代码中的任何 Intel DAAL 算法时,编程模型都有一个易于遵循的顺序来组合各种组件:

  1. 为选定的算法和选定的处理模式创建一个算法对象。
  2. 使用算法对象的 `input.set` 方法设置输入数据。
  3. 在算法对象上调用 `compute` 方法。
  4. 使用算法对象的 `getResult` 方法检索结果。

图 3 中的第 25-28 行展示了在实现 C++ 训练函数时执行此序列。请注意,在此序列之前,我们首先反序列化输入(训练数据和相应的标签),然后将其恢复为 NumericTables。在此序列之后,我们序列化结果——模型对象——为原始字节。这样,模型就可以传递给预测函数,用于对新数据进行分类。

1 # Naive Bayes: train a model
2 train <- '
3     using namespace daal;
4     using namespace daal::algorithms;
5     using namespace daal::algorithms::multinomial_naive_bayes;
6     using namespace daal::data_management;
7
8     // Inputs:
9     // X - training dataset
10     // y - training data groundtruth
11     // nclasses - number of classes
12     Rcpp::RawVector Xr(X);
13     Rcpp::RawVector yr(y);
14     int nClasses = Rcpp::as<int>(nclasses);
15
16     // Deserialize data and labels
17     OutputDataArchive dataArch(&Xr[0], Xr.length());
18     services::SharedPtr<NumericTable> ntData(new HomogenNumericTable<double>());
19     ntData->deserialize(dataArch);
20     OutputDataArchive labelsArch(&yr[0], yr.length());
21     services::SharedPtr<NumericTable> ntLabels(new HomogenNumericTable<int>());
22     ntLabels->deserialize(labelsArch);
23
24     // Train a model
25     training::Batch<> algorithm(nClasses);
26     algorithm.input.set(classifier::training::data, ntData);
27     algorithm.input.set(classifier::training::labels, ntLabels);
28     algorithm.compute();
29
30     // Get result
31     services::SharedPtr<training::Result> result = algorithm.getResult();
32     InputDataArchive archive;
33     result->get(classifier::training::model)->serialize(archive);
34
35     Rcpp::RawVector out(archive.getSizeOfArchive());
36     archive.copyArchiveToArray(&out[0], archive.getSizeOfArchive());
37     return out;
38 '
图 3 - 训练朴素贝叶斯模型的 C++ 函数

预测新数据的标签

图 4 展示了 C++ 预测函数。同样,代码的相同序列出现在第 28-31 行。预测结果是一个 n×1 的 NumericTable,其中包含预测的标签,n 是我们预测的观测值数量。代码的最后一部分从结果 NumericTable 中读取标签,并将它们组合成一个 `Rcpp::IntegerVector` 对象。然后,该对象无缝地转换为 R 空间,并成为一个整数数组。

1 # Naive Bayes: predict
2 predict <- '
3     using namespace daal;
4     using namespace daal::algorithms;
5     using namespace daal::algorithms::multinomial_naive_bayes;
6     using namespace daal::data_management;
7
8     // Inputs:
9     // model - a trained model
10     // X - input data
11     // nclasses - number of classes
12     Rcpp::RawVector modelBytes(model);
13     Rcpp::RawVector dataBytes(X);
14     int nClasses = Rcpp::as<int>(nclasses);
15
16     // Retrieve model
17     OutputDataArchive modelArch(&modelBytes[0], modelBytes.length());
18     services::SharedPtr<multinomial_naive_bayes::Model> nb(
19         new multinomial_naive_bayes::Model());
20     nb->deserialize(modelArch);
21
22     // Deserialize data
23     OutputDataArchive dataArch(&dataBytes[0], dataBytes.length());
24     services::SharedPtr<NumericTable> ntData(new HomogenNumericTable<double>());
25     ntData->deserialize(dataArch);
26
27     // Predict for new data
28     prediction::Batch<> algorithm(nClasses);
29     algorithm.input.set(classifier::prediction::data, ntData);
30     algorithm.input.set(classifier::prediction::model, nb);
31     algorithm.compute();
32
33     // Return newlabels
34     services::SharedPtr<NumericTable> predictionResult =
35         algorithm.getResult()->get(classifier::prediction::prediction);
36     BlockDescriptor<int> block;
37     int n = predictionResult->getNumberOfRows();
38     predictionResult->getBlockOfRows(0, n, readOnly, block);
39     int* newlabels = block.getBlockPtr();
40     IntegerVector predictedLabels(n);
41     std::copy(newlabels, newlabels+n, predictedLabels.begin());
42     return predictedLabels;
43 '
图 4 - 使用训练模型预测新数据标签的 C++ 函数

整合所有部分

我们可以将图 1-4 中所示的所有代码放入一个脚本中——例如,NaiveBayesClassifierDaal.R。可以使用 R 的 `source()` 函数将其导入 R 环境。然后,新的函数可用作 `loadData()`、`nbTrain()` 和 `nbPredict()`。图 5 展示了如何在 R 中使用这些函数。还显示了如何使用 `microbenchmark()` 来对训练和预测步骤的性能进行基准测试。

每次对脚本进行源化到 R 时,都会自动启动编译和链接过程,以将 C++ 代码构建为 R 扩展。此过程确实需要时间。希望避免此开销的用户应考虑使用 Rcpp 编写专门的 R 包。

专门的 R 包包含预编译的动态库,因此在将其加载到 R 时不需要编译和链接。用于使用 Intel DAAL 的完全相同的 C++ 代码可用于构建 R 包。编写 R 包超出了本次讨论的范围。如果您有兴趣,请参阅官方《编写 R 扩展》手册以获取详细信息8

1 source("NaiveBayesClassifierDaal.R")
2
3 # DAAL Naive Bayes
4 test <- loadData("traindata.csv", nfeatures)
5 trainperf.daal <- microbenchmark(
6     model.daal <- nbTrain(test$data, test$labels, nclasses))
7 scoreperf.daal <- microbenchmark(
8     labels.daal <- nbPredict(model.daal, test$data, nclasses))
图 5 - 使用 Intel® Data Analytics Acceleration Library 构建的朴素贝叶斯分类器

性能提升

我们的解决方案显著加快了模型训练和预测的速度。如上所述,e1071 朴素贝叶斯需要 200 多秒才能训练完成,1 小时 45 分钟才能预测完成。现在,这两个步骤中的每一个都可以在 0.25 秒内完成

我们对使用 Intel DAAL 的朴素贝叶斯实现的 R 扩展在不同大小的数据集上进行了基准测试。我们在相同的数据集上运行了 e1071 朴素贝叶斯。不出所料,我们的实现对训练步骤实现了超过 1000 倍的加速,对预测步骤实现了高达 30,000 倍的加速。下面的图表 1 和 **图表 2** 显示了这些数据集的加速数字。

图表 1. 相对于 e1071 朴素贝叶斯分类器的训练步骤加速

图表 2. 相对于 e1071 朴素贝叶斯分类器的预测步骤加速

结论

Intel Xeon Phi 处理器是一个高性能、大规模并行平台,非常适合机器学习和数据分析工作负载。然而,数据科学界流行的 R 等主流软件工具可能尚未针对在 Intel Xeon Phi 处理器上有效运行进行优化。在此,我们为希望立即利用 Intel Xeon Phi 处理器优势的 R 程序员介绍了一个解决方案。

该解决方案涉及通过编写使用 Rcpp 的 R 扩展,将 Intel DAAL 函数集成到 R 环境中。Intel DAAL 是一个提供现成算法的数据分析、机器学习和深度学习的库。这些算法已针对 Intel Xeon Phi 处理器以及各种 Intel® Xeon、Core™ 和 Atom™ 处理器进行了优化。我们的基准测试结果表明,此解决方案与 e1071 包提供的原生 R 解决方案相比,具有巨大的性能优势。我们的方法立即为许多 R 应用程序带来了利用 Intel Xeon Phi 处理器优势的可能性。

我们以多项式朴素贝叶斯算法为例来说明这些观点。但同样的也可以应用于将其他 Intel DAAL 算法集成到 R 中。Intel DAAL 提供了一套丰富的算法,包括:线性回归、SVM、朴素贝叶斯、带提升的分类、推荐系统、聚类、深度神经网络等。

查看 Github* 上的 Intel DAAL 开源项目 >

使用的配置和工具

用于基准测试的系统配置

  • Intel Xeon Phi 自引导系统
    • CPU:Intel Xeon Phi-D B0,68 核 @ 1.40 GHz,34 MB L2 缓存
    • 内存:16 GB MCDRAM,96 GB DDR4
  • 操作系统版本:Red Hat Enterprise Linux* 7.2 (kernel 3.10.0-327.0.1.el7.x86_64, glibc 2.17-105.el7.x86_64)

此示例中使用的软件工具

  • Intel DAAL 2017 Beta 更新 1
  • R 版本 3.3.1 (Bug in Your Hair)
  • Rcpp 包版本 0.12.5
  • Inline 包版本 0.3.14
  • e1071 包版本 1.6-7
  • g++ 版本 4.8.5

用于性能测试的软件和工作负载可能已针对仅在 Intel 微处理器上运行进行了优化。性能测试,如 SYSmark 和 MobileMark,使用特定的计算机系统、组件、软件、操作和功能进行测量。更改任何这些因素都可能导致结果有所不同。您应参考其他信息和性能测试,以帮助您全面评估您打算购买的产品,包括该产品与其他产品结合时的性能。Intel 技术的功能和优势取决于系统配置,并可能需要启用硬件、软件或服务激活。性能因系统配置而异。请咨询您的系统制造商或零售商,或访问 intel.com 了解更多信息。本文档不授予任何知识产权的明示或暗示(包括禁止反悔或以其他方式)许可。Intel 否认所有明示和暗示的保证,包括但不限于适销性、特定用途的适用性和非侵权性的暗示保证,以及任何由履约过程、交易过程或商业惯例产生的保证。本文档包含有关正在开发的产品、服务和/或流程的信息。此处提供的所有信息如有更改,恕不另行通知。请联系您的 Intel 代表以获取最新的预测、计划、规格和路线图。描述的产品和服务可能存在缺陷或错误,称为勘误,这可能导致与已发布规格的偏差。当前已勘误的勘误可应要求提供。可通过致电 1-800-548-4725 或访问 www.intel.com/design/literature.htm 获取本文档中引用的具有订购号的文档副本。

此示例源代码根据 Intel 示例源代码许可协议 发布。

参考文献

  1. R* 项目统计计算,r-project.org/
  2. e1071:统计系、概率论组的杂项函数,David Meyer,cran.rproject.org/web/packages/e1071/index.html
  3. 电子统计教科书,StatSoft, Inc.,塔尔萨,OK,2013。
  4. Intel® Data Analytics Acceleration Library 的开发者指南和参考,2016。 http://software.intel.com/sites/products/documentation/doclib/daal/daal-user-and-reference-guides/index.htm
  5. Rcpp:无缝的 R* 和 C++ 集成,Dirk Eddelbuettel,cran.r-project.org/web/packages/Rcpp/index.html
  6. 使用 Rcpp 进行无缝的 R* 和 C++ 集成,D. Eddelbuettel,Springer,2013。
  7. inline:从 R* 中内联 C、C++、Fortran 函数调用的函数,Oleg Sklyar,cran.r-project.org/web/packages/inline/index.html
  8. 编写 R* 扩展,cran.r-project.org/doc/manuals/r-release/R-exts.html
© . All rights reserved.