• TensorFlow 2.x


    一、TensorFlow层次结构

    总共五层:硬件层、内核层、低阶API,中阶API,高阶API。
    低阶API:由python实现的操作符,提供封装了C++内核的低级API指令
    中阶API:由python实现的模型组件。主要包括数据管道tf.data,特征列tf.feature_column,激活函数tf.nn,模型层tf.keras.layers,损失函数tf.keras.losses,评估函数tf.keras.metrics,优化器tf.keras.optimizer,回调函数tf.keras.callbacks。
    高阶:由python实现的模型成品,主要为tf.keras.models提供的模型的类接口,主要包括:
    模型的构建(Sequential、functional API、Model子类化)、
    模型的训练(内置fit方法、内置train_on_batch方法、自定义训练循环、单GPU训练模型、多GPU训练模型、TPU训练模型)、
    模型的部署(tensorflow serving部署模型、使用spark(scala)调用tensorflow模型)。
    在这里插入图片描述

    二、基础

    2.1 创建张量

    2.1.2 张量的创建方式

    # 常数
    tf.constant(value, dtype = tf.float32)
    a = tf.constant([1,2], dtype = tf.float32)
    # 生成间隔为delta的张量
    tf.range(start, limit = end, delta = 1) # [start, end - 1]
    a = tf.range(0, 50)  # type(a) tensorflow.python.framework.ops.EagerTensor
    # 生成间隔均匀的张量
    tf.linspace(start, stop, num)
    # 生成num个全0张量
    
    
    tf.range(0, 10, 5) # numpy=array([0, 5], dtype=int32)
    tf.linspace(0, 10, 5) # numpy=array([ 0. ,  2.5,  5. ,  7.5, 10. ])
    
    
    tf.zeros(num)
    # 生成Num个全1张量
    tf.ones(num)
    # 生成和input一样大小的全0张量
    tf.zeros_like(input)
    # 生成shape为dim,全为value的张量
    tf.fill(dims, value)
    
    import numpy as np
    a = np.array(((1,2,3),(4,5,6)))
    tf.zeros_like(a)
    #
    
    # 基于分布生成随机数
    tf.random.uniform([5], minval=0, maxval=10)     # 均匀分布随机
    tf.random.normal([3, 3], mean=0.0, stddev=1.0)  # 正态分布随机
    
    tf.Variable(initial_value)  # 变量,trainable:设置为False可以关闭梯度计算
    a = tf.Variable(5)
    a.assign(4) #重新赋值
    a.assign_sub(4) # 减
    a.assign_add(4) # 加
    
    tf.rank(a) #求矩阵的秩
    
    • 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
    • 39

    通过tf.Variable构造一个variable添加进图中,Variable()构造函数需要变量的初始值(是一个任意类型、任意形状的tensor),这个初始值指定variable的类型和形状。通过Variable()构造函数后,此variable的类型和形状固定不能修改了,但值可以用assign方法修改。
    如果想修改variable的shape,可以使用一个assign op,令validate_shape=False.
    通过Variable()生成的variables就是一个tensor,可以作为graph中其他op的输入。
    Tensor类重载的所有操作符都被转载到此variables中,所以可以通过对变量调用方法,将节点添加到图形中。

    2.1.2 维度变换

    tf.reshape():改变张量的形状
    tf.squeeze():减少维度为1的维度
    tf.expand_dims(input, axis):增加维度
    tf.transpose(a, perm=Non):交换维度

    a = tf.Variable(((1,2,3,8),(4,5,6,9)))
    print('a', a.shape, a)
    b = tf.reshape(b,(4,2))
    print('b', b.shape, b)
    c = tf.reshape(b,(2,4,1))
    print('c', c.shape, c)
    d = tf.squeeze(c)
    print('d', d.shape, d)
    e = tf.expand_dims(d,0)
    print('e',e.shape, e)
    f = tf.transpose(e, perm = (0,2,1))
    print('f', f.shape, f)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.1.3 合并分割

    tf.concat([a,b], axis):连接张量,不会增加维度
    tf.stack([a,b], axis):堆叠,会增加维度,是指在axis进行堆叠
    tf.split(input, axis):分割张量

    a = tf.Variable([[1,1,1,1],[1,1,1,1]])
    b = tf.Variable([[0,0,0,0],[0,0,0,0]])
    c = tf.concat([a,b], 0) #(4,4)
    d = tf.concat([a,b], 1) #(2,8)
    e = tf.stack([a,b],0) #(2,2,4)
    [[[1 1 1 1]
      [1 1 1 1]]
    
     [[0 0 0 0]
      [0 0 0 0]]]
    f = tf.stack([a,b],1) #(2,2,4)
    [[[1 1 1 1]
      [0 0 0 0]]
    
     [[1 1 1 1]
      [0 0 0 0]]]
    g = tf.stack([a,b], 2) #(2,4,2)
    [[[1 0]
      [1 0]
      [1 0]
      [1 0]]
    
     [[1 0]
      [1 0]
      [1 0]
      [1 0]]]
    
    a1 = tf.split(g,2, axis=2)
    a1,a2 = tf.split(g,2, axis=2)
    a1 = tf.squeeze(a1)
    a2 = tf.squeeze(a2)
    tf.Tensor([[1 1 1 1]
     [1 1 1 1]], shape=(2, 4), dtype=int32) 
     tf.Tensor([[0 0 0 0]
     [0 0 0 0]], shape=(2, 4), dtype=int32)
    
    • 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

    2.1.3 Tensor和Array转换

    #tf->array
    array_a1 = np.array(a1)
    array_a1 = a1.numpy()
    # array->tf
    tensor_a1 = tf.convert_to_tensor(array_a1)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.1.4 数学运算

    tf.add(a,b) # 加法 尝试了a数组,b张量,也可以计算
    tf.multiply(a,b) # 逐元素乘法
    tf.matmul(a,b) # 矩阵乘法
    
    • 1
    • 2
    • 3

    2.1.5 类型转换

    tensorflow支持的模型有:tf.float16、tf.float64、tf.int8、tf.int16、tf.int32…

    b = tf.Variable([[2,3,4,5],[6,7,8,9]], dtype=tf.float64)
    a = tf.cast(b, dtype = tf.int32)
    
    • 1
    • 2

    2.2 计算图

    TensorFlow有三种计算图的构建方式:静态计算图、动态计算图以及Autograph。
    TensorFlow1.0,用的是静态计算图。先使用TensorFlow的各种算子创建计算图,然后再开启一个会话Session,显式执行计算图。
    TensorFlow2.0,用的是动态计算图。每使用一个算子后,该算子会被动态加入到隐含的默认计算图中立即执行得到结果,而无需开启Session。程序方便调试,但是运行效率相对低一些。 如果TensorFlow2.0要使用静态图,可以使用@tf.function装饰器,将普通python函数转换成对应TensorFlow计算图构建代码。但是需要注意

    1. @tf.function修饰的函数应尽可能使用TensorFlow中的函数而不是Python中的其他函数。例如使用tf.print而不是print,使用tf.range而不是range,使用tf.constant(True)而不是True.
    2. 避免在@tf.function修饰的函数内部定义tf.Variable
    3. @tf.function修饰的函数不可修改该函数外部的列表或字典等数据结构变量

    计算图由**节点(nodes)线(edges)**组成。节点表示操作符Operator,或者称之为算子,线便是计算之间的以来:数据传递、控制依赖(即先后顺序)
    在这里插入图片描述

    @tf.function
    def strjoin(x,y):
        z = tf.strings.join([x, y], separator = " ")
        tf.print(z)
        return z
    
    result = strjoin(tf.constant("hello"), tf.constant("world"))
    print(result)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    静态图、动态图性能差异测试

    import timeit
    x = tf.random.uniform(shape=[10, 10], minval=-1, maxval=2, dtype=tf.dtypes.int32)
    
    def power(x, y):
      result = tf.eye(10, dtype=tf.dtypes.int32)
      for _ in range(y):
        result = tf.matmul(x, result)
      return result
    
    print("Eager execution:", timeit.timeit(lambda: power(x, 100), number=1000))        
    
    # 将python函数转换为图形
    power_as_graph = tf.function(power)
    print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
    # Eager execution: 5.905397668480873
    # Graph execution: 1.1285493150353432
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    也可以使用@tf.function调用tf.function。还可以使用tf.config.run_functions_eagerly(True)关闭Function创建和运行图形能力。

    在构建计算Autograph应避免在@tf.function内部定义tf.Variable。但是如果在函数外部定义的话,这个函数对外部又有变量依赖,可以用类的方式,将tf.Variable在类的初始化使用。

    class DemoModule(tf.Module):
        def __init__(self, init_value=tf.constant(0.0), name=None):
            super(DemoModule, self).__init__(name=name)
            with self.name_scope:  # 相当于with tf.name_scope("demo_module")
                self.x = tf.Variable(init_value, dtype=tf.float32, trainable=True)
    
        @tf.function
        def addprint(self, a):
            with self.name_scope:
                self.x.assign_add(a)
                tf.print(self.x)
                return self.x
    
    a = DemoModule(init_value = 10)
    a.addprint(10)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.3 自动微分

    自动微分主要用神经网络训练的反向传播。TensorFlow 会记住在前向传递过程中哪些运算以何种顺序发生。随后,在后向传递期间,以相反的顺序遍历此运算列表来计算梯度。
    TensorFlow一般使用tf.GradientTape来记录正向计算过程,然后反向计算自动计算梯度值。

    2.3.1 一阶导数求法

    # 全部都要打小数点
    x = tf.Variable(0.0, name = 'x', dtype = tf.float32)
    a = tf.constant(1.0)
    b = tf.constant(-2.0)
    c = tf.constant(1.0)
    
    with tf.GradientTape() as tape:
        y = a * tf.pow(x, 2) + b * x + c
    
    dy_dx = tape.gradient(y, x)
    print(dy_dx)
    
    # tf.Tensor(-2.0, shape=(), dtype=float32)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    对常量张量求导,加watch

    with tf.GradientTape() as tape:
        tape.watch([a,b,c])
        y = a * tf.pow(x, 2) + b * x + c
    dy_dx, dy_da, dy_db, dy_dc = tape.gradient(y, [x, a, b, c])
    print(dy_da)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3.2 二阶导数求法

    # 全部都要打小数点
    x = tf.Variable(0.0, name = 'x', dtype = tf.float32)
    a = tf.constant(1.0)
    b = tf.constant(-2.0)
    c = tf.constant(1.0)
    
    with tf.GradientTape() as tape2:
        with tf.GradientTape() as tape:
            y = a * tf.pow(x, 2) + b * x + c
        dy_dx = tape.gradient(y, x)
    dy2_dx2 = tape2.gradient(dy_dx, x)
    print(dy_dx)
    print(dy2_dx2)
    
    # tf.Tensor(-2.0, shape=(), dtype=float32)
    # tf.Tensor(2.0, shape=(), dtype=float32)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.3.3 利用梯度和优化器求最小值

    # 全部都要打小数点
    x = tf.Variable(0.0, name = 'x', dtype = tf.float32)
    a = tf.constant(1.0)
    b = tf.constant(-2.0)
    c = tf.constant(1.0)
    
    optimizer = tf.keras.optimizers.SGD(learning_rate = 0.01)
    
    for _ in range(1000):
        with tf.GradientTape() as tape:
            y = a * tf.pow(x, 2) + b * x + c
        dy_dx = tape.gradient(y, x)
        optimizer.apply_gradients(grads_and_vars=[(dy_dx,x)])
    
    tf.print("y = ", y, "x = ", x)
    
    # y =  0 x =  0.99999851
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    不想被梯度计算,可以设置

    with tf.GradientTape(watch_accessed_variables=False) as tape:
        pass
    
    • 1
    • 2

    三、TensorFlow实现神经网络模型的一般流程

    1. 准备数据
    2. 定义模型
    3. 训练模型
    4. 评估模型
    5. 推理模型
    6. 保存模型

    四、数据输入

    4.1 支持格式

    支持Numpy数组、Pandas,Python生成器、csv文件、文本文件、文件路径、TFrecords文件等方式构建数据管道。
    如果数据很小并且适合内存,建议使用tf.data.Dataset.from_tensor_slices()从Numpy array构建数据管道。

    • Dataset: 有大型数据集并且需要进行分布式训练
    • Sequence: 有大型数据集并且需要执行大量在 TensorFlow 中无法完成的自定义 Python 端处理(例如,如果您依赖外部库进行数据加载或预处理)
    • 通过tfrecords文件方式构建数据管道较为复杂,需要对样本构建tf.Example后压缩成字符串写到tfrecoreds文件,读取后再解析成tf.Example。但tfrecoreds文件的优点是压缩后文件较小,便于网络传播,加载速度较快。

    4.2 数据集构建

    4.2.1 Numpy构建数据

    官方推荐使用 tf.data.Dataset.from_tensors()tf.data.Dataset.from_tensor_slices() 创建数据集,Dataset支持一类特殊的操作Trainformation(打乱、生成epoch…等操作)

    data.map(function):将转换函数映射到数据集每一个元素
    data.batch(batch_size):构建batch
    data.shuffle(buffer_size):随机打乱输入数据,从该缓冲区中随机采样元素
    data.repeat():repeat的功能就是将整个序列重复多次,一般不带参数
    data.prefetch(tf.data.experimental.AUTOTUNE)  :预先取 数据
    data.take():采样,从开始位置取前几个元素
    
    ## 举例
    features = np.arange(0, 100, dtype=np.int32)    # # (100,)
    labels = np.zeros(100, dtype=np.int32)  # (100,)
    
    data = tf.data.Dataset.from_tensor_slices((features, labels))   # 创建数据集
    data = data.repeat()    # 无限期地补充数据
    data = data.shuffle(buffer_size=100)    # 打乱数据
    data = data.batch(batch_size=4)     # 批量数据
    data = data.prefetch(buffer_size=1)     # 预取批处理(预加载批处理,消耗更快)
    
    for batch_x, batch_y in data.take(5):
        print(batch_x.shape, batch_y.shape)     # (4,) (4,)
        break
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    如果多次调用,可以使用迭代器的方式

    ite_data = iter(data)
    for i in range(5):
    batch_x, batch_y = next(ite_data)
    print(batch_x, batch_y)
    
    • 1
    • 2
    • 3
    • 4

    4.2.2 提升管道性能的方法

    1. 使用 prefetch 方法让数据准备和参数迭代两个过程相互并行。
    2. 使用 interleave 方法可以让数据读取过程多进程执行,并将不同来源数据夹在一起。
    3. 使用 map 时设置num_parallel_calls 让数据转换过程多进行执行。
    4. 使用 cache 方法让数据在第一个epoch后缓存到内存中,仅限于数据集不大情形。
    5. 使用 map转换时,先batch,然后采用向量化的转换方法对每个batch进行转换。

    4.3 生成器构建数据

    4.3.1 代码生成

    def generate_features():
        # 函数生成一个随机字符串
        def random_string(length):
            return ''.join(random.choice(string.ascii_letters) for m in range(length))
        # 返回一个随机字符串、一个随机向量和一个随机整数
        yield random_string(4), np.random.uniform(size=4), random.randint(0, 10)
    
    
    data = tf.data.Dataset.from_generator(generate_features, output_types=(tf.string, tf.float32, tf.int32))
    data = data.repeat()        # 无限期地补充数据
    data = data.shuffle(buffer_size=100)    # 打乱数据
    data = data.batch(batch_size=4)     # 批量数据(将记录聚合在一起)
    data = data.prefetch(buffer_size=1)     # 预取批量(预加载批量以便更快的消耗)
    
    # Display data.
    for batch_str, batch_vector, batch_int in data.take(5):
        # (4,) (4, 4) (4,)
        print(batch_str.shape, batch_vector.shape, batch_int.shape)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.3.2 keras.utils.Sequence

    keras.utils.Sequence该类提供了一个简单的接口来构建 Python 数据生成器,该生成器可以感知多处理并且可以洗牌。(pytorch也有类似的类)
    Sequence必须实现两种方法:
    getitem:返回一个batch数据
    len:整型,返回batch的数量

    import tensorflow as tf
    from keras.utils.data_utils import Sequence
    
    class SequenceDataset(Sequence):
        def __init__(self, batch_size):
            self.input_data = tf.random.normal((640, 8192, 1))
            self.label_data = tf.random.normal((640, 8192, 1))
            self.batch_size = batch_size
    
        def __len__(self):
            return int(tf.math.ceil(len(self.input_data) / float(self.batch_size)))
    
        # 每次输出一个batch
        def __getitem__(self, idx):
            batch_x = self.input_data[idx * self.batch_size:(idx + 1) * self.batch_size]
            batch_y = self.label_data[idx * self.batch_size:(idx + 1) * self.batch_size]
            return batch_x, batch_y
    
    
    sequence = SequenceDataset(batch_size=64)
    
    for batch_idx, (x, y) in enumerate(sequence):
        print(batch_idx, x.shape, y.shape)  # tf.float32
        # 0 (64, 8192, 1) (64, 8192, 1)
        break
    
    • 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

    五、搭建模型

    深度学习模型一般由各种模型层组合而成,如果这些内置模型层不能够满足需求,可以通过编写tf.keras.Lambda匿名模型层或继承tf.keras.layers.Layer基类构建自定义的模型层。其中tf.keras.Lambda匿名模型层只适用于构造没有学习参数的模型层。

    搭建模型有以下3种方式构建模型:

    1. Sequential顺序模型:用于简单的层堆栈, 其中每一层恰好有一个输入张量和一个输出张量
    2. 函数式API模型:多输入多输出,或者模型需要共享权重,或者模型具有残差连接等非顺序结构
    3. 继承Model基类自定义模型:如果无特定必要,尽可能避免使用Model子类化的方式构建模型,这种方式提供了极大的灵活性,但也有更大的概率出错

    5.1 顺序建模keras.Sequential

    # 有点类似list方式
    model = keras.Sequential([
        layers.Dense(2, activation="relu", name="layer1"),
        layers.Dense(3, activation="relu", name="layer2"),
        layers.Dense(4, name="layer3"),
    ])
    
    # add方式
    model = keras.Sequential()
    model.add(layers.Dense(2, activation="relu"))
    model.add(layers.Dense(3, activation="relu"))
    model.add(layers.Dense(4))
    
    # 引入keras不成功的,可以用tensorflow.keras代替
    import tensorflow as tf
    from tensorflow.keras.models import Sequential
    from tensorflow.keras.layers import Dense
    
    model = Sequential()
    model.add(tf.keras.layers.Dense(2, activation="relu", input_shape=(4,)))
    tf.print(model.summary())
    model.add(tf.keras.layers.Dense(3, activation="relu"))
    tf.print(model.summary())
    model.add(tf.keras.layers.Dense(4))
    tf.print(model.summary())
    
    • 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

    在这里插入图片描述
    这个时候模型不知道输入shape,所以最开始模型没有权重,需要告知模型输入shape

    # 在第一层添加Input
    model.add(keras.Input(shape=(4,)))
    model.add(layers.Dense(2, activation="relu"))
    
    # 或者在第一层添加input_shape
    model.add(layers.Dense(2, activation="relu", input_shape=(4,)))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.2 函数式API建模

    函数式API搭建模型比Sequential更加灵活,可以处理具有非线性拓扑、共享层甚至多个输入或输出的模型。

    from tensorflow.keras.models import Sequential
    import tensorflow.keras.layers as layers
    import tensorflow.keras as keras
    
    
    inputs = keras.Input(shape=(784,))
    x = layers.Dense(64, activation = 'relu')(inputs)
    x = layers.Dense(64, activation = 'relu')(x)
    outputs = layers.Dense(10)(x)
    model = keras.Model(inputs = inputs, outputs = outputs, name = "mnist_mode")
    model.summary()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    将模型绘制成图

    keras.utils.plot_model(model, "my_first_model_with_shape_info.png", show_shapes=True)
    
    • 1

    函数式模型可以嵌套

    5.3 自定义建模

    TensorFlow中,模型类的继承关系为:
    tf.keras.Model(模型) → \to tf.keras.layers.Layer(层) → \to tf.Module(模块)。
    通常使用Layer类来定义内部计算块,使用Model类来定义外部模型(训练的对象)
    tf.Module例子,主要用于创建模块:

    class CustomDense(tf.Module):
        def __init__(self, in_features, out_features, **kwargs):
            super().__init__(**kwargs)
            self.weight = tf.Variable(tf.random.normal([in_features, out_features]), name = 'weight')
            self.bias = tf.Variable(tf.random.normal([out_features]), name = 'bias')
            
        def __call__(self, x):
            x = tf.matmul(x, self.weight) + self.bias
            x = tf.nn.relu(x)
            return x
        
    model = CustomDense(in_features = 5, out_features = 10)
    x = tf.random.normal(shape=(32, 5))
    outputs = model(x)
    print(outputs.shape)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    继承tf.keras.layers.Layer例子,主要用于创建层:

    # 使用CustomDense 搭建一个模型
    class CustomLayer(tf.keras.layers.Layer):
        # 添加 **kwargs来支持基本Keras层参数
        def __init__(self, in_features, out_features, **kwargs):
            super().__init__(**kwargs)
            # 这将很快转移到 build ;见下代码
            self.weight = tf.Variable(tf.random.normal([in_features, out_features]), name='weight')
            self.bias = tf.Variable(tf.zeros([out_features]), name='bias')
    
        # Keras 层有自己的 __call__,他然后调用 call()
        def call(self, x):
            x = tf.matmul(x, self.weight) + self.bias
            x = tf.nn.relu(x)
            return x
    
    model = CustomLayer(in_features=5, out_features=10)
    x = tf.random.normal(shape=(64, 5))
    outputs = model(x)
    print("CustomLayer outputs", outputs.shape)  # (64, 10)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用build的例子

    class FlexibleDense(tf.keras.layers.Layer):
        # 添加 **kwargs来支持基本Keras层参数
        def __init__(self, out_features, **kwargs):
            super().__init__(**kwargs)
            self.out_features = out_features
    
        # 创建层的状态(权重)
        # 在call前会先调用 build,使得权重的形状固定下来,这样我们就不用操心输入的shape
        def build(self, input_shape):
            self.weight = tf.Variable(tf.random.normal([input_shape[-1], self.out_features]), name='weight')
            self.bias = tf.Variable(tf.zeros([self.out_features]), name='bias')
    
        def call(self, inputs):
            return tf.matmul(inputs, self.weight) + self.bias
    
    
    model = FlexibleDense(out_features=10)
    x = tf.random.normal(shape=(64, 5))
    outputs = model(x)
    print("FlexibleDense outputs", outputs.shape)  # (64, 10)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    继承tf.keras.Model的例子,主要用于创建模型

    class CustomModel(tf.keras.Model):
        def __init__(self, in_features=5, out_features=10, **kwargs):
            super().__init__(**kwargs)
            self.dense_1 = CustomLayer(in_features, out_features)
            self.dense_2 = CustomLayer(out_features, out_features)
    
        def call(self, x):
            x = self.dense_1(x)
            x = self.dense_2(x)
            return x
    
    
    model = CustomLayer(in_features=5, out_features=10)
    x = tf.random.normal(shape=(64, 5))
    outputs = model(x)
    print("CustomModel outputs", outputs.shape)  # (64, 10)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    keras层有很多额外的功能:

    • 可选损失函数
    • 对度量函数的支持
    • 对可选training参数的内置支撑,用于区分训练和推断用途
    • get_configfrom_config,允许准确配置,用于python中克隆莫模型。

    keras.Model类和keras.Layer类具有相同的API,但是有以下的区别:

    • Model类提供了内置的训练,model.fit()model.evaluate()model.predict()API
    • Model类 可以通过 model.layers 属性公开内层的列表。
    • Model类 提供了保存和序列化 API save()save_weights()

    下面是VAE例子,首先是模型构建,然后模型训练
    VAE模型构建

    import tensorflow as tf
    from tensorflow import keras
    from tensorflow.keras import layers
    
    
    class Sampling(layers.Layer):
        """Uses (z_mean, z_log_var) to sample z, the vector encoding a digit."""
        def call(self, inputs):
            z_mean, z_log_var = inputs
            batch = tf.shape(z_mean)[0]
            dim = tf.shape(z_mean)[1]
            epsilon = tf.keras.backend.random_normal(shape=(batch, dim))
            return z_mean + tf.exp(0.5 * z_log_var) * epsilon
    
    
    class Encoder(layers.Layer):
        """Maps MNIST digits to a triplet (z_mean, z_log_var, z)."""
        def __init__(self, latent_dim=32, intermediate_dim=64, name="encoder", **kwargs):
            super(Encoder, self).__init__(name=name, **kwargs)
            self.dense_proj = layers.Dense(intermediate_dim, activation="relu")
            self.dense_mean = layers.Dense(latent_dim)
            self.dense_log_var = layers.Dense(latent_dim)
            self.sampling = Sampling()
    
        def call(self, inputs):
            x = self.dense_proj(inputs)
            z_mean = self.dense_mean(x)
            z_log_var = self.dense_log_var(x)
            z = self.sampling((z_mean, z_log_var))
            return z_mean, z_log_var, z
    
    
    class Decoder(layers.Layer):
        """Converts z, the encoded digit vector, back into a readable digit."""
        def __init__(self, original_dim, intermediate_dim=64, name="decoder", **kwargs):
            super(Decoder, self).__init__(name=name, **kwargs)
            self.dense_proj = layers.Dense(intermediate_dim, activation="relu")
            self.dense_output = layers.Dense(original_dim, activation="sigmoid")
    
        def call(self, inputs):
            x = self.dense_proj(inputs)
            return self.dense_output(x)
    
    
    class VariationalAutoEncoder(keras.Model):
        """Combines the encoder and decoder into an end-to-end model for training."""
        def __init__(self, original_dim, intermediate_dim=64, latent_dim=32, name="autoencoder", **kwargs):
            super(VariationalAutoEncoder, self).__init__(name=name, **kwargs)
            self.original_dim = original_dim
            self.encoder = Encoder(latent_dim=latent_dim, intermediate_dim=intermediate_dim)
            self.decoder = Decoder(original_dim, intermediate_dim=intermediate_dim)
    
        def call(self, inputs):
            z_mean, z_log_var, z = self.encoder(inputs)
            reconstructed = self.decoder(z)
            # Add KL divergence regularization loss.
            kl_loss = -0.5 * tf.reduce_mean(
                z_log_var - tf.square(z_mean) - tf.exp(z_log_var) + 1
            )
            self.add_loss(kl_loss)  # 添加损失
            return reconstructed
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    模型训练,可以自己写训练过程,也可以使用内置的方法fit()

    original_dim = 784
    vae = VariationalAutoEncoder(original_dim, 64, 32)
    
    optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
    mse_loss_fn = tf.keras.losses.MeanSquaredError()
    
    loss_metric = tf.keras.metrics.Mean()
    
    (x_train, _), _ = tf.keras.datasets.mnist.load_data()
    x_train = x_train.reshape(60000, 784).astype("float32") / 255
    
    train_dataset = tf.data.Dataset.from_tensor_slices(x_train)
    train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)
    
    epochs = 2
    
    # Iterate over epochs.
    for epoch in range(epochs):
        print("Start of epoch %d" % (epoch,))
    
        # Iterate over the batches of the dataset.
        for step, x_batch_train in enumerate(train_dataset):
            with tf.GradientTape() as tape:
                reconstructed = vae(x_batch_train)
                # Compute reconstruction loss
                loss = mse_loss_fn(x_batch_train, reconstructed)
                loss += sum(vae.losses)  # Add KLD regularization loss
    
            grads = tape.gradient(loss, vae.trainable_weights)
            optimizer.apply_gradients(zip(grads, vae.trainable_weights))
    
            loss_metric(loss)
    
            if step % 100 == 0:
                print("step %d: mean loss = %.4f" % (step, loss_metric.result()))
    
    
    ## fit方法
    vae = VariationalAutoEncoder(784, 64, 32)
    
    optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
    
    vae.compile(optimizer, loss=tf.keras.losses.MeanSquaredError())
    vae.fit(x_train, x_train, epochs=2, batch_size=64)
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    自定义的kears.Model还可以和函数式API混搭训练(个人不建议,代码不优雅)
    PS:自定义层
    如果自定义模型层没有需要被训练的参数,一般推荐使用Lamda层实现。

    mypower = layers.Lambda(lambda x:tf.math.pow(x,2))
    mypower(tf.range(5))
    
    • 1
    • 2

    如果自定义模型层有需要被训练的参数,则可以通过继承Layer基类实现。Layer的子类化一般需要重新实现初始化方法,Build方法和Call方法。
    如果built = False,调用__call__时会先调用build方法初始化变量,并将built变量设为True 再调用call方法。这样在第二次调用__call__时将不再有变量被初始化。
    另外一个需要注意的地方在于:self.built = True
    built保证build() 方法只被调用一次

    六 损失函数

    一般来说,损失值 由 损失函数正则化项 组成(Objective = Loss + Regularization
    对于keras模型,正则化项一般在各层中指定,例如使用Dense的 kernel_regularizerbias_regularizer 等参数指定权重使用 L1 或者 L2 正则化项,此外还可以用kernel_constraintbias_constraint 等参数约束权重的取值范围,这也是一种正则化手段。
    损失函数在 model.compile 时候指定。

    • 对于回归模型,通常使用的损失函数是平方损失函数 mean_squared_error,简写为 mse,类实现形式为 MeanSquaredError 和 MSE
    • 对于二分类模型,通常使用的是二元交叉熵损失函数 binary_crossentropy。
    • 对于多分类模型,如果label是one-hot编码的,则使用交叉熵损失函数 categorical_crossentropy。如果label是序号编码的,则需要使用稀疏类别交叉熵损失函数 sparse_categorical_crossentropy。
    • 以自定义损失函数,自定义损失函数需要接收两个张量y_true,y_pred作为输入参数,并输出一个标量作为损失函数值。

    6.1 自定义损失函数

    6.1.1 函数实现:输入预测值和真实值,返回损失值

    def custom_mean_squared_error(y_true, y_pred):
        return tf.math.reduce_mean(tf.square(y_true - y_pred))
    model.compile(optimizer=..., loss=custom_mean_squared_error)
    
    • 1
    • 2
    • 3

    6.1.2 类实现:继承tf.keras.losses.Loss

    在init中初始化参数,在call中重写计算逻辑

    class CustomMSE(keras.losses.Loss):
        def __init__(self, regularization_factor=0.1, name="custom_mse"):
            super().__init__(name=name)
            self.regularization_factor = regularization_factor
    
        def call(self, y_true, y_pred):
            mse = tf.math.reduce_mean(tf.square(y_true - y_pred))
            reg = tf.math.reduce_mean(tf.square(0.5 - y_pred))
            return mse + reg * self.regularization_factor
    
    model.compile(optimizer=..., loss=CustomMSE())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    七、度量函数、评估函数

    通常会通过度量函数来从另一个方面评估模型的好坏,可以在 model.compile 时,通过列表形式指定单/多个评估指标。

    • 可以自定义评估指标。自定义评估指标需要接收两个张量y_true,y_pred作为输入参数,并输出一个标量作为评估值。
    • 可以继承 tf.keras.metrics.Metric 自定义度量方法,update_state方法,result方法实现评估指标的计算逻辑,从而得到评估指标的类的实现

    7.1 自定义度量

    继承 tf.keras.metrics.Metric,并且实现4个类方法:

    • init(self):创建状态变量
    • update_state(self, y_true, y_pred, sample_weight=None):根据 y_true 和 y_pred 来更新状态变量
    • result(self):使用状态变量来计算 最终结果
    • reset_state(self):重新初始化状态变量
    class SnrCustomMetrics(metrics.Metric):
        def __init__(self, name="snr_metrics", **kwargs):
            # 创建状态变量
            super(SnrCustomMetrics, self).__init__(name=name, **kwargs)
            self.metrics_value = self.add_weight(name="snr_value", initializer="zeros")
    
        def update_state(self, y_true, y_pred, sample_weight=None):
            """使用目标 y_true 和模型预测 y_pred 来更新状态变量
            :param y_true: target
            :param y_pred: 预测值
            :param sample_weight: 权重
            """
            labels_pow = tf.pow(y_true, 2)
            noise_pow = tf.pow(y_pred - y_true, 2)
            snr_value = 10 * tf.math.log(tf.reduce_sum(labels_pow) / tf.reduce_sum(noise_pow)) / tf.math.log(10.)
    
            if sample_weight is not None:
                sample_weight = tf.cast(sample_weight, "float32")
                snr_value = tf.multiply(snr_value, sample_weight)
            self.metrics_value.assign_add(snr_value)
    
        # 使用状态变量来计算最终结果
        def result(self):
            return self.metrics_value
    
        # metrics 的状态将在每个epoch开始时重置。
        def reset_state(self):
            self.metrics_value.assign(0.0)
    
    model.compile(optimizer=..., loss=..., metrics=[SnrCustomMetrics()])
    
    • 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

    八、优化器

    深度学习优化算法大概经历了 SGD -> SGDM -> NAG ->Adagrad -> Adadelta(RMSprop) -> Adam -> Nadam 这样的发展历程。
    model.compile(optimizer=optimizers.SGD(learning_rate=0.01), loss=loss)

    • SGD 默认参数为纯SGD, 设置momentum参数不为0实际上变成SGDM, 考虑了一阶动量, 设置 nesterov为True后变成NAG,即 Nesterov Acceleration Gradient,在计算梯度时计算的是向前走一步所在位置的梯度
    • Adagrad:考虑了二阶动量,对于不同的参数有不同的学习率,即自适应学习率。缺点是学习率单调下降,可能后期学习速率过慢乃至提前停止学习
    • Adagrad:考虑了二阶动量,对于不同的参数有不同的学习率,即自适应学习率。缺点是学习率单调下降,可能后期学习速率过慢乃至提前停止学习
    • Adadelta:考虑了二阶动量,与RMSprop类似,但是更加复杂一些,自适应性更强
    • Adam:同时考虑了一阶动量和二阶动量,可以看成RMSprop上进一步考虑了Momentum
    • Nadam:在Adam基础上进一步考虑了 Nesterov Acceleration

    优化器主要使用方法有

    1. apply_gradients方法传入变量和对应梯度从而来对给定变量进行迭代
    2. 直接使用minimize方法对目标函数进行迭代优化
    3. 在编译时将优化器传入model.fit
    # 第一种
    optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)
    
    with tf.GradientTape() as tape:
        ...
    grads = tape.gradient(loss, model.trainable_weights)  # 根据损失 求梯度
    optimizer.apply_gradients(zip(grads, model.trainable_weights))  # 根据梯度 优化模型
    
    # 第二种
    @tf.function
    def train(epoch=1000):
        for _ in tf.range(epoch):
            optimizer.minimize(loss, model.trainable_weights)
        tf.print("epoch = ", optimizer.iterations)
        return loss
    
    # 第三种
    model.compile(optimizer=optimizers.SGD(learning_rate=0.01), loss=loss)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    九 回调函数

    tf.keras的回调函数实际上是一个类,一般是在model.fit时作为参数指定,一般收集一些日志信息,改变学习率等超参数,提前终止训练过程等等

    大部分时候,keras.callbacks子模块中定义的回调函数类已经足够使用了,如果有特定的需要,我们也可以通过对keras.callbacks.Callbacks实施子类化构造自定义的回调函数

    • BaseLogger: 收集每个epoch上metrics平均值,对stateful_metrics参数中的带中间状态的指标直接拿最终值无需对各个batch平均,指标均值结果将添加到logs变量中。该回调函数被所有模型默认添加,且是第一个被添加的
    • History: 将BaseLogger计算的各个epoch的metrics结果记录到history这个dict变量中,并作为model.fit的返回值。该回调函数被所有模型默认添加,在BaseLogger之后被添加
    • EarlyStopping: 当被监控指标在设定的若干个epoch后没有提升,则提前终止训练
    • TensorBoard: 为Tensorboard可视化保存日志信息。支持评估指标,计算图,模型参数等的可视化
    • ModelCheckpoint: 在每个epoch后保存模型
    • ReduceLROnPlateau:如果监控指标在设定的若干个epoch后没有提升,则以一定的因子减少学习率
    • TerminateOnNaN:如果遇到loss为NaN,提前终止训练
    • LearningRateScheduler:学习率控制器。给定学习率lr和epoch的函数关系,根据该函数关系在每个epoch前调整学习率
    • CSVLogger:将每个epoch后的logs结果记录到CSV文件中
    • ProgbarLogger:将每个epoch后的logs结果打印到标准输出流中

    十、模型训练

    一般有三种方法

    1. 内置fit方法,支持对numpy array,tf.data.Dataset以及 Python generator数据进行训练,并且可以通过设置回调函数实现对训练过程的复杂控制逻辑。
    2. 内置train_on_batch方法
    3. 自定义训练循环

    10.1 model.fit

    model.fit(
        x=None, y=None,  # 输入和输出
        batch_size=None, # 默认32
        epochs=1, 	# 训练epoch
        verbose='auto', #日志打印
        callbacks=None, # 训练期间调用的回调列表。见tf.keras.callbacks
        validation_split=0.0, # 划分验证集,[0,1]
        validation_data=None, # 验证集数据
        shuffle=True, 
        initial_epoch=0, # 整数,开始训练的epoch,可以用于重训
        steps_per_epoch=None)
      
    # 返回:history,调用 history.history 可以查看训练期间损失值和度量值的记录
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    10.2 train_on_batch

    相比较fit方法更加灵活,可以不通过回调函数而直接在batch层次上更加精细地控制训练的过程。

    for epoch in tf.range(1, epoches + 1):
        for x, y in ds_train:
            train_result = model.train_on_batch(x, y)
    
        for x, y in ds_valid:
            valid_result = model.test_on_batch(x, y, reset_metrics=False)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    10.3 自定义训练循环

    自定义训练循环无需编译模型,直接利用优化器根据损失函数反向传播迭代参数,拥有最高的灵活性。
    训练循环包括按顺序重复执行四个任务:

    • 给模型输入batch数据以生成输出
    • 通过将输出与标签进行比较来计算损失
    • 使用GradientTape计算梯度
    • 使用这些梯度优化变量
    optimizer = keras.optimizers.SGD(learning_rate=1e-3)  # 实例化一个优化器
    loss_fn = keras.losses.BinaryCrossentropy()  # 实例化损失函数
    
    train_loss = keras.metrics.Mean(name='train_loss')
    valid_loss = keras.metrics.Mean(name='valid_loss')
    
    train_metric = keras.metrics.BinaryAccuracy(name='train_accuracy')
    valid_metric = keras.metrics.BinaryAccuracy(name='valid_accuracy')
    
    
    @tf.function
    def train_step(features, labels):
        with tf.GradientTape() as tape:
            logits = model(features, training=True)
            loss_value = loss_fn(labels, logits)
            # loss_value += sum(model.losses)   # 添加额外的损失
        grads = tape.gradient(loss_value, model.trainable_weights)
        optimizer.apply_gradients(zip(grads, model.trainable_weights))
    
        train_loss.update_state(loss_value)
        train_metric.update_state(labels, logits)
    
    
    @tf.function
    def valid_step(features, labels):
        val_logits = model(features, training=False)
    
        loss_value = loss_fn(labels, val_logits)
        valid_loss.update_state(loss_value)
        valid_metric.update_state(labels, val_logits)
    
    
    epochs = 2
    for epoch in range(epochs):
        start_time = time.time()
        for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
            loss_value = train_step(x_batch_train, y_batch_train)
        # 在每个epoch结束时运行验证循环
        for x_batch_val, y_batch_val in val_dataset:
            valid_step(x_batch_val, y_batch_val)
    
        if epoch % 5 == 0:
            print('Epoch={},Loss:{},Accuracy:{},Valid Loss:{},Valid Accuracy:{}'.format(epoch, train_loss.result(),
                                                                                        train_metric.result(),
                                                                                        valid_loss.result(),
                                                                                        valid_metric.result()))
        train_loss.reset_states()
        valid_loss.reset_states()
        train_metric.reset_states()
        valid_metric.reset_states()
    
        print("运行时间: %.2fs" % (time.time() - start_time))
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    十一、模型评估

    通过自定义训练循环训练的模型没有经过编译,无法直接使用model.evaluate(ds_valid)方法

    model.evaluate(x = x_test,y = y_test)
    
    • 1

    十二、模型推理

    1. model.predict(ds_test)
    2. model(x_test)
    3. model.call(x_test)
    4. model.predict_on_batch(x_test)
      推荐优先使用model.predict(ds_test)方法,既可以对Dataset,也可以对Tensor使用。

    十三、保存和加载模型

    Keras模型由多个组件组成:

    • 模型架构,指定模型包含的层及其连接方式
    • 权重值
    • 优化器及其状态(如果有)(这使您可以在离开的地方重新开始训练)
    • 模型的编译信息(损失和度量,通过编译模型或通过调用 add_loss() 或 add_metric() 来定义)

    可以通过 Keras API 将这些片段一次性保存到磁盘,或仅选择性地保存其中某一部分:

    • 所有内容以 TensorFlow SavedModel 格式(或较早的 Keras H5 格式)保存
    • 保存架构/配置,通常保存为 JSON 文件
    • 仅保存权重值。通常在训练模型时使用

    13.1 保存和加载整个模型

    1. 低级API tf.saved_model

    保存模型 tf.saved_model.save(model, path_to_dir)
    加载模型 model = tf.saved_model.load(path_to_dir)

    1. 高级API tf.keras.Model(如果模型不包含自定义子类 keras 模型,则非常推荐)

    保存模型 model.save(SaveModel_path)
    tf.keras.models.save_model(model, SaveModel_path)
    加载模型 model = tf.keras.models.load_model(SaveModel_path)

    SaveModel_path = 'path/to/location'
    model.save(SaveModel_path)  
    # 或者 
    tf.keras.models.save_model(model, SaveModel_path)  
    # 加载模型
    model = tf.keras.models.load_model(SaveModel_path)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    SavedModel 格式会存储类名称、调用函数、损失和权重(如果已实现,还包括配置)。调用函数会定义模型/层的计算图。
    如果将 save_format=‘h5’ 传递给 save(),则模型将以H5的格式保存。
    Keras H5 格式(较旧的):包含模型架构、权重值和compile()信息。它是SavedModel的轻量级替代品。
    通过model.add_loss()和model.add_metric()添加的外部损失和度量 不会被保存
    自定义对象(如自定义层)的计算图不包含在保存的h5文件中。在加载时,Keras需要访问这些对象的Python类/函数来重构模型。

    13.2 保存模型架构

    模型的配置(或架构)指定模型包含的层,以及这些层的连接方式。如果有模型的配置,则可以使用权重的新初始化状态创建模型,而无需编译信息。

    • 请注意,这仅适用于使用函数式或序列式 API 定义的模型,不适用于子类化模型。

    13.2.1 get_config()from_config()

    config = model.get_config()返回一个包含模型配置的 Python 字典。
    Sequential.from_config(config) (针对 Sequential 模型)或 Model.from_config(config) (针对函数式 API 模型)重建同一模型。

    # -----------------层示例
    layer = keras.layers.Dense(3, activation="relu")
    layer_config = layer.get_config()
    new_layer = keras.layers.Dense.from_config(layer_config)
    
    # -----------------序列模型示例
    model = keras.Sequential([keras.Input((32,)), keras.layers.Dense(1)])
    config = model.get_config()
    new_model = keras.Sequential.from_config(config)
    
    # -----------------函数式模式示例
    inputs = keras.Input((32,))
    outputs = keras.layers.Dense(1)(inputs)
    model = keras.Model(inputs, outputs)
    config = model.get_config()
    new_model = keras.Model.from_config(config)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    13.2.2 to_json()tf.keras.models.model_from_json()

    这与 get_config / from_config 类似,不同之处在于它会将模型转换成 JSON 字符串,之后该字符串可以在没有原始模型类的情况下进行加载。它还特定于Model,不适用于Layer。

    json_str = model.to_json()  # 保存模型结构
    model_json = tf.keras.models.model_from_json(json_str)  # 恢复模型结构
    
    • 1
    • 2

    本博文学习资料来源 https://www.cnblogs.com/LXP-Never/p/15917498.html#blogTitle1

  • 相关阅读:
    MySQL3
    Verilog HDL中的数据类型
    链表错误:AddressSanitizer: heap-use-after-free on address
    C++初阶(九)内存管理
    P1950 长方形
    【Express】文件上传管理 multer 中间件
    ONLYOFFICE 桌面编辑器 8.1全新发布,更强大的编辑工具
    Java异步
    【虹科干货】TWAMP:什么是双向主动测量协议?
    信息系统的生命周期 与 ITSS(信息技术服务标准)定义的IT服务生命周期
  • 原文地址:https://blog.csdn.net/eight_Jessen/article/details/127317864