使用OpenMP加速随机森林
- 前言
- 使用OpenMP
- 对RF训练的加速
- 代码
- 加速效果
- 对特征重要性评估的加速
- 代码
- 加速效果
- 附录
- Windows中使用OpenMP
- Linux中使用OpenMP
项目主页:randomforest
C++ implementation of random forests classification, regression, proximity and variable importance.
推荐阅读:
- Random Forests C++实现:细节,使用与实验
- 随机森林特征重要性(Variable importance)评估方法
- 随机森林Proximity实现及应用
前言
OpenMP用于编写多线程应用程序的 API。它是一组编译器指令和库例程,使并行编程比使用显式线程更容易它实际上大大简化了用 Fortran 、C/ C++ 编写多线程编程程序。主流编译器(gcc/g++,Visual C++)都支持OpenMP,在Windows和Linux操作系统上都比较容易上手,对原始代码的侵入较少。
随机森林由N棵随机树(Randomized Trees)组成,在训练与其他衍生算法中,很多情形下每棵树内的运算是独立的,且与树的顺序无关。因此,可以采用OpenMP进行加速,将对随机树的运算分配到多个线程中,然后由CPU的多核进行并行处理。主要采用OpenMP对for循环加速功能,将顺序执行的循环并行化,大大提升了算法整体速度。下面的章节介绍了OpenMP在随机森林加速中的应用,也对OpenMP的使用方法进行简单解释。主要分为对随机森林训练算法的加速、对预测算法的加速、对Proximity的加速以及对特征重要性算法的加速给出了具体的实验结果。
使用OpenMP
对RF训练的加速
代码
随机森林训练其中每棵随机数是独立的过程,所以很容易采用OpenMP对训练每棵随机树的循环进行并行化。
#include "omp.h"
... ...
int TrainRandomForestClassifierOMP(float **data, int *label, RandomCForests_info RFinfo, LoquatCForest *&loquatForest, int jobs, int trace)
{
... ...
jobs = jobs>omp_get_max_threads() ? omp_get_max_threads() : jobs;
omp_set_num_threads(jobs);
int growed_num = 0;
... ...
#pragma omp parallel for
for (int i = 0; i < Ntrees; i++)
{
GrowRandomizedCLoquatTreeRecursively(data, label, RFinfo, loquatForest->loquatTrees[i]);
#pragma omp critical
{
growed_num++;
if (trace > 0 && growed_num % trace == 0)
{
cout << "Tree: " << growed_num << endl;
}
}
}
... ...
return 0;
}
omp_set_num_threads 设置了线程数量,如果线程数<=CPU核心数,那么每个线程可以又一个独立核心处理,所以这个线程数应该设置为CPU核心数或更少;
#pragma omp parallel for 对紧跟其后的for循环进行多核并行加速;
#pragma omp critical 指定后面大括号里的代码块同一时间只能被一条线程执行的代码区域,为了是统计到目前为止有多少颗随机树完成训练。
加速效果
实验平台为:Intel i5-13400F(10核), win11操作系统
对不同数据集进行训练,记录原始算法实现(without OpenMP)、4线程 (With OpenMP T=4)与8线程 (With OpenMP T=8) 加速,三种情况下的训练耗时。
数据集 (样本数) | 参数 | Without OpenMP (秒) | With OpenMP T=4 (秒) | With OpenMP T=8 (秒) |
---|---|---|---|---|
mnist (60000) | [200, 27*, 40, 2] | 97.3 | 27.6 | 16.843 |
pendigits (7494) | [200,4*, 40, 2] | 1.869 | 0.527 | 0.309 |
MAGIC_Gamma_Telescope (19020) | [200,3*, 40, 2] | 9.066 | 2.554 | 1.464 |
Sensorless_drive_diagnosis (58509) | [200,6*, 40, 2] | 50.02 | 13.89 | 7.91 |
理想情况下,4线程和8线程应该分别为原始算法耗时的 1 4 \frac {1}{4} 41和 1 8 \frac {1}{8} 81。从实验结果来看,加速倍数与理论接近,考虑到线程调度等也需要耗时,所以实际并没有达到理论倍数是正常的。
对特征重要性评估的加速
特征重要性的实现请见:随机森林特征重要性(Variable importance)评估方法
代码
#pragma omp parallel for
for (tr = 0; tr < Ntrees; tr++)
{
struct LoquatCTreeStruct* pTree = loquatForest->loquatTrees[tr];
int *pIndex = pTree->outofbag_samples_index;
int oobnum = pTree->outofbag_samples_num;
if (pIndex == NULL || oobnum == 0)
continue;
oobFound = true;
oobOfTrees[tr] = oobnum;
int* permuted_order = new int[oobnum];
int index, predicted_class_index;
float confidence = 0.f;
int correct_num = 0, correct_num_premute = 0;
int i = 0;
for (; i < oobnum; i++)
{
index = pIndex[i];
PredictAnTestSampleOnOneTree(data[index], variables_num, pTree, predicted_class_index, confidence);
if (predicted_class_index == label[index])
correct_num++;
}
float* tmp_data = new float[variables_num];
for (int var = 0; var < variables_num; var++)
{
// permuting (var)th variables
// permute [0, oobnum-1]
permute(oobnum, permuted_order);
for (correct_num_premute = 0, i = 0; i < oobnum; i++)
{
index = pIndex[permuted_order[i]];
memcpy(tmp_data, data[pIndex[i]], variables_num * sizeof(float));
tmp_data[var] = data[index][var];
PredictAnTestSampleOnOneTree(tmp_data, variables_num, pTree, predicted_class_index, confidence);
if (predicted_class_index == label[pIndex[i]])
correct_num_premute++;
}
DeltMatrix[tr][var] = correct_num - correct_num_premute;
mean_var[var] += (correct_num - correct_num_premute) / (float)oobOfTrees[tr];
}
delete [] permuted_order;
delete [] tmp_data;
}
与训练加速的方法相同,也是通过for循环的并行化进行加速。与原始代码相比,OpenMP版本的代码将循环内要用到的变量都在循环内部定义,这样每个循环就有一份独立的变量,并行时不会造成冲突。
加速效果
数据集 (样本数,特征数) | 参数 | Without OpenMP (秒) | With OpenMP T=4 (秒) | With OpenMP T=8 (秒) |
---|---|---|---|---|
mnist (60000,720) | [200, 27*, 40, 2] | 9589 | 4720 | 1681 |
pendigits (7494,16) | [200,4*, 40, 2] | 1.364 | 0.430 | 0.323 |
MAGIC_Gamma_Telescope (19020,10) | [200,3*, 40, 2] | 17.06 | 4.806 | 2.965 |
Sensorless_drive_diagnosis (58509,48) | [200,6*, 40, 2] | 915.83 | 261.02 | 156.57 |
附录
Windows中使用OpenMP
Visual studio中开启openmp的方法见下图
开启OpenMP后多核处理的截图如下
Linux中使用OpenMP
编译时增加命令 -fopenmp