Цели данной математической функции для вычисления расстояния между двумя (или более) белковыми структурами с использованием двугранных углов:Оптимизация и ускорение математической функции в Python
Это очень полезно в структурно биологии, например. И я уже кодирую эту функцию в python, используя numpy, но цель состоит в том, чтобы иметь более быструю реализацию. В качестве ссылки на вычисление времени я использую функцию евклидовой дистанции, доступную в пакете scikit-learn.
Вот код у меня есть на данный момент:
import numpy as np
import numexpr as ne
from sklearn.metrics.pairwise import euclidean_distances
# We have 10000 structures with 100 dihedral angles
n = 10000
m = 100
# Generate some random data
c = np.random.rand(n,m)
# Generate random int number
x = np.random.randint(c.shape[0])
print c.shape, x
# First version with numpy of the dihedral_distances function
def dihedral_distances(a, b):
l = 1./a.shape[0]
return np.sqrt(l* np.sum((0.5)*(1. - np.cos(a-b)), axis=1))
# Accelerated version with numexpr
def dihedral_distances_ne(a, b):
l = 1./a.shape[0]
tmp = ne.evaluate('sum((0.5)*(1. - cos(a-b)), axis=1)')
return ne.evaluate('sqrt(l* tmp)')
# The function of reference I try to be close as possible
# in term of computation time
%timeit euclidean_distances(c[x,:], c)[0]
1000 loops, best of 3: 1.07 ms per loop
# Computation time of the first version of the dihedral_distances function
# We choose randomly 1 structure among the 10000 structures.
# And we compute the dihedral distance between this one and the others
%timeit dihedral_distances(c[x,:], c)
10 loops, best of 3: 21.5 ms per loop
# Computation time of the accelerated function with numexpr
%timeit dihedral_distances_ne(c[x,:], c)
100 loops, best of 3: 9.44 ms per loop
9,44 мс это очень быстро, но это очень медленно, если вам нужно запустить его в миллион раз. Теперь вопрос в том, как это сделать? Каким будет следующий шаг? Cython? PyOpenCL? У меня есть опыт работы с PyOpenCL, но я никогда не кодирую что-то столь же сложное, как это. Я не знаю, можно ли вычислить двугранные расстояния за один шаг на GPU, как я делаю с numpy и как продолжить.
Спасибо, что помогли мне!
EDIT: Спасибо, ребята! В настоящее время я работаю над полным решением, и как только он будет закончен, я поставлю код здесь.
Cython версия:
%load_ext cython
import numpy as np
np.random.seed(1234)
n = 10000
m = 100
c = np.random.rand(n,m)
x = np.random.randint(c.shape[0])
print c.shape, x
%%cython --compile-args=-fopenmp --link-args=-fopenmp --force
import numpy as np
cimport numpy as np
from libc.math cimport sqrt, cos
cimport cython
from cython.parallel cimport parallel, prange
# Define a function pointer to a metric
ctypedef double (*metric)(double[: ,::1], np.intp_t, np.intp_t)
cdef extern from "math.h" nogil:
double cos(double x)
double sqrt(double x)
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cdef double dihedral_distances(double[:, ::1] a, np.intp_t i1, np.intp_t i2):
cdef double res
cdef int m
cdef int j
res = 0.
m = a.shape[1]
for j in range(m):
res += 1. - cos(a[i1, j] - a[i2, j])
res /= 2.*m
return sqrt(res)
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cdef double dihedral_distances_p(double[:, ::1] a, np.intp_t i1, np.intp_t i2):
cdef double res
cdef int m
cdef int j
res = 0.
m = a.shape[1]
with nogil, parallel(num_threads=2):
for j in prange(m, schedule='dynamic'):
res += 1. - cos(a[i1, j] - a[i2, j])
res /= 2.*m
return sqrt(res)
@cython.boundscheck(False)
@cython.wraparound(False)
def pairwise(double[: ,::1] c not None, np.intp_t x, p = True):
cdef metric dist_func
if p:
dist_func = &dihedral_distances_p
else:
dist_func = &dihedral_distances
cdef np.intp_t i, n_structures
n_samples = c.shape[0]
cdef double[::1] res = np.empty(n_samples)
for i in range(n_samples):
res[i] = dist_func(c, x, i)
return res
%timeit pairwise(c, x, False)
100 loops, best of 3: 17 ms per loop
# Parallel version
%timeit pairwise(c, x, True)
10 loops, best of 3: 37.1 ms per loop
Так я следую вашему link, чтобы создать версию Cython функции двугранного расстояния. Мы получаем некоторую скорость, не так много, но она все еще медленнее, чем версия numexpr (17 мс против 9,44 мс). Поэтому я попытался распараллелить функцию, используя prange, и это хуже (37.1ms против 17ms против 9.4ms)!
Пропустить что-нибудь?
Пары Небольшие улучшения: 1) положить '* 0,5' вне суммы; и, 2) суммируйте 'cos' перед вычитанием из' 1' (что будет более точным, так как сумма будет зависать около 1). Для меня это приносит от 25 мс до 17 мс. Я знаю, что вы ищете больше, но это то, что я получил, и подумал, что это может немного помочь. – tom10
Простите cython легко и можете дать довольно ускорение (YMMV, конечно): https://jakevdp.github.io/blog/2012/08/08/memoryview-benchmarks/ –
@ tom10 Эффективно с 1) я получаю более 1 мс, но 2) дайте мне ошибку 'RuntimeWarning: недопустимое значение, встречающееся в sqrt'. – NoExiT