Потенциальное использование Python-декоратора или другая рефакторизация: итеративная оптимизация - PullRequest
1 голос
/ 21 февраля 2010

Прости меня за еще один вопрос о декораторах Python. Я прочитал многие из них, но мне интересно, как лучше решить конкретную проблему.

Я написал несколько функций, которые выполняют некую форму градиентного спуска в numpy / scipy. Учитывая матрицу X, я пытаюсь итеративно минимизировать некоторое расстояние, d (X, AS), как функции от A и S. Каждый алгоритм следует той же самой базовой процедуре, но у каждого есть различное правило обновления. Например, здесь были две мои функции (обратите внимание, единственное отличие заключается в правиле обновления):

def algo1(X, A=None, S=None, K=2, maxiter=10, c=0.1):
    M, N = X.shape
    if A is None:
        A = matrix(rand(M, K))
    if S is None:
        S = matrix(rand(K, N))
    for iter in range(maxiter):
        # Begin update rule.
        A = multiply(A, (X*S.T + c)/(A*S*S.T + c))
        S = multiply(S, (A.T*X + c)/(A.T*A*S + c))
        # End update rule.
        for k in range(K):
            na = norm(A[:,k])
            A[:,k] /= na
            S[k,:] *= na
    return A, S

... и другие:

def algo2(X, A=None, S=None, K=2, maxiter=10, c=0.1):
    M, N = X.shape
    O = matrix(ones([M, N]))
    if A is None:
        A = matrix(rand(M, K))
    if S is None:
        S = matrix(rand(K, N))
    for iter in range(maxiter):
        # Begin update rule.
        A = multiply(A, ((X/(A*S))*S.T + c)/(O*S.T + c))
        S = multiply(S, (A.T*(X/(A*S)) + c)/(A.T*O + c))
        # End update rule.
        for k in range(K):
            na = norm(A[:,k])
            A[:,k] /= na
            S[k,:] *= na
    return A, S

Обе функции успешны сами по себе. Очевидно, что эти функции требуют рефакторинга. Единица кода, которая отличается, - это правило обновления. Итак, вот моя попытка рефакторинга:

@iterate
def algo1(X, A=None, S=None, K=2, maxiter=10, c=0.1):
    A = multiply(A, (X*S.T + c)/(A*S*S.T + c))
    S = multiply(S, (A.T*X + c)/(A.T*A*S + c))

@iterate
def algo2(X, A=None, S=None, K=2, maxiter=10, c=0.1):
    A = multiply(A, ((X/(A*S))*S.T + c)/(O*S.T + c))
    S = multiply(S, (A.T*(X/(A*S)) + c)/(A.T*O + c))

Вот некоторые потенциальные вызовы функций:

A, S = algo1(X)
A, S = algo1(X, A0, S0, maxiter=50, c=0.2)
A, S = algo1(X, K=10, maxiter=40)

Вопросы:

  1. Какая техника лучше всего подходит для рефакторинга этого кода? Функция декораторов?
  2. Если так, как бы вы написали iterate? Меня, в частности, смущают аргументы / параметры, например, значения «против» и «без», доступ к ним в декораторе и в «обертке» и т. Д. Например, сами правила обновления не требуют K, но код инициализации делает, поэтому мне интересно, правильные ли подписи моей функции.

РЕДАКТИРОВАТЬ: Спасибо за помощь. Больше вопросов:

  1. Правда ли, что обертка (например, inner) необходима только при передаче параметров? Потому что я вижу примеры декораторов без оберток, и никакие параметры не передаются, и они работают просто отлично.
  2. После прочтения документации по Python functools представляется полезным; является ли его основной целью сохранение метаданных исходной функции (например, algo1.__name__ и algo1.__doc__)?
  3. С подписями def algo1(X, A, S, c) и def inner(X, A=None, S=None, K=2, maxiter=10, c=0.1) звонок algo1(X, maxiter=20) все еще работает. Синтаксически, я не уверен, почему это так. В целях обучения, не могли бы вы уточнить (или привести ссылку)? Спасибо!

1 Ответ

5 голосов
/ 21 февраля 2010

В качестве декоратора, который вы хотите использовать, должно работать следующее:

import functools

def iterate(update):
    @functools.wraps(update)
    def inner(X, A=None, S=None, K=2, maxiter=10, c=0.1):
        M, N = X.shape
        O = matrix(ones([M, N]))
        if A is None:
            A = matrix(rand(M, K))
        if S is None:
            S = matrix(rand(K, N))
        for iter in range(maxiter):
            A, S = update(X, A, S, K, maxiter, c)
            for k in range(K):
                na = norm(A[:,k])
                A[:,k] /= na
                S[k,:] *= na
        return A, S
    return inner

Как вы заметили, вы могли бы упростить сигнатуры algo1 и algo2, но это на самом деле не важная часть, и, возможно, сохранение нетронутыми сигнатур может упростить ваше тестирование и рефакторинг. Если вы do хотите упростить, вы измените операторы def для тех, скажем, на

def algo1(X, A, S, c):

и аналогичным образом упрощают вызов в декорации iterator - нет необходимости ни в двух аргументах, ни в значениях по умолчанию. Однако отказ от этой части упрощения может на самом деле сделать вашу жизнь проще - обычно проще, если декорированная функция и результат ее декорирования сохраняют точно такую ​​же сигнатуру, что и другие, если только у вас нет особых особых требований.

edit : OP продолжает копить вопросы на этот вопрос ...:

РЕДАКТИРОВАТЬ: Спасибо за помощь. Больше вопросов:

Правда ли, что обертка (например, внутренняя) необходима только когда параметры передаются? Потому что я смотрите примеры декораторов без обертки, и никаких параметров прошло, и они работают просто отлично.

A декоратор , используемый без параметров (при использовании @decorname), вызывается с декорируемой функцией и должен возвращать функцию; декоратор, используемый с параметрами (например, @decorname(23)), должен возвращать функцию ("более высокого порядка"), которая в свою очередь вызывается с декорируемой функцией, и должна возвращать функцию. Независимо от того, принимает ли декорируемая функция параметр или нет, этот набор правил не меняется. Технически возможно достичь этого без внутренних функций (что, я полагаю, вы подразумеваете под «обертками»?), Но это довольно редко.

Из чтения некоторых документов Python более того, functools кажется полезным; это его Основная цель сохранения метаданных исходной функции (например, algo1. name и algo1. doc )?

Да, functools.wraps используется именно для этой цели (functools также содержит partial, что имеет совершенно другое назначение).

С подписями def algo1(X, A, S, c) и def inner(X, A=None, S=None, K=2, maxiter=10, c=0.1), звонок algo1(X, maxiter=20) все еще работает. Синтаксически, я не уверен, почему это является. Не могли бы вы уточнить (или привести ссылку)? Спасибо!

Это потому, что inner - это функция, которая на самом деле вызывается с этими параметрами (после оформления algo1) и передается только (к «реальным базовым algo1) параметрам X, A, S, c (в версии, где обернутый algo1 имеет упрощенную подпись.) Проблема, как я уже упоминал выше, состоит в том, что это делает метаданные (в частности, подпись) различными между декорируемой функцией и результирующей декорированной функцией, что довольно запутанно читать поддерживать, чтобы на обоих уровнях обычно была одна и та же подпись, за исключением особых обстоятельств.

...