Как передать аргументы через цепочку вложенных функций для вычисления результата?

Мой вопрос быстрый, но я предоставил здоровенный фрагмент кода, чтобы лучше проиллюстрировать мою проблему, поскольку я не понял ответа, прочитав соответствующие сообщения.

Приведенный ниже код предназначен для выбора оптимизированных параметров, которые являются частью списка аргументов. Список аргументов должен быть одной записью как x0 в scipy docs < / а>. Я надеюсь найти правильную комбинацию аргументов, которая наилучшим образом соответствует данным. Модули scipy optimize должны колебать значения моих аргументов, чтобы найти комбинацию, которая минимизирует мою ошибку в наибольшей степени. Но у меня возникают проблемы с передачей аргументов из одной функции в другую.

Иногда я ставлю * или **, но мой успех - это скорее промах, чем попадание. Я хочу знать, как передавать аргументы из одной функции в другую, позволяя им изменять значение, чтобы найти их оптимизированное значение. (Оптимизированное значение уменьшает ошибку, как описано ниже). У меня есть несколько функций, которые служат входными данными для других функций, и здесь мне не хватает ключевой концепции. Нужны ли для чего-то кварги? Если аргументы являются кортежем, могут ли они по-прежнему изменять значение для поиска оптимизированных параметров? Я знаю, что здесь, на SO, задавались несколько схожие вопросы, но я еще не смог понять это с помощью этих ресурсов.

Код поясняется ниже (после импорта).

import numpy as np
import random
import matplotlib.pyplot as plt
from math import exp
from math import log
from math import pi
from scipy.integrate import quad ## integrate f(x)dx from x_i to x_i+1
from scipy.stats import norm
from scipy.stats import chisquare
from scipy.optimize import basinhopping
from scipy.stats import binned_statistic as bstat

Я сгенерировал случайную выборку распределения Гаусса из 1000 точек данных со средним mu = 48 и стандартным отклонением sigma = 7. Я могу гистограмму данных, и моя цель - найти параметры mu, sigma и normc (коэффициент масштабирования или константа нормализации), которые лучше всего подходят для гистограммы выборочных данных. Существует множество методов анализа ошибок, но для моей цели наилучшее соответствие определяется как соответствие, которое минимизирует хи-квадрат (описанный ниже). Я знаю, что код длинный (даже слишком длинный), но мой вопрос требует небольшой настройки.

## generate data sample
a, b = 48, 7 ## mu, sigma
randg = []
for index in range( 1000 ):
    randg.append( random.gauss(a,b) )
data = sorted( randg )

small = min( data )
big = max( data )
domain = np.linspace(small,big,3000) ## for fitted plot overlay on histogram of data

Затем я организовал ячейки для гистограммы.

numbins = 30 ## number of bins

def binbounder( small , big , numbins ):
    ## generates list of bound bins for histogram ++ bincount
    binwide = ( big - small ) / numbins ## binwidth
    binleft = [] ## left edges of bins
    for index in range( numbins ):
        binleft.append( small + index * binwide )
    binbound = [val for val in binleft]
    binbound.append( big ) ## all bin edges
    return binbound

binborders = binbounder( small , big , numbins )
## useful if one performs plt.hist(data, bins = binborders, ...)

def binmidder( small , big , numbins ):
    ## all midtpts of bins
    ## for x-ticks on histogram
    ## useful to visualize over/under -estimate of error
    binbound = binbounder( small , big , numbins )
    binwide = ( big - small ) / numbins
    binmiddles = []
    for index in range( len( binbound ) - 1 ):
        binmiddles.append( binwide/2 + index * binwide )
    return binmiddles

binmids = binmidder( small , big , numbins )

Чтобы выполнить анализ хи-квадрат, необходимо ввести ожидаемые значения на интервал (E_i) и кратности наблюдаемых значений на интервал (O_i) и вывести сумму по всем ячейкам квадрата их разницы по ожидаемому значению на ячейку.

def countsperbin( xdata , args = [ small , big , numbins ]):
    ## calculates multiplicity of observed values per bin
    binborders = binbounder( small , big , numbins )
    binmids = binmidder( small , big , numbins )
    values = sorted( xdata ) ## function(xdata) ~ f(x)
    bincount = []
    for jndex in range( len( binborders ) ):
        if jndex != len( binborders ) - 1:
            summ = 0
            for val in values:
                if val > binborders[ jndex ] and val <= binborders[ jndex + 1 ]:
                    summ += 1
            bincount.append( summ )
        if jndex == len( binborders ) - 1:
            pass
    return bincount

obsperbin = countsperbin( binborders , data ) ## multiplicity of observed values per bin

Каждое ожидаемое значение для каждого бина, которое необходимо для вычисления и минимизации хи-квадрат, определяется как интеграл функции распределения от x_i = левый край до x_i + 1 = правый край.

Мне нужно разумное начальное предположение для моих оптимизированных параметров, так как они дадут мне разумное предположение для минимизированного значения Хи-квадрат. Я выбираю mu, sigma и normc, чтобы они были близки к их истинным значениям, но не равны им, чтобы я мог проверить, сработала ли минимизация.

def maxbin( perbin ):
    ## perbin is a list of observed data per bin
    ## returns largest multiplicity of observed values with index
    ## useful to help guess scaling factor "normc" (outside exponential in GaussDistrib)
    for index, maxval in enumerate( perbin ):
        if maxval == max( perbin ):
            optindex = index
    return optindex, perbin[ optindex ] 

mu, sigma, normc = np.mean( data ) + 30, np.std( data ) + 20, maxbin( obsperbin )

Поскольку мы интегрируем f (x) dx, точки данных (или xdata) здесь не важны.

def GaussDistrib( xdata , args = [ mu , sigma , normc ] ): ## G(x)
    return normc * exp( (-1) * (xdata - mu)**2 / (2 * sigma**2) )

def expectperbin( args ):
    ## calculates expectation values per bin
    ## needed with observation values per bin for ChiSquared
    ## expectation value of single bin is equal to area under Gaussian curve from left binedge to right binedge
    ## area under curve for ith bin = integral G(x)dx from x_i (left edge) to x_i+1 (right edge)
    ans = []
    for index in range(len(binborders)-1): # ith index does not exist for rightmost boundary
        ans.append( quad( GaussDistrib , binborders[ index ] , binborders[ index + 1 ], args = [ mu , sigma , normc ])[0])
    return ans

Моя определенная функция chisq вызывает chisquare из модуля scipy для возврата результата.

def chisq( args ):
    ## args[0] = mu
    ## args[1] = sigma
    ## args[2] = normc
    ## last subscript [0] gives chi-squared value, [1] gives 0 ≤ p-value ≤ 1
    ## can also minimize negative p-value to find best fitting chi square
    return chisquare( obsperbin , expectperbin( args[0] , args[1] , args[2] ))[0]

Я не знаю как, но я хотел бы наложить ограничения на свою систему. В частности, максимальное значение списка высот разделенных данных должно быть больше нуля (как и Хи-квадрат из-за экспоненциального члена, который остается после дифференцирования).

def miniz( chisq , chisqguess , niter = 200 ):
    minimizer = basinhopping( chisq , chisqguess , niter = 200 )
    ## Minimization methods available via https://docs.scipy.org/doc/scipy-0.18.1/reference/optimize.html
    return minimizer

expperbin = expectperbin( args = [mu , sigma , normc] )
# chisqmin = chisquare( obsperbin , expperbin )[0]
# chisqmin = result.fun

""" OPTIMIZATION """

print("")
print("initial guess of optimal parameters")

initial_mu, initial_sigma, initial_normc = np.mean(data)+30 , np.std(data)+20 , maxbin( (obsperbin) )
## check optimized result against:  mu = 48, sigma = 7 (via random number generator for Gaussian Distribution)

chisqguess = chisquare( obsperbin , expectperbin( args[0] , args[1] , args[2] ))[0]
## initial guess for optimization

result = miniz( chisqguess, args = [mu, sigma, normc] )
print(result)
print("")

Смысл минимизации заключался в том, чтобы найти оптимизированные параметры, которые дают наилучшее соответствие.

optmu , optsigma , optnormc = result.x[0], abs(result.x[1]), result.x[2]

chisqcheck = chisquare(obsperbin, expperbin)
chisqmin = result.fun
print("chisqmin --  ",chisqmin,"        ",chisqcheck," --   check chi sq")

print("""
""")

## CHECK
checkbins = bstat(xdata, xdata, statistic = 'sum', bins = binborders) ## via SCIPY (imports)
binsum = checkbins[0]
binedge = checkbins[1]
binborderindex = checkbins[2]
print("binsum",binsum)
print("")
print("binedge",binedge)
print("")
print("binborderindex",binborderindex)
# Am I doing this part right?

tl; dr: я хочу result, который вызывает функцию minimiz, которая вызывает модуль scipy для минимизации Chi Squared с использованием значения предположения. Чи-квадрат и значение предположения вызывают друг друга функции и т. Д. Как я могу правильно передать свои аргументы?


person Community    schedule 03.03.2017    source источник
comment
Просто замечание, args = (mu, sigma, normc]). args обычно представляет собой кортеж, а не список. Это делает args1 = (x,)+args, your_function(*args1).   -  person hpaulj    schedule 03.03.2017
comment
Если я помещу оптимизируемые параметры в кортеж, можно ли их изменять? Я думал, что записи в кортежах нельзя изменить? Кроме того, означает ли это, что я должен поместить кортеж из подкортежей, каждый из которых содержит возможную комбинацию (mu, sigma, normc) для проверки значений хи-квадрат?   -  person    schedule 03.03.2017
comment
args содержит текущие константы; оптимизация по 1-му аргументу. args значения могут быть изменены в некотором внешнем цикле.   -  person hpaulj    schedule 03.03.2017
comment
Есть ли способ оптимизировать более 3 аргументов или лучше использовать какой-нибудь итератор оптимизированных значений? И как передать их во внешний цикл?   -  person    schedule 03.03.2017
comment
Есть многовариантные методы.   -  person hpaulj    schedule 03.03.2017
comment
Я знаю некоторые по scipy, но мне трудно заставить их работать должным образом.   -  person    schedule 03.03.2017


Ответы (1)


Вы можете получить доступ ко всей информации из OptimizeResult, возвращаемый из optimize.basinhopping.

Я абстрагировался от генерации случайной выборки и сократил количество ваших функций до тех 5 функций, которые действительно необходимы для запуска оптимизации.

Единственная "сложная" часть при передаче параметров - это передать параметры mu и sigma в функцию GaussDistrib внутри вызова quad, но это легко объясняется в четырехъядерный документ. Кроме этого, я не вижу здесь реальной проблемы с передачей параметров.

Ваше длительное использование normc ошибочно. Таким образом, вы не получите правильные значения из гауссиана (и нет необходимости изменять 3 независимых параметра, когда достаточно 2). Кроме того, чтобы получить правильные значения для хи-квадрат, вы должны умножить вероятности из гауссиана на количество выборок (вы сравниваете абсолютные значения из obsperbin бинов с вероятностями по гауссиану, что явно неверно).

from math import exp
from math import pi
from scipy.integrate import quad
from scipy.stats import chisquare
from scipy.optimize import basinhopping


# smallest value in the sample
small = 26.55312337811099
# largest value in the sample
big   = 69.02965763016027

# a random sample from N(48, 7) with 999 sample
# values binned into 30 equidistant bins ranging
# from 'small' (bin[0] lower bound) to 'big'
# (bin[29] upper bound) 
obsperbin = [ 1,  1,  2,  4,  8, 10, 13, 29, 35, 45,
             51, 56, 63, 64, 96, 89, 68, 80, 61, 51,
             49, 30, 34, 19, 22,  3,  7,  5,  1,  2]

numbins = len(obsperbin) #  30
numobs  = sum(obsperbin) # 999

# intentionally wrong guesses of mu and sigma
# to be provided as optimizer's initial values
initial_mu, initial_sigma = 78.5, 27.0


def binbounder( small , big , numbins ):
    ## generates list of bound bins for histogram ++ bincount
    binwide = ( big - small ) / numbins ## binwidth
    binleft = [] ## left edges of bins
    for index in range( numbins ):
        binleft.append( small + index * binwide )
    binbound = [val for val in binleft]
    binbound.append( big ) ## all bin edges
    return binbound

# setup the bin borders
binborders = binbounder( small , big , numbins )


def GaussDistrib( x , mu , sigma ):
    return 1/(sigma * (2*pi)**(1/2)) * exp( (-1) * (x - mu)**2 / ( 2 * (sigma **2) ) )


def expectperbin( musigma ):
    ## musigma[0] = mu
    ## musigma[1] = sigma
    ## calculates expectation values per bin
    ## expectation value of single bin is equal to area under Gaussian
    ## from left binedge to right binedge multiplied by the sample size
    e = []
    for i in range(len(binborders)-1): # ith i does not exist for rightmost boundary
        e.append( quad( GaussDistrib , binborders[ i ] , binborders[ i + 1 ],
                         args = ( musigma[0] , musigma[1] ))[0] * numobs)
    return e


def chisq( musigma ):
    ## first subscript [0] gives chi-squared value, [1] gives 0 = p-value = 1
    return chisquare( obsperbin , expectperbin( musigma ))[0]


def miniz( chisq , musigma ):
    return basinhopping( chisq , musigma , niter = 200 )


## chisquare value for initial parameter guess
chisqguess = chisquare( obsperbin , expectperbin( [initial_mu , initial_sigma] ))[0]

res = miniz( chisq, [initial_mu , initial_sigma] )

print("chisquare from initial guess:" , chisqguess)
print("chisquare after optimization:" , res.fun)
print("mu, sigma after optimization:" , res.x[0], ",", res.x[1])

chisquare из первоначального предположения: 3772.70822797

chisquare после оптимизации: 26.351284911784447

му, сигма после оптимизации: 48.2701027439, 7.046156286

Между прочим, бассейновый шопинг - это излишнее решение для такого рода проблем. Я бы остался с fmin (Нельдер -Mead).

person Stefan Zobel    schedule 03.03.2017
comment
Если я правильно понимаю, GaussDistrib вызывается expectperbin. Поскольку входные данные expectperbin - это args, а входные данные GaussDistrib - это x и (mu, sigma), это означает, что mu, sigma должны быть указаны как args при передаче из expectperbin в GaussDistrib. Таким образом, для любой функции, которая использует одни и те же входные данные и вызывает expectperbin, не нужно указывать аргументы. Если бы вход GaussDistrib был только x, мне потребовалась бы отдельная функция, которая определяет и возвращает аргументы, которые будут использоваться внешними функциями. Верный? Что касается метода минимизации, poolhop указан как один из трех глобальных решателей min, а fmin - нет. - person ; 04.03.2017
comment
Кроме того, я знаю, каким должно быть значение normc для нормализованного распределения. Почему решатель не может / не может оптимизировать для normc? Это вопрос сугубо эффективности? - person ; 04.03.2017
comment
normc, постоянная интегрирования, должна быть 1 / sqrt(2 * PI *(sigma ** 2)) (где sigma может изменяться во время оптимизации). В противном случае функция больше не является плотностью вероятности (не интегрируется в 1). Вероятности из области под кривой больше не будут вероятностями. Невозможно, чтобы нормальное распределение могло иметь 3 независимых параметра. Дело в математической корректности, а не в эффективности. И нет никаких оснований полагать, что, если бы он был введен, normc сходился бы к правильному значению при оптимизации. Вместо этого вы получите бесполезный / неправильный результат для всех трех переменных. - person Stefan Zobel; 04.03.2017
comment
Что касается вашего первого комментария. Извините, я не поняла. - person Stefan Zobel; 04.03.2017
comment
В этом есть смысл. Первоначально я пытался масштабировать свои данные до нормализованного соответствия вместо нормализации, чтобы вернуть исходную ненормализованную гистограмму. Что касается передачи аргументов, я не совсем понимаю, почему синтаксис аргументов меняется от функции к функции. Я думаю, это связано с тем, какая функция передает аргументы другой функции, но я не уверен на 100%. В вашем коде chisqguess указывает, какой параметр является аргументом; chisq принимает аргументы в качестве входных данных; expectperbin разделяет аргументы, которые вызываются входами gaussdistrib. Но я не слежу за тем, что конкретно вызывает изменение синтаксиса. - person ; 04.03.2017
comment
Я отредактировал и переименовал параметры. Возможно, это прояснит ситуацию. - person Stefan Zobel; 04.03.2017
comment
Вы мне очень помогли. Я исправил старые ошибки и значительно улучшил свое понимание благодаря вашей помощи. Я ценю, что вы делаете для меня все возможное, спасибо! - person ; 04.03.2017
comment
Дополнительные параметры (mu, sigma), переданные в GaussDistrib из quad, должны быть именованным кортежем с именем args. Это из определения quad. Все остальные функции могут использовать то, что хотят. мы используем здесь списки, но это может быть что-то еще. - person Stefan Zobel; 04.03.2017
comment
Я понимаю, что quad args должен быть кортежем и называться args. Но, например, аргументы указываются в строке добавления expectperbin перед оператором return; это необходимо или выбор удобства? Я спрашиваю, потому что указание аргументов аналогичным образом для функций, которые не возвращают список, не работает (если я чего-то не упускаю). Кроме того, указаны ли они как таковые, потому что quad вызывает функцию gaussdistrib, которая, в свою очередь, использует аргументы в качестве входных данных? - person ; 04.03.2017
comment
указаны ли они как таковые, потому что quad вызывает функцию gaussdistrib, которая, в свою очередь, использует аргументы в качестве входных данных ?. Итак, quad извлекает параметры из кортежа и передает их как отдельные значения в GaussDistrib. - person Stefan Zobel; 04.03.2017
comment
Это было чрезвычайно полезно. Спасибо еще раз!! - person ; 04.03.2017
comment
Если я могу спросить еще одну вещь: в функции expectperbin есть строка, которая читает args = ( musigma[0] , musigma[1] ))[0] * numobs). Почему * numobs в конце? Это количество раз распаковки кортежа? - person ; 04.03.2017
comment
Он умножает вероятность для bin [i] на количество выборок, чтобы получить ожидаемое количество наблюдений для этого бина и выборку того размера, который у вас есть. Вы получите неправильные значения хи-квадрат, если забудете этот шаг. - person Stefan Zobel; 04.03.2017