tf.dataset,多路径输入,每批映射加载图片

tf.dataset, multiple path inputs, and mapping per batch to load images

我正在加载包含多个输入图像的数据集。输入图像路径应该只在批处理时解码,以便处理大型数据集。

数据集是N个图片路径输入,M个浮点输出。每个输入的图像具有不同的分辨率。

Data is ([img_input_1.png, img_input_2.png, ...], [0.65, 0.7, 0.8])

该模型在符号模式下使用 Keras 函数 api。

这是最近编辑的代码

from itertools import zip_longest

def read_image(path, shape):
    try:
        image = tf.io.read_file(path)
        image = tf.image.decode_png(image)
        image = tf.image.resize(image, [shape[1],shape[2]])
        image /= 255.0
        return image
    except:
        print('ERROR: preprocess_image: bad path', path)    

def load_image(x, y, shp):
    pout = [(k, x[k]) for k in x.keys()]
    l1   = tf.convert_to_tensor(list(x))
    l2   = tf.convert_to_tensor(list(x.values()))

    pl = tf.map_fn(
        lambda args: (read_image(args[0], shp), args[1]), [l1, l2], dtype=(tf.float32, tf.float32)
    )
    pl = {path: (pl[0][i], pl[1][i]) for i, path in enumerate(x)}
    return (pl,y)

def dataset_prep(json_data, seq, batch_size):
    # LOAD DATA FROM JSON
    x,y = json_parse_x_y(json_data[seq])
    xx  = [*zip_longest(*x)] # NOTE: goes from variable sized input to {'input_N':...}
    yy  = [*zip_longest(*y)]

    # GET SHAPES (hard coded atm)
    lns = [[len(xxx)] for xxx in xx]
    rzs = [[24,512,1],[96,512,1]] # TEMP TODO! grab grom [(v['h'],v['w'],v['c']) for v in xx]
    shp = [*zip_longest(*[lns,rzs])]
    shp = [list(s) for s in shp]
    shp = [[*itertools.chain.from_iterable(s)] for s in shp]

    xd  = dict([[ "input_{}".format(i+1),np.array(y)] for i,y in [*enumerate(xx)]])
    yd  = dict([["output_{}".format(i+1),np.array(y)] for i,y in [*enumerate(yy)]])

    ds  = tf.data.Dataset.from_tensor_slices((xd, yd))

    ds  = ds.shuffle(10000)
    ds  = ds.repeat()
    ds  = ds.map(map_func=lambda x,y: load_image(x, y, shp), num_parallel_calls=AUTOTUNE)
    ds  = ds.batch(batch_size) if batch_size else ds 
    ds  = ds.prefetch(AUTOTUNE) 
    return ds

这是我遇到的错误:

Traceback (most recent call last):
    File "/home/me/.local/bin/wavfeat", line 11, in <module>
        load_entry_point('wavfeat==0.1.0', 'console_scripts', 'wavfeat')()
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/__main__.py", line 91, in main
        analysis_batch_sql(obj)
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/analysis_run_csv.py", line 50, in analysis_batch_sql
        qy = [*map(lambda c: run_elm(c[0], c[1]), ch)]
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/analysis_run_csv.py", line 50, in <lambda>
        qy = [*map(lambda c: run_elm(c[0], c[1]), ch)]
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/analysis_run_csv.py", line 23, in run_elm
        out = fn(input, elm)
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_onset.py", line 196, in one_sec_onset_train
        return train(input, elm)
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_onset.py", line 182, in train
        ts = dataset_prep(jd, 'train', bc)
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_onset.py", line 123, in dataset_prep
        ds  = ds.map(map_func=lambda x,y: load_image(x, y, shp), num_parallel_calls=AUTOTUNE)
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 1146, in map
        self, map_func, num_parallel_calls, preserve_cardinality=True)
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 3264, in __init__
        use_legacy_function=use_legacy_function)
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 2591, in __init__
        self._function = wrapper_fn._get_concrete_function_internal()
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1366, in _get_concrete_function_internal
        *args, **kwargs)
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1360, in _get_concrete_function_internal_garbage_collected
        graph_function, _, _ = self._maybe_define_function(args, kwargs)
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1648, in _maybe_define_function
        graph_function = self._create_graph_function(args, kwargs)
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1541, in _create_graph_function
        capture_by_value=self._capture_by_value),
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/framework/func_graph.py", line 716, in func_graph_from_py_func
        func_outputs = python_func(*func_args, **func_kwargs)
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 2585, in wrapper_fn
        ret = _wrapper_helper(*args)
    File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 2530, in _wrapper_helper
        ret = func(*nested_args)
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_onset.py", line 123, in <lambda>
        ds  = ds.map(map_func=lambda x,y: load_image(x, y, shp), num_parallel_calls=AUTOTUNE)
    File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_data_loader.py", line 91, in load_image
        print("x['input_1'].values(): ", x['input_1'].values())
AttributeError: 'Tensor' object has no attribute 'values'

我在做什么阻止加载路径?

编辑:

在尝试 pandrey 的修复时,我遇到了输入错误。这是 from_tensor_slices 和 ds.map 之前以及之后的数据:

pre_from_tensor_slices x:  {'input_1': array(['/media/me/sp_data/sp_data/datasets/chr_01/one_sec_onset_11_oac-leg/7388_39216_30--id=7388__sql_table=oac_1__sql_idx=405167__pitch=30__onset=39216.png',
       '/media/me/sp_data/sp_data/datasets/chr_01/one_sec_onset_11_oac-leg/2447_864_27--id=2447__sql_table=oac_1__sql_idx=415458__pitch=27__onset=864.png',
       '/media/me/sp_data/sp_data/datasets/chr_01/one_sec_onset_11_oac-leg/2386_20208_38--id=2386__sql_table=oac_1__sql_idx=433248__pitch=38__onset=20208.png',
       ...,
       '/media/me/sp_data/sp_data/datasets/chr_01/one_sec_onset_11_oac-leg/6261_24528_57--id=6261__sql_table=oac_1__sql_idx=449753__pitch=57__onset=24528.png',
       '/media/me/sp_data/sp_data/datasets/chr_01/one_sec_onset_11_oac-leg/3727_22944_31--id=3727__sql_table=oac_1__sql_idx=407620__pitch=31__onset=22944.png',
       '/media/me/sp_data/sp_data/datasets/chr_01/one_sec_onset_11_oac-leg/1668_7056_60--id=1668__sql_table=oac_1__sql_idx=381152__pitch=60__onset=7056.png'],
      dtype='<U162'), 'input_2': array(['/media/me/sp_data/sp_data/datasets/mel_01/one_sec_onset_11_oac-leg/7388_39216_30--id=7388__sql_table=oac_1__sql_idx=405167__pitch=30__onset=39216.png',
       '/media/me/sp_data/sp_data/datasets/mel_01/one_sec_onset_11_oac-leg/2447_864_27--id=2447__sql_table=oac_1__sql_idx=415458__pitch=27__onset=864.png',
       '/media/me/sp_data/sp_data/datasets/mel_01/one_sec_onset_11_oac-leg/2386_20208_38--id=2386__sql_table=oac_1__sql_idx=433248__pitch=38__onset=20208.png',
       ...,
       '/media/me/sp_data/sp_data/datasets/mel_01/one_sec_onset_11_oac-leg/6261_24528_57--id=6261__sql_table=oac_1__sql_idx=449753__pitch=57__onset=24528.png',
       '/media/me/sp_data/sp_data/datasets/mel_01/one_sec_onset_11_oac-leg/3727_22944_31--id=3727__sql_table=oac_1__sql_idx=407620__pitch=31__onset=22944.png',
       '/media/me/sp_data/sp_data/datasets/mel_01/one_sec_onset_11_oac-leg/1668_7056_60--id=1668__sql_table=oac_1__sql_idx=381152__pitch=60__onset=7056.png'],
      dtype='<U162')}
pre_from_tensor_slices y:  {'output_1': array([0.817, 0.018, 0.421, ..., 0.511, 0.478, 0.147])}
_________________________
y:  {'output_1': <tf.Tensor 'args_2:0' shape=() dtype=float64>}
x:  {'input_1': <tf.Tensor 'args_0:0' shape=() dtype=string>, 'input_2': <tf.Tensor 'args_1:0' shape=() dtype=string>}
x.values():  dict_values([<tf.Tensor 'args_0:0' shape=() dtype=string>, <tf.Tensor 'args_1:0' shape=() dtype=string>])
x['input_1']:  Tensor("args_0:0", shape=(), dtype=string)

运行 x['input_1'].values() 抛出错误:'Tensor' object has no attribute 'values'

我在 map_fn

附近收到一个错误
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/framework/constant_op.py", line 284, in _constant_impl
    allow_broadcast=allow_broadcast))
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/framework/tensor_util.py", line 455, in make_tensor_proto
    raise ValueError("None values not supported.")
ValueError: None values not supported.

编辑 2

尝试最新的我得到以下错误

Traceback (most recent call last):
  File "/home/me/.local/bin/wavfeat", line 11, in <module>
    load_entry_point('wavfeat==0.1.0', 'console_scripts', 'wavfeat')()
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/__main__.py", line 91, in main
    analysis_batch_sql(obj)
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/analysis_run_csv.py", line 50, in analysis_batch_sql
    qy = [*map(lambda c: run_elm(c[0], c[1]), ch)]
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/analysis_run_csv.py", line 50, in <lambda>
    qy = [*map(lambda c: run_elm(c[0], c[1]), ch)]
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/analysis_run_csv.py", line 23, in run_elm
    out = fn(input, elm)
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_onset.py", line 216, in one_sec_onset_train
    return train(input, elm)
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_onset.py", line 203, in train
    vs = validation_prep(jd, 'validation', bc)
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_onset.py", line 176, in validation_prep
    ds  = ds.map(map_func=load_and_preprocess_from_path_label, num_parallel_calls=AUTOTUNE)
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 1146, in map
    self, map_func, num_parallel_calls, preserve_cardinality=True)
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 3264, in __init__
    use_legacy_function=use_legacy_function)
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 2591, in __init__
    self._function = wrapper_fn._get_concrete_function_internal()
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1366, in _get_concrete_function_internal
    *args, **kwargs)
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1360, in _get_concrete_function_internal_garbage_collected
    graph_function, _, _ = self._maybe_define_function(args, kwargs)
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1648, in _maybe_define_function
    graph_function = self._create_graph_function(args, kwargs)
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/eager/function.py", line 1541, in _create_graph_function
    capture_by_value=self._capture_by_value),
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/framework/func_graph.py", line 716, in func_graph_from_py_func
    func_outputs = python_func(*func_args, **func_kwargs)
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 2585, in wrapper_fn
    ret = _wrapper_helper(*args)
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/data/ops/dataset_ops.py", line 2530, in _wrapper_helper
    ret = func(*nested_args)
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_data_loader.py", line 47, in load_and_preprocess_from_path_label
    pl   = dict([(pk, tf.map_fn(load_and_preprocess_image, po, dtype=tf.float32)) for pk,po in pout])
  File "/home/me/.local/lib/python3.6/site-packages/wavfeat/one_sec_data_loader.py", line 47, in <listcomp>
    pl   = dict([(pk, tf.map_fn(load_and_preprocess_image, po, dtype=tf.float32)) for pk,po in pout])
  File "/home/me/.local/lib/python3.6/site-packages/tensorflow/python/ops/map_fn.py", line 214, in map_fn
    raise ValueError("elems must be a 1+ dimensional Tensor, not a scalar")
ValueError: elems must be a 1+ dimensional Tensor, not a scalar

附加组件:不使用字典结构

这是一个完整的代码(除了定义 json_parse_x_y 和声明 AUTOTUNE),无需使用字典结构即可实现您正在尝试的目标。

我测试过 make_dataset 有效(见下面的示例),所以如果您遇到问题,应该是由于 load_tensors.

的规范错误
from itertools import zip_longest

import tensorflow as tf

# additionnally, `json_parse_x_y` must be defined
# and `AUTOTUNE` must be declared (in my example, I set it to 2)


def read_image(path, shape):
    """Read an image of givent filepath and tensor shape.

    Return a float tensor of given shape.
    """
    try:
        image = tf.io.read_file(path)
        image = tf.image.decode_png(image)
        image = tf.image.resize(image, [shape[1], shape[2]])
        image /= 255.0
        return image
    except:
        raise FileNotFoundError("preprocess_image: bad path '%s'" % path)


def load_images(paths, shapes):
    """Load an ensemble of images (associated with a single sample).

    paths  : rank-1 string Tensor
    shapes : list of images' shapes (same length as `paths`)

    Return a tuple of float tensors containing the loaded images.
    """
    return tuple((
        read_image(paths[i], shapes[i])
        for i in range(len(shapes))
    ))


def load_tensors(json_data, seq):
    """Load images descriptors from a json dump.

    Return a tuple containing:
        * a rank-2 tensor containing lists of image paths (str)
        * a rank-2 tensor containing resolution values (float)
        * a list of image shapes, of same length as the rank-2
          tensor's second axis
    """
    x,y = json_parse_x_y(json_data[seq])
    xx  = [*zip_longest(*x)] # NOTE: goes from variable sized input to {'input_N':...}
    yy  = [*zip_longest(*y)]

    # GET SHAPES (hard coded atm)
    lns = [[len(xxx)] for xxx in xx]
    rzs = [[24,512,1],[96,512,1]] # TEMP TODO! grab grom [(v['h'],v['w'],v['c']) for v in xx]
    shp = [*zip_longest(*[lns,rzs])]
    shp = [list(s) for s in shp]
    shp = [[*itertools.chain.from_iterable(s)] for s in shp]
    return (xx, yy, shp)


def make_dataset(xx, yy, shp, batch_size):
    """Build a Dataset instance containing loaded images.

    xx, yy, shp : see the specification of `load_tensors`'s outputs
    batch_size  : batch size to set on the Dataset

    Return a Dataset instance where each batched sample is a tuple
    containing two elements: first, a tuple containing N loaded images'
    rank-3 tensors; second, a rank-1 tensor containing M float values.
    (to be clear: batching adds a dimension to all those tensors)
    """
    data = tf.data.Dataset.from_tensor_slices((xx, yy))
    data = data.shuffle(10000)
    data = data.map(lambda x, y: (load_images(x, shapes), y))
    data = data.repeat()
    data = data.batch(batch_size) if batch_size else data
    data = data.prefetch(AUTOTUNE) 
    return data


def dataset_prep(json_data, seq, batch_size):
    """Full pipeline to making a Dataset from json."""
    xx, yy, shapes = load_tensors(json_data, seq)
    return make_dataset(xx, yy, shapes)

例如,使用“手工制作”值;所有图像实际上都是 this classic image,形状为 [512, 512, 3]。


import numpy as np
import tensorflow as tf

# import previous code

# Here, N = 2, and I make 2 samples.
x = tf.convert_to_tensor(np.array([
    ['image_1a.png', 'image_1b.png'],
    ['image_2a.png', 'image_2b.png']
]))
shapes = [[1, 512, 512], [1, 512, 512]]  # images are initially [512, 512, 3]
# Here, M = 3, and I make 2 samples. Values are purely random.
y = tf.convert_to_tensor(np.array([
    [.087, .92, .276],
    [.242, .37, .205]
]))

# This should work.
data = make_dataset(x, y, shapes, batch_size=1)
# Output signature is <PrefetchDataset shapes:
#     (((None, 512, 512, None), (None, 512, 512, None)), (None, 3)),
#     types: ((tf.float32, tf.float32), tf.float64)
# >
# Where the first None is actually `batch_size`
# and the second is, in this case, 3.

当前问题的答案:

好的,您现在遇到的问题是修改后的load_image函数不符合数据集的规范,因此引发了异常。请在下面找到一个似乎有效的完整编辑代码(我 运行 在我的计算机上使用自定义图像进行测试,xd / yd dict 初始化为看起来像你报告的 x 和 y -数据集张量)。它不漂亮,我个人建议放弃 dict 结构,但它有效:

from itertools import zip_longest

def read_image(path, shape):
    try:
        image = tf.io.read_file(path)
        image = tf.image.decode_png(image)
        image = tf.image.resize(image, [shape[1],shape[2]])
        image /= 255.0
        return image
    except:
        raise FileNotFoundError("preprocess_image: bad path '%s'" % path)

# CHANGED: load_image is actually useless

def dataset_prep(json_data, seq, batch_size):
    # LOAD DATA FROM JSON
    x,y = json_parse_x_y(json_data[seq])
    xx  = [*zip_longest(*x)] # NOTE: goes from variable sized input to {'input_N':...}
    yy  = [*zip_longest(*y)]

    # GET SHAPES (hard coded atm)
    lns = [[len(xxx)] for xxx in xx]
    rzs = [[24,512,1],[96,512,1]] # TEMP TODO! grab grom [(v['h'],v['w'],v['c']) for v in xx]
    shp = [*zip_longest(*[lns,rzs])]
    shp = [list(s) for s in shp]
    shp = [[*itertools.chain.from_iterable(s)] for s in shp]

    xd  = dict([[ "input_{}".format(i+1),np.array(y)] for i,y in [*enumerate(xx)]])
    yd  = dict([["output_{}".format(i+1),np.array(y)] for i,y in [*enumerate(yy)]])

    ds  = tf.data.Dataset.from_tensor_slices((xd, yd))
    ds  = ds.shuffle(10000)

    # CHANGED: the following line, to run images import (also moved epeat instruction later)
    ds  = ds.map(
        lambda x, y: (
            {key: read_image(path, shp[i]) for i, (key, path) in enumerate(x.items())},
            y
        ),
        num_parallel_calls=AUTOTUNE
    )
    ds  = ds.repeat()
    ds  = ds.batch(batch_size) if batch_size else ds 
    ds  = ds.prefetch(AUTOTUNE) 
    return ds

初始答案(问题编辑前):

我只会处理此答案中 load_image 提出的异常,但可能还有其他工作要做 - 我没有对此进行测试,因为手头没有方便的数据集。

异常消息实际上非常明确:您正在传递标量元素(例如 n in [(k, tf.map_fn(lambda x: read_image(x, shp), n, dtype=tf.float32)) for k,n in pout])作为elems参数tf.map_fn,当它需要张量(或(可能嵌套的)张量列表或元组)时,如其文档中明确指定的那样。

您还在引用的代码行中以错误的方式使用了 tf.map_fn,因为基本上您将它与 python 意图列表混在一起,而您应该使用其中一个或另一个.

带有意图列表(也替换了 load_image 函数之前无用的行): pl = {path: (load_image(path, shp), res) for path, res in x.items()}

tf.map_fn:

# Read all images, return two tensors, one with images, the other with resolutions.
# (so, resolutions inclusion in this is actually useless and should be redesigned)
pl = tf.map_fn(
    lambda args: (read_image(args[0], shp), args[1]),
    [tf.convert_to_tensor(list(x)), tf.convert_to_tensor(list(x.values()))],
    dtype=(tf.float32, tf.float32)
)
# If you really, really want to return a dict, but is it an optimal design?
pl = {path: (pl[0][i], pl[1][i]) for i, path in enumerate(x)}

我不知道返回以这种方式指定的 dict 是否与 Dataset 实例化是最佳的(甚至兼容),但是如果您的其余代码正常工作,这应该可以解决问题。

无论如何,如果您想遍历一个字典,请继续使用第一个版本或第二个版本的修改版本(这可能具有并行图像读取的优势)。

希望对您有所帮助:-)