【问题标题】:Potential use of Python decorator or other refactorization: iterative optimizationPython 装饰器或其他重构的潜在用途:迭代优化
【发布时间】:2010-02-21 14:32:34
【问题描述】:

请原谅我提出另一个关于 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?尤其让我感到困惑的是参数/参数,例如,有 vs. 没有默认值,在装饰器和“包装器”中访问它们等等。例如,更新规则本身不需要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) 仍然有效。从语法上讲,我不确定为什么会这样。出于学习目的,您能否澄清(或引用参考)?谢谢!

【问题讨论】:

  • 我建议把装饰器的名字改成更有意义的名字——“迭代”在 Python 中已经有了特定的含义。 “gradient_descent”或“minimise_matrix_distance”怎么样?
  • 是的,在我的原始代码中,装饰器和函数名称是不同的。谢谢你的建议。

标签: python refactoring numpy decorator


【解决方案1】:

以下应该可以很好地作为您要使用的装饰器:

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 的签名,但这并不是真正重要的部分,也许保持签名完整可以简化您的测试和重构。如果您确实想要简化,您可以将 def 语句更改为,例如,

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

同样简化了iterator 装饰中的调用——不需要两个参数,也不需要默认值。然而,避免这个简化部分实际上可以让你的生活更简单——如果装饰的函数和装饰它的结果保持完全相同的签名,通常会更简单,除非你有相反的特殊需求。

编辑:OP一直在这个问题上提出问题......:

编辑:感谢您的帮助。更多问题:

包装器(例如, inner) 仅在以下情况下是必需的 正在传递参数?因为我 请参阅没有的装饰器示例 包装器,并且没有参数 通过了,它们工作得很好。

一个不带参数使用的装饰器(在@decorname 中使用)被装饰的函数调用,并且必须返回一个函数;使用 with 参数的装饰器(如@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(在Wrapped algo1 被赋予了简化的签名)。正如我上面提到的,问题是这使得元数据(特别是签名)在被修饰的函数和得到的修饰函数之间有所不同;读起来很混乱,而且维护,因此通常在两个级别都保持相同的签名,除非是特殊情况。

【讨论】:

  • 再次感谢您!答案很好地阐明了我的担忧,特别是关于参数的使用。很抱歉把它堆起来——没有更多的问题。 :-)
猜你喜欢
  • 2020-02-10
  • 2012-03-20
  • 2019-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-28
  • 2020-12-03
相关资源
最近更新 更多