替换 TensorFlow v2 的占位符

29

为了我的项目,我需要将一个有向图转换成一个tensorflow实现的图,就好像它是一个神经网络一样。在tensorflow版本1中,我可以将所有输入定义为占位符,然后使用广度优先搜索生成输出的数据流图。然后,我只需使用feed_dict输入我的输入数据。但是,在TensorFlow v2.0中,他们决定完全放弃占位符。

如果不使用占位符,我该如何为每个图创建一个tf.function,它接受可变数量的输入并返回可变数量的输出?

我想生成一个这样的tf.function,它适用于任意无环有向图,以便在生成图之后利用tensorflow GPU支持连续运行几千次图前馈。


编辑代码示例:

我的图被定义为一个字典。每个键表示一个节点,并具有另一个字典的相应值,该字典指定带权重的传入和传出链接。

{
    "A": {
        "incoming": [("B", 2), ("C", -1)],
        "outgoing": [("D", 3)]
    }
}

为了简洁起见,我省略了B、C和D的条目。以下是我在tensorflow v1.0中构建所需代码的方式,其中inputs只是一个严格输入到图形中的键值列表。

def construct_graph(graph_dict, inputs, outputs):
    queue = inputs[:]
    make_dict = {}
    for key, val in graph_dict.items():
        if key in inputs:
            make_dict[key] = tf.placeholder(tf.float32, name=key)
        else:
            make_dict[key] = None
    # Breadth-First search of graph starting from inputs
    while len(queue) != 0:
        cur = graph_dict[queue[0]]
        for outg in cur["outgoing"]:
            if make_dict[outg[0]]: # If discovered node, do add/multiply operation
                make_dict[outg[0]] = tf.add(make_dict[outg[0]], tf.multiply(outg[1], make_dict[queue[0]]))
            else: # If undiscovered node, input is just coming in multiplied and add outgoing to queue
                make_dict[outg[0]] = tf.multiply(make_dict[queue[0]], outg[1])
                for outgo in graph_dict[outg[0]]["outgoing"]:
                    queue.append(outgo[0])
        queue.pop(0)
    # Returns one data graph for each output
    return [make_dict[x] for x in outputs]

如果我提供一个feed_dict,那么我就可以多次运行这些输出,因为它们只是带有占位符的图形。

显然,在TensorFlow v2.0中,这不是预期的方式,因为他们似乎强烈反对在这个新版本中使用占位符。

关键是,我只需要对一个图进行一次预处理,因为它返回一个与graph_dict定义无关的datagraph。


1
如果没有你尝试做什么的示例,很难找出解决方案。你能举一个“接受可变数量的输入并返回可变数量的输出”的函数的例子吗?这个例子在TF 2.0 tf.function中无法转换吗? - jdehesa
@jdehesa 代码示例已添加。 - Em Eldar
@OmerEldar,inputsoutputs里面有什么? - thushv89
如果您能够包含一个示例图形,例如graph_dictinputsoutputs的结果,那将非常有帮助。 - thushv89
1个回答

52

让你的代码在TF 2.0中发挥作用

以下是一个示例代码,可以在TF 2.0中使用。 它依赖于兼容性API, 该API可作为tensorflow.compat.v1访问,并需要禁用v2行为。 如果它不能按照您的预期运行,请向我们提供更多说明以便更好地帮助您。

import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()

@tf.function
def construct_graph(graph_dict, inputs, outputs):
    queue = inputs[:]
    make_dict = {}
    for key, val in graph_dict.items():
        if key in inputs:
            make_dict[key] = tf.placeholder(tf.float32, name=key)
        else:
            make_dict[key] = None
    # Breadth-First search of graph starting from inputs
    while len(queue) != 0:
        cur = graph_dict[queue[0]]
        for outg in cur["outgoing"]:
            if make_dict[outg[0]]: # If discovered node, do add/multiply operation
                make_dict[outg[0]] = tf.add(make_dict[outg[0]], tf.multiply(outg[1], make_dict[queue[0]]))
            else: # If undiscovered node, input is just coming in multiplied and add outgoing to queue
                make_dict[outg[0]] = tf.multiply(make_dict[queue[0]], outg[1])
                for outgo in graph_dict[outg[0]]["outgoing"]:
                    queue.append(outgo[0])
        queue.pop(0)
    # Returns one data graph for each output
    return [make_dict[x] for x in outputs]

def main():
    graph_def = {
        "B": {
            "incoming": [],
            "outgoing": [("A", 1.0)]
        },
        "C": {
            "incoming": [],
            "outgoing": [("A", 1.0)]
        },
        "A": {
            "incoming": [("B", 2.0), ("C", -1.0)],
            "outgoing": [("D", 3.0)]
        },
        "D": {
            "incoming": [("A", 2.0)],
            "outgoing": []
        }
    }
    outputs = construct_graph(graph_def, ["B", "C"], ["A"])
    print(outputs)

if __name__ == "__main__":
    main()
[<tf.Tensor 'PartitionedCall:0' shape=<unknown> dtype=float32>]

将代码迁移到TF 2.0

虽然上面的代码片段是有效的,但它仍然与TF 1.0相关联。 要将其迁移到TF 2.0,您需要稍微重构一下代码。

我建议您不要返回可调用的张量列表,而是返回一个keras.layers.Model列表。

下面是一个有效的示例:

import tensorflow as tf

def construct_graph(graph_dict, inputs, outputs):
    queue = inputs[:]
    make_dict = {}
    for key, val in graph_dict.items():
        if key in inputs:
            # Use keras.Input instead of placeholders
            make_dict[key] = tf.keras.Input(name=key, shape=(), dtype=tf.dtypes.float32)
        else:
            make_dict[key] = None
    # Breadth-First search of graph starting from inputs
    while len(queue) != 0:
        cur = graph_dict[queue[0]]
        for outg in cur["outgoing"]:
            if make_dict[outg[0]] is not None: # If discovered node, do add/multiply operation
                make_dict[outg[0]] = tf.keras.layers.add([
                    make_dict[outg[0]],
                    tf.keras.layers.multiply(
                        [[outg[1]], make_dict[queue[0]]],
                    )],
                )
            else: # If undiscovered node, input is just coming in multiplied and add outgoing to queue
                make_dict[outg[0]] = tf.keras.layers.multiply(
                    [make_dict[queue[0]], [outg[1]]]
                )
                for outgo in graph_dict[outg[0]]["outgoing"]:
                    queue.append(outgo[0])
        queue.pop(0)
    # Returns one data graph for each output
    model_inputs = [make_dict[key] for key in inputs]
    model_outputs = [make_dict[key] for key in outputs]
    return [tf.keras.Model(inputs=model_inputs, outputs=o) for o in model_outputs]

def main():
    graph_def = {
        "B": {
            "incoming": [],
            "outgoing": [("A", 1.0)]
        },
        "C": {
            "incoming": [],
            "outgoing": [("A", 1.0)]
        },
        "A": {
            "incoming": [("B", 2.0), ("C", -1.0)],
            "outgoing": [("D", 3.0)]
        },
        "D": {
            "incoming": [("A", 2.0)],
            "outgoing": []
        }
    }
    outputs = construct_graph(graph_def, ["B", "C"], ["A"])
    print("Builded models:", outputs)
    for o in outputs:
        o.summary(120)
        print("Output:", o((1.0, 1.0)))

if __name__ == "__main__":
    main()

需要注意什么?

  • placeholder更改为keras.Input,需要设置输入的形状。
  • 使用keras.layers.[add|multiply]进行计算。这可能不是必需的,但要坚持一个接口。然而,它需要在列表中包装因子(以处理批处理)
  • 构建keras.Model并返回
  • 使用值元组调用模型(不再是字典)

以下是代码的输出。

Builded models: [<tensorflow.python.keras.engine.training.Model object at 0x7fa0b49f0f50>]
Model: "model"
________________________________________________________________________________________________________________________
Layer (type)                           Output Shape               Param #       Connected to                            
========================================================================================================================
B (InputLayer)                         [(None,)]                  0                                                     
________________________________________________________________________________________________________________________
C (InputLayer)                         [(None,)]                  0                                                     
________________________________________________________________________________________________________________________
tf_op_layer_mul (TensorFlowOpLayer)    [(None,)]                  0             B[0][0]                                 
________________________________________________________________________________________________________________________
tf_op_layer_mul_1 (TensorFlowOpLayer)  [(None,)]                  0             C[0][0]                                 
________________________________________________________________________________________________________________________
add (Add)                              (None,)                    0             tf_op_layer_mul[0][0]                   
                                                                                tf_op_layer_mul_1[0][0]                 
========================================================================================================================
Total params: 0
Trainable params: 0
Non-trainable params: 0
________________________________________________________________________________________________________________________
Output: tf.Tensor([2.], shape=(1,), dtype=float32)

2
你的迁移解决方案正是我正在寻找的,我不知道keras api在模型构建方面有如此好的定义。非常棒的回答。 - Em Eldar
@Alexis,目前脚本出现以下错误: ValueError: 应该在输入列表上调用合并层。 - Hirak Sarkar
@Hirak 这个在TF 2.0版本中是有效的。自那时以来,可能有一些API发生了变化。仅凭这一行错误很难说出问题出在哪里,但我认为它与迁移过程无关。 - AlexisBRENON
@AlexisBRENON,感谢您的回复。这是带有错误的ipynb链接:https://colab.research.google.com/drive/1ZmM_SMftYbn0lQP0uJhXOZR2l1Ly0VhP?usp=sharing - Hirak Sarkar
@AlexisBRENON,仅供参考,如果其他人在进行此更正时遇到相同的错误:将outg[1]更改为K.constant([outg[1]])即可解决该错误。 - Hirak Sarkar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接