Skip to content

Commit c6c8a87

Browse files
authored
Add PCA benchmarks (#5)
* Add native, scikit-learn, daal4py PCA benchmark. * Add NPY serializer
1 parent fcda222 commit c6c8a87

File tree

5 files changed

+802
-0
lines changed

5 files changed

+802
-0
lines changed

daal4py/pca.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Copyright (C) 2017-2019 Intel Corporation
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
from __future__ import print_function
6+
import numpy as np
7+
import timeit
8+
from numpy.random import rand
9+
from daal4py import pca, pca_transform, normalization_zscore
10+
from daal4py.sklearn.utils import getFPType
11+
from sklearn.utils.extmath import svd_flip
12+
from args import getArguments, coreString
13+
from bench import prepare_benchmark
14+
15+
import argparse
16+
parser = argparse.ArgumentParser(description='daal4py PCA benchmark')
17+
parser.add_argument('--svd-solver', type=str, choices=['daal', 'full'],
18+
default='daal', help='SVD solver to use')
19+
parser.add_argument('--n-components', type=int, default=None,
20+
help='Number of components to find')
21+
parser.add_argument('--whiten', action='store_true', default=False,
22+
help='Perform whitening')
23+
parser.add_argument('--write-results', action='store_true', default=False,
24+
help='Write results to disk for verification')
25+
args = getArguments(parser)
26+
REP = args.iteration if args.iteration != '?' else 10
27+
core_number, daal_version = prepare_benchmark(args)
28+
29+
30+
def st_time(func):
31+
def st_func(*args, **keyArgs):
32+
times = []
33+
for n in range(REP):
34+
t1 = timeit.default_timer()
35+
r = func(*args, **keyArgs)
36+
t2 = timeit.default_timer()
37+
times.append(t2-t1)
38+
print(min(times))
39+
return r
40+
return st_func
41+
42+
p = args.size[0]
43+
n = args.size[1]
44+
X = rand(p,n)
45+
Xp = rand(p,n)
46+
47+
if args.n_components:
48+
n_components = args.n_components
49+
else:
50+
n_components = min((n, (2 + min((n, p))) // 3))
51+
52+
def pca_fit_daal(X, n_components):
53+
54+
if n_components < 1:
55+
n_components = min(X.shape)
56+
57+
fptype = getFPType(X)
58+
59+
centering_algo = normalization_zscore(
60+
fptype=fptype,
61+
doScale=False
62+
)
63+
64+
pca_algorithm = pca(
65+
fptype=fptype,
66+
method='svdDense',
67+
normalization=centering_algo,
68+
resultsToCompute='mean|variance|eigenvalue',
69+
isDeterministic=True,
70+
nComponents=n_components
71+
)
72+
73+
pca_result = pca_algorithm.compute(X)
74+
eigenvectors = pca_result.eigenvectors
75+
eigenvalues = pca_result.eigenvalues.ravel()
76+
singular_values = np.sqrt((X.shape[0] - 1) * eigenvalues)
77+
78+
return pca_result, eigenvalues, eigenvectors, singular_values
79+
80+
81+
def pca_transform_daal(pca_result, X, n_components, fit_n_samples,
82+
eigenvalues, eigenvectors,
83+
whiten=False, scale_eigenvalues=False):
84+
85+
fptype = getFPType(X)
86+
87+
tr_data = {}
88+
tr_data['mean'] = pca_result.dataForTransform['mean']
89+
90+
if whiten:
91+
if scale_eigenvalues:
92+
tr_data['eigenvalue'] = (fit_n_samples - 1) * pca_result.eigenvalues
93+
else:
94+
tr_data['eigenvalue'] = pca_result.eigenvalues
95+
elif scale_eigenvalues:
96+
tr_data['eigenvalue'] = np.full((1, pca_result.eigenvalues.size),
97+
fit_n_samples - 1, dtype=X.dtype)
98+
99+
transform_algorithm = pca_transform(fptype=fptype, nComponents=n_components)
100+
transform_result = transform_algorithm.compute(X, pca_result.eigenvectors, tr_data)
101+
return transform_result.transformedData
102+
103+
104+
def pca_fit_full_daal(X, n_components):
105+
106+
fit_result, eigenvalues, eigenvectors, S = pca_fit_daal(X, min(X.shape))
107+
U = pca_transform_daal(fit_result, X, min(X.shape), X.shape[0],
108+
eigenvalues, eigenvectors,
109+
whiten=True, scale_eigenvalues=True)
110+
V = fit_result.eigenvectors
111+
112+
U, V = svd_flip(U, V)
113+
114+
eigenvalues = fit_result.eigenvalues[:n_components].copy()
115+
eigenvectors = fit_result.eigenvectors[:n_components].copy()
116+
117+
return fit_result, eigenvalues, eigenvectors, U, S, V
118+
119+
120+
@st_time
121+
def test_fit(X):
122+
if args.svd_solver == 'full':
123+
return pca_fit_full_daal(X, n_components)
124+
else:
125+
return pca_fit_daal(X, n_components)
126+
127+
@st_time
128+
def test_transform(Xp, pca_result, eigenvalues, eigenvectors):
129+
return pca_transform_daal(pca_result, Xp, n_components, X.shape[0],
130+
eigenvalues, eigenvectors,
131+
whiten=args.whiten)
132+
133+
134+
print (','.join([args.batchID, args.arch, args.prefix, "PCA.fit", coreString(args.num_threads), "Double", "%sx%s" % (p,n)]), end=',')
135+
res = test_fit(X)
136+
print (','.join([args.batchID, args.arch, args.prefix, "PCA.transform", coreString(args.num_threads), "Double", "%sx%s" % (p,n)]), end=',')
137+
tr = test_transform(Xp, res[0], res[1], res[2])
138+
139+
140+
if args.write_results:
141+
np.save('pca_daal4py_X.npy', X)
142+
np.save('pca_daal4py_Xp.npy', Xp)
143+
np.save('pca_daal4py_eigvals.npy', res[1])
144+
np.save('pca_daal4py_eigvecs.npy', res[2])
145+
np.save('pca_daal4py_tr.npy', tr)

native/common.hpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "CLI11.hpp"
99
#include "daal.h"
10+
#include "npyfile.h"
1011

1112
namespace dm = daal::data_management;
1213
namespace ds = daal::services;
@@ -133,6 +134,76 @@ dm::NumericTablePtr make_table(T *data, size_t rows, size_t cols) {
133134
}
134135

135136

137+
/*
138+
* Write a NumericTable to npy format.
139+
*/
140+
template <typename T>
141+
void write_table(dm::NumericTablePtr table,
142+
const std::string descr, const std::string fn) {
143+
144+
dm::BlockDescriptor<T> block;
145+
table->getBlockOfRows(0, table->getNumberOfRows(), dm::readOnly, block);
146+
T *data = block.getBlockPtr();
147+
148+
size_t shape[2] = {table->getNumberOfRows(), table->getNumberOfColumns()};
149+
150+
char *c_descr = new char[descr.size() + 1];
151+
strcpy(c_descr, descr.c_str());
152+
const struct npyarr arr {
153+
.descr = c_descr,
154+
.fortran_order = false,
155+
.shape_len = 2,
156+
.shape = shape,
157+
.data = data
158+
};
159+
160+
save_npy(&arr, fn.c_str(), sizeof(T));
161+
162+
table->releaseBlockOfRows(block);
163+
164+
}
165+
166+
167+
/*
168+
* Create a new HomogenNumericTable from a submatrix of an existing
169+
* HomogenNumericTable.
170+
*
171+
* equivalent in python:
172+
* return src[row_start:row_end, col_start:col_end].copy()
173+
*/
174+
template <typename T>
175+
dm::NumericTablePtr
176+
copy_submatrix(dm::NumericTablePtr src,
177+
size_t row_start, size_t row_end,
178+
size_t col_start, size_t col_end) {
179+
180+
dm::BlockDescriptor<T> srcblock;
181+
src->getBlockOfRows(0, src->getNumberOfRows(), dm::readOnly, srcblock);
182+
T *srcdata = srcblock.getBlockPtr();
183+
184+
size_t cols = col_end - col_start;
185+
size_t rows = row_end - row_start;
186+
187+
dm::NumericTablePtr dest = dm::HomogenNumericTable<T>::create(
188+
cols, rows, dm::NumericTable::doAllocate);
189+
dm::BlockDescriptor<T> destblock;
190+
dest->getBlockOfRows(0, dest->getNumberOfRows(), dm::readWrite, destblock);
191+
T *destdata = destblock.getBlockPtr();
192+
193+
for (int i = 0; i < rows; i++) {
194+
for (int j = 0; j < cols; j++) {
195+
destdata[i*cols + j] = srcdata[(i+col_start)*cols + j+row_start];
196+
}
197+
}
198+
199+
src->releaseBlockOfRows(srcblock);
200+
dest->releaseBlockOfRows(destblock);
201+
202+
return dest;
203+
204+
}
205+
206+
136207
/*
137208
* Generate an array of random numbers.
138209
*/

native/npyfile.h

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <stdbool.h>
1111
#include <stdio.h>
1212
#include <stdlib.h>
13+
#include <string.h>
1314

1415
#ifndef _NPYFILE_H_
1516
#define _NPYFILE_H_
@@ -281,6 +282,105 @@ struct npyarr *load_npy(const char *path) {
281282
free_npy(arr);
282283
return NULL;
283284
}
285+
286+
287+
/*
288+
* Write an array to disk.
289+
* N.B. elem_size must be the correct size of each element in the
290+
* array.
291+
*/
292+
void save_npy(const struct npyarr *arr, const char *path, size_t elem_size) {
293+
294+
if (path == NULL || arr == NULL) {
295+
return;
296+
}
297+
298+
/* open file */
299+
FILE *f;
300+
if ((f = fopen(path, "wb")) == NULL) {
301+
return;
302+
}
303+
304+
/* Determine header_len */
305+
unsigned int header_len = 0;
306+
static const char header_1[] = "{'descr': '";
307+
static const char header_2[] = "', 'fortran_order': ";
308+
static const char header_3[] = ", 'shape': (";
309+
static const char header_4[] = ")}";
310+
header_len += strlen(header_1) + strlen(header_2);
311+
header_len += strlen(header_3) + strlen(header_4);
312+
313+
header_len += strlen(arr->descr);
314+
header_len += (arr->fortran_order) ? 4 : 5;
315+
header_len += 1; /* the terminating newline */
316+
317+
/* Determine shape length */
318+
unsigned int shape_len = 0;
319+
for (int i = 0; i < arr->shape_len; i++) {
320+
/* Number of digits in the number */
321+
if (arr->shape[i] > 0) {
322+
shape_len += floor(log10(arr->shape[i]) + 1);
323+
} else {
324+
shape_len += 1;
325+
}
326+
if (i < arr->shape_len - 1 && arr->shape_len != 1) {
327+
/* Adding commas where needed */
328+
shape_len += 2;
329+
}
330+
}
331+
332+
header_len += shape_len;
284333

334+
/* 16-byte alignment */
335+
unsigned int bytes_left = 16 - (strlen(NPY_HEADER) + 4 + header_len) % 16;
336+
header_len += bytes_left;
337+
338+
unsigned char major_version = 0x01;
339+
if (header_len > 65535) {
340+
major_version = 0x02;
341+
}
342+
unsigned char minor_version = 0x00;
343+
344+
fputs(NPY_HEADER, f);
345+
fputc(major_version, f);
346+
fputc(minor_version, f);
347+
348+
/* writing header_len in little-endian format always */
349+
for (int i = 0; i < ((major_version >= 0x02) ? 4 : 2); i++) {
350+
fputc((header_len >> (i * 8)) & 0xff, f);
351+
}
352+
353+
fputs(header_1, f);
354+
fputs(arr->descr, f);
355+
fputs(header_2, f);
356+
fputs((arr->fortran_order) ? "True" : "False", f);
357+
fputs(header_3, f);
358+
359+
size_t nelem = 1;
360+
for (int i = 0; i < arr->shape_len; i++) {
361+
fprintf(f, "%d", arr->shape[i]);
362+
nelem *= arr->shape[i];
363+
if (i < arr->shape_len - 1 && arr->shape_len != 1) {
364+
fputs(", ", f);
365+
}
366+
}
367+
fputs(header_4, f);
368+
369+
/* Pad with spaces */
370+
for (int i = 0; i < bytes_left; i++) {
371+
fputc(' ', f);
372+
}
373+
374+
/* Add terminating newline */
375+
fputc('\n', f);
376+
377+
/* Write array data */
378+
fwrite(arr->data, elem_size, nelem, f);
379+
380+
/* done. */
381+
fflush(f);
382+
fclose(f);
383+
384+
}
285385

286386
#endif /* _NPYFILE_H_ */

0 commit comments

Comments
 (0)