《Tensorflow 2.0 神经网络实践》学习笔记(1)Tensorflow 2.0 架构

Keras框架

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)

用(顺序)函数式API定义模型(无法用于定义任意模型)

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

eager执行模式

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)

2.0引入了GrandintTape来自动微分计算梯度

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

Keras不能覆盖全部的训练和评估情况

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()

2.0引入了可存档对象的概念

每个继承自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,记录图像的方法。
代码参考上上标题。

AutoGraph

此功能将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的迁移。

你可能感兴趣的:(tensorflow,python,深度学习,机器学习,tensorflow,keras)