【问题标题】:How to make a generator callable?如何使生成器可调用?
【发布时间】:2020-06-04 11:47:50
【问题描述】:

我正在尝试从具有 784 位长行的 CSV 文件创建数据集。这是我的代码:

import tensorflow as tf

f = open("test.csv", "r")
csvreader = csv.reader(f)
gen = (row for row in csvreader)
ds = tf.data.Dataset()
ds.from_generator(gen, [tf.uint8]*28**2)

我收到以下错误:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-22-4b244ea66c1d> in <module>()
     12 gen = (row for row in csvreader_pat_trn)
     13 ds = tf.data.Dataset()
---> 14 ds.from_generator(gen, [tf.uint8]*28**2)

~/Documents/Programming/ANN/labs/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py in from_generator(generator, output_types, output_shapes)
    317     """
    318     if not callable(generator):
--> 319       raise TypeError("`generator` must be callable.")
    320     if output_shapes is None:
    321       output_shapes = nest.map_structure(

TypeError: `generator` must be callable.

docs 说我应该有一个生成器传递给from_generator(),所以我就是这样做的,gen 是一个生成器。但现在它抱怨我的生成器不可可调用。如何使生成器可调用,以便让它工作?

编辑: 我想补充一点,我使用的是 python 3.6.4。这是错误的原因吗?

【问题讨论】:

    标签: python


    【解决方案1】:

    generator 参数(可能令人困惑)实际上不应是生成器,而是返回可迭代的可调用对象(例如,生成器函数)。这里最简单的选择可能是使用lambda。此外,还有几个错误:1) tf.data.Dataset.from_generator 是作为类工厂方法调用的,而不是从实例调用的 2) 函数(就像 TensorFlow 中的其他一些函数一样)对参数非常挑剔,它希望你将 dtype 序列和每个数据行指定为 tuples(而不是 CSV 阅读器返回的 lists),您可以使用例如 map

    import csv
    import tensorflow as tf
    
    with open("test.csv", "r") as f:
        csvreader = csv.reader(f)
        ds = tf.data.Dataset.from_generator(lambda: map(tuple, csvreader),
                                            (tf.uint8,) * (28 ** 2))
    

    【讨论】:

    • 感谢您简单明了的解释
    【解决方案2】:

    糟糕,两年后……但是,嘿!另一个解决方案! :D

    这可能不是最干净的答案,但对于更复杂的生成器,您可以使用装饰器。我制作了一个生成两个字典的生成器,例如:

    >>> train,val = dataloader("path/to/dataset")
    >>> x,y = next(train)
    >>> print(x)
    {"data": [...], "filename": "image.png"}
    
    >>> print(y)
    {"category": "Dog", "category_id": 1, "background": "park"}
    

    当我尝试使用 from_generator 时,它给了我错误:

    >>> ds_tf = tf.data.Dataset.from_generator(
        iter(mm),
        ({"data":tf.float32, "filename":tf.string},
        {"category":tf.string, "category_id":tf.int32, "background":tf.string})
        )
    TypeError: `generator` must be callable.
    

    但后来我写了一个装饰函数

    >>> def make_gen_callable(_gen):
            def gen():
                for x,y in _gen:
                     yield x,y
            return gen
    >>> train_ = make_gen_callable(train)
    
    >>> train_ds = tf.data.Dataset.from_generator(
        train_,
        ({"data":tf.float32, "filename":tf.string},
        {"category":tf.string, "category_id":tf.int32, "background":tf.string})
        )
    
    >>> for x,y in train_ds:
            break
    
    >>> print(x)
    {'data': <tf.Tensor: shape=(320, 480), dtype=float32, ... >,
     'filename': <tf.Tensor: shape=(), dtype=string, ...> 
    }
    
    >>> print(y)
    {'category': <tf.Tensor: shape=(), dtype=string, numpy=b'Dog'>,
     'category_id': <tf.Tensor: shape=(), dtype=int32, numpy=1>,
     'background': <tf.Tensor: shape=(), dtype=string, numpy=b'Living Room'>
    }
    

    但是现在,请注意,为了迭代 train_,必须调用它

    >>> for x,y in train_():
            do_stuff(x,y)
            ...
    

    【讨论】:

    • def make_gen_callable(_gen):... 部件确实有帮助。关键是在make_gen_callable() 中不应将任何参数传递给gen()。采用此方法后,我不需要制作 2 个不同的可调用生成器。
    • 您可以使用yield from _gen 而不是from x,y in _gen: yield x,y(这通常是“通过”生成器的首选方式,因为它在for 循环上具有several advantages)。但是,在这种情况下,您可以直接返回包装好的生成器,因此即使像 train_ = lambda: train 这样的东西也应该可以工作。
    【解决方案3】:

    From the docs,你链接的:

    generator 参数必须是一个可调用对象,该对象返回一个 支持iter() 协议的对象(例如生成器函数)

    这意味着您应该能够执行以下操作:

    import tensorflow as tf
    import csv
    
    with open("test.csv", "r") as f:
        csvreader = csv.reader(f)
        gen = lambda: (row for row in csvreader)
        ds = tf.data.Dataset()
        ds.from_generator(gen, [tf.uint8]*28**2)
    

    换句话说,你传递的函数在调用时必须产生一个生成器。将其设为匿名函数(lambda)时,这很容易实现。

    或者试试这个,它更接近文档中的完成方式:

    import tensorflow as tf
    import csv
    
    
    def read_csv(file_name="test.csv"):
        with open(file_name) as f:
            reader = csv.reader(f)
            for row in reader:
                yield row
    
    ds = tf.data.Dataset.from_generator(read_csv, [tf.uint8]*28**2)
    

    (如果你需要一个不同于你设置的默认文件名,你可以使用functools.partial(read_csv, file_name="whatever.csv")。)

    区别在于read_csv函数调用时返回的是生成器对象,而你构造的已经是生成器对象,相当于做:

    gen = read_csv()
    ds = tf.data.Dataset.from_generator(gen, [tf.uint8]*28**2)  # does not work
    

    【讨论】:

    • 真的吗?文档中的示例是带有 yield 的普通旧生成器函数,而不是 returns 生成器的函数,不是吗?
    • 哦,是的,你是对的,克里斯。 gen 是文档中的生成器,而不是返回生成器的函数。
    • @Chris_Rands 是的,这很奇怪。所以显然定义为函数的生成器也可以工作。
    • @Chris_Rands 我认为不同之处在于,如果您调用该函数(它是一个生成器),它会返回生成器对象。而(x for x in ...) 已经是生成器对象本身。
    • 是的,我理解其中的区别,但老实说,我认为 tensor 的部分名称应该更清晰,例如 from_generator_function
    猜你喜欢
    • 1970-01-01
    • 2015-02-19
    • 2015-08-30
    • 1970-01-01
    • 2014-11-23
    • 2022-10-19
    • 1970-01-01
    • 2012-08-17
    • 2011-08-18
    相关资源
    最近更新 更多