在SO上至少还有两个类似的问题,但一个都没有得到回答。
我有一个数据集的形式。
<TensorSliceDataset shapes: ((512,), (512,), (512,), ()), types: (tf.int32, tf.int32, tf.int32, tf.int32)>
和另一个形式的数据集。
<BatchDataset shapes: ((None, 512), (None, 512), (None, 512), (None,)), types: (tf.int32, tf.int32, tf.int32, tf.int32)>
我找了又找,但我找不到将这些数据集保存到文件中的代码,以便以后可以加载。最接近的是 本页在TensorFlow文档中,这建议使用 tf.io.serialize_tensor
然后用 tf.data.experimental.TFRecordWriter
.
然而,当我使用这段代码尝试时。
dataset.map(tf.io.serialize_tensor)
writer = tf.data.experimental.TFRecordWriter('mydata.tfrecord')
writer.write(dataset)
我在第一行得到一个错误:
TypeError: serialize_tensor()需要1到2个位置参数,但有4个参数被给出
如何修改上面的内容(或者做其他的事情)来达到我的目标?
TFRecordWriter
似乎是最方便的选择,但不幸的是它只能写出每个元素只有一个张量的数据集。这里有几个你可以使用的变通方法。首先,由于你所有的张量都具有相同的类型和相似的形状,你可以将它们全部连成一个,并在以后加载时将它们分割回来。
import tensorflow as tf
# Write
a = tf.zeros((100, 512), tf.int32)
ds = tf.data.Dataset.from_tensor_slices((a, a, a, a[:, 0]))
print(ds)
# <TensorSliceDataset shapes: ((512,), (512,), (512,), ()), types: (tf.int32, tf.int32, tf.int32, tf.int32)>
def write_map_fn(x1, x2, x3, x4):
return tf.io.serialize_tensor(tf.concat([x1, x2, x3, tf.expand_dims(x4, -1)], -1))
ds = ds.map(write_map_fn)
writer = tf.data.experimental.TFRecordWriter('mydata.tfrecord')
writer.write(ds)
# Read
def read_map_fn(x):
xp = tf.io.parse_tensor(x, tf.int32)
# Optionally set shape
xp.set_shape([1537]) # Do `xp.set_shape([None, 1537])` if using batches
# Use `x[:, :512], ...` if using batches
return xp[:512], xp[512:1024], xp[1024:1536], xp[-1]
ds = tf.data.TFRecordDataset('mydata.tfrecord').map(read_map_fn)
print(ds)
# <MapDataset shapes: ((512,), (512,), (512,), ()), types: (tf.int32, tf.int32, tf.int32, tf.int32)>
但是,更普遍的是,你可以简单的为每个张量建立一个单独的文件,然后把它们全部读取。
import tensorflow as tf
# Write
a = tf.zeros((100, 512), tf.int32)
ds = tf.data.Dataset.from_tensor_slices((a, a, a, a[:, 0]))
for i, _ in enumerate(ds.element_spec):
ds_i = ds.map(lambda *args: args[i]).map(tf.io.serialize_tensor)
writer = tf.data.experimental.TFRecordWriter(f'mydata.{i}.tfrecord')
writer.write(ds_i)
# Read
NUM_PARTS = 4
parts = []
def read_map_fn(x):
return tf.io.parse_tensor(x, tf.int32)
for i in range(NUM_PARTS):
parts.append(tf.data.TFRecordDataset(f'mydata.{i}.tfrecord').map(read_map_fn))
ds = tf.data.Dataset.zip(tuple(parts))
print(ds)
# <ZipDataset shapes: (<unknown>, <unknown>, <unknown>, <unknown>), types: (tf.int32, tf.int32, tf.int32, tf.int32)>
可以将整个数据集放在一个文件中,每个元素都有多个独立的张量,也就是一个TFRecords文件,其中包括 tf.train.Example
但我不知道是否有办法在TensorFlow中创建这些数据,也就是说,不用把数据从数据集中获取到Python中,然后写到记录文件中。
我也一直在研究这个问题,到目前为止,我已经写了以下的工具(可以找到) 在我的回购中也是)
def cache_with_tf_record(filename: Union[str, pathlib.Path]) -> Callable[[tf.data.Dataset], tf.data.TFRecordDataset]:
"""
Similar to tf.data.Dataset.cache but writes a tf record file instead. Compared to base .cache method, it also insures that the whole
dataset is cached
"""
def _cache(dataset):
if not isinstance(dataset.element_spec, dict):
raise ValueError(f"dataset.element_spec should be a dict but is {type(dataset.element_spec)} instead")
Path(filename).parent.mkdir(parents=True, exist_ok=True)
with tf.io.TFRecordWriter(str(filename)) as writer:
for sample in dataset.map(transform(**{name: tf.io.serialize_tensor for name in dataset.element_spec.keys()})):
writer.write(
tf.train.Example(
features=tf.train.Features(
feature={
key: tf.train.Feature(bytes_list=tf.train.BytesList(value=[value.numpy()]))
for key, value in sample.items()
}
)
).SerializeToString()
)
return (
tf.data.TFRecordDataset(str(filename), num_parallel_reads=tf.data.experimental.AUTOTUNE)
.map(
partial(
tf.io.parse_single_example,
features={name: tf.io.FixedLenFeature((), tf.string) for name in dataset.element_spec.keys()},
),
num_parallel_calls=tf.data.experimental.AUTOTUNE,
)
.map(
transform(
**{name: partial(tf.io.parse_tensor, out_type=spec.dtype) for name, spec in dataset.element_spec.items()}
)
)
.map(
transform(**{name: partial(tf.ensure_shape, shape=spec.shape) for name, spec in dataset.element_spec.items()})
)
)
return _cache
有了这个util,我可以做到。
dataset.apply(cache_with_tf_record("filename")).map(...)
也可以直接加载数据集供以后使用 只需使用该工具的第二部分。
我还在努力,所以以后可能会改变,特别是用正确的类型而不是所有的字节来序列化,以节省空间(我想)。