更新至 2018-9-15 版本
我们推荐使用高级 API tf.keras
构建神经网络,也就是说,大多数 TensorFlow API 都在 eager execution 下可用。
import tensorflow as tf
tfe = tf.contrib.eager
tf.enable_eager_execution()
层:常用的操作集
大多数时候,在为机器学习模型编写代码时,你希望在更高的抽象级别上操作,而不是单个操作以及对单个变量的操作。
许多机器学习模型都可以表示为相对简单的层的组合和堆叠,而 TensorFlow 既提供了一组常见的层,也提供了一种简单的方法,让你可以从头开始编写自己应用程序的层,或者对现有层进行组合。
TensorFlow 在 tf.keras
包中包含了完整的 Keras API,建立自己的模型时,Keras 层十分有用。
# 在 tf.keras.layers 包中, 层是对象。建立层就是建立对象。
# 大多数层的第一个参数是输出维度/通道的数量。
layer = tf.keras.layers.Dense(100)
# 通常不需要输入维度的数量,
# 因为它可以在层第一次使用时推断出来,
# 在一些复杂模型中也可以手动指定。
layer = tf.keras.layers.Dense(10, input_shape=(None, 5))
现有层的完整列表可以在文档中查看到。它包括密集层(全连接层)、Conv2D、LSTM、批归一化、Dropout 等。
# 要使用一个层,只需要调用它
layer(tf.zeros([10, 5]))
<tf.Tensor: id=30, shape=(10, 10), dtype=float32, numpy=
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>
# 层有许多有用的方法。例如,你可以通过调用 layer.variables 检查层中的所有变量。
# 这种情况下,全连接层有权重和偏差变量。
layer.variables
[<tf.Variable 'dense_1/kernel:0' shape=(5, 10) dtype=float32, numpy=
array([[ 0.5491831 , -0.5334953 , -0.33720493, 0.15916592, 0.02661264,
-0.54477704, -0.10383385, -0.3822977 , -0.12068653, 0.07283705],
[-0.6046467 , -0.3235745 , -0.3678471 , 0.42985398, -0.32315367,
0.01954406, -0.5670417 , -0.22975442, 0.410201 , -0.6054503 ],
[-0.21158552, 0.16337538, -0.55872625, -0.4717918 , -0.14633152,
0.4356466 , -0.29068574, -0.365243 , -0.13362256, -0.38409072],
[ 0.33547556, 0.13281083, -0.04601526, -0.42355436, -0.0060991 ,
-0.5032983 , -0.1809687 , -0.22183692, 0.08809811, 0.09949732],
[-0.60682243, -0.3103218 , 0.38421983, -0.5180574 , -0.42757726,
-0.21736482, 0.07201356, -0.254764 , -0.28456295, -0.02735662]],
dtype=float32)>,
<tf.Variable 'dense_1/bias:0' shape=(10,) dtype=float32, numpy=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)>]
# 变量也可以通过存取器访问
layer.kernel, layer.bias
(<tf.Variable 'dense_1/kernel:0' shape=(5, 10) dtype=float32, numpy=
array([[ 0.5491831 , -0.5334953 , -0.33720493, 0.15916592, 0.02661264,
-0.54477704, -0.10383385, -0.3822977 , -0.12068653, 0.07283705],
[-0.6046467 , -0.3235745 , -0.3678471 , 0.42985398, -0.32315367,
0.01954406, -0.5670417 , -0.22975442, 0.410201 , -0.6054503 ],
[-0.21158552, 0.16337538, -0.55872625, -0.4717918 , -0.14633152,
0.4356466 , -0.29068574, -0.365243 , -0.13362256, -0.38409072],
[ 0.33547556, 0.13281083, -0.04601526, -0.42355436, -0.0060991 ,
-0.5032983 , -0.1809687 , -0.22183692, 0.08809811, 0.09949732],
[-0.60682243, -0.3103218 , 0.38421983, -0.5180574 , -0.42757726,
-0.21736482, 0.07201356, -0.254764 , -0.28456295, -0.02735662]],
dtype=float32)>,
<tf.Variable 'dense_1/bias:0' shape=(10,) dtype=float32, numpy=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)>)
实现自定义层
实现自己的层的最佳方法是扩展 tf.keras.Layer
类并且实现:
__init__
,执行所有独立于输入的初始化build
,在这里你知道输入张量的形状,并且进行剩余的初始化call
,执行前向计算
请注意,你不必等到调用 build
来创建变量,还可以在 __init__
中创建它们。然而,在 build
中创建它们的优点是,它支持基于层将要操作的输入的形状创建后期变量。另一方面,在 __init__
中创建变量意味着需要显式指定创建变量所需的形状。
class MyDenseLayer(tf.keras.layers.Layer):
def __init__(self, num_outputs):
super(MyDenseLayer, self).__init__()
self.num_outputs = num_outputs
def build(self, input_shape):
self.kernel = self.add_variable("kernel",
shape=[input_shape[-1].value,
self.num_outputs])
def call(self, input):
return tf.matmul(input, self.kernel)
layer = MyDenseLayer(10)
print(layer(tf.zeros([10, 5])))
print(layer.variables)
tf.Tensor(
[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]], shape=(10, 10), dtype=float32)
[<tf.Variable 'my_dense_layer/kernel:0' shape=(5, 10) dtype=float32, numpy=
array([[ 0.04571068, -0.4502709 , 0.03811407, -0.39218163, 0.3836605 ,
-0.06154126, 0.5084277 , 0.5511847 , -0.01946509, -0.24898636],
[ 0.55244833, 0.04234415, 0.367557 , -0.16904458, 0.5583802 ,
0.5395512 , 0.23943478, 0.26102585, 0.44694608, -0.17953226],
[-0.27093476, -0.06255698, -0.36378002, -0.52204967, -0.4386331 ,
0.04419768, 0.31893957, 0.5911831 , -0.16269788, -0.34716034],
[ 0.1851421 , 0.28576833, 0.32846957, -0.060004 , -0.4525128 ,
-0.24665937, 0.22914791, -0.19578329, -0.33844247, -0.57873464],
[-0.5436014 , 0.27190197, -0.3022089 , -0.39169455, -0.62015975,
0.48647386, 0.17486733, 0.21422738, 0.3566833 , 0.12303311]],
dtype=float32)>]
尽可能使用标准层,则整体代码更易于阅读和维护,因为其他读者将熟悉标准层的行为。如果你想使用 tf.keras.layers
或 tf.contrib.layers
中不存在的层,考虑提交一个 github issue,或者,更好的是发送一个 pull request!
模型:组合层
机器学习模型中许多有趣的网络层都是通过组合现有层来实现的。例如,resnet 中的每个残差块都是卷积、批归一化和 shortcut 的组合。
创建包含其他层的层的主要类是 tf.keras.Model
,可以通过继承 tf.keras.Model
实现。
class ResnetIdentityBlock(tf.keras.Model):
def __init__(self, kernel_size, filters):
super(ResnetIdentityBlock, self).__init__(name='')
filters1, filters2, filters3 = filters
self.conv2a = tf.keras.layers.Conv2D(filters1, (1, 1))
self.bn2a = tf.keras.layers.BatchNormalization()
self.conv2b = tf.keras.layers.Conv2D(filters2, kernel_size, padding='same')
self.bn2b = tf.keras.layers.BatchNormalization()
self.conv2c = tf.keras.layers.Conv2D(filters3, (1, 1))
self.bn2c = tf.keras.layers.BatchNormalization()
def call(self, input_tensor, training=False):
x = self.conv2a(input_tensor)
x = self.bn2a(x, training=training)
x = tf.nn.relu(x)
x = self.conv2b(x)
x = self.bn2b(x, training=training)
x = tf.nn.relu(x)
x = self.conv2c(x)
x = self.bn2c(x, training=training)
x += input_tensor
return tf.nn.relu(x)
block = ResnetIdentityBlock(1, [1, 2, 3])
print(block(tf.zeros([1, 2, 3, 3])))
print([x.name for x in block.variables])
tf.Tensor(
[[[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]]], shape=(1, 2, 3, 3), dtype=float32)
...
然而,大多数时候,组合许多层的模型只需要简单地调用一个又一个层。使用 tf.keras.Sequential
可以在非常少的代码中完成这一步。
my_seq = tf.keras.Sequential([tf.keras.layers.Conv2D(1, (1, 1)),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2D(2, 1,
padding='same'),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.Conv2D(3, (1, 1)),
tf.keras.layers.BatchNormalization()])
my_seq(tf.zeros([1, 2, 3, 3]))
<tf.Tensor: id=508, shape=(1, 2, 3, 3), dtype=float32, numpy=
array([[[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]],
[[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]]]], dtype=float32)>
下一步
现在,你可以回到前面的教程中,使用层和模型修改线性回归示例,从而优化结构。