有关于tf.py_function的一些认识
申明:本博文主要记录了跑网络过程中出现的问题以及解决方法,主要参考了该
文章
。
最近跑神经网络的时候需要用cv2做图像的预处理,但是数据的读入使用了
dataset.map
,如果直接使用cv2.imread(image_path)就会出现如下错误:
TypeError: expected str, bytes or os.PathLike object, not Tensor
因为使用了
dataset.map
,所以这里的image_path其实是
Tensor
的格式。那我们需要做的就是将Tensor转为str或者bytes的格式。在Tensorflow2.0中可以通过tensor.numpy()来获取到tensor内部的值。但是直接使用image_path.numpy()的导致报错:
AttributeError: 'Tensor' object has no attribute 'numpy'
报错内容的意思就是说Tensor没有.numpy()的属性。这里就需要区分一下tf.EagerTensor和tf.Tensor这两种不同的Tensor类型了。
tf.EagerTensor和tf.Tensor
EagerTensor是实时的,可以在任何时候获取到它的值,即通过numpy获取。
Tensor是非实时的,它是静态图中的组件,只有当喂入数据、运算完成才能获得该Tensor的值(即在tf.1x中需要经过session后才能获得Tensor的值)。
需要注意的是,在这里data.map所产生的image_path是Tensor而非EagerTensor。因此我们需要通过tf.py_function来包装自定义的python函数(包装后函数的输入会变为eagertensor)。
tf.py_function(func, inp, Tout, name=None)
func
:自己定义的python函数名称
inp
:自定义函数的参数列表(需要写成列表的形式),[param1、param2、…] 列表的每一个元素是一个Tensor对象。
Tout
:自定义函数的输出类型(列表形式,有几个输出就要申明几个类型,如[tf.float32、tf.int64、…])
def load_and_preprocess_image_cv(image_path):
image_path = image_path.numpy()
image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), 1)
image = (image - 128.0) / 128.0
image = cv2.GaussianBlur(image, (3, 3), 0)
image = cv2.Laplacian(image, -1)
return image
def wrap_function(x):
out = tf.py_function(load_and_preprocess_image_cv, inp=[x], Tout=tf.float32)
return out
这里的
load_and_preprocess_image_cv
就是自定的python函数,
wrap_function
中调用
tf.py_function
进行包装。
另外需要注意的一点:
这样写可能会出现
tf.py_function
的输出为
unknown
的情况。
def get_dataset(lr_path, hr_path, ext):
lr_sorted_paths = get_sorted_image_path(lr_path, ext)
hr_sorted_paths = get_sorted_image_path(hr_path, ext)
# 打包hr和lr的所有地址 并进行shuffle打乱
lr_hr_sorted_paths = list(zip(lr_sorted_paths[:], hr_sorted_paths[:]))
random.shuffle(lr_hr_sorted_paths)
lr_sorted_paths, hr_sorted_paths = zip(*lr_hr_sorted_paths)
# 将hr和lr组合成元组形式
ds = tf.data.Dataset.from_tensor_slices((list(lr_sorted_paths), list(hr_sorted_paths)))
def load_and_preprocess_lr_hr_images(lr_path, hr_path, ext=ext):
lr = load_and_preprocess_image(lr_path, ext)
hr = load_and_preprocess_image(hr_path, ext)
struct = wrap_function(hr_path)
struct.set_shape(hr.shape)
inputs = {"lr_img":lr,"hr_img":hr,"struct_img":struct}
targets = {}
return inputs, targets
lr_hr_ds = ds.map(load_and_preprocess_lr_hr_images, num_parallel_calls=4)
return lr_hr_ds, len(lr_sorted_paths)
输出如下
<RepeatDataset shapes: ({lr_img: (None, None, None, 3), hr_img: (None, None, None, 3), struct_img: <unknown>}, {}), types: ({lr_img: tf.float32, hr_img: tf.float32, struct_img: tf.float32}, {})>
可以发现这里的struct_img的输出为unkown,这是因为在autograph环境中,eager tensor是不可见的(不知道shape,无法访问其内容),如果这个eager tensor跟当前autograph环境中的tensor进行计算,可能会报错。(其实这一块我一直没弄懂,感兴趣的可以参考
这篇文章
)
这个问题困扰了我一整天QAQ,后来的解决办法就是直接给struct指定一个shape。
struct = load_and_preprocess_image_cv(hr_path)
struct.set_shape(hr.shape) //struct中图像的尺寸和hr图像的尺寸是一样的
这样就可以输出形状了
<RepeatDataset shapes: ({lr_img: (None, None, None, 3), hr_img: (None, None, None, 3), struct_img: (None, None, None, 3)}, {}), types: ({lr_img: tf.float32, hr_img: tf.float32, struct_img: tf.float32}, {})>
关于eagertensor的一些认识:
- 在tf2.0里所创建的默认为eagertensor
const = tf.constant(range(6), shape=(2, 3)) # EagerTensor
print(const.numpy())
>>>
<class 'tensorflow.python.framework.ops.EagerTensor'>
[[0 1 2]
[3 4 5]]
- 使用tf.function来修饰函数会将其编译为静态图
@tf.function
def iterate_tensor(tensor):
tf.print(type(tensor)) # EagerTensor
(x1, x2, x3), (x4, x5, x6) = tensor
return tf.stack([x2, x4, x6])
const = tf.constant(range(6), shape=(2, 3)) # EagerTensor
o = iterate_tensor(const)
print(o)
>>>
OperatorNotAllowedInGraphError: iterating over `tf.Tensor` is not allowed: AutoGraph did not convert this function. Try decorating it directly with @tf.function.
这里报错说tf.tensor是不可迭代的,因为经过@tf.function修饰后的函数已经被编译为静态图了,那么对应的eagertensor也变为了tf.tensor。