这个问题的目的是提供一个最低限度的指导,以使某人熟悉TensorFlow 1和TensorFlow2。我觉得这是一个很长的篇幅,因为TensorFlow进行了重大修订,并且在不断发展。快速的步伐。在这篇文章的结尾,您应该对TF如何执行代码以及开发深度学习模型时需要注意的一些常见问题感到满意。
之所以这样做,是因为TensorFlow的发展非常迅速,对于一个新的入门者来说可能不堪重负。即使您对此感到满意,每隔一段时间返回并刷新您的知识也没有什么害处。
这是给你的,
不幸的是,这对您来说不,
供我参考时
在这里,我将尽力解决以下重复出现的问题。
TF1 / TF2如何工作?及其差异
TF1和TF2中有哪些不同的数据类型?
什么是Keras,它如何适合所有这些?
陷阱
TF1和TF2之间的性能差异
答案 0 :(得分:4)
TF1遵循一种称为define-then-run的执行方式。这与按运行方式定义相反,后者是Python执行风格。但是,这是什么意思?定义然后运行意味着,仅因为您调用/定义了某项而未执行。您必须明确执行定义的内容。
TF具有 Graph 的概念。首先,定义所需的所有计算(例如,神经网络的所有层计算,损失计算和使损失最小化的优化器-这些表示为 ops 或 operations )。定义计算/数据流图之后,您可以使用 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
类比:想想你在做蛋糕。您可以从互联网上下载配方。然后,您开始执行实际制作蛋糕的步骤。配方就是图表,制作蛋糕的过程就是会话执行的操作(即图表的执行)。
TF2遵循立即执行样式或按运行定义。您调用/定义一些东西,它就被执行了。让我们来看一个例子。
a = tf.constant(5.0)
b = tf.constant(3.0)
c = tf_a + (tf_b * 2.0)
print(c.numpy())
哇!与TF1示例相比,它看起来是如此干净。一切看起来都是Pythonic。
类比分析:现在,您认为您正在动手制作蛋糕。您正在按照老师的说明做蛋糕。然后讲师立即解释每个步骤的结果。因此,与前面的示例不同,您不必等到烤蛋糕就看是否做对了(这是无法调试代码的事实的参考)。但是您会得到有关您的表现的即时反馈(您知道这意味着什么)。
是的,不是的。您应该了解TF2中的两个功能,这些功能包括eager execution和AutoGraph函数。
提示:确切地说,TF1也渴望执行(默认情况下为关闭),可以使用
tf.enable_eager_execution()
启用它。 TF2默认情况下启用eager_execution。
急切的执行可以立即执行Tensor
和Operation
。这就是您在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分析给定功能的触发器。
警告:AutoGraph不是灵丹妙药,不能天真地使用。 AutoGraph也有各种limitations。
tf.Session()
对象来执行图形,而TF2则不需要您已经看到许多主要数据类型。但是您可能对它们的功能以及行为方式有疑问。好了,本节就是关于这一点的。
tf.placeholder
:这是您向计算图提供输入的方式。顾名思义,它没有附加值。而是在运行时输入值。 tf_a
和tf_b
是这些示例。将此视为一个空盒子。您可以根据需要将水/沙子/蓬松的泰迪熊装满。
tf.Variable
:这是用于定义神经网络的参数的工具。与占位符不同,变量使用一些值初始化。但是它们的价值也可以随着时间而改变。这就是反向传播过程中神经网络的参数发生的情况。
tf.Operation
:操作是可以在占位符,张量和变量上执行的各种转换。例如tf.add()
和tf.mul()
是运算符。这些操作(大多数情况下)返回张量。如果要证明操作不返回张量,请检查this。
tf.Tensor
:从某种意义上说,它类似于变量,具有初始值。但是,一旦定义它们,它们的值就无法更改(即它们是不可变的)。例如,上一个示例中的tf_c
是tf.Tensor
。
tf.Variable
tf.Tensor
tf.Operation
就行为而言,从TF1到TF2的数据类型没有太大变化。唯一的主要区别是tf.placeholders
不见了。您还可以查看full list of data types。
Keras曾经是一个单独的库,提供主要用于深度学习模型的组件(例如层和模型)的高级实现。但是自从更高版本的TensorFlow以来,Keras已集成到TensorFlow中。
因此,正如我所解释的,如果要使用裸骨TensorFlow,Keras隐藏了许多不必要的复杂处理。 Keras为实现NN提供了两个主要目的:Layer
对象和Model
对象。 Keras还具有两个最常用的模型API,可让您开发模型:顺序API 和功能API 。让我们在一个简单的示例中了解Keras和TensorFlow有何不同。让我们构建一个简单的CNN。
提示:Keras使您可以轻松实现TF所能达到的目标。但是Keras还提供了TF尚不强大的功能(例如text processing功能)。
height=64
width = 64
n_channels = 3
n_outputs = 10
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()
直接实现简单的模型
不能用于实现复杂的模型(例如具有多个输入的模型)
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()
可用于实现涉及多个输入和输出的复杂模型
需要对输入输出的形状以及每一层期望作为输入的内容有很好的了解
# 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)
对于涉及非典型操作(例如动态更改图层大小)的前沿研究非常有用
可读性差
在这里,我将列出使用TF时需要注意的几件事(根据我的经验)。
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
输入值。因此,请确保将值提供给全部依赖占位符以计算结果。
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)
TypeError:“添加”操作的输入“ y”具有类型float32,与参数“ x”的int32类型不匹配。
您能发现错误吗?这是因为在将数据类型传递给操作时必须匹配数据类型。否则,请使用tf.cast()
操作将您的数据类型转换为兼容的数据类型。
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。收到完整的图形:[None,64,64]
在这里,您已经定义了输入形状[None, height, width]
(添加批次尺寸时)。但是Conv2D
需要4D输入[None, height, width, n_channels]
。因此,您会收到上述错误。一些通常被误解/容易出错的层是
Conv2D
层-需要4D输入[None, height, width, n_channels]
。要详细了解卷积层/操作,请查看此answer LSTM
层-需要3D输入[None, timesteps, n_dim]
ConvLSTM2D
层-需要5D输入[None, timesteps, height, width, n_channels]
Concatenate
层-除轴外,在所有其他维度上串联的数据必须相同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]
的输入。
这已经在讨论here中。所以我不会重复其中的内容。
我要留下一些进一步阅读的链接。