简单粗暴TensorFlow2

基于简单粗暴TensorFlow2的笔记记录

TensorFlow 基础

with语句

  • with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 __enter__() ,执行完语句体之后会执行 __exit__()
  • 上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象
  • 如果用as指定了一个目标,会将__enter__()返回值赋予这个目标

    其他介绍

  • 可以用with 语句来代替 try…except…finally
  • 一个上下文管理器(context manager)是一个对象,定义了运行一个with语句时候要建立的运行时上下文(runtime context),掌控了何处进入,何处退出以及一个代码块运行所需的运行时上下文

    张量

  • 张量 (Tensor)为TensorFlow中数据的基本单位,在概念上等同于多维数组——描述标量(0 维数组)、向量(1 维数组)、矩阵(2 维数组)
    1
    2
    3
    4
    5
    6
    7
    8
    # 定义一个随机数(标量)
    random_float = tf.random.uniform(shape=())

    # 定义一个有2个元素的零向量
    zero_vector = tf.zeros(shape=(2))

    # 定义一个2×2的常量矩阵
    A = tf.constant([[1., 2.], [3., 4.]])
  • 属性包括形状、类型和值,通过张量的 shapedtype 属性和 numpy() 方法获取
  • 张量中元素的类型默认为 tf.float32,可以通过加入 dtype 参数来自行指定类型:zero_vector = tf.zeros(shape=(2), dtype=tf.int32)
  • 将已有的张量运算后得到新的张量:张量相加 C = tf.add(A, B)

    自动求导

  • tf.GradientTape() 实现自动求导
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 计算y = x^2 在3的导数
    import tensorflow as tf
    x = tf.Variable(initial_value=3.)
    with tf.GradientTape() as tape: # 在 tf.GradientTape() 的上下文内,所有计算步骤都会被记录以用于求导
    y = tf.square(x)
    y_grad = tape.gradient(y, x) # 计算y关于x的导数
    print([y, y_grad])

    输出:
    [array([9.], dtype=float32), array([6.], dtype=float32)]
  • x 是变量,同样具有形状、类型和值三种属性。变量需要有一个初始化过程,通过在 tf.Variable() 中指定 initial_value 初始化。
  • 变量与普通张量的一个重要区别是其默认能够被自动求导机制所求导,因此常被用于定义模型的参数
  • 同样可计算偏导、矩阵或向量的导数
  • tf.square() :对输入张量的每一个元素求平方,不改变张量形状
  • tf.reduce_sum() :对输入张量的所有元素求和,输出一个形状为空的纯量张量,可通过 axis 参数来指定求和的维度

    示例:线性回归

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    # 对一组离散值(X,Y)线性回归
    import numpy as np
    # 定义数据
    X_raw = np.array([2013, 2014, 2015, 2016, 2017], dtype=np.float32)
    y_raw = np.array([12000, 14000, 15000, 16500, 17500], dtype=np.float32)
    # 数据归一化
    X = (X_raw - X_raw.min()) / (X_raw.max() - X_raw.min())
    y = (y_raw - y_raw.min()) / (y_raw.max() - y_raw.min())

    * 梯度下降过程:
    * 初始化自变量为$x_0$,$k=0$
    * 迭代:
    * 求函数$f(x)$关于自变量$x$的梯度$\nabla f(x_k)$
    * 更新自变量:$x_{k+1}=x_k-\gamma \nabla f(x_k)$,$\gamma$为学习率
    * $k\leftarrow k+1$
    ```python
    X = tf.constant(X)
    y = tf.constant(y)

    a = tf.Variable(initial_value=0.)
    b = tf.Variable(initial_value=0.)
    variables = [a, b]

    num_epoch = 10000
    optimizer = tf.keras.optimizers.SGD(learning_rate=1e-3)
    #声明了一个梯度下降优化器 Optimizer,学习率为 1e-3,计算出求导结果更新模型参数,以最小化特定的损失函数,具体使用方式是调用其apply_gradients() 方法
    for e in range(num_epoch):
    # 使用tf.GradientTape()记录损失函数的梯度信息
    with tf.GradientTape() as tape:
    y_pred = a * X + b
    loss = 0.5 * tf.reduce_sum(tf.square(y_pred - y))
    # TensorFlow自动计算损失函数关于自变量(模型参数)的梯度——求出 tape 中记录的 loss 关于 variables = [a, b] 中每个变量的偏导数
    grads = tape.gradient(loss, variables)
    # TensorFlow自动根据梯度更新参数
    # 参数 grads_and_vars,即待更新的变量,和损失函数关于这些变量的偏导数
    optimizer.apply_gradients(grads_and_vars=zip(grads, variables))

    print(a, b)
  • zip() 为内置函数:若 a = [1, 3, 5], b = [2, 4, 6],则 zip(a, b) = [(1, 2), (3, 4), (5, 6)]zip() 返回一个 zip 对象,本需要调用 list() 来将生成器转换成列表

TensorFlow 模型建立与训练

模型(Model)与层(Layer)

  • 常使用 Keras( tf.keras )构建模型

  • 有两个重要概念: 模型(Model) 和 层(Layer)

    • 层封装各种计算流程和变量
    • 模型组织和连接各种层,封装成一个整体
  • Keras 模型以类的形式使用,可以通过继承 tf.keras.Model 类来定义新的模型。继承类中,需要重写__init__()(构造函数,初始化)和 call(input)(模型调用)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class MyModel(tf.keras.Model):
    def __init__(self):
    super().__init__()
    # 添加初始化代码(包含 call 方法中会用到的层),例如
    # layer1 = tf.keras.layers.BuiltInLayer(...)
    # layer2 = MyCustomLayer(...)

    def call(self, input):
    # 添加模型调用的代码(处理输入并返回输出),例如
    # x = layer1(input)
    # output = layer2(x)
    return output

    Keras 的全连接层

  • tf.keras.layers.Dense输入矩阵 $A$ 进行 $f(AW + b)$ 的线性变换 + 激活函数操作。不指定激活函数,则为线性变换 $AW + b$。

  • 输入矩阵 $A$ = [batch_size, input_dim]

  • 输出二维张量 [batch_size, units]

  • 常用的激活函数包括 tf.nn.relutf.nn.tanhtf.nn.sigmoid

  • 可训练变量:

    • 权重矩阵 $W$:kernel = [input_dim, units]
    • 偏置向量 $b$: bias = [units]

      示例:多层感知机 MLP

  • 多层全连接神经网络

  • 完成 MNIST 手写体数字图片数据集的分类任务

    数据获取及预处理: tf.keras.datasets

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class MNISTLoader():
    def __init__(self):
    mnist = tf.keras.datasets.mnist # 从网上自动下载 MNIST 数据集并加载,放置在:C:\Users\ASUS\.keras/dataset 目录下
    (self.train_data, self.train_label), (self.test_data, self.test_label) = mnist.load_data() # 载入数据
    # MNIST中的图像默认为uint8(0-255的数字)。将其归一化到0-1之间的浮点数,并在最后增加一维作为颜色通道
    self.train_data = np.expand_dims(self.train_data.astype(np.float32) / 255.0, axis=-1) # [60000, 28, 28, 1]
    self.test_data = np.expand_dims(self.test_data.astype(np.float32) / 255.0, axis=-1) # [10000, 28, 28, 1]
    self.train_label = self.train_label.astype(np.int32) # [60000]
    self.test_label = self.test_label.astype(np.int32) # [10000]
    self.num_train_data, self.num_test_data = self.train_data.shape[0], self.test_data.shape[0]

    def get_batch(self, batch_size):
    # 从数据集中随机取出batch_size个元素并返回
    index = np.random.randint(0, np.shape(self.train_data)[0], batch_size)
    return self.train_data[index, :], self.train_label[index]
  • 图像数据集的一种典型表示是 [图像数目,长,宽,色彩通道数] 的四维张量

    模型的构建: tf.keras.Model 和 tf.keras.layers

  • 引入非线性激活函数 ReLU

  • 输入一个向量(这里是拉直的 1×784 手写体数字图片),输出 10 维的向量,代表图片属于 0 到 9 的概率

  • 代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class MLP(tf.keras.Model):
    def __init__(self):
    super().__init__()
    self.flatten = tf.keras.layers.Flatten() # Flatten层将除第一维(batch_size)以外的维度展平 这也对应神经网络的输入层
    self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu) # 中间层
    self.dense2 = tf.keras.layers.Dense(units=10) # 输出层

    def call(self, inputs): # [batch_size, 28, 28, 1]
    x = self.flatten(inputs) # [batch_size, 784]
    x = self.dense1(x) # [batch_size, 100]
    x = self.dense2(x) # [batch_size, 10]
    output = tf.nn.softmax(x)
    return output
  • tf.nn.softmax: 归一化指数函数,将模型的原始输出归一化,使输出向量:

    • 该向量的每个元素属于 [0, 1]
    • 该向量所有元素之和为 1
  • 原本输入为 28*28 的图片(向量),通过 tf.keras.layers.Flatten() 将其展平为 784*1 的向量

    2.2.3. 模型的训练: tf.keras.losses 和 tf.keras.optimizer

  • tf.keras.losses 计算损失函数,tf.keras.optimizer 优化模型

  • 定义模型超参数——模型超参数是模型外部的配置,其值不能从数据估计

    1
    2
    3
    num_epochs = 5
    batch_size = 50
    learning_rate = 0.001
  • 实例化模型和数据读取类,并实例一个优化器

    1
    2
    3
    model = MLP()
    data_loader = MNISTLoader()
    optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) # Adam优化器
  • 迭代:

    • DataLoader 中随机取一批训练数据送入模型,计算出模型的预测值
    • 模型预测值与真实值进行比较,计算损失函数(loss)——使用交叉熵
    • 计算损失函数关于模型变量的导数
    • 导数值传入优化器,使用 apply_gradients 更新模型参数以最小化损失函数
  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    num_batches = int(data_loader.num_train_data // batch_size * num_epochs)
    for batch_index in range(num_batches):
    X, y = data_loader.get_batch(batch_size)
    with tf.GradientTape() as tape:
    y_pred = model(X)
    loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred) # 交叉熵函数,参数为模型预测值和真实标签值
    loss = tf.reduce_mean(loss)
    print("batch %d: loss %f" % (batch_index, loss.numpy()))
    grads = tape.gradient(loss, model.variables)
    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

    模型的评估: tf.keras.metrics

  • 测试集评估模型的性能:tf.keras.metrics 中的 SparseCategoricalAccuracy 评估器来评估性能,能够对模型预测的结果与真实结果进行比较,并输出预测正确的样本数占总样本数的比例

  • 代码

    1
    2
    3
    4
    5
    6
    7
    sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()  # 实例化一个评估器
    num_batches = int(data_loader.num_test_data // batch_size)
    for batch_index in range(num_batches):
    start_index, end_index = batch_index * batch_size, (batch_index + 1) * batch_size
    y_pred = model.predict(data_loader.test_data[start_index: end_index])
    sparse_categorical_accuracy.update_state(y_true=data_loader.test_label[start_index: end_index], y_pred=y_pred) # 参数为预测出的结果和真实结果
    print("test accuracy: %f" % sparse_categorical_accuracy.result()) # result() 输出评估指标值

    卷积神经网络 CNN

    预备知识

  • 结构类似于视觉系统的人工神经网络,包含一个或多个卷积层(Convolutional Layer)、池化层(Pooling Layer)和全连接层

    CNN中的卷积:
    $$ s(t) = (XW)(t) $$
    $$ s(i,j) = (X
    W)(i,j) = \sum_m \sum_n x(i+m,j+n)w(m,n) $$
    直观上,两个矩阵对应位置元素相乘,之后相加
    $W$为卷积核,也称感受野,$X$为输入
    有时会存在偏置

  • 卷积层+池化层(卷积层+卷积层)的组合可以在隐藏层出现很多次,最常见的 CNN 都是若干卷积层+池化层的组合

  • 输出层使用 Softmax 激活函数来做图像识别的分类

  • 卷积层:

    • 进行特征提取
    • 感受野深度和图像深度应当一致
    • 输入 3*4 ,核矩阵 2*2,则输出为 2*3
    • 对于卷积后的输出,一般会通过 ReLU 激活函数,将输出的张量中小于0的位置对应的元素值都变为0
    • 示例:
      ![](简单粗暴 TensorFlow 2/1.PNG)
      • 这里图片原本为 5*5,但增加了一个边界,边界元素为 0(+pad 1)。增加的圈数一般为经验值
      • 此处卷积步幅为2,感受野深度和图像深度为3——卷积核由三个矩阵组成
      • 两个卷积核的对应偏置不同
  • 池化层:

    • 对输入张量的各个子矩阵进行压缩,使特征图变小,并将特征压缩
    • 常见的池化标准有2个,MAX Pooling或者是Average Pooling。即取对应区域的最大值或者平均值作为池化后的元素值——将 n*n 的矩阵变成一个元素

      2.3.2. Keras 实现卷积神经网络

  • CNN 类与多层感知机 类相似

  • 代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class CNN(tf.keras.Model):
    def __init__(self):
    super().__init__()
    self.conv1 = tf.keras.layers.Conv2D(
    filters=32, # 卷积层神经元(卷积核)数目
    kernel_size=[5, 5], # 感受野大小
    padding='same', # padding策略(vaild 或 same)
    activation=tf.nn.relu # 激活函数
    )
    self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2) # 池化
    self.conv2 = tf.keras.layers.Conv2D(
    filters=64,
    kernel_size=[5, 5],
    padding='same',
    activation=tf.nn.relu
    )
    self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2) # 池化
    self.flatten = tf.keras.layers.Reshape(target_shape=(7 * 7 * 64,))
    self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu)
    self.dense2 = tf.keras.layers.Dense(units=10)

    def call(self, inputs):
    x = self.conv1(inputs) # [batch_size, 28, 28, 32]
    x = self.pool1(x) # [batch_size, 14, 14, 32]
    x = self.conv2(x) # [batch_size, 14, 14, 64]
    x = self.pool2(x) # [batch_size, 7, 7, 64]
    x = self.flatten(x) # [batch_size, 7 * 7 * 64]
    x = self.dense1(x) # [batch_size, 1024]
    x = self.dense2(x) # [batch_size, 10]
    output = tf.nn.softmax(x)
    return output

    2.3.3. Keras 中预定义的卷积神经网络

  • tf.keras.applications 中有预定义好的经典卷积神经网络结构,如 VGG16VGG19ResNetMobileNet

  • 实例化 MobileNet(参数均取默认值):

    1
    model = tf.keras.applications.MobileNetV2()
  • 一些通用的模型参数:

    • input_shape :输入张量的形状(不含第一维的 Batch)
    • include_top :网络是否包含全连接层,默认为 True
    • weights :预训练权值,默认为 'imagenet',即模型载入在 ImageNet 数据集上预训练的权值。如需随机初始化变量,设为 None
    • classes :分类数(类似于输出层的神经元数目),默认为 1000。修改时需要 include_top 参数为 Trueweights 参数为 None

      循环神经网络 RNN

      预备知识

  • 适合处理序列数据——语言模型、文本生成、机器翻译——对于一个序列,当前的输出与前面的输出也有关

  • 隐藏层之间的节点有连接,隐藏层的输入来自输入层的输出和上一时刻隐藏层的输出
    ![](简单粗暴 TensorFlow 2/2.PNG)

    $$s_t = f(Us_t + Wx_{t-1}) $$

  • RNN 对于长时记忆的困难主要来自梯度爆炸/梯度消失

    2.4.2. 示例:RNN 自动生成尼采风格文本

  • 设置 DataLoader 类读取文本,以字符为单位进行编码:字符种类数为 num_chars,每种字符赋予一个唯一整数编号 i(0 到 num_chars-1)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class DataLoader():
    def __init__(self): # 创建对象会自动调用初始化方法__init__
    path = tf.keras.utils.get_file('nietzsche.txt',
    origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt') # 从远程网址下载数据,并保存到文件中
    # tf.keras.utils.get_file(fname=TRAIN_URL.split('/')[-1],orgin=TRAIN_URL, cache_dir='.') fname:文件命名;origin:文件的url地址;cache_dir:存储地址,此处指存储到当前目录下,默认为:C:\Users\ASUS\.keras\datasets
    with open(path, encoding='utf-8') as f:
    self.raw_text = f.read().lower()
    self.chars = sorted(list(set(self.raw_text))) # set()创建一个无序不重复元素集
    # list()将元组转换为列表
    # sorted()对所有可迭代的对象从小到大排序
    self.char_indices = dict((c, i) for i, c in enumerate(self.chars)) # enumerate()将可遍历数据对象组合为一个索引序列,序列的元素为数据和数据对应下标,即(0,数据0),(1,数据1)
    # dict() 转为字典,如{'a': 0, 'h': 1}
    self.indices_char = dict((i, c) for i, c in enumerate(self.chars))
    self.text = [self.char_indices[c] for c in self.raw_text] # 根据字典的索引找到对应数字,如'ah'变为[1,0]

    def get_batch(self, seq_length, batch_size):
    seq = []
    next_char = []
    for i in range(batch_size):
    index = np.random.randint(0, len(self.text) - seq_length)
    seq.append(self.text[index:index+seq_length])
    next_char.append(self.text[index+seq_length])
    return np.array(seq), np.array(next_char) # [batch_size, seq_length], [num_batch] np即numpy
  • 模型的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class RNN(tf.keras.Model):
    def __init__(self, num_chars, batch_size, seq_length):
    super().__init__()
    self.num_chars = num_chars
    self.seq_length = seq_length
    self.batch_size = batch_size
    self.cell = tf.keras.layers.LSTMCell(units=256) # 实例化LSTMCell
    self.dense = tf.keras.layers.Dense(units=self.num_chars) # 实例化全连接层

    def call(self, inputs, from_logits=False):
    inputs = tf.one_hot(inputs, depth=self.num_chars) # [batch_size, seq_length, num_chars]
    # 编码:独热码,序列转为[seq_length,num_chars] 张量
    state = self.cell.get_initial_state(batch_size=self.batch_size, dtype=tf.float32) # 初始化cell状态
    for t in range(self.seq_length): # 将序列送入RNN单元
    output, state = self.cell(inputs[:, t, :], state) # 当前
    logits = self.dense(output) # 取最后一次的输出
    if from_logits:
    return logits
    else:
    return tf.nn.softmax(logits)
  • 训练:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 超参数设置
    num_batches = 1000
    seq_length = 40
    batch_size = 50
    learning_rate = 1e-3
    # 训练
    data_loader = DataLoader()
    model = RNN(num_chars=len(data_loader.chars), batch_size=batch_size, seq_length=seq_length)
    optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
    for batch_index in range(num_batches): # 迭代
    X, y = data_loader.get_batch(seq_length, batch_size) # 取数据
    with tf.GradientTape() as tape: # 送入模型,计算损失函数
    y_pred = model(X)
    loss = tf.keras.losses.sparse_categorical_crossentropy(y_true=y, y_pred=y_pred)
    loss = tf.reduce_mean(loss)
    print("batch %d: loss %f" % (batch_index, loss.numpy()))
    grads = tape.gradient(loss, model.variables) # 关于求导
    optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables)) # 优化器更新模型
  • 预测与生成文本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def predict(self, inputs, temperature=1.): # temperature参数控制分布的形状,参数越大分布越平缓(最大值和最小值差值小)
    batch_size, _ = tf.shape(inputs)
    logits = self(inputs, from_logits=True)
    prob = tf.nn.softmax(logits / temperature).numpy()
    return np.array([np.random.choice(self.num_chars, p=prob[i, :])
    for i in range(batch_size.numpy())])

    X_, _ = data_loader.get_batch(seq_length, 1)
    for diversity in [0.2, 0.5, 1.0, 1.2]:
    X = X_
    print("diversity %f:" % diversity)
    for t in range(400):
    y_pred = model.predict(X, diversity)
    print(data_loader.indices_char[y_pred[0]], end='', flush=True)
    X = np.concatenate([X[:, 1:], np.expand_dims(y_pred, axis=1)], axis=-1)
    print("\n")

    TensorFlow 常用模块

    预备知识:Python迭代器

  • 迭代器是一个可以记住遍历位置的对象,有两个基本的方法:iter()next()

  • 每调用一次 next() 返回下一个元素,若下一个元素不存在返回 StopIteration 异常

    1
    2
    3
    4
    5
    6
    s = 'ab'
    it = iter(s)
    print(it.__next__())
    'a'
    print(it.__next__())
    'b'

    tf.train.Checkpoint类 :变量的保存与恢复

  • 方法 save()restore() 保存和恢复 Checkpointable State 的对象——tf.keras.optimizertf.Variabletf.keras.Layer 或者 tf.keras.Model 实例

    保存

  • 只保存模型的参数,不保存模型的计算过程

  • tf.train.Checkpoint() 的实例,接受的初始化参数是一个 **kwargs,即一系列的键值对。键名随意取,值为需要保存的对象。恢复变量的时候,还将使用同一键名

    1
    2
    3
    # 举例:
    checkpointA = tf.train.Checkpoint(model=model) # 实例化
    checkpointB = tf.train.Checkpoint(myAwesomeModel=model, myAwesomeOptimizer=optimizer)
  • 模型训练后需要保存时:checkpointA.save('保存文件目录+前缀')

    1
    2
    3
    4
    5
    6
    7
    # 具体框架如下:
    model = MyModel()
    # 实例化Checkpoint,指定保存对象为model
    checkpoint = tf.train.Checkpoint(myModel=model)
    # ...(模型训练代码)
    # 模型训练完毕后将参数保存到文件(也可在模型训练过程中每隔一段时间就保存一次)
    checkpoint.save('./save/model.ckpt')

    3.2.2. 载入

  • 为模型重新载入之前保存的参数时,再次实例化一个 checkpoint,并保持键名一致

  • 例如:

    1
    2
    3
    model_to_be_restored = MyModel()                                        # 待恢复参数的同一模型
    checkpoint = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored) # 键名保持为“myAwesomeModel”
    checkpoint.restore(save_path_with_prefix_and_index) # 之前保存的文件的目录 + 前缀 + 编号,即:checkpoint.restore('./save/model.ckpt-1') ,载入前缀为 model.ckpt,序号为 1 的文件来恢复模型
  • 载入最新的模型: tf.train.latest_checkpoint(save_path)

    删除旧的 Checkpoint

  • tf.train.CheckpointManager

    • 训练过程产生大量 Checkpoint,但只想保留最后的几个
    • 使用别的编号方式(例如使用当前 Batch 的编号作为文件编号
  • 在定义 Checkpoint 后定义一个 CheckpointManager

    1
    2
    checkpoint = tf.train.Checkpoint(model=model)
    manager = tf.train.CheckpointManager(checkpoint, directory='./save', checkpoint_name='model.ckpt', max_to_keep=k)
  • directory 为文件保存的路径, checkpoint_name 为文件名前缀(默认为ckpt), max_to_keep 为保留的 Checkpoint 数目

  • manager.save()

    • 保存模型
    • 自行指定保存的 Checkpoint 的编号——加入 checkpoint_number 参数,如 manager.save(checkpoint_number=100)

      TensorBoard:训练过程可视化

  • 代码目录下建立文件夹(如 ./tensorboard )存放记录文件,并实例化一个记录器:

    1
    summary_writer = tf.summary.create_file_writer('./tensorboard')     # 参数为保存的目录
  • with 语句指定使用的记录器,并记录参数在 step 时候的值: tf.summary.scalar(name, tensor, step=batch_index)(参数为标量时,用 scalar() )。运行一次 tf.summary.scalar() ,记录器向记录文件中写入一条记录

  • 一般设置 step 为当前训练过程中的 batch 序号。代码如下

    1
    2
    3
    4
    5
    6
    7
    summary_writer = tf.summary.create_file_writer('./tensorboard')
    # 模型训练
    for batch_index in range(num_batches):
    # ...(训练代码,变量loss存储batch的损失值)
    with summary_writer.as_default(): # 希望使用的记录器
    tf.summary.scalar("loss", loss, step=batch_index)
    tf.summary.scalar("MyScalar", my_scalar, step=batch_index)
  • 代码目录打开终端,运行 tensorboard --logdir=./tensorboard,访问命令行所输出的网址

    tf.data :数据集的构建与预处理

    数据集对象的建立

  • tf.data 的核心是 tf.data.Dataset 类,由一系列可迭代访问的元素组成——元素包含一个或多个张量(如:一个 长*宽*通道数 的图片张量或图片张量与标签张量组成的元组)

  • tf.data.Dataset.from_tensor_slices() 适用于数据能整个装入内存的情况——所有张量可拼接成一个大的张量

    • 如[60000,28,28,1](60000张 28*28的单通道灰度图像),此时数据集元素数量和第0维大小相同,即60000
    • 多个张量作为输入时,第0维必须相同,且必须将张量拼接成元组
      1
      2
      3
      4
      5
      6
      7
      8
      import tensorflow as tf

      X = tf.constant([2013, 2014, 2015, 2016, 2017])
      Y = tf.constant([12000, 14000, 15000, 16500, 17500]) # 创建常量张量

      dataset = tf.data.Dataset.from_tensor_slices((X, Y)) # 形成[5,2]的张量
      for x, y in dataset:
      print(x.numpy(), y.numpy())
  • 下载keras的datasets中得数据: data = tf.keras.datasets.mnist.load_data()

  • 对特别巨大而无法完整载入内存的数据集,可以先将数据集处理为 TFRecord 格式

    数据集对象的预处理

  • tf.data.Dataset 类中常用的处理方法:

    • Dataset.map(f) :数据集的每个元素应用函数 f ,得到一个新数据集
    • Dataset.shuffle(buffer_size) :将数据集打乱(设定一个固定大小的缓冲区(Buffer),取出前 buffer_size 个元素放入,从缓冲区中随机采样,采样后的数据输出,对应空缺位置补后续数据,重复采样)
    • Dataset.batch(batch_size) :将数据集分成批次——每 batch_size 个元素在第 0 维合并,成为一个元素,三维图片张量变为四维张量

      image = tf.image.rot90(image) 将图片image逆时针旋转90度

      数据集元素的获取与使用

  • for 循环迭代

  • iter() 显式创建 Python 迭代器,next() 获取下一个元素

    1
    2
    3
    4
    dataset = tf.data.Dataset.from_tensor_slices((A, B, C, ...))
    it = iter(dataset)
    a_0, b_0, c_0, ... = next(it)
    a_1, b_1, c_1, ... = next(it)

    3.4.4. TFRecord :TensorFlow 数据集存储格式

  • 可视为一组序列化的 tf.train.Example 元素所组成的列表文件,tf.train.Example 又由若干个 tf.train.Feature 的字典组成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [
    { # example 1 (tf.train.Example)
    'feature_1': tf.train.Feature,
    ...
    'feature_k': tf.train.Feature
    },
    ...
    { # example N (tf.train.Example)
    'feature_1': tf.train.Feature,
    ...
    'feature_k': tf.train.Feature
    }
    ]
  • 存储为TFRecord:

    • 读入元素到内存
    • 建立 Feature 字典,再建立 tf.train.Feature 的字典,并将元素转换为tf.train.Example对象
    • 序列化为字符串,通过 tf.io.TFRecordWriter 写入文件
  • 读取TFRecord:

    • tf.data.TFRecordDataset 读入原始文件,获得 tf.data.Dataset 对象
    • Dataset.map(f)tf.io.parse_single_example,反序列化 tf.train.Example 字符串
  • 存储:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    with tf.io.TFRecordWriter(tfrecord_file) as writer: # tfrecord_file 为保存的目录
    for filename, label in zip(train_filenames, train_labels):
    image = open(filename, 'rb').read() # 读取数据集图片到内存,image 为一个 Byte 类型的字符串
    feature = { # 建立 tf.train.Feature 字典
    'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])), # 图片是一个 Bytes 对象
    'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label])) # 标签是一个 Int 对象
    }
    example = tf.train.Example(features=tf.train.Features(feature=feature)) # 通过字典建立 Example
    writer.write(example.SerializeToString()) # 将Example序列化并写入 TFRecord 文件
  • 读取:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    raw_dataset = tf.data.TFRecordDataset(tfrecord_file)    # 读取 TFRecord 文件

    feature_description = { # 类似于数据集的 “描述文件”,定义Feature结构,告诉解码器每个Feature的类型是什么
    'image': tf.io.FixedLenFeature([], tf.string),
    'label': tf.io.FixedLenFeature([], tf.int64),
    }

    def _parse_example(example_string): # 将 TFRecord 文件中的每一个序列化的 tf.train.Example 解码
    feature_dict = tf.io.parse_single_example(example_string, feature_description)
    feature_dict['image'] = tf.io.decode_jpeg(feature_dict['image']) # 解码
    return feature_dict['image'], feature_dict['label']

    dataset = raw_dataset.map(_parse_example)

    TensorFlow 模型导出

  • 将训练好的整个模型完整导出为一系列标准格式的文件,以在不同的平台上部署模型

SavedModel 完整导出模型

  • SavedModel 包含了 TensorFlow 程序的完整信息:参数的权值和计算的流程

  • 使用继承 tf.keras.Model 类建立的 Keras 模型,其需要导出到 SavedModel 格式的方法(比如 call )都需要使用 @tf.function 修饰

  • 导出

    1
    tf.saved_model.save(model, "保存的目标文件夹名称")
  • 导入

    1
    model = tf.saved_model.load("保存的目标文件夹名称")
  • 继承 tf.keras.Model 类建立的 Keras 模型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class MLP(tf.keras.Model):
    def __init__(self):
    super().__init__()
    self.flatten = tf.keras.layers.Flatten()
    self.dense1 = tf.keras.layers.Dense(units=100, activation=tf.nn.relu)
    self.dense2 = tf.keras.layers.Dense(units=10)

    @tf.function # 转化为 SavedModel 支持的计算图
    def call(self, inputs): # [batch_size, 28, 28, 1]
    x = self.flatten(inputs) # [batch_size, 784]
    x = self.dense1(x) # [batch_size, 100]
    x = self.dense2(x) # [batch_size, 10]
    output = tf.nn.softmax(x)
    return output

    model = MLP()

Keras自有的模型导出格式

  • 导出

    1
    model.save('mnist_cnn.h5')
  • 导入

    1
    keras.models.load_model("mnist_cnn.h5")