RSVP for your your local TensorFlow Everywhere event today!

基本训练循环

查看 TensorFlow.org 运行于 Google Colab 查看源码 GitHub 下载 notebook

在前面的教程里,您已经了解了tensors, variables, gradient tape, 和modules。在这篇教程,您将把它们放在一起训练模型。

TensorFlow 同样具有更高层次的神经网络应用程序编程接口 tf.Keras。它提供有效的抽象来减少代码量。然而,这篇教程中,您将使用基础类。

创建

import tensorflow as tf

解决机器学习问题

解决一个机器学习问题通常包含以下步骤:

  • 获得训练数据。
  • 定义模型。
  • 定义损失函数。
  • 遍历训练数据,从目标值计算损失。
  • 计算该损失的梯度,并使用optimizer调整变量以适合数据。
  • 计算结果。

为了便于说明,在本指南中,您将开发一个简单的线性模型, $f(x) = x * W + b$, 其中包含两个变量: $W$ (权重) 和 $b$ (偏差)。

这是一个最基础的机器学习问题: 已知 $x$ 和 $y$, 尝试解得直线的斜率和偏移量。 [simple linear regression](https://en.wikipedia.org/wiki/Linear_regression#Simple_and_multiple_linear_regress

数据

监督学习使用 inputs (一般表示为 x) 和 outputs (表示为 y, 经常称作 labels)。目标是从成对的输入和输出目的是从成对的输入和输出中学习,以便您可以预测输入的输出值。

TensorFlow中几乎每个输入数据都是由张量表示,并且通常是向量。监督学习中,输出(即想到预测值)同样是个张量。

这是通过将高斯(即正态分布)噪声添加到直线上的点而合成的一些数据。

# 实际的线
TRUE_W = 3.0
TRUE_B = 2.0

NUM_EXAMPLES = 1000

# 随机向量x
x = tf.random.normal(shape=[NUM_EXAMPLES])

# 生成噪声
noise = tf.random.normal(shape=[NUM_EXAMPLES])

# 计算y
y = x * TRUE_W + TRUE_B + noise
# 绘制所有的数据
import matplotlib.pyplot as plt

plt.scatter(x, y, c="b")
plt.show()

png

张量通常以 batches 的形式聚集在一起,或者是成组的输入和输出堆叠在一起。批处理能够对训练过程带来一些好处,并且可以与加速器和矢量化计算很好地配合使用。给定此数据集的大小,您可以将整个数据集视为一个批次。

定义模型

使用 tf.Variable 代表模型中的所有权重。tf.Variable 能够存储值,并根据需要以张量形式提供它。详情请见 variable guide

使用 tf.Module 封装变量和计算。您可以使用任何Python对象,但是通过这种方式可以轻松保存它。

这里,您可以定义 wb 为变量。

class MyModel(tf.Module):
  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    # 初始化权重值为`5.0`,偏差值为`0.0`
    # 实际项目中,应该随机初始化
    self.w = tf.Variable(5.0)
    self.b = tf.Variable(0.0)

  def __call__(self, x):
    return self.w * x + self.b

model = MyModel()

# 列出变量tf.modules的内置变量聚合
print("Variables:", model.variables)

# 验证模型是否有效
assert model(3.0).numpy() == 15.0
Variables: (<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>, <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=5.0>)

初始化变量在这里是以固定方式设置的,但是Keras附带了许多您可以使用的 initalizers 。无论是否使用其余的Keras模块,都不影响你对初始化器的使用。

定义损失函数

损失函数衡量给定输入的模型输出与目标输出的匹配程度。目的是在训练过程中尽量减少这种差异。定义标准的L2损失,也称为“均方误差”:

# 计算整个批次的单个损失值
def loss(target_y, predicted_y):
  return tf.reduce_mean(tf.square(target_y - predicted_y))

在训练模型之前,您可以可视化损失值。使用红色绘制模型的预测值,使用蓝色绘制训练数据。

plt.scatter(x, y, c="b")
plt.scatter(x, model(x), c="r")
plt.show()

print("Current loss: %1.6f" % loss(model(x), y).numpy())

png

Current loss: 9.036523

定义训练循环

训练循环按顺序重复执行以下任务:

  • 发送一批输入值,通过模型生成输出值
  • 通过比较输出值与输出(标签),来计算损失值
  • 使用梯度带(GradientTape)找到梯度值
  • 使用这些梯度优化变量

这个例子中,您可以使用 gradient descent训练数据。

tf.keras.optimizers中有许多梯度下降的变量。但是本着搭建的第一原则,您将在这里 借助tf.GradientTape的自动微分和tf.assign_sub的递减值(结合了tf.assigntf.sub)自己实现基本数学:

# 给定一个可调用的模型,输入,输出和学习率...
def train(model, x, y, learning_rate):

  with tf.GradientTape() as t:
    # 可训练变量由GradientTape自动跟踪
    current_loss = loss(y, model(x))

  # 使用GradientTape计算相对于W和b的梯度
  dw, db = t.gradient(current_loss, [model.w, model.b])

  # 减去由学习率缩放的梯度
  model.w.assign_sub(learning_rate * dw)
  model.b.assign_sub(learning_rate * db)

为了了解训练,您可以发送同一批* x y * 经过循环训练,同时查看Wb的变化情况。

model = MyModel()

# 收集W值和b值的历史记录以供以后绘制
Ws, bs = [], []
epochs = range(10)

# 定义用于训练的循环
def training_loop(model, x, y):

  for epoch in epochs:
    # 用单个大批次处理更新模型
    train(model, x, y, learning_rate=0.1)

    # 在更新之前进行跟踪
    Ws.append(model.w.numpy())
    bs.append(model.b.numpy())
    current_loss = loss(y, model(x))

    print("Epoch %2d: W=%1.2f b=%1.2f, loss=%2.5f" %
          (epoch, Ws[-1], bs[-1], current_loss))
print("Starting: W=%1.2f b=%1.2f, loss=%2.5f" %
      (model.w, model.b, loss(y, model(x))))

# 开始训练
training_loop(model, x, y)

# 绘制
plt.plot(epochs, Ws, "r",
         epochs, bs, "b")

plt.plot([TRUE_W] * len(epochs), "r--",
         [TRUE_B] * len(epochs), "b--")

plt.legend(["W", "b", "True W", "True b"])
plt.show()
Starting: W=5.00 b=0.00, loss=9.03652
Epoch  0: W=4.61 b=0.42, loss=6.06299
Epoch  1: W=4.30 b=0.76, loss=4.18448
Epoch  2: W=4.05 b=1.02, loss=2.99763
Epoch  3: W=3.85 b=1.23, loss=2.24770
Epoch  4: W=3.69 b=1.40, loss=1.77379
Epoch  5: W=3.56 b=1.53, loss=1.47428
Epoch  6: W=3.46 b=1.63, loss=1.28497
Epoch  7: W=3.38 b=1.71, loss=1.16530
Epoch  8: W=3.31 b=1.78, loss=1.08963
Epoch  9: W=3.26 b=1.83, loss=1.04179

png

# 可视化训练后的模型如何执行
plt.scatter(x, y, c="b")
plt.scatter(x, model(x), c="r")
plt.show()

print("Current loss: %1.6f" % loss(model(x), y).numpy())

png

Current loss: 1.041793

使用Keras完成相同的解决方案

将上面的代码与Keras中的等效代码进行对比很有用。

如果您将tf.keras.Model子类化,则定义模型与其看起来完全相同。请记住,Keras模型最终从模块继承。

class MyModelKeras(tf.keras.Model):
  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    # 初始化权重为`5.0`,偏差为`0.0`
    # 实际中应该随机初始化该值
    self.w = tf.Variable(5.0)
    self.b = tf.Variable(0.0)

  def __call__(self, x, **kwargs):
    return self.w * x + self.b

keras_model = MyModelKeras()

# 使用Keras模型重新进行循环训练
training_loop(keras_model, x, y)

# 您同样可以使用Keras内置的功能保存检查点(checkpoint)
keras_model.save_weights("my_checkpoint")
Epoch  0: W=4.61 b=0.42, loss=6.06299
Epoch  1: W=4.30 b=0.76, loss=4.18448
Epoch  2: W=4.05 b=1.02, loss=2.99763
Epoch  3: W=3.85 b=1.23, loss=2.24770
Epoch  4: W=3.69 b=1.40, loss=1.77379
Epoch  5: W=3.56 b=1.53, loss=1.47428
Epoch  6: W=3.46 b=1.63, loss=1.28497
Epoch  7: W=3.38 b=1.71, loss=1.16530
Epoch  8: W=3.31 b=1.78, loss=1.08963
Epoch  9: W=3.26 b=1.83, loss=1.04179

您可以使用Keras的内置功能作为捷径,而不必在每次创建模型时都编写新的训练循环。当您不想编写或调试Python训练循环时,这很有用。

如果您使用Keras,您将会需要使用 model.compile() 去设置参数, 使用model.fit() 进行训练。借助Keras实现L2损失和梯度下降需要的代码量更少,就像一个捷径。Keras损失和优化器也可以在这些便利功能之外使用,而前面的示例也可以使用它们。

keras_model = MyModelKeras()

# 编译设置培训参数
keras_model.compile(
    # 默认情况下,fit()调用tf.function()。
    # Debug时你可以关闭这一功能,但是现在是打开的。
    run_eagerly=False,

    # 使用内置的优化器,配置为对象
    optimizer=tf.keras.optimizers.SGD(learning_rate=0.1),

    # Keras内置MSE
    # 您也可以使用损失函数像上面一样进行定义
    loss=tf.keras.losses.mean_squared_error,
)

Kerasfit期望批处理数据或完整的数据集作为NumPy数组。 NumPy数组分为多个批次,默认批次大小为32。

这一案例中,为了匹配手写训练循环,您应该以大小为1000的单批次传递x。

print(x.shape[0])
keras_model.fit(x, y, epochs=10, batch_size=1000)
1000
Epoch 1/10
1/1 [==============================] - 0s 215ms/step - loss: 9.0365
Epoch 2/10
1/1 [==============================] - 0s 2ms/step - loss: 6.0630
Epoch 3/10
1/1 [==============================] - 0s 2ms/step - loss: 4.1845
Epoch 4/10
1/1 [==============================] - 0s 2ms/step - loss: 2.9976
Epoch 5/10
1/1 [==============================] - 0s 2ms/step - loss: 2.2477
Epoch 6/10
1/1 [==============================] - 0s 2ms/step - loss: 1.7738
Epoch 7/10
1/1 [==============================] - 0s 2ms/step - loss: 1.4743
Epoch 8/10
1/1 [==============================] - 0s 2ms/step - loss: 1.2850
Epoch 9/10
1/1 [==============================] - 0s 2ms/step - loss: 1.1653
Epoch 10/10
1/1 [==============================] - 0s 2ms/step - loss: 1.0896

<tensorflow.python.keras.callbacks.History at 0x7f26503f1780>

请注意,Keras会在训练后而不是之前打印出损失,因此第一次损失会显得较低。否则,这表明本质上相同的训练效果。

下一步

在这篇引导中,您已经看到怎样使用tensors, variables, modules, 和gradient tape去构建并训练模型,也进一步了解到这些是如何与Keras对应的。

然而,这是一个十分简单的问题。有关更实际的介绍,参见 Custom training walkthrough.

有关使用内置Keras训练循环的更多信息,参见this guide。有关训练循环和Keras的更多信息,参见 this guide。有关自定义的分布式训练循环,参见 this guide