本主题主要阐述下Keras框架中的模型Model的使用,主要包含的内容:
  1.模型的两种使用方式;
  2.经典模型的实现(定制模型);
  3.模型的定制训练;

一. 模型使用的两种方式

  • Keras通过两个API提供两种计算模型:

    • 函数式模型:通过Model类API;
    • 顺序式模型:通过Sequential类API;
  • 本文的业务背景还是是深度全链接网络;
    实现4 -> 8 -> 4 -> 1网络。

1. 函数式模型

  • 函数式模型的编程特点是:
    1. 程序员构建层,通过Layer对象的可调用特性,或者使用apply与call实现链式函数调用;
    2. Model只需要通过inputs与outputs;
  • 这种模式之所以称为函数式模型,是因为Layer提供了几种函数式调用方式,通过这种调用建立层之间的网络模型。
    1. Layer是可调用对象,提供__call__可调用运算符(...)
    2. apply函数;
  1. 函数式模型的示意图

     

  2. 函数式模型的示例代码 - 使用Layer的可调用运算
    # Author:Louis Young
    # Note:使用Iris数据集,构建的4-8-4-1的全链接神经网络
    import tensorflow as tf
    import tensorflow.keras as keras
    import tensorflow.keras.layers as layers
    import sklearn.datasets as datasets
    
    # 1. 定义Layer层;
    #   1.1. 输入层:必须是InputLayer或者Input创建的Tensor;
    input_layer = keras.Input(shape=(4,))   # 4是Iris的特征维数(4个特征:可以参考sklearn中关于iris数据集的特征说明)
    #   1.2. 隐藏层:8-4
    hide1_layer = layers.Dense(units=8, activation='relu')
    hide2_layer = layers.Dense(units=4, activation='relu')
    #   1.3. 输出层:1
    output_layer = layers.Dense(units=1, activation='sigmoid')
    
    #  2. 构建Layer之间的函数链式关系
    hide1_layer_tensor = hide1_layer(input_layer)      # <----------------------使用可调用特性
    hide2_layer_tensor = hide2_layer(hide1_layer_tensor)
    output_layer_tensor = output_layer(hide2_layer_tensor)
    
    # 3. 使用inputs与outputs建立函数链式模型;
    model = keras.Model(inputs=input_layer, outputs=output_layer_tensor)   # inputs与outputs一定是Layer调用输出的张量
    
    # 4. 训练
    #   4.1. 训练参数
    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['mse'])
    #   4.2. 训练
    data, target = datasets.load_iris(return_X_y=True)
    data = data[:100, :]   # 取前面100个样本(第一类与第二类)
    target = target[:100]
    model.fit(x=data, y=target, batch_size=10, epochs=1000, verbose=0)
    #   4.3. 预测与评估
    # 6.预测
    pre_result = model.predict(data)
    category = [0 if item<=0.5 else 1 for item in pre_result ]
    accuracy = (target == category).mean()
    print(F'分类准确度:{accuracy *100.0:5.2f}%', )
    分类准确度:100.00%


     

  3. 函数式模型的示例代码-使用Layer的apply函数
    apply函数实际是__call__运算符的别名。

    # Author:Louis Young
    # Note:使用Iris数据集,构建的4-8-4-1的全链接神经网络
    import tensorflow as tf
    import tensorflow.keras as keras
    import tensorflow.keras.layers as layers
    import sklearn.datasets as datasets
    
    # 1. 定义Layer层;
    #   1.1. 输入层:必须是InputLayer或者Input创建的Tensor;
    input_layer = keras.Input(shape=(4,))   # 4是Iris的特征维数(4个特征:可以参考sklearn中关于iris数据集的特征说明)
    #   1.2. 隐藏层:8-4
    hide1_layer = layers.Dense(units=8, activation='relu')
    hide2_layer = layers.Dense(units=4, activation='relu')
    #   1.3. 输出层:1
    output_layer = layers.Dense(units=1, activation='sigmoid')
    
    #  2. 构建Layer之间的函数链式关系    
    hide1_layer_tensor = hide1_layer.apply(input_layer)            # <----------------------使用apply
    hide2_layer_tensor = hide2_layer.apply(hide1_layer_tensor)
    output_layer_tensor = output_layer.apply(hide2_layer_tensor)
    # 3. 使用inputs与outputs建立函数链式模型;
    model = keras.Model(inputs=input_layer, outputs=output_layer_tensor)   # inputs与outputs一定是Layer调用输出的张量
    
    # 4. 训练
    #   4.1. 训练参数
    model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['mse'])
    #   4.2. 训练
    data, target = datasets.load_iris(return_X_y=True)
    data = data[:100, :]   # 取前面100个样本(第一类与第二类)
    target = target[:100]
    model.fit(x=data, y=target, batch_size=10, epochs=1000, verbose=0)
    #   4.3. 预测与评估
    # 5.预测
    pre_result = model.predict(data)
    category = [0 if item<=0.5 else 1 for item in pre_result ]
    accuracy = (target == category).mean()
    print(F'分类准确度:{accuracy *100.0:5.2f}%', )
    
Tensor("dense_29/Identity:0", shape=(None, 1), dtype=float32)
分类准确度:100.00%

 

2. 顺序式模型

顺序式模型的编程特点:

  1. Layer提供input与output属性;
  2. Sequential类通过Layer的input与output属性来维护层之间的关系,构建网络模型;
    • 第一个Layer必须是InputLayer或者Input函数构建的张量;

1.顺序式模型示意图

2.顺序式模型的示例代码-layers参数构建模型

# Author:Louis Young
# Note:使用Iris数据集,构建的4-8-4-1的全链接神经网络
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import sklearn.datasets as datasets


# 1. 构建Layer层
# 1. 定义Layer层;
#   1.1. 输入层:必须是InputLayer或者Input创建的Tensor;
input_layer = keras.Input(shape=(4,))   # 4是Iris的特征维数(4个特征:可以参考sklearn中关于iris数据集的特征说明)
#   1.2. 隐藏层:8-4
hide1_layer = layers.Dense(units=8, activation='relu')
hide2_layer = layers.Dense(units=4, activation='relu')
#   1.3. 输出层:1
output_layer = layers.Dense(units=1, activation='sigmoid')

# 2. 使用Sequential构建顺序模型
seq_model = keras.Sequential(layers=[input_layer, hide1_layer, hide2_layer, output_layer])

# -------------------下面部分与上面代码完全相同
# 3. 训练
#   3.1. 训练参数
seq_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['mse'])
#   3.2. 训练
data, target = datasets.load_iris(return_X_y=True)
data = data[:100, :]   # 取前面100个样本(第一类与第二类)
target = target[:100]
seq_model.fit(x=data, y=target, batch_size=10, epochs=1000, verbose=0)
#   3.3. 预测与评估
# 4.预测
pre_result = seq_model.predict(data)
category = [0 if item<=0.5 else 1 for item in pre_result ]
accuracy = (target == category).mean()
print(F'分类准确度:{accuracy *100.0:5.2f}%', )
分类准确度:100.00%

3.顺序式模型的示例代码-使用add方法构建模型

# Author:Louis Young
# Note:使用Iris数据集,构建的4-8-4-1的全链接神经网络
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import sklearn.datasets as datasets


# 1. 构建Layer层
# 1. 定义Layer层;
#   1.1. 输入层:必须是InputLayer或者Input创建的Tensor;
input_layer = keras.Input(shape=(4,))   # 4是Iris的特征维数(4个特征:可以参考sklearn中关于iris数据集的特征说明)
#   1.2. 隐藏层:8-4
hide1_layer = layers.Dense(units=8, activation='relu')
hide2_layer = layers.Dense(units=4, activation='relu')
#   1.3. 输出层:1
output_layer = layers.Dense(units=1, activation='sigmoid')

# 2. 使用Sequential构建顺序模型
seq_model = keras.Sequential()
seq_model.add(input_layer)
seq_model.add(hide1_layer)
seq_model.add(hide2_layer)
seq_model.add(output_layer)

# -------------------下面部分与上面代码完全相同
# 3. 训练
#   3.1. 训练参数
seq_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['mse'])
#   3.2. 训练
data, target = datasets.load_iris(return_X_y=True)
data = data[:100, :]   # 取前面100个样本(第一类与第二类)
target = target[:100]
seq_model.fit(x=data, y=target, batch_size=10, epochs=1000, verbose=0)
#   3.3. 预测与评估
# 4.预测
pre_result = seq_model.predict(data)
category = [0 if item<=0.5 else 1 for item in pre_result ]
accuracy = (target == category).mean()
print(F'分类准确度:{accuracy *100.0:5.2f}%', )
分类准确度:100.00%

4.Layer类input与output属性的意义

# Author:Louis Young
# Note:使用Iris数据集,构建的4-8-4-1的全链接神经网络
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import sklearn.datasets as datasets


# 1. 构建Layer层
# 1. 定义Layer层;
#   1.1. 输入层:必须是InputLayer或者Input创建的Tensor;
input_layer = keras.Input(shape=(4,))   # 4是Iris的特征维数(4个特征:可以参考sklearn中关于iris数据集的特征说明)
#   1.2. 隐藏层:8-4
hide1_layer = layers.Dense(units=8, activation='relu')
hide2_layer = layers.Dense(units=4, activation='relu')
#   1.3. 输出层:1
output_layer = layers.Dense(units=1, activation='sigmoid')

# 2. 使用Sequential构建顺序模型
seq_model = keras.Sequential()
seq_model.add(input_layer)
seq_model.add(hide1_layer)
 
# seq_model = keras.Sequential([input_layer, hide1_layer])   与上面三个语句的作用一样。

# add方法本质也是自动调用Layer的可调用对象,只有调用后,Layer才具有input与output属性
print(hide1_layer.input)    # 可以注销22行。会出现错误,就可以理解add函数的作用。
Tensor("input_17:0", shape=(None, 4), dtype=float32)

二. 经典的模型实现-定制模型Model

专业的模型应用:建议子类化Model实现自己的模型。

1. 子类化Model说明

  • 模型的子类化,就是重载call函数。

    • call函数来自model的父类:tensorflow.python.keras.engine.base_layer.Layer
      • Model应该是特殊的Layer。
  • Mode的继承结构
     

        tf.keras.models.Model
            |- tensorflow.python.keras.engine.network.Network
                |- tensorflow.python.keras.engine.base_layer.Layer
                    |- tensorflow.python.module.module.Module
                        |- tensorflow.python.training.tracking.tracking.AutoTrackable

    通过继承 Model 类并在 call 方法中实现自己的前向传播,以创建自己的完全定制化的模型。注意:Model 类继承 API 引入于 Keras 2.2.0。

2. 使用定制Model构建模型

  1. 定制过程

    • 继承Model类;
    • 定制构造器,实现定制属性传递;
    • 定制call函数,实现模型的输出;
  2. 定制Model示例代码
    # Author:Louis Young
    # Note:使用Iris数据集,构建的4-8-4-1的全链接神经网络
    import tensorflow as tf
    import tensorflow.keras as keras
    import tensorflow.keras.layers as layers
    import sklearn.datasets as datasets
    
    
    class ModelException(Exception):
        def __init__(self, msg='模型构建异常'):
            self.message = msg
    
    # 1. 构建模型
    class IrisModel(keras.Model):
        # 构造器
        def __init__(self, networks):
            super(IrisModel, self).__init__(name='iris_model')
            # 判定参数类型是否是list
            if not isinstance(networks, list):
                raise ModelException('networks指定网络结构,类型是列表')
            # 生成networks属性
            self.networks = networks
            # 构建层
            self._layers = []
            for _net in networks[:-1]:  # 不考虑输入层
                layer = layers.Dense(units=_net, activation='relu')
                self._layers.append(layer)
    
            # 最后一层使用sigmoid函数
            layer = layers.Dense(units=networks[-1], activation='sigmoid')
            self._layers.append(layer)
    
        # forward方法:构建网络模型
        def call(self, inputs, **kwargs):
            # 根据层构建模型输出
            # 第一层的输入来自参数inputs
            x = self._layers[0](inputs)
            for _layer in self._layers[1:]:
                # 上一层输出作为下一层输入调用参数
                x = _layer(x)
    
            # 返回最后一层作为输出
            return x
    
    
    # 2. 创建模型实例
    model = IrisModel([8, 4, 1])  # 不包含输入的层4
    # ----------------------------------------< 以上为典型的模型构建方式。
    
    # 3. 定义训练参数
    model.compile(
        optimizer='adam',     # 指定优化器
        loss='binary_crossentropy',   # 指定损失函数
        metrics=['accuracy']
    )
    
    # 4. 数据加载
    data, target = datasets.load_iris(return_X_y=True)
    data = data[:100, :]   # 取前面100个样本(第一类与第二类)
    target = target[:100]
    # 5. 训练
    model.fit(x=data, y=target, batch_size=10, epochs=100, verbose=0)
    
    # 6.预测
    # pre_result = model.predict(data)
    pre_result = model(data)    # 与上一语句作用一样 predict函数等价于对象的调用
    category = [0 if item <= 0.5 else 1 for item in pre_result ]
    accuracy = (target == category).mean()
    print(F'分类准确度:{accuracy *100.0:5.2f}%', )
    分类准确度:100.00%

     

三. 模型的定制训练

  • 使用Model提供的compile与fit实现训练,确实很方便、易于阅读理解。但想提供更强大的训练控制,就需要理解Model的compile与fit函数实现的细节。
    • 这种细节实际上还是tensorflow的封装,下面我们从更加底层来了解模型的训练。
  • 我们提供一个例子,通过例子来解释模型的训练细节。

1. 定义训练参数

1.1. 优化器对象

  1. Adam构造器说明
    
        __init__(
            learning_rate=0.001,      # 学习率
            beta_1=0.9,
            beta_2=0.999,
            epsilon=1e-07,
            amsgrad=False,
            name='Adam',
            **kwargs
        )

     

  2. 优化器的优化调用
        apply_gradients(             # 梯度更新
            grads_and_vars,  
            name=None
        )

     

  3. 优化器创建代码
    import tensorflow.keras.optimizers as optimizers
    optimizer = optimizers.Adam(learning_rate=0.0001)
     


     

1.2. 损失对象

- 该对象是可调用对象,用来实现损失计算。
  1. CategoricalCrossentropy损失类的构造器说明:
    
    
        __init__(
            from_logits=False,
            label_smoothing=0,
            reduction=losses_utils.ReductionV2.AUTO,
            name='categorical_crossentropy'
        )

     

  2. 可调用对象运算符说明
        __call__(
            y_true,        # 标签值
            y_pred,        # 预测值
            sample_weight=None
        )

    调用后,可以使用result函数获取结果。

  3. 损失对象构造
     

    import tensorflow.keras.losses as losses
    loss =losses.CategoricalCrossentropy()


     

1.3. 梯度计算

- 使用`tf.GradientTape`计算梯度
  1. 上下文环境
        __enter__()
        
        __exit__(
            typ,
            value,
            traceback
    )

     

  2. 计算梯度
    返回与sources一样的结构的梯度结构。
     

        gradient(
            target,                # 误差项
            sources,
            output_gradients=None,
            unconnected_gradients=tf.UnconnectedGradients.NONE
        )

     

  3. 梯度对象与梯度计算代码
     

    with tf.GradientTape() as tape:
        gradients = tape.gradient(
            loss,      # 损失张量(不是对象)
            model.trainable_variables)    # 需要更新的可训练权重张量


     

1.4. 评估度量

- 评估方式用来计算准确率,精准率,召回率等。
- 下面是Accuracy类的说明
  1. Accuracy构造器
        __init__(
            name='accuracy',
            dtype=None
        )
    
     可调用运算符
    Accuracy类的继承结构是:
        Accuracy
            |- MeanMetricWrapper
                |- Mean
                    |- Reduce
                        |- Metric
                            |- tensorflow.python.keras.engine.base_layer.Layer
    
    其中可调用运算符定义:
        def __call__(self, *args, **kwargs):
    
    调用后,可以使用result函数获取结果。
    import tensorflow.keras.metrics as metrics
    metric = metrics.Accuracy()
    

     

2. 训练过程实现

实现步骤:

  1. 计算模型输出
  2. 计算损失值(损失张量,不是损失对象)
  3. 根据损失值,计算误差项
  4. 使用优化器更新梯度

2.1. 训练准备-创建模型

采用上面重复的代码:

# Author:Louis Young
# Note:使用Iris数据集,构建的4-8-4-1的全链接神经网络
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import tensorflow.keras.optimizers as optimizers
import tensorflow.keras.losses as losses
import tensorflow.keras.metrics as metrics
import sklearn.datasets as datasets


class ModelException(Exception):
    def __init__(self, msg='模型构建异常'):
        self.message = msg

# 1. 构建模型
class IrisModel(keras.Model):
    # 构造器
    def __init__(self, networks):
        super(IrisModel, self).__init__(name='iris_model')
        # 判定参数类型是否是list
        if not isinstance(networks, list):
            raise ModelException('networks指定网络结构,类型是列表')
        # 生成networks属性
        self.networks = networks
        # 构建层
        self._layers = []
        for _net in networks[:-1]:  # 不考虑输入层
            layer = layers.Dense(units=_net, activation='relu')
            self._layers.append(layer)

        # 最后一层使用sigmoid函数
        layer = layers.Dense(units=networks[-1], activation='sigmoid')
        self._layers.append(layer)

    # forward方法:构建网络模型
    def call(self, inputs, **kwargs):
        # 根据层构建模型输出
        # 第一层的输入来自参数inputs
        x = self._layers[0](inputs)
        for _layer in self._layers[1:]:
            # 上一层输出作为下一层输入调用参数
            x = _layer(x)

        # 返回最后一层作为输出
        return x


# 2. 创建模型实例
model = IrisModel([8, 4, 1])  # 不包含输入的层4
# ----------------------------------------< 以上为典型的模型构建方式。

2.2. 定义训练需要的对象 

一般训练必须的对象

  • 优化器:optimizers
  • 损失:losses
  • 梯度计算:tf.GradientTape

可选的对象:如果需要在训练的时候,获取一些评估数据,则可以使用。

  • 评估度量:metrics
# 1. 优化器对象
optimizer = optimizers.Adam()
# 2. 损失计算对象
losser = losses.BinaryCrossentropy()
# 3. 准确率计算对象
accuracier = metrics.Accuracy()
# 4. 梯度计算对象  # GradientTape构建后只能调用一次,所以在with中使用
# tape = tf.GradientTape()

2.3. 训练实现

  1. 准备数据
    # 准备数据
    data, target = datasets.load_iris(return_X_y=True)
    data = data[:100, :]   # 取前面100个样本(第一类与第二类)
    target = target[:100]
    

     

  2. 训练
    epochs = 100
    batch_size = 10
    batch_num = int(math.ceil(len(data) / batch_size))
    
    for epoch in range(epochs):
        for i in range(len(data)):
    
            with tf.GradientTape() as tape:
                start_ = i * batch_size
                end_ = (i + 1) * batch_size
                # 计算输出
                predictions = model(data[start_: end_])
                # 计算损失
                loss_value = losser(target[start_: end_], predictions)
            # 计算梯度,不能在with中调用
            gradients = tape.gradient(loss_value, model.trainable_variables)
            # 更新权重
            optimizer.apply_gradients(zip(gradients, model.trainable_variables))
    
    pre_result = model(data)
    category = [0 if item <= 0.5 else 1 for item in pre_result ]
    accuracy = (target == category).mean()
    print(F'分类准确度:{accuracy *100.0:5.2f}%')
    print(pre_result)
    
分类准确度:100.00%
tf.Tensor(
[[0.05429551]
 [0.06767229]
 [0.06427257]
 [0.06895139]

...省略  

 [0.99639302]
 [0.95713528]
 [0.99546698]], shape=(100, 1), dtype=float64)

        训练100轮,每轮批次大小10,批次数量是10,训练完成后(共训练次数是100*10=1000次),观察分类预测输出值,训练的效果还不错(因为是鸢尾花简单的数据集:D) 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐