【问题标题】:How to Define Custom Theano Types Allowing Differentiation如何定义允许区分的自定义 Theano 类型
【发布时间】:2017-01-25 17:53:34
【问题描述】:

我正在尝试按照 Theano 中的 create a double type 的文档,并按照 here 的描述实现此类型的操作。当前状态如下:

import theano

class Double(theano.gof.Type):

    def filter(self, value, strict = False, allow_downcast = None):
        if strict:
            # we need to return a type, but if the value is incompatible raise an exception
            if isinstance(value, float):
                return value
            else:
                raise TypeError('Expected a float!')
        elif allow_downcast:
            return float(value)
        else:
            value_float = float(value)
            if value_float == value:
                return value_float
            else:
                raise TypeError('The double type cannot be accurately represent %s of type %s' % (value, type(value)))

    def values_eq_approx(self, value_a, value_b, tolerance = 1e-6):
        return abs(value_a - value_b) / (abs(value_a) + abs(value_b)) < tolerance

double = Double()

class DoubleAddOp(theano.Op):

    __props__ = ()

    def make_node(self, x, y):
        # check input types
        if isinstance(x, (int, float)):
            x = theano.gof.Constant(double, x)
        if isinstance(y, (int, float)):
            y = theano.gof.Constant(double, y)

        if x.type != double or y.type != double:
            raise TypeError('DoubleAddOp only works on doubles.')

        return theano.gof.Apply(self, [x, y], [double()])

    def perform(self, node, inputs, output_storage):
        x = inputs[0]
        y = inputs[1]
        z = output_storage[0]
        z[0] = x + y

    def infer_shape(self, node, input_shapes):
        return [input_shapes[0]]

    def grad(self, inputs, output_grads):
        return [output_grads[0]*1, output_grads[0]*1]

    def __str__(self):
        return 'DoubleAddOp'

dadd = DoubleAddOp()

为了测试代码,我写了几个单元测试:

import theano
import random
import unittest
from double import double, dadd

class TestDoubleOps(unittest.TestCase):

    # the forward pass runs fine ...
    def test_DoubleAddOpPerform(self):
        x = double('x')
        y = double('y')
        z = dadd(x, y)
        f = theano.function([x, y], z)

        for i in range(100):
            x_value = random.random()
            y_value = random.random()
            self.assertAlmostEqual(f(x_value, y_value), x_value + y_value)

    # I am trying to get the gradient computation working here,
    # this is what I have so far:
    def test_DoubleAddOpGrad(self):
        x = double('x')
        y = double('y')
        z = dadd(x, y)
        gx = theano.tensor.grad(z, x) # <---
        gy = theano.tensor.grad(z, y)
        f = theano.function([x, y], [gx, gy])

        for i in range(100):
            x_value = random.random()
            y_value = random.random()

            print(f(x_value, y_value))

if __name__ == '__main__':
    unittest.main()

但是,在测试梯度计算时,我在标记线处收到以下错误:

Traceback (most recent call last):
  File "~/theano/double-type-python/double_test.py", line 32, in test_DoubleAddOpGrad
    gx = theano.tensor.grad(z, x)
  File "~/.local/lib/python3.5/site-packages/theano/gradient.py", line 436, in grad
    if cost is not None and cost.ndim != 0:
AttributeError: 'Variable' object has no attribute 'ndim'

看来这是上面定义的double类型的问题。但是,类型本身是比例,所以我应该能够使用theano.tensor.grad 计算梯度。不幸的是,我找不到演示自定义类型的梯度计算的示例,也无法了解有关ndim 属性的更多信息...

感谢任何帮助;谢谢!

更新。当试图欺骗theano.tensor.grad,例如通过显式设置z.ndim = 0,问题继续存在,例如

Traceback (most recent call last):
  File "~/theano/double-type-python/double_test.py", line 33, in test_DoubleAddOpGrad
    gx = theano.tensor.grad(z, x)
  File "/usr/local/lib/python3.4/dist-packages/theano/gradient.py", line 477, in grad
    g_cost = _float_ones_like(cost)
  File "/usr/local/lib/python3.4/dist-packages/theano/gradient.py", line 1340, in _float_ones_like
    dtype = x.type.dtype
AttributeError: 'Double' object has no attribute 'dtype'

因此,我似乎在这里遗漏了一些基本的东西,并且定义的 Double 类型遗漏了文档中未提及的几个不同的特定于类型的信息。

更新。 重新阅读文档并查看 Theano 的源代码后,正确的问题是:是否可以在 Theano 中定义允许区分的自定义(非张量)类型?

更新。根据 nouiz 的回答,我遇到了下一个问题 - 这些给我的印象是梯度计算不适用于非 TensorType 类型:

Traceback (most recent call last):
  File "~/theano/double-type-python/double_test.py", line 32, in test_DoubleAddOpGrad
    gx = theano.tensor.grad(z, x)
  File "~/.local/lib/python3.5/site-packages/theano/gradient.py", line 477, in grad
    g_cost = _float_ones_like(cost)
  File "~/.local/lib/python3.5/site-packages/theano/gradient.py", line 1344, in _float_ones_like
    return tensor.ones_like(x, dtype=dtype)
  File "~/.local/lib/python3.5/site-packages/theano/tensor/basic.py", line 2377, in ones_like
    return fill(model, ret)
  File "~/.local/lib/python3.5/site-packages/theano/gof/op.py", line 604, in __call__
    node = self.make_node(*inputs, **kwargs)
  File "~/.local/lib/python3.5/site-packages/theano/tensor/elemwise.py", line 577, in make_node
    inputs = list(map(as_tensor_variable, inputs))
  File "~/.local/lib/python3.5/site-packages/theano/tensor/basic.py", line 171, in as_tensor_variable
    "Variable type field must be a TensorType.", x, x.type)
theano.tensor.var.AsTensorError: ('Variable type field must be a TensorType.', DoubleAddOp.0, <double.Double object at 0x7fb623a5b9b0>)

【问题讨论】:

    标签: python theano


    【解决方案1】:

    答案是肯定的。你可以。我们自己为稀疏变量和 GPU 变量执行此操作。

    但是您遇到了不支持 theano.grad() 的极端情况。大多数情况下,它需要一个 ndim 参数和一个 dtype 参数。添加 dtype="float64" 参数应该可以解决这个问题。

    ndim 很容易通过这个差异在 Theano 中修复:

    diff --git a/theano/gradient.py b/theano/gradient.py
    index 6d6fbaf..3b4d706 100644
    --- a/theano/gradient.py
    +++ b/theano/gradient.py
    @@ -433,7 +433,7 @@ def grad(cost, wrt, consider_constant=None,
                              "cost is NaN because " +
                              cost.type.why_null)
    
    -    if cost is not None and cost.ndim != 0:
    +    if cost is not None and getattr(cost, 'ndim', 0) != 0:
             raise TypeError("cost must be a scalar.")
    
         if isinstance(wrt, set):
    

    对于 dtype,它更复杂,因为我们在很多地方都使用它来进行验证(你不能拿整数的 grad 为例)以及初始化梯度链(或者你可以通过known_grad 参数)

    更新:这个更大的差异可以修复新错误:

    diff --git a/theano/gradient.py b/theano/gradient.py
    index 6d6fbaf..6a9ec03 100644
    --- a/theano/gradient.py
    +++ b/theano/gradient.py
    @@ -433,7 +433,7 @@ def grad(cost, wrt, consider_constant=None,
                              "cost is NaN because " +
                              cost.type.why_null)
    
    -    if cost is not None and cost.ndim != 0:
    +    if cost is not None and getattr(cost, 'ndim', 0) != 0:
             raise TypeError("cost must be a scalar.")
    
         if isinstance(wrt, set):
    @@ -1341,7 +1341,7 @@ def _float_ones_like(x):
         if dtype not in tensor.float_dtypes:
             dtype = theano.config.floatX
    
    -    return tensor.ones_like(x, dtype=dtype)
    +    return x.ones_like(dtype=dtype)
    
    
     class numeric_grad(object):
    diff --git a/theano/tensor/var.py b/theano/tensor/var.py
    index 2ecb9f0..6b08a45 100644
    --- a/theano/tensor/var.py
    +++ b/theano/tensor/var.py
    @@ -727,6 +727,9 @@ class _tensor_py_operators(object):
         def zeros_like(model, dtype=None):
             return theano.tensor.basic.zeros_like(model, dtype=dtype)
    
    +    def ones_like(model, dtype=None):
    +        return theano.tensor.basic.ones_like(model, dtype=dtype)
    +
         def cumsum(self, axis=None):
             return theano.tensor.extra_ops.cumsum(self, axis)
    

    您需要将 one_like 方法添加到您的变量中,如下所示: def my_ones_like(模型,dtype=None): 返回 ... double.ones_like = my_ones_like

    【讨论】:

    • 感谢您的澄清。所以我应用了上面的修复并添加了“dtype='float64'”,它可以解决上面提到的两个问题。但是,我不认为梯度计算旨在在 as_tensor_variable 调用中使用非 TensorType 类型。我将使用相应的堆栈跟踪更新我的问题。您是否还可以提供一个链接到您对稀疏变量执行类似操作的实现(如您所提到的)?谢谢!
    • 我忘了,对于稀疏矩阵,它只有在成本是张量时才有效。这可以通过我添加到答案中的更大差异来解决。您还需要将方法 one_like() 添加到您的类中。我对此也给出了指示。通过实现一个新的变量子类会更好,但我没有找到关于它的文档。
    • 所以在我看来,新的差异只有在我从 theano.tensor.TensorType 派生双精度类型时才有意义。但是当从 theano.tensor.TensorType 派生时,我不需要差异,因为它也可以没有。但是,当我遇到问题时,例如实现乘法梯度运算return [inputs[1]*output_grads[0], inputs[0]*output_grads[0]],因为乘法似乎会导致问题。总的来说,这似乎不是一条很有成效的道路——也许我会从 Sparse Tensor 代码中学到一些东西,或者不使用 Theano。不过非常感谢您的帮助!
    • 我们没有很好地实现这种情况,但我认为它不难解决。从长远来看,使用 theano 并拥有我们已经提供的许多功能可能会更容易。从 TensorType 继承并不是一个好主意。我们从未从 Types 继承,您可能会遇到一些细微的错误。你能创建一个新类型,将代码放在 github repo/gist 中,然后通过电子邮件发送给 Theano-dev 吗?这将是一个更好的地方来帮助实现这一点。另一个更安全的解决方法是创建从新类型转换为 TensorType 的操作,并确保成本是 TensorVariable。
    猜你喜欢
    • 1970-01-01
    • 2012-01-13
    • 2015-12-13
    • 2022-10-03
    • 1970-01-01
    • 1970-01-01
    • 2019-06-07
    • 1970-01-01
    • 2011-10-10
    相关资源
    最近更新 更多