Skip to content
Merged

Fcma #97

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
08764d3
add CMake support to the isntallation
yidawang Aug 17, 2016
17ab76a
initialize fcma
yidawang Aug 17, 2016
1bfccc7
first path of voxel selection with hard-coded path
yidawang Aug 24, 2016
2e7c064
sort files before processing; only process the results in master
yidawang Aug 24, 2016
570bd15
use self-defined mask applying method, instead of NiftiMasker
yidawang Aug 24, 2016
0c19f91
add Fisher tranformation to the within subject normalization
yidawang Aug 24, 2016
7b69c76
fix a bug in Fisher Transformation
yidawang Aug 25, 2016
70c653d
use C++ to do within-subject normalization, add running example
yidawang Aug 25, 2016
1bb5282
check compiler type in CMake
yidawang Aug 25, 2016
7cbad63
enable finding blas library/MKL in different OSes
yidawang Aug 26, 2016
b9f4d9c
use new data structure to depict epoch infomation, add an example epo…
yidawang Aug 26, 2016
54da6f0
simplify the fcma example, encapsulate MPI communicatation
yidawang Aug 26, 2016
d9ea112
broadcast the data subject by subject to prevent size overflow
yidawang Aug 26, 2016
38db4e3
minor bug fix in fcma example
yidawang Aug 26, 2016
3935570
formalize comments
yidawang Aug 28, 2016
aab70df
impose scikit-learn svm cross validation to use all CPUs
yidawang Aug 29, 2016
67d97d1
tiny bug fixed in the worker output
yidawang Aug 29, 2016
d0af529
change to column major in preparation for using BLAS APIs under SciPy…
yidawang Aug 30, 2016
55565af
change the input data to row-major
yidawang Aug 30, 2016
29f6d47
minor bugs fixed
yidawang Aug 30, 2016
044d34c
use blas wrapper by scipy cython
yidawang Aug 30, 2016
849a061
format output
yidawang Aug 30, 2016
2bdd569
add in classifier flexibility, use regular compiling for fcma_extensi…
yidawang Aug 31, 2016
5d7ea8b
add fcma test, modify run-tests.sh for testing out MPI processes and …
yidawang Sep 1, 2016
193ce84
make two processes of fcma test exit in different time
yidawang Sep 1, 2016
7dc574d
add data type to index for better performance on Cython code; allow r…
yidawang Sep 1, 2016
7d91308
increase time for cython compiling
yidawang Sep 1, 2016
ffe9c44
change module name to fcma.voxelselector and address other comments f…
yidawang Sep 1, 2016
e02f751
use top-level .gitignore and remove an empty file
yidawang Sep 1, 2016
3ac5368
make mpi tags within class VoxelSelector
yidawang Sep 1, 2016
b9d9b17
fix code format issue
yidawang Sep 1, 2016
45936ee
use logging instead of print
yidawang Sep 7, 2016
a009225
formatting
yidawang Sep 7, 2016
5bd0fa3
add extra link flag for cython compiling under MKL
yidawang Sep 8, 2016
7f822fa
add compiler specific links to cython compiling
yidawang Sep 8, 2016
7699260
make master rank selectable for better coverage in testing
yidawang Sep 9, 2016
b1cdbd2
make the test example conciser, delete the example epoch data
yidawang Sep 9, 2016
926cb66
better c compiler detection in the pyxbld file
yidawang Sep 10, 2016
1365da8
compile cython_blas in __init__.py using only one process, no need to…
yidawang Sep 10, 2016
af31649
Merge remote-tracking branch 'upstream/master' into fcma
yidawang Sep 14, 2016
f35f03d
add run-tests.sh back
yidawang Sep 16, 2016
1260a42
add run-tests.sh back
yidawang Sep 16, 2016
b5f2c0e
chmod of run-tests.sh
yidawang Sep 16, 2016
9e47e15
remove cmake related info for this PR
yidawang Sep 16, 2016
a77b51f
restore original setup.py and address other review comments
yidawang Sep 17, 2016
8f510f7
add fcma extension back
yidawang Sep 17, 2016
44225eb
fortmatting issue
yidawang Sep 17, 2016
f65f6c8
copy *pyxbld files when installing
yidawang Sep 19, 2016
ef554a2
Compile Cython code during install, not at runtime
mihaic Sep 20, 2016
0343ade
Merge pull request #5 from mihaic/fcma-compile-at-install
yidawang Sep 20, 2016
0b7b078
only add c++11/14 to C++ extension compiling; check correctness in FC…
yidawang Sep 21, 2016
5eadef1
explicitly specify language for each extension
yidawang Sep 21, 2016
39f9f07
use == to check if two strings equal
yidawang Sep 21, 2016
031d5fb
set icc flags to c_opts correctly
yidawang Sep 21, 2016
3d86e86
detect extension language in a smarter way; let fcma test code read i…
yidawang Sep 21, 2016
f862c9b
use deep copy to assign compiling flags
yidawang Sep 21, 2016
f75d9f7
switch the test sequence
yidawang Sep 21, 2016
257882d
make kernel matrix scale setting adjustable to the initial values; re…
yidawang Sep 21, 2016
3f14b80
tolerate one sample error in cross validation
yidawang Sep 21, 2016
9a3f00b
delete unnecessary data file; improve fcma test coverage
yidawang Sep 21, 2016
03c6957
add logistic regression to fcma test
yidawang Sep 21, 2016
e4823b1
rename the code file in fcma example
yidawang Sep 21, 2016
d0204ab
Merge remote-tracking branch 'upstream/master' into fcma
yidawang Sep 21, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*.swp
*.lib
*.o
*.npy
*.log

brainiak.egg-info
build
Expand All @@ -18,3 +20,6 @@ htmlcov
.idea
.ipynb_checkpoints
junit-*.xml
__pycache__

brainiak/fcma/cython_blas.c
14 changes: 14 additions & 0 deletions brainiak/fcma/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright 2016 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Full correlation matrix analysis"""
203 changes: 203 additions & 0 deletions brainiak/fcma/cython_blas.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# Copyright 2016 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cimport scipy.linalg.cython_blas as blas

def compute_correlation(py_trans_a, py_trans_b, py_m, py_n, py_k, py_alpha, py_a, py_lda,
int py_start_voxel, py_ldb, py_beta, py_c, py_ldc, int py_start_epoch):
""" use blas API wrapped by scipy.linalg.cython_blas to compute correlation

The blas APIs process matrices in column-major,
but our matrices are in row-major,
so we play the transpose trick here, i.e. A*B=(B^T*A^T)^T.
The resulting matrix in shape [num_assigned_voxels, num_voxels]
is stored in an alternate way to make sure that
the correlation vectors of the same voxel stored continuously

Parameters
----------
py_trans_a: str
do transpose or not for the first matrix A

py_trans_b: str
do transpose or not for the first matrix B

py_m: int
the row of the resulting matrix C
in our case, is num_voxels

py_n: int
the column of the resulting matrix C
in our case, is num_assigned_voxels

py_k: int
the collapsed dimension of the multiplying matrices
i.e. the column of the first matrix after transpose if necessary
the row of the second matrix after transpose if necessary

py_alpha: float
the weight applied to the first matrix A

py_a: 2D array in shape [epoch_length, num_voxels] in our case
the activity data of an epoch

py_lda: int
the stride of the first matrix A

py_start_voxel: int
the starting voxel of assigned voxels
used to locate the second matrix B

py_ldb: int
the stride of the second matrix B

py_beta: float
the weight applied to the resulting matrix C

py_c: 3D array in shape [num_selected_voxels, num_epochs, num_voxels]
place to store the resulting correlation values

py_ldc: int
the stride of the resulting matrix
in our case, num_voxels*num_epochs

py_start_epoch: int
the epoch over which the correlation is computed

Returns
-------
py_c: 3D array in shape [num_selected_voxels, num_epochs, num_voxels]
write the resulting correlation values in an alternate way
for the processing epoch
"""
cdef bytes by_trans_a=py_trans_a.encode()
cdef bytes by_trans_b=py_trans_b.encode()
cdef char* trans_a = by_trans_a
cdef char* trans_b = by_trans_b
cdef int M, N, K, lda, ldb, ldc
M = py_m
N = py_n
K = py_k
lda = py_lda
ldb = py_ldb
ldc = py_ldc
cdef float alpha, beta
alpha = py_alpha
beta = py_beta
cdef float[:, ::1] A
A = py_a
cdef float[:, :, ::1] C
C = py_c
blas.sgemm(trans_a, trans_b, &M, &N, &K, &alpha, &A[0, 0], &lda,
&A[0, py_start_voxel], &ldb, &beta, &C[0, py_start_epoch,0], &ldc)

def compute_kernel_matrix(py_uplo, py_trans, py_n, py_k, py_alpha, py_a, int py_start_voxel, py_lda,
py_beta, py_c, py_ldc):
""" use blas API wrapped by scipy.linalg.cython_blas to compute kernel matrix of SVM

The blas APIs process matrices in column-major, but our matrices are in row-major,
so we play the transpose trick here, i.e. A*B=(B^T*A^T)^T

In SVM with linear kernel, the distance of two samples
is essentially the dot product of them.
Therefore, the kernel matrix can be obtained by matrix multiplication.
Since the kernel matrix is symmetric, ssyrk is used,
the other half of the matrix is assigned later.
In our case, the dimension of samples is much larger than
the number samples, so we proportionally shrink the values of the kernel matrix
for getting more robust alpha values in SVM iteration.

Parameters
----------
py_uplo: str
getting the upper or lower triangle of the matrix

py_trans: str
do transpose or not for the input matrix A

py_n: int
the row and column of the resulting matrix C
in our case, is num_epochs

py_k: int
the collapsed dimension of the multiplying matrices
i.e. the column of the first matrix after transpose if necessary
the row of the second matrix after transpose if necessary
in our case, is num_voxels

py_alpha: float
the weight applied to the input matrix A

py_a: 3D array in shape [num_assigned_voxels, num_epochs, num_voxels] in our case
the normalized correlation values of a voxel

py_start_voxel: int
the processed voxel
used to locate the input matrix A

py_lda: int
the stride of the input matrix A

py_beta: float
the weight applied to the resulting matrix C

py_c: 2D array in shape [num_epochs, num_epochs]
place to store the resulting kernel matrix

py_ldc: int
the stride of the resulting matrix

Returns
-------
py_c: 2D array in shape [num_epochs, num_epochs]
write the resulting kernel_matrix
for the processing voxel
"""
cdef bytes by_uplo=py_uplo.encode()
cdef bytes by_trans=py_trans.encode()
cdef char* uplo = by_uplo
cdef char* trans = by_trans
cdef int N, K, lda, ldc
N = py_n
K = py_k
lda = py_lda
ldc = py_ldc
cdef float alpha, beta
alpha = py_alpha
beta = py_beta
cdef float[:, :, ::1] A
A = py_a
cdef float[:, ::1] C
C = py_c
blas.ssyrk(uplo, trans, &N, &K, &alpha, &A[py_start_voxel, 0, 0], &lda,
&beta, &C[0, 0], &ldc)
# shrink the values for getting more stable alpha values in SVM training iteration
num_digits = len(str(int(py_c[0, 0])))
if (num_digits > 2):
proportion = 10**(2-num_digits)
py_c *= proportion
# complete the other half of the kernel matrix
if (py_uplo=='L'):
for j in range(py_c.shape[0]):
for k in range(j):
py_c[j, k] = py_c[k, j]
else:
for j in range(py_c.shape[0]):
for k in range(j):
py_c[k, j] = py_c[j, k]

def installed():
"""
This is an empty method for installing cython_blas library
"""
97 changes: 97 additions & 0 deletions brainiak/fcma/src/fcma_extension.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
// Copyright 2016 Intel Corporation
// //
// // Licensed under the Apache License, Version 2.0 (the "License");
// // you may not use this file except in compliance with the License.
// // You may obtain a copy of the License at
// //
// // http://www.apache.org/licenses/LICENSE-2.0
// //
// // Unless required by applicable law or agreed to in writing, software
// // distributed under the License is distributed on an "AS IS" BASIS,
// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// // See the License for the specific language governing permissions and
// // limitations under the License.
// */
//
#include "Python.h"
#include <omp.h>
#include <iostream>
#include <sys/time.h>
#include <math.h>
#include <inttypes.h>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

namespace py = pybind11;

void within_subject_norm_native(py::array_t<float, py::array::c_style |
py::array::forcecast> py_data, int epochsPerSubj)
{
//This function calculates the within-subject normalization of the input correlation values
//
//Input parameters are:
//py_data: the correlation value array, in shape [num_voxels, num_epochs, num_selected_voxels]
// containing the raw correlation values
//epochsPerSubj: the number pf epochs per subject
//<Note> assuming all subjects have the same number of epochs
//
//Ouput parameters are:
//py_data: the correlation value array, in shape [num_voxels, num_epochs, num_selected_voxels]
// containing the normalized correlation values
//
py::buffer_info buf = py_data.request();
float* data = (float*)buf.ptr;
// sanity check
if (buf.ndim != 3)
throw std::runtime_error("The multi-subject correlation data structure must be 3D");
size_t nSelectedVoxels = buf.shape[0];
size_t nEpochs = buf.shape[1];
size_t nVoxels = buf.shape[2];
size_t nSubjs = nEpochs / epochsPerSubj;

#pragma omp parallel for
for(int v = 0 ; v < nSelectedVoxels*nSubjs ; v++)
{
int s = v % nSubjs; // subject id
int i = v / nSubjs; // voxel id

float *mat = data+i*nEpochs*nVoxels;
#pragma simd
for(int j = 0 ; j < nVoxels ; j++)
{
float mean = 0.0f;
float std_dev = 0.0f;
for(int b = s*epochsPerSubj; b < (s+1)*epochsPerSubj; b++)
{
float num = 1.0f + mat[b*nVoxels+j];
float den = 1.0f - mat[b*nVoxels+j];
num = (num <= 0.0f) ? 1e-4 : num;
den = (den <= 0.0f) ? 1e-4 : den;
mat[b*nVoxels+j] = 0.5f * logf(num/den);
mean += mat[b*nVoxels+j];
std_dev += mat[b*nVoxels+j] * mat[b*nVoxels+j];
}
mean = mean / (float)epochsPerSubj;
std_dev = std_dev / (float)epochsPerSubj - mean*mean;
float inv_std_dev = (std_dev <= 0.0f) ? 0.0f : 1.0f / sqrt(std_dev);
for(int b = s*epochsPerSubj; b < (s+1)*epochsPerSubj; b++)
{
mat[b*nVoxels+j] = (mat[b*nVoxels+j] - mean) * inv_std_dev;
}
}
}
return;
}

PYBIND11_PLUGIN(fcma_extension) {
py::module m("fcma_extension");
m.def("normalization", &within_subject_norm_native, "Within-subejct correlation normalization");

#ifdef VERSION_INFO
m.attr("__version__") = py::str(VERSION_INFO);
#else
m.attr("__version__") = py::str("dev");
#endif
return m.ptr();
}
Loading