Объединить Python и C ++ или Cython, чтобы оптимизировать функцию; пример максимального правдоподобия; немного знания с ++ - PullRequest
0 голосов
/ 25 апреля 2018

Я знаю Python, но я не знаю C ++. Я пытаюсь максимизировать функцию, которая требует много времени для оценки. Я полагаю, что хорошим рабочим процессом было бы написание функции, которая оценивает функцию в C ++, и использование этой функции с scipy.optim.minimize, чтобы найти оптимальный. В качестве примера предположим, что я максимально увеличиваю вероятность.

import pandas as pd
import numpy as np
from scipy.optimize import minimize
from scipy.stats import norm

# simulating data
means = np.array([10, 20, 30])
cov = np.diag([1, 4, 10])

N = 1000

df = pd.DataFrame(np.random.multivariate_normal(mean=means, cov=cov, size=N),
    columns=['a', 'b', 'c'])
df[np.random.choice([True, False], size=(N, 3), p=[0.3, 0.7])] = np.nan

# a function to print parameters used in likelihood function
def print_params(params):
    print('Means: {}'.format(params[:3]))
    print('Variances: {}'.format(np.exp(params[3:])**2))

# defining likelihood
def llf(params):
    logll = 0
    for i in df.index:
        for j,col in enumerate(['a', 'b', 'c']):
            if not np.isnan(df.loc[i, col]):
                m = params[j]
                sd = np.exp(params[j+3])
                logll += np.log(norm.pdf(df.loc[i, col], loc=m, scale=sd))

    print_params(params)
    return -logll


opt = minimize(llf, x0=np.array([0, 0, 0, 1, 1, 1]), options={'maxiter':30})
print_params(opt.x)

Могут быть более эффективные способы написания функции llf на чистом Python, и, безусловно, есть способы ускорить процедуру оптимизации (например, путем выбора определенного оптимизатора, подходящего для данной проблемы, или с помощью производных ресурсов), но это не фокус этого вопроса. Я выбрал этот конкретный пример, потому что у меня есть цикл (я использую все данные, включая строки, в которых некоторые столбцы имеют пропущенные значения) для оценки вероятности, которая занимает много времени в чистом Python, особенно если размер моей выборки увеличивается .

Как я могу написать функцию правдоподобия на C ++ и объединить ее с процедурой минимизации Python? Имейте в виду, у меня нет опыта работы с C ++, но я хочу учиться. Однако многие ресурсы, доступные для этого, предполагают знание C ++, например, Расширение Python . Я ищу ресурсы для тех, кто знает Python, но совершенно не знает C ++ и методов для объединения Python с C ++. РЕДАКТИРОВАТЬ: Возможно, будет полезен пример того, как это сделать, используя мой пример или информацию о вероятных выгодах от объединения Python и C ++.

1 Ответ

0 голосов
/ 26 апреля 2018

Как и предполагалось, я попробовал решение Cython. Поскольку я никогда раньше не использовал Cython, я завершу шаги, которые я использовал для реализации решения Cython.

Сначала я установил Cython. Затем я написал файл с именем fastllf.pyx, который содержал следующий код Cython:

#cython: boundscheck=False, wraparound=False, nonecheck=False

from libc.math cimport exp, sqrt, pi, log, isnan

cdef double SQ_PI = sqrt(2*pi)


cdef double norm_pdf(double x, double loc, double scale):
    return (1/(SQ_PI*scale))*exp(-(0.5)*((x - loc)**2)/(scale**2))

cdef double llf_c(double[:, :] X, double[:] params):

    cdef double logll = 0
    cdef int N = X.shape[0]
    cdef int K = X.shape[1]
    cdef int i, j
    cdef double m, sd

    for i in range(N):
        for j in range(K):
            if not isnan(X[i, j]):
                m = params[j]
                sd = exp(params[j+K])

                logll += log(norm_pdf(X[i, j], m, sd))
    return -logll

def llf(double[:, :] X, double[:] params):
    return llf_c(X, params)

Затем я создал setup.py файл, который включал следующее:

from distutils.core import setup
from Cython.Build import cythonize

setup(name="fastllf", ext_modules=cythonize('fastllf.pyx'))

Затем я скомпилировал код Cython, используя следующую команду в терминале.

$ python3 setup.py build_ext --inplace

Наконец, я сравнил результаты между моей старой, чистой реализацией Python (слегка модифицированной для использования массивов вместо фреймов данных) и реализацией Cython.

import numpy as np
from scipy.stats import norm
import time
from fastllf import llf as cython_llf

# simulating data
means = np.array([10, 20, 30])
cov = np.diag([1, 4, 10])

N = 100000
np.random.seed(10)

X = np.random.multivariate_normal(mean=means, cov=cov, size=N)
X[np.random.choice([True, False], size=(N, 3), p=[0.3, 0.7])] = np.nan

def norm_pdf(x, loc, scale):
    return (1/(np.sqrt(2*np.pi)*scale))*np.exp(-(0.5)*((x-loc)**2)/(scale**2))

def llf(X, params):

    logll = 0
    N = X.shape[0]
    K = X.shape[1]

    for i in range(N):
        for j in range(K):
            if not np.isnan(X[i, j]):
                m = params[j]
                sd = np.exp(params[j+K])

                logll += np.log(norm_pdf(X[i, j], loc=m, scale=sd))    
    return -logll    

def timeit(fun, *args):
    start = time.time()
    rslt = fun(*args)
    end = time.time()
    print(rslt)
    print(end - start)

params = np.array([1.,1,1,1,1,1])
timeit(llf, X, params)
timeit(cython_llf, X, params)

И я получил следующие результаты:

Python Value: 6570173.7597125955
Python Time:  1.9558300971984863 seconds
Cython Value: 6570173.7597125955
Cython Time:  0.016242027282714844 seconds

Это делает оптимизацию по максимальному правдоподобию гораздо более осуществимой, особенно когда моя проблема усложняется. Единственная проблема заключается в том, что мне нужно найти математические и статистические функции, которые мне нужны, чтобы написать llf функцию на Cython, или мне нужно написать свою собственную, как я делал для обычного pdf выше.

Будем благодарны за любые комментарии по поводу моей реализации.

...