Skip to main content
 首页 » 编程设计

python之在 TensorFlow 中创建图形时确定张量形状

2024年09月07日30lvdongjie

我正在尝试编写一大块可重用代码来读取一个张量的形状,然后使用生成的对象来定义其他张量的形状。我可以选择使用 tf.shape(tensor) 读取张量的动态形状,或者使用 tensor.get_shape() 读取张量的静态形状。玩具示例如下所示(使用两种不同的策略):

def my_function_strategy_1(x, y): 
    x_shape = tf.shape(x) 
    a = tf.reshape(y, x_shape) 
    b = tf.zeros(x_shape) 
    num_x_values = x_shape[0] 
    c = tf.reshape(y, [num_x_values, 4]) 
    d = tf.zeros([num_x_values, 4]) 
    return a, b, c, d 
 
def my_function_strategy_2(x, y): 
    x_shape = x.get_shape() 
    a = tf.reshape(y, x_shape) 
    b = tf.zeros(x_shape) 
    num_x_values = x_shape[0] 
    c = tf.reshape(y, [num_x_values, 4]) 
    d = tf.zeros([num_x_values, 4]) 
    return a, b, c, d 

我想在不同的图表中使用这段代码。有时输入张量的形状是已知的,有时是未知的:

graph_A = tf.Graph() 
with graph_A.as_default(): 
    x = tf.placeholder(tf.float32, [2, 4]) 
    y = tf.placeholder(tf.float32, [8]) 
    a, b, c, d = my_function(x, y) 
 
with graph_B.as_default(): 
    x = tf.placeholder(tf.float32) 
    y = tf.placeholder(tf.float32) 
    a, b, c, d = my_function(x, y) 

我想要的行为是:(A) 当输入张量的形状已知时(如 graph_A 中),我希望 TensorFlow 在图创建时计算图中的所有形状(因此它可以有效地分配资源等),以及 (B) 当输入张量的形状未知时(如 graph_B 中),我希望 TensorFlow 等到运行时计算所有图中的形状。

函数的strategy_1 版本几乎 就是这样做的。它实现了 (B),但并没有完全实现 (A),因为 TensorFlow 留下了一些未知的张量形状。例如,在上面的玩具示例中,a、b 和 c 的形状是在图形创建时计算的,但是形状d 是未知的(即使 d 使用非常相似的操作)。您可以通过打印 a.get_shape()、b.get_shape() 等来检查这一点。

相反,函数的 strategy_2 版本实现了图中所有张量的 (A),但没有实现 (B),因为 TensorFlow(可以理解)在尝试使用时抛出异常输入张量的(未知)静态形状以塑造其他张量。

有没有办法在一个函数中同时实现 (A) 和 (B)? strategy_1 版本如何/为什么适用于图中的大多数张量,但不是全部?

请您参考如下方法:

您可以仔细挑选您知 Prop 有“两全其美”结果的形状元素:

def my_get_shape(tensor): 
    if tensor.shape.ndims is None: 
        # Fully dynamic 
        return tf.shape(tensor) 
    if tensor.shape.is_fully_defined(): 
        # Fully static 
        return tensor.shape 
    # Partially static 
    dyn_shape = tf.shape(tensor) 
    shape = [] 
    for i, d in enumerate(tensor.shape): 
        shape.append(d.value if d.value is not None else dyn_shape[i]) 
    return shape 
 
def my_function(x, y): 
    x_shape = my_get_shape(x)  # Or just tf.shape(x)! - see edit 
    a = tf.reshape(y, x_shape) 
    b = tf.zeros(x_shape) 
    num_x_values = x_shape[0] 
    c = tf.reshape(y, [num_x_values, 4]) 
    d = tf.zeros([num_x_values, 4]) 
    return a, b, c, d 
 
# Fully static 
with tf.Graph().as_default(): 
    x = tf.placeholder(tf.float32, [2, 4]) 
    y = tf.placeholder(tf.float32, [8]) 
    a, b, c, d = my_function(x, y) 
print('a:', a.shape, ', b:', b.shape, ', c:', c.shape, ', d:', d.shape) 
# a: (2, 4) , b: (2, 4) , c: (2, 4) , d: (2, 4) 
 
# Fully dynamic 
with tf.Graph().as_default(): 
    x = tf.placeholder(tf.float32) 
    y = tf.placeholder(tf.float32) 
    a, b, c, d = my_function(x, y) 
print('a:', a.shape, ', b:', b.shape, ', c:', c.shape, ', d:', d.shape) 
# a: <unknown> , b: <unknown> , c: (?, 4) , d: (?, 4) 
 
# Partially static 
with tf.Graph().as_default(): 
    x = tf.placeholder(tf.float32, [None, 4]) 
    y = tf.placeholder(tf.float32) 
    a, b, c, d = my_function(x, y) 
print('a:', a.shape, ', b:', b.shape, ', c:', c.shape, ', d:', d.shape) 
# a: (?, 4) , b: (?, 4) , c: (?, 4) , d: (?, 4) 

编辑:

实际上,将前面代码段中的 my_get_shape 替换为 tf.shape 效果完全相同。似乎 tf.shape 应该是默认值(注意不要用它填充图表)除非您明确希望保持未定义的维度。

我调查了一下,我无法完全解决整个问题。我不知道这是否有用,但这里有一些我发现的东西。显然,TensorFlow 在 C++ 级别(似乎以前在 Python 中使用过,但现在不再使用了)具有“形状推断”机制。例如,如果您查看 tensorflow/core/ops/array_ops.cc ) 你会看到每个操作声明在末尾都包含一个 .SetShapeFn,这是一个使用 InferenceContext 的函数尝试猜测操作的输出形状。除其他外,此类可以检查张量中的值是否已知,例如当给定张量为静态时对于 tf.shape 或对于 tf.fill(以及相关的 tf.ones)具有已知值。 shape inference algorithm的resolution是Python中设置为tensor shape的,可以通过call_cpp_shape_fn直接调用(虽然没看出有什么用) :

from tensorflow.python.framework.common_shapes import call_cpp_shape_fn 
with tf.Graph().as_default(): 
    print(call_cpp_shape_fn(tf.reshape(tf.placeholder(tf.float32), tf.fill([2], 3)).op)) 
    # Shows this: 
    # { 
    #   'shapes': [dim { size: 3 } dim { size: 3 }], 
    #   'handle_data': [None], 
    #   'inputs_needed': b'\x12\x01\x01' 
    # } 
    print(call_cpp_shape_fn(tf.reshape(tf.placeholder(tf.float32), (2 * tf.fill([2], 3))).op)) 
    # Shows this: 
    # { 
    #   'shapes': [dim { size: -1 } dim { size: -1 }], 
    #   'handle_data': [None], 
    #   'inputs_needed': b'\x12\x01\x01' 
    # } 

您可以看到,虽然正确检查了 tf.fill([2], 3),但 TensorFlow 没有计算出 2 * tf.fill([2], 3) 是 [6, 6],大概是因为静态跟踪乘法等操作被认为过于昂贵,即使操作数是已知常量也是如此。

我还没有发现的是操作在哪里声明它们的值可以静态知道,或者这些值在哪里/如何被准确地检索到。例如,对于 tf.shape,它似乎能够专门挑选已知值并将其余值保留为未定义。