TF1/TF2 如何工作?还有他们的区别
TF1
TF1 遵循称为“定义然后运行”的执行风格。这与“按运行定义”相反,后者是 Python 执行风格。但是,这是什么意思?定义然后运行意味着,仅仅因为您调用/定义了某些内容,它就不会被执行。您必须明确执行您定义的内容。
TF有这样一个概念Graph。首先,定义所需的所有计算(例如,神经网络的所有层计算、损失计算和最小化损失的优化器 - 这些表示为ops or 运营)。定义计算/数据流图后,您可以使用Session。让我们看一个简单的实际例子。
# Graph generation
tf_a = tf.placeholder(dtype=tf.float32)
tf_b = tf.placeholder(dtype=tf.float32)
tf_c = tf.add(tf_a, tf.math.multiply(tf_b, 2.0))
# Execution
with tf.Session() as sess:
c = sess.run(tf_c, feed_dict={tf_a: 5.0, tf_b: 2.0})
print(c)
计算图(也称为数据流图)如下所示。
tf_a tf_b tf.constant(2.0)
\ \ /
\ tf.math.multiply
\ /
tf.add
|
tf_c
Analogy: 想想你正在做蛋糕。您从互联网上下载食谱。然后你就开始按照步骤实际制作蛋糕。食谱就是图表,制作蛋糕的过程就是会话所做的事情(即图表的执行)。
TF2
TF2 遵循立即执行风格或按运行定义。你调用/定义一些东西,它就会被执行。让我们看一个例子。
a = tf.constant(5.0)
b = tf.constant(3.0)
c = tf_a + (tf_b * 2.0)
print(c.numpy())
哇!与 TF1 示例相比,它看起来非常干净。一切看起来都很Pythonic。
Analogy:现在想象一下您正在一个亲自动手制作蛋糕的工作坊中。您正在按照教练的说明制作蛋糕。讲师会立即解释每一步的结果是什么。因此,与前面的示例不同,您不必等到烤蛋糕才知道是否正确(这是指您无法调试代码)。但你会得到关于你的表现的即时反馈(你知道这意味着什么)。
这是否意味着 TF2 不构建图表?惊恐发作
嗯,是的,也不是。 TF2 有两个你应该了解的功能急于执行 https://www.tensorflow.org/guide/eager and 签名 https://www.tensorflow.org/guide/function功能。
Tip:确切地说,TF1 也具有急切执行功能(默认情况下关闭),并且可以使用以下命令启用tf.enable_eager_execution()
。 TF2 默认情况下启用 eager_execution。
热切的执行力
Eager Execution 可以立即执行Tensor
s and Operation
s。这就是您在 TF2 示例中观察到的情况。但另一方面是它不构建图表。因此,例如,您使用急切执行来实现和运行神经网络,它会非常慢(因为神经网络一遍又一遍地执行非常重复的任务(前向计算 - 损失计算 - 后向传递))。
签名
这就是 AutoGraph 功能可以发挥作用的地方。 AutoGraph 是 TF2 中我最喜欢的功能之一。它的作用是,如果您在函数中执行“TensorFlow”操作,它会分析该函数并为您构建图表(心碎)。例如,您执行以下操作。 TensorFlow 构建图表。
@tf.function
def do_silly_computation(x, y):
a = tf.constant(x)
b = tf.constant(y)
c = tf_a + (tf_b * 2.0)
return c
print(do_silly_computation(5.0, 3.0).numpy())
因此,您需要做的就是定义一个函数,它接受必要的输入并返回正确的输出。最重要的是添加@tf.function
装饰器,因为它是 TensorFlow AutoGraph 分析给定函数的触发器。
Warning:AutoGraph 不是灵丹妙药,不要天真地使用。有各种局限性 https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/autograph/g3doc/reference/limitations.mdAutoGraph 也是如此。
TF1 和 TF2 之间的区别
- TF1 需要一个
tf.Session()
对象来执行图形,而 TF2 则不会
- 在 TF1 中,Python GC 不收集未引用的变量,但在 TF2 中,它们是
- TF1 不会促进代码模块化,因为您需要在开始计算之前定义完整的图。然而,使用 AutoGraph 功能代码模块化是受到鼓励的
TF1 和 TF2 中有哪些不同的数据类型?
您已经了解了许多主要数据类型。但您可能对他们的工作和行为方式有疑问。嗯,本节就是关于这些的。
TF1 数据类型/数据结构
tf.placeholder
:这就是向计算图提供输入的方式。顾名思义,它没有附加值。相反,您在运行时提供一个值。tf_a
and tf_b
就是这些例子。将其视为一个空盒子。根据需要,您可以在其中填充水/沙子/毛茸茸的泰迪熊。
tf.Variable
:这是您用来定义神经网络参数的内容。与占位符不同,变量是用某个值初始化的。但它们的价值也会随着时间而改变。这就是反向传播过程中神经网络参数发生的情况。
tf.Operation
:操作是可以对占位符、张量和变量执行的各种转换。例如tf.add()
and tf.mul()
是操作。这些操作返回一个张量(大多数时候)。如果您想要不返回张量的操作的证明,请检查this https://www.tensorflow.org/api_docs/python/tf/no_op out.
tf.Tensor
:这类似于变量,因为它有一个初始值。然而,一旦定义了它们,它们的值就不能改变(即它们是不可变的)。例如,tf_c
在前面的例子中是一个tf.Tensor
.
TF2 数据类型/数据结构
tf.Variable
tf.Tensor
tf.Operation
就行为而言,从 TF1 到 TF2 的数据类型没有太大变化。唯一的主要区别是,tf.placeholders
消失了。您还可以查看数据类型的完整列表 https://www.tensorflow.org/api_docs/python/tf/dtypes/DType.
Keras 是什么?它如何适应所有这些?
Keras 曾经是一个独立的库,提供主要用于深度学习模型的组件(例如层和模型)的高级实现。但从 TensorFlow 的更高版本开始,Keras 就集成到了 TensorFlow 中。
正如我所解释的,Keras 隐藏了许多不必要的复杂问题,如果您要使用裸机 TensorFlow,则必须处理这些问题。 Keras 提供了两个主要功能Layer
物体和Model
实现神经网络的对象。 Keras 还具有两个最常见的模型 API,可让您开发模型:顺序API和函数式API。让我们通过一个简单的示例来看看 Keras 和 TensorFlow 有什么不同。让我们构建一个简单的 CNN。
Tip:Keras 可以让您更轻松地实现使用 TF 所能实现的目标。但 Keras 还提供了 TF 中尚不强大的功能(例如文本处理 https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/text/Tokenizer能力)。
height=64
width = 64
n_channels = 3
n_outputs = 10
Keras(顺序 API)示例
model = Sequential()
model.add(Conv2D(filters=32, kernel_size=(2,2),
activation='relu',input_shape=(height, width, n_channels)))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(filters=64, kernel_size=(2,2), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Flatten())
model.add(Dense(n_outputs, activation='softmax'))
model.compile(loss='binary_crossentropy', optimizer='adam')
model.summary()
Pros
直接实现简单模型
Cons
不能用于实现复杂模型(例如具有多个输入的模型)
Keras(函数式 API)示例
inp = Input(shape=(height, width, n_channels))
out = Conv2D(filters=32, kernel_size=(2,2), activation='relu',input_shape=(height, width, n_channels))(inp)
out = MaxPooling2D(pool_size=(2,2))(out)
out = Conv2D(filters=64, kernel_size=(2,2), activation='relu')(out)
out = MaxPooling2D(pool_size=(2,2))(out)
out = Flatten()(out)
out = Dense(n_outputs, activation='softmax')(out)
model = Model(inputs=inp, outputs=out)
model.compile(loss='binary_crossentropy', optimizer='adam')
model.summary()
Pros
可用于实现涉及多个输入和输出的复杂模型
Cons
需要很好地理解输入输出的形状以及每层的预期输入是什么
TF1 示例
# Input
tf_in = tf.placeholder(shape=[None, height, width, n_channels], dtype=tf.float32)
# 1st conv and max pool
conv1 = tf.Variable(tf.initializers.glorot_uniform()([2,2,3,32]))
tf_out = tf.nn.conv2d(tf_in, filters=conv1, strides=[1,1,1,1], padding='SAME') # 64,64
tf_out = tf.nn.max_pool2d(tf_out, ksize=[2,2], strides=[1,2,2,1], padding='SAME') # 32,32
# 2nd conv and max pool
conv2 = tf.Variable(tf.initializers.glorot_uniform()([2,2,32,64]))
tf_out = tf.nn.conv2d(tf_out, filters=conv2, strides=[1,1,1,1], padding='SAME') # 32, 32
tf_out = tf.nn.max_pool2d(tf_out, ksize=[2,2], strides=[1,2,2,1], padding='SAME') # 16, 16
tf_out = tf.reshape(tf_out, [-1, 16*16*64])
# Dense layer
dense = conv1 = tf.Variable(tf.initializers.glorot_uniform()([16*16*64, n_outputs]))
tf_out = tf.matmul(tf_out, dense)
Pros
非常适合涉及非典型操作的前沿研究(例如动态改变层的大小)
Cons
可读性差
注意事项和陷阱
这里我将列出一些使用 TF 时需要注意的事项(来自我的经验)。
TF1 - 忘记提供所有依赖占位符来计算结果
tf_a = tf.placeholder(dtype=tf.float32)
tf_b = tf.placeholder(dtype=tf.float32)
tf_c = tf.add(tf_a, tf.math.multiply(tf_b, 2.0))
with tf.Session() as sess:
c = sess.run(tf_c, feed_dict={tf_a: 5.0})
print(c)
InvalidArgumentError:您必须使用 dtype float 为占位符张量“Placeholder_8”提供值
[[节点 Placeholder_8 (定义于 /usr/local/lib/python3.6/dist-packages/tensorflow_core/python/framework/ops.py:1748)]]
您在这里收到错误的原因是因为您没有向tf_b
。因此,请确保您将价值观提供给all用于计算结果的依赖占位符。
TF1 - 对数据类型要非常小心
tf_a = tf.placeholder(dtype=tf.int32)
tf_b = tf.placeholder(dtype=tf.float32)
tf_c = tf.add(tf_a, tf.math.multiply(tf_b, 2.0))
with tf.Session() as sess:
c = sess.run(tf_c, feed_dict={tf_a: 5, tf_b: 2.0})
print(c)
类型错误:“Add”Op 的输入“y”的类型为 float32,与参数“x”的 int32 类型不匹配。
你能发现错误吗?这是因为在将数据传递给操作时必须匹配数据类型。否则,使用tf.cast()
将数据类型转换为兼容数据类型的操作。
Keras - 了解每层期望的输入形状
model = Sequential()
model.add(Conv2D(filters=32, kernel_size=(2,2),
activation='relu',input_shape=(height, width)))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(filters=64, kernel_size=(2,2), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Flatten())
model.add(Dense(n_outputs, activation='softmax'))
model.compile(loss='binary_crossentropy', optimizer='adam')
ValueError:层 conv2d_8 的输入 0 与该层不兼容:预期 ndim=4,发现 ndim=3。收到的完整形状:[无、64、64]
在这里,您定义了输入形状[None, height, width]
(当您添加批次维度时)。但Conv2D
需要 4D 输入[None, height, width, n_channels]
。因此你会得到上面的错误。一些常见的被误解/容易出错的层是,
-
Conv2D
层 - 需要 4D 输入[None, height, width, n_channels]
。要更详细地了解卷积层/操作,请查看此内容answer https://stackoverflow.com/questions/42883547/intuitive-understanding-of-1d-2d-and-3d-convolutions-in-convolutional-neural-n/59099278#59099278
-
LSTM
层 - 需要 3D 输入[None, timesteps, n_dim]
-
ConvLSTM2D
层 - 需要 5D 输入[None, timesteps, height, width, n_channels]
-
Concatenate
层 - 除轴外,所有其他维度上连接的数据需要相同
Keras - 期间输入错误的输入/输出形状fit()
height=64
width = 64
n_channels = 3
n_outputs = 10
Xtrain = np.random.normal(size=(500, height, width, 1))
Ytrain = np.random.choice([0,1], size=(500, n_outputs))
# Build the model
# fit network
model.fit(Xtrain, Ytrain, epochs=10, batch_size=32, verbose=0)
ValueError:检查输入时出错:预期 conv2d_9_input 的形状为 (64, 64, 3),但得到的数组的形状为 (64, 64, 1)
你应该知道这个。我们正在输入形状的输入[batch size, height, width, 1]
当我们应该喂食时[batch size, height, width, 3]
input.
TF1 和 TF2 之间的性能差异
这已经在讨论了here https://stackoverflow.com/questions/58441514/why-is-tensorflow-2-much-slower-than-tensorflow-1。所以我不会重复里面的内容。
我希望可以谈论但不能谈论的事情
我留下了一些进一步阅读的链接。
- tf.data.Dataset https://www.tensorflow.org/api_docs/python/tf/data/Dataset
- tf.RaggedTensor https://www.tensorflow.org/guide/ragged_tensor