Keras不是一个高层的机器学习框架的包装,而是一个用来定义和训练机器学习模型的API规范。
import tensorflow as tf
from tensorflow.keras.datasets import fashion_mnist
n_classes = 10
model = tf.keras.Sequential([ # 堆叠keras层构建Model
tf.keras.layers.Conv2D(32, (5, 5), activation=tf.nn.relu, input_shape=(28, 28, 1)), # input_shape定义输入参数的形状
tf.keras.layers.MaxPool2D((2, 2), (2, 2)), # 除第一层,每层都需要输入,并产生输出
tf.keras.layers.Conv2D(64, (3, 3), activation=tf.nn.relu), # 使用ReLU函数作为激活函数
tf.keras.layers.MaxPool2D((2, 2), (2, 2)),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(1024, activation=tf.nn.relu),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(n_classes)
])
model.summary() # 模型概述
(train_x, train_y), (test_x, test_y) = fashion_mnist.load_data()
# Scale input in [-1, 1] range
train_x = train_x / 255. * 2 - 1
test_x = test_x / 255. * 2 - 1
# tf.expands_dims代替np.expands_dims 给输入的tensor(张量)添加了一个维度
train_x = tf.expands_dims(train_x, -1).numpy() # compile输入np作为输入,因此进行转化
test_x = tf.expands_dims(test_x, -1).numpy()
# 标准分类任务,可使用简单的三行定义训练循环:优化器、损失函数、监控指标
model.compile()(
optimizer=tf.keras.optimizers.Adam(1e-5),
loss='sparse_categorical_crossentropy',
metrics=['accuracy']
)
# 在传递来的数据上启动训练
model.fit(train_x, train_y, epochs=10) # batch_size=32 默认
model.evaluate(test_x, test_y)
import tensorflow as tf
input_shape = (100, ) # 接受一个100维输入
inputs = tf.keras.layers.Input(input_shape)
net = tf.keras.layers.Dense(units=64, activation=tf.nn.elu, name='fc1')(inputs)
net = tf.keras.layers.Dense(units=64, activation=tf.nn.elu, name='fc2')(net) # 使用ELU作为激活函数
net = tf.keras.layers.Dense(units=1, name="G")(net)
model = tf.keras.Model(inputs=inputs, outputs=net)
更灵活,但更容易出错,并且难以调试。
不推荐使用此方法,其将层定义和使用分开了。
但有时候只能使用此方法定义前向传递,尤其在使用循环神经网络时。
import tensorflow as tf
class Generator(tf.keras.Model):
def __init__(self):
# Model的子类Generator对象仍然是一个Model对象,因此可以使用compile和fit进行训练
super(Generator, self).__init__()
self.dense_1 = tf.keras.layers.Dense(units=64, activation=tf.nn.elu, name='fc1')
self.dense_2 = tf.keras.layers.Dense(units=64, activation=tf.nn.elu, name='fc2')
self.output = tf.keras.layers.Dense(units=1, name="G")
def call(self, inputs, training=None, mask=None):
# Build the model in functional style here
# and return the output tensor
net = self.dense_1(inputs)
net = self.dense_2(net)
net = self.output(net)
return net
Keras可以用来训练和评估模型,但Tensorflow2.0有了eager执行模式,可以编写自定义训练循环,从而可以实现完全控制训练,调试也更简单。
官方文档描述:
Tensorflow的eager执行模式是一个重要的编程环境(前面的是图模式)。
它能立即评估运算,而且无需构建图,运算时会实时返回值。
import tensorflow as tf
A = tf.constant([[1, 2], [3, 4]], dtype=tf.float32) # constant 恒定的;常数
x = tf.constant([[0, 10], [0, 0.5]])
b = tf.constant([[-1, 1]], dtype=tf.float32)
y = tf.add(tf.matmul(A, x), b, name='result') # y可作为一个tf.tensor对象,可被用作其他任意Tensorflow的输入
print(y)
# 相比1.x版本,不再返回np数组,而是tf.tensor对象
'''
tf.Tensor(
[[-1. 12.]
[-1. 33.]], shape=(2, 2), dtype=float32)
'''
# 提取tf.tensor对象的值:执行tf.Tensor.numpy
print(y.numpy())
# eager模式代码实例
# 如何写python便如何写eager模式
import tensorflow as tf
def multiply(x, y):
"""Matrix multiplication.
It requires the input shape of both input to match.
:param x: tf.Tensor a matrix
:param y: tf.Tensor a matrix
:return: The matrix multiplication x * y
"""
assert x.shape == y.shape # 断言,在false时触发异常
return tf.matmul(x, y)
def add(x, y):
"""Add two tensors.
:param x: the left hand operand(操作对象)
:param y: the right hand operand. It should be compatible(兼容的) with x.
:return: x + y
"""
return x + y
def main():
"""
Main program.
:return:
"""
A = tf.constant([[1, 2], [3, 4]], dtype=tf.float32)
x = tf.constant([[0, 10], [0, 0.5]])
b = tf.constant([[1, -1]], dtype=tf.float32)
z = multiply(A, x)
y = add(z, b)
print(y)
if __name__ == '__main__':
main()
# eager模式代码实例
# 可以使用python解释器控制流程,不再使用tf.while_loop,而使用for、while
import tensorflow as tf
x = tf.Variable(1, dtype=tf.int32)
y = tf.Variable(2, dtype=tf.int32)
for _ in range(5):
y.assign_add(1)
out = x * y
print(out)
tf.GradientTape()自动创建一个记录所有自动微分运算的上下文"磁带"。
如果上下文管理器中至少有一个输入是可监控的,而且正在被监控,那么每个在上下文管理器中执行的运算,都会被记录在磁带中。
当出现以下情况,输入可被监控:
1.它是一个有tf.Variable创建的可训练变量。
2.它正在被磁带监视,这可以使用tf.Tensor对象的watch方法实现。
这个磁带记录了所有在上下文中执行的用于构建前向传递图的运算。然后这个磁带可以被展开,从而应用反向自动微分来计算梯度。
有些情况,我们可以多次调用tf.GradientTape.gradient(),由开发者手动释放资源。
这需要创建一个持久性的磁带,多次gradient而不全部释放资源
这可以通过del删除对磁带的引用做到。
也可以将多个tf.GradientTape()对象组成一个高阶导数。
import tensorflow as tf
x = tf.constant(4.0)
with tf.GradientTape() as tape:
tape.watch(x) # 明确指定磁带监视x常量
y = tf.pow(x, 2) # y = tf.Tensor(16.0, shape=(), dtype=float32)
# Will compute y = 2 * x, x = 8
dy_dx = tape.gradient(y, x) # 一旦tf.GradientTape.gradient()被调用,磁带就会释放其全部保存的资源
print(dy_dx)
x = tf.Variable(4.0)
y = tf.Variable(2.0)
with tf.GradientTape(persistent=True) as tape:
z = x + y
w = tf.pow(x, 2)
dz_dy = tape.gradient(z, y) # z = 4 + y => z = 1
dz_dx = tape.gradient(z, x) # z = x + 2 => z = 1
dw_dx = tape.gradient(w, x) # w = x * 2 => w = 2x => w = 8
print(dz_dy, dz_dx, dw_dx)
# tf.Tensor(1.0, shape=(), dtype=float32) tf.Tensor(1.0, shape=(), dtype=float32) tf.Tensor(8.0, shape=(), dtype=float32)
# Release the resources
del tape
compile和fit对循环的定制非常有限。
1.0可以用于构建定制的训练循环。
梯度爆炸问题:梯度下降时,损失函数发散,直到变成NaN。
应对策略:裁剪梯度值或者阈值限制,梯度更新值不能比阈值更大。
常用裁剪策略:L2正则梯度裁剪。
在此策略中,梯度向量被归一化,使其L2范数小于或等于阈值。
实践中,我们用此方法更新梯度:gradients = gradients * threshold / 12(gradients)
Tensorflow有一个处理此任务的API:tf.clip_by_norm,我们只需获取梯度、更新规则,然后输入到所选择的优化器中。
import tensorflow as tf
from tensorflow.keras.datasets import fashion_mnist
def make_model(n_classes):
return tf.keras.Sequential([
tf.keras.layers.Conv2D(32, (5, 5), activation=tf.nn.relu, input_shape=(28, 28, 1)),
tf.keras.layers.MaxPool2D((2, 2), (2, 2)),
tf.keras.layers.Conv2D(64, (3, 3), activation=tf.nn.relu),
tf.keras.layers.MaxPool2D((2, 2), (2, 2)),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(1024, activation=tf.nn.relu),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(n_classes)
])
def load_data():
(train_x, train_y), (test_x, test_y) = fashion_mnist.load_data()
# Scale input in [-1, 1] range
train_x = tf.expand_dims(train_x, -1)
train_x = (tf.image.convert_image_dtype(train_x, tf.float32) - 0.5) * 2
train_y = tf.expand_dims(train_y, -1)
test_x = test_x / 255. * 2 - 1
test_x = (tf.image.convert_image_dtype(test_x, tf.float32) - 0.5) * 2
test_y = tf.expand_dims(test_y, -1)
return (train_x, train_y), (test_x, test_y)
def train():
# Define the model
n_classes = 10
model = make_model(n_classes)
# Input data
(train_x, train_y), (test_x, test_y) = load_data()
# Training parameters
loss = tf.losses.SparseCategoricalCrossentropy(from_logits=True) # 交叉熵损失函数
# from_logits=True表示还没经过sigmoid或者softmax的概率化
step = tf.Variable(1, name='global_step')
optimizer = tf.optimizers.Adam(1e-3)
ckpt = tf.train.Checkpoint(step=step, optimizer=optimizer, model=model)
manager = tf.train.CheckpointManager(ckpt, './checkpoints', max_to_keep=3)
ckpt.restore(manager.latest_checkpoint)
if manager.latest_checkpoint:
print(f'Restored from {manager.latest_checkpoint}')
else:
print('Initializing from scratch.')
# 在训练迭代的末尾,可以调用metrics的.update_state方法来聚合和保存,在对象状态内计算的值,然后调用.result()方法
accuracy = tf.metrics.Accuracy()
mean_loss = tf.metrics.Mean(name='loss')
# Train step function
def train_step(inputs, labels):
with tf.GradientTape as tape:
logits = model(inputs)
loss_value = loss(labels, logits)
gradients = tape.gradient(loss_value, model.trainable_variables)
# TODO: apply gradient clipping here
optimizer.apply_gradients(zip(gradients, model.trainable_variables))
step.assign_add(1)
# accuracy_value = accuracy(labels, tf.argmax(logits, -1))
accuracy.update_state(labels, tf.argmax(logits, -1))
return loss_value, accuracy_value
epochs = 10
batch_size = 32
nr_batches_train = int(train_x.shape[0] / batch_size)
print(f'Batch size: {batch_size}')
print(f'Number of batches per epoch: {nr_batches_train}')
# 为了将总结记录保存在硬盘上,你需要创建一个File/Summary writer对象
train_summary_writer = tf.summary.create_file_writer('./log/train')
with train_summary_writer.as_default(): # as_default()定一了一个上下文管理器,每个在上下文中调用的tf.summary.*都会将其结果加入默认的Summary writer中
for epoch in range(epochs):
for t in range(nr_batches_train):
start_from = t * batch_size
to = (t + 1) * batch_size
features, labels = train_x[start_from:to], train_y[start_from:to]
loss_value, accuracy_value = train_step(features, labels)
mean_loss.update_state(loss_value)
if t % 10 == 0:
print(f'{step.numpy()}: {loss_value} - accuracy: {accuracy_value}')
save_path = manager.save()
print(f'Checkpoint saved: {save_path}')
tf.summary.image('train_set', features, max_outputs=3, step=step.numpy())
tf.summary.scalar('accuray', accuracy_value, step=step.numpy())
tf.summary.scalar('loss', mean_loss.result(), step=step.numpy()) # result方法负责在聚合值上正确地计算指标;完成计算后可通过reset_states()重制指标的内部状态
accuracy.reset_states()
mean_loss.reset_states()
print(f'Epoch {epoch} terminated')
# Measuring accuracy on the whole training set at the end of the epoch
for t in range(nr_batches_train):
start_from = t * batch_size
to = (t + 1) * batch_size
features, labels = train_x[start_from:to], train_y[start_from:to]
logits = model(features)
accuracy.update_state(labels, tf.argmax(logits, -1))
print(f'Training accuracy: {accuracy_value}')
accuracy.reset_states()
if __name__ == '__main__':
train()
每个继承自tf.train.Checkpointable的对象都可自动序列化,这意味其可以被保存在一个检查点中。
有两种方法可以保存模型:
1.使用检查点:一种简单的在硬盘上保存变量的方法。
2.使用SavedModel:模型结构及检查点。
可以用两个对象保存和恢复模型参数:
1.tf.train.Checkpoint是一个基于对象的序列化/反序列化器。
2.tf.train.CheckpointManager是一个能用tf.train.Checkpoint实例来存储和管理检查点的对象。
Checkpoint.save、Checkpoint.restore基于对象的检查点。
tf.train.Saver只能读写基于variable.name的检查点。
用保存对象代替保存变量的方式更健壮。
代码参考上一标题。
TensorBorad仍然是TensorFlow默认和推荐的数据记录和可视化工具。
tf.summary包,包含了所有的方法,来保存标量、图像、直方图、分布等。
再加上tf.metrics,使得记录聚合的数据成为可能。
tf.summary.image,记录图像的方法。
代码参考上上标题。
此功能将python自动转化成其图表示。
2.0中,用@tf.function修饰某函数,AutoGraph自动应用于此函数。
关于图的构建需要学习一下1.x版本。
需要注意的是:图模式下,一个变量是一个持久性的对象,会一直存在,即使与其关联的python变量已经不在作用域中,并已经被收回了。
因此,若想使用图加速,需要开发者避免此函数在每次调用时重新创建变量。
有两种方法解决此问题:
1.通过输入参数传递变量。
2.通过打破函数范围,并且从外层范围继承变量。
# 第一种方式需要改变函数的定义
# 有问题的代码
def f():
a = tf.constant([10, 10], [11., 1.])
x = tf.constant([1., 0.], [0., 1.])
b = tf.Variable(12.) # 第二次调用会再次创建变量
y = tf.matmul(a, x) + b
return y
# 第一种解决方法
@tf.function()
def f(b):
a = tf.constant([10, 10], [11., 1.])
x = tf.constant([1., 0.], [0., 1.])
y = tf.matmul(a, x) + b
return y
var = tf.Variable(12.)
f(var)
f(15)
f(tf.contant(1))
第二种方式需要打破函数作用域,不推荐使用全局变量,推荐使用Keras类对象。
class F():
def __init__(self):
self._b = None
@tf.function()
def __call_(self):
a = tf.constant([10, 10], [11., 1.])
x = tf.constant([1., 0.], [0., 1.])
if self._b is None:
self._b = tf.Variable(12.)
y = tf.matmul(a, x) + self._b
return y
f = F()
f()
当涉及优化训练过程时,AutoGraph和图加速过程最有效。
训练中计算需求最大的部分依次是:前向传递、梯度计算、参数更新。
训练迭代过程是一个无状态的函数,它使用了从外域继承来的变量,因此可以通过@tf.function修饰,以转化成图表达,进而加速。
@tf.function
def train_step(inputs, labels):
# function body
简单场景下,eager和图一样快。
当模型更深,更复杂,性能的提升会非常明显。
需要注意:AutoGraph自动将python结构转化成对应的tf.*,但转化代码时保留原有语法不是一个简单的工作,最好帮助其进行转化。
AutoGraph不能自动将print转化成tf.print。
range、assert也应该手动转化为tf.range、tf.assert。
# 将AutoGraph生成的源码,以可视化代码形式查看
print(tf.autograph.to_code(f.python_function))
通过官方提供的迁移脚本和部分手动修改实现1.x到2.0的迁移。