【问题标题】:Deleting variables after use during assignment在赋值期间使用后删除变量
【发布时间】:2019-07-22 00:46:12
【问题描述】:

我倾向于看到在查看单个行时不清楚您在计算中的位置的代码。例如:

def forward(self, X):
    X = layer(X)
    X = activation(X)
    X = layer(X)
    X = activation(X)
    return X

显然,第 2 行和第 4 行以及第 3 行和第 5 行彼此无法区分。

我更愿意编写更具可读性的代码,在查看特定代码行时,您在计算中所处的位置很清楚。例如(使用 Andrew Ng 的符号):

def forward(self, X):
    A0 = X
    Z1 = layer(A0)
    A1 = activation(Z1)
    Z2 = layer(A1)
    A2 = activation(Z2)
    return A2

但这可能会在给定大变量的情况下导致内存错误,因此可能需要执行以下操作...

def forward(self, X):
    A0 = X
    Z1 = layer(A0); del A0
    A1 = activation(Z1); del Z1
    Z2 = layer(A1); del A1
    A2 = activation(Z2); del Z2
    return A2

但是,这会影响可读性并没有利用垃圾收集器的属性。我不确定这个替代方案是否会有所作为。

如果 A0、Z1、A1、Z2、A2 的大小都相同,那么理想的解决方案就是最多只占用内存中两个变量的大小。

有没有一种pythonic的方法可以在计算左侧之后删除右侧使用的所有变量,或者有一些类似的效果?或者,您是否可以创建一个执行删除和分配的函数(鉴于范围限制)以及那里的效率权衡是什么?

【问题讨论】:

  • 这可能会有所帮助。 stackoverflow.com/questions/26545051/…
  • 假设A0 的内容仍然被layer() 中的任何函数引用,那么好的变量名的唯一额外成本是对对象的本地引用。坚持可读的代码,如果你真的有问题,只担心额外的复杂性。

标签: python python-3.x neural-network deep-learning


【解决方案1】:

理想情况下,解决方案最多只占用内存中两个变量的大小。

del 在 Python 中并不经常需要。重复是代码的味道。不要重复自己(DRY 原则)。您可以使用循环删除重复。

def forward(self, A):
    for _ in range(2):
        Z = layer(A)
        A = activation(Z)
    return A

您将重复使用AZ 这两个变量。

您可以通过嵌套调用进一步压缩它,这将完全删除 Z

def forward(self, A):
    for _ in range(2):
        A = activation(layer(A))
    return A

如果您倾向于使用功能,这种模式称为“减少”(有时也称为“折叠”)。这可能不那么“Pythonic”,但函数式风格在 Python 代码中仍然很常用。

from functools import reduce

def forward(self, X):
    return reduce(lambda A, _: activation(layer(A)), range(2), X)

甚至,

def forward(self, X):
    return reduce(lambda x, f: f(x), [layer, activation]*2, X)

流行的toolz 库也实现了这种模式

from toolz.functoolz import thread_first

def forward(self, X):
    return thread_first(X, layer, activation, layer, activation)

不需要中间变量,但如果它让你感觉更好,你可以添加 cmet。

def forward(self, X):
    return thread_first(
        X,  # A0
        layer, # Z1
        activation, # A1
        layer,  # Z2
        activation,  # A2
    )

这些不是强制执行的。


其实变量根本就不需要,只是参数而已。

def forward(self, X):
    return activate(layer(activate(layer(X))))

函数真的这么简单,变量名的大惊小怪似乎过于复杂了。

对于只有两层,这可能没问题,但循环/减少版本通过更新 range() 参数(甚至可以是 .forward() 方法的另一个参数)可以更轻松地添加更多层。


您能否创建一个执行删除和分配的函数(给定范围限制)以及那里的效率权衡是什么?

除了del(或超出范围时),您不能真正删除本地人。但是,您可以创建自己的命名空间,而不是本地人。这是由 dict 支持的,它的效率仅比本地人略低,在这里还不够重要。

from types import SimpleNamespace

class MonoNamespace(SimpleNamespace):
    """A namespace that holds only one attribute at a time."""
    def __setattr__(self, name, value):
        vars(self).clear()
        vars(self)[name] = value

def forward(self, X):
    ns = MonoNamespace(A0=X)
    ns.Z1 = layer(ns.A0)
    ns.A1 = activation(ns.Z1)
    ns.Z2 = layer(ns.A1)
    ns.A2 = activation(ns.Z2)
    return ns.A2

【讨论】:

  • 最后一个例子是一个完整的反模式——属性不应该进行昂贵的计算——而前面的大多数都无助于使代码更易于阅读、跟踪和维护。实际上,第一个 sn-p 无疑是最具可读性和可维护性的(因此也是最 Pythonic)。
  • @brunodesthuilliers 这只是语法,但在正常情况下我并不完全不同意。我在谈论什么是可能的,而不是什么是传统的。 @property 的例子不是很相关,我已经删除了它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-29
  • 2020-06-29
  • 1970-01-01
  • 2023-01-17
  • 2016-04-24
  • 2018-06-29
  • 2020-04-12
相关资源
最近更新 更多