《蜥蜴书》_讲义及源码解读_12

使用TensorFlow自定义模型和训练

TensorFlow速览

TensorFlow是一个强大的数值计算库,特别适合做和微调大规模机器学习(但也可以用来做其它的重型计算)。TensorFlow是谷歌大脑团队开发的,支持了谷歌的许多大规模服务,包括谷歌云对话、谷歌图片和谷歌搜索。TensorFlow是 2015 年 11 月开源的,(按文章引用、公司采用、GitHub 星数)是目前最流行的深度学习库。无数的项目是用 TensorFlow来做各种机器学习任务,包括图片分类、自然语言处理、推荐系统和时间序列预测。

TensorFlow提供的功能如下:

  • TensorFlow的核心与 NumPy很像,但 TensorFlow支持 GPU

  • TensorFlow支持分布式计算;

  • TensorFlow使用了即时JIT编译器对计算速度和内存使用优化。编译器的工作是从 Python 函数提取出计算图,然后对计算图优化(比如剪切无用的节点),最后高效运行(比如自动并行运行独立任务);

  • 计算图可以导出为迁移形式,因此可以在一个环境中训练一个 TensorFlow模型(比如使用 Python 或 Linux),然后在另一个环境中运行(比如在安卓设备上用 Java 运行);

  • TensorFlow实现了自动微分,并提供了一些高效的优化器,比如 RMSPropNAdam,因此可以容易的最小化各种损失函数。

  • TensorFlow 还提供了许多其他功能:最重要的是 tf.keras,还有数据加载和预处理操作(tf.datatf.io 等等),图片处理操作(tf.image),信号处理操作(tf.signal),等等(如图:总结了 TensorFlowPython API

  • 在这里插入图片描述

  • TensorFlow 的底层都是用高效的 C++实现的。许多操作有多个实现,称为核:每个核对应一个具体的设备型号,比如 CPU、GPU 甚至 TPU(张量处理单元)。GPU 通过将任务分成小块,在多个 GPU 线程中并行运行,可以极大提高提高计算的速度。TPU 更快:TPU 是自定义的 ASIC 芯片,专门用来做深度学习运算的。

  • TensorFlow 的架构见图

  • 在这里插入图片描述

  • TensorFlow不仅可以运行在 Windows、LinuxmacOS上,也可以运行在移动设备上(使用 TensorFlow Lite),包括 iOS 和安卓。如果不想使用 Python API,还可以使用 C++、Java、GoSwiftAPI。甚至还有 JavaScript 的实现 TensorFlow.js,它可以直接在浏览器中运行。

  • TensorFlow处于一套可扩展的生态系统库的核心位置。

    • 首先,TensorBoard 可以用来可视化。
    • 其次,TensorFlow Extended(TFX),是谷歌推出的用来生产化的库,包括:数据确认、预处理、模型分析和服务(使用 TF Serving)。
    • 谷歌的 TensorFlow Hub 上可以方便下载和复用预训练好的神经网络。
    • 可以从 TensorFlowmodel garden(https://github.com/tensorflow/models/)获取许多神经网络架构,其中一些是预训练好的。
    • TensorFlow Resources和 https://github.com/jtoy/awesome-tensorflow上有更多的资源。你可以在 GitHub 上找到数百个 TensorFlow 项目,无论干什么都可以方便地找到现成的代码。
    • 提示:越来越多的 ML 论文都附带了实现过程,一些甚至带有预训练模型。可以在https://paperswithcode.com/找到。
    • 最后,TensorFlow 有一支热忱满满的开发者团队,也有庞大的社区。要是想问技术问题,可以去http://stackoverflow.com/,问题上打上 tensorflowpython 标签。还可以在GitHub上提 bug 和新功能。
NumPy 一样使用 TensorFlow
张量和运算

使用tf.constant()创建张量

t=tf.constant([[1., 2., 3.], [4., 5., 6.]]) #  矩阵
s=tf.constant(42) # 标量
t.shape
t.dtype
t + 10
tf.square(t)
t @ tf.transpose(t) # 矩阵乘法
#<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
#array([[14., 32.],
#       [32., 77.]], dtype=float32)>

可以在 TensorFlow 中找到所有基本的数学运算(tf.add()、tf.multiply()、tf.square()、tf.exp()、tf.sqrt()),以及 NumPy 中的大部分运算(比如 tf.reshape()、tf.squeeze()、tf.tile() )。

一些 TensorFlow 中的函数与 NumPy中不同,例如,tf.reduce_mean()、tf.reduce_sum()、tf.reduce_max()、tf.math.log() 等同于 np.mean()、np.sum()、np.max()和np.log()

当函数名不同时,通常都是有原因的。例如,TensorFlow 中必须使用tf.transpose(t),不能像 NumPy 中那样使用t.T。原因是函数tf.transpose(t) 所做的和NumPy的属性T并不完全相同:在 TensorFlow中,是使用转置数据的复制来生成张量的,而在NumPy 中,t.T 是数据的转置视图。

相似的,tf.reduce_sum()操作之所以这么命名,是因为它的 GPU 核(即 GPU实现)所采用的 reduce 算法不能保证元素相加的顺序,因为 32 位的浮点数精度有限,每次调用的结果可能会有细微的不同。tf.reduce_mean()也是这样(tf.reduce_max() 结果是确定的)。

许多函数和类都有假名。比如,tf.add()tf.math.add()是相同的。这可以让 TensorFlow 对于最常用的操作有简洁的名字,同时包可以有序安置。

Keras 的低级 API
Keras API有自己的低级 API,位于keras.backend,包括:函数square()、exp()、sqrt()。在tf.keras中,这些函数通常通常只是调用对应的TensorFlow操作。如果你想写一些可以迁移到其它 Keras实现上,就应该使用这些 Keras函数。但是这些函数不多,

from tensorflow import keras
K = keras.backend
K.square(K.transpose(t)) + 10
张量和 NumPy

张量和 NumPy 融合地非常好:使用 NumPy 数组可以创建张量,张量也可以创建 NumPy 数组。可以在 NumPy 数组上运行 TensorFlow 运算,也可以在张量上运行NumPy运算

a = np.array([2., 4., 5.])
t=tf.constant(a)
st =tf.square(a)
t.numpy()
n=np.array(t)
sn=np.square(t)

类型转换

类型转换对性能的影响非常大,并且如果类型转换是自动完成的,不容易被注意到。为了避免这样,TensorFlow 不会自动做任何类型转换:只是如果用不兼容的类型执行了张量运算,TensorFlow就会报异常。例如,不能用浮点型张量与整数型张量相加,也不能将 32 位张量与 64 位张量相加:

try:
    tf.constant(2.0) + tf.constant(40)
except tf.errors.InvalidArgumentError as ex:
    print(ex)
    
try:
    tf.constant(2.0) + tf.constant(40., dtype=tf.float64)
except tf.errors.InvalidArgumentError as ex:
    print(ex)
    
t2 = tf.constant(40., dtype=tf.float64)
tf.constant(2.0) + tf.cast(t2, tf.float32)

使用tf.Variable()创建可修改的张量:变量
v = tf.Variable([[1., 2., 3.], [4., 5., 6.]])
v.assign(2 * v)
#<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=
#array([[ 2.,  4.,  6.],
#       [ 8., 10., 12.]], dtype=float32)>
v[0, 1].assign(42)
#<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=
#array([[ 2., 42.,  6.],
#       [ 8., 10., 12.]], dtype=float32)>

v[:, 2].assign([0., 1.])
#<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=
#array([[ 2., 42.,  0.],
#       [ 8., 10.,  1.]], dtype=float32)>

v.scatter_nd_update(indices=[[0, 0], [1, 2]],
                    updates=[100., 200.])
#<tf.Variable 'UnreadVariable' shape=(2, 3) dtype=float32, numpy=
#array([[100.,  42.,   0.],
#       [  8.,  10., 200.]], dtype=float32)>

自定义模型和训练算法
自定义损失函数

举例:

训练一个回归模型,但训练集有噪音::

  • 通过清除或修正异常值来清理数据集
  • 数据集还是有噪音,该用什么损失函数呢?
  • 均方差可能对大误差惩罚过重,导致模型不准确。
  • 均绝对值误差不会对异常值惩罚过重,但训练可能要很长时间才能收敛,训练模型也可能不准确。

此时使用 Huber 损失就比 MSE 好多了,只需创建一个函数,参数是标签和预测值,使用 TensorFlow 运算计算每个实例的损失

def huber_fn(y_true, y_pred):
    error = y_true - y_pred
    is_small_error = tf.abs(error) < 1 
    squared_loss = tf.square(error) / 2
    linear_loss  = tf.abs(error) - 0.5
    return tf.where(is_small_error, squared_loss, linear_loss)
	# is_small_error 为真,squared_loss作为函数值,为假linear_loss作为函数值
    # 描图如下~

在这里插入图片描述

保存、加载包含自定义组件的模型
input_shape = X_train.shape[1:]

model = keras.models.Sequential([
    keras.layers.Dense(30, activation="selu", kernel_initializer="lecun_normal",
                       input_shape=input_shape),
    keras.layers.Dense(1),
])
model.compile(loss=huber_fn, optimizer="nadam", metrics=["mae"])   
# 使用了自定义损失函数 huber_fn
model.fit(X_train_scaled, y_train, epochs=20,
          validation_data=(X_valid_scaled, y_valid))  # 训练拟合

model.save("my_model_with_a_custom_loss.h5") # 保存
model = keras.models.load_model("my_model_with_a_custom_loss.h5",
                                custom_objects={"huber_fn": huber_fn})  

# 调用时提供一个字典,保存的函数名,映射,当前实际的函数名
自定义激活函数、初始化器、正则器和约束

Keras 的大多数功能,比如损失、正则器、约束、初始化器、指标、激活函数、层,甚至是完整的模型,都可以用相同或相似的方式方法做自定义。

实例代码:

#自定义激活函数
def my_softplus(z): 
    return tf.math.log(tf.exp(z) + 1.0)   
#自定义权重参数初始化
def my_glorot_initializer(shape, dtype=tf.float32):
    stddev = tf.sqrt(2. / (shape[0] + shape[1]))  # 扇入数量+扇出数量
    return tf.random.normal(shape, stddev=stddev, dtype=dtype)  # 正态分布标准差

#自定义l1正则,这里对权重进行L1正则,还可以对bias,对输出进行正则
def my_l1_regularizer(weights):
    return tf.reduce_sum(tf.abs(0.01 * weights))
#自定义约束,确保权重均为正值
def my_positive_weights(weights): # return value is just tf.nn.relu(weights)
    return tf.where(weights < 0., tf.zeros_like(weights), weights)

如下图,softplus可以看作是ReLu的平滑。根据神经科学家的相关研究,softplusReLu 与脑神经元激活频率函数有神似的地方。也就是说,相比于早期的激活函数,softplusReLu更加接近脑神经元的激活模型,而神经网络正是基于脑神经科学发展而来,这两个激活函数的应用促成了神经网络研究的新浪潮。

在这里插入图片描述

model = keras.models.Sequential([
    keras.layers.Dense(30, activation="selu", kernel_initializer="lecun_normal",
                       input_shape=input_shape),
    keras.layers.Dense(1, activation=my_softplus,     #激活函数
                       kernel_regularizer=my_l1_regularizer,   #正则
                       kernel_constraint=my_positive_weights,  #约束
                       kernel_initializer=my_glorot_initializer), #权重初始化
])
model.compile(loss="mse", optimizer="nadam", metrics=["mae"])
model.fit(X_train_scaled, y_train, epochs=2,
          validation_data=(X_valid_scaled, y_valid))

此例:

  • 激活函数会应用到这个Dense层的输出上,结果会传递到下一层。
  • 层的权重会使用初始化器的返回值。
  • 在每个训练步骤中,权重会传递给正则化函数以计算正则化损失,这个损失会与主损失相加,得到训练的最终损失。
  • 最后,会在每个训练步骤结束后调用约束函数,经过约束的权重会替换层的权重。
自定义指标

损失和指标的概念不同:

梯度下降使用损失(比如交叉熵损失)来训练模型,因此损失必须是可微分的(至少是在评估点可微分),梯度不能在所有地方都是 0。另外,就算损失比较难解释也没有关系。

指标(比如准确率)是用来评估模型的:指标的解释性一定要好,可以是不可微分的,或者可以在任何地方的梯度都是 0。但,在多数情况下,定义一个自定义指标函数和定义一个自定义损失函数是完全一样的。

示例代码:

def create_huber(threshold=1.0):
    def huber_fn(y_true, y_pred):
        error = y_true - y_pred
        is_small_error = tf.abs(error) < threshold
        squared_loss = tf.square(error) / 2
        linear_loss  = threshold * tf.abs(error) - threshold**2 / 2
        return tf.where(is_small_error, squared_loss, linear_loss)
    return huber_fn

model = keras.models.Sequential([
    keras.layers.Dense(30, activation="selu", kernel_initializer="lecun_normal",
                       input_shape=input_shape),
    keras.layers.Dense(1),
])
model.compile(loss="mse", optimizer="nadam", metrics=[create_huber(2.0)])
model.fit(X_train_scaled, y_train, epochs=2)

自定义层

如果模型的层顺序是 A、B、C、A、B、C、A、B、C,则完全可以创建一个包含 A、B、C 的自定义层 D,模型就可以简化为 D、D、D。

创建没有权重的自定义层:无状态层

最简单的方法是编写一个函数,将其包装进keras.layers.Lambda

实例代码:

exponential_layer = keras.layers.Lambda(lambda x: tf.exp(x))  # 做指数预算
model = keras.models.Sequential([
    keras.layers.Dense(30, activation="relu", input_shape=input_shape),
    keras.layers.Dense(1),
    exponential_layer  # 输出层输出时做指数预算
])
model.compile(loss="mse", optimizer="sgd")
model.fit(X_train_scaled, y_train, epochs=5,
          validation_data=(X_valid_scaled, y_valid))
model.evaluate(X_test_scaled, y_test)

要创建自定义状态层(有权重的层)

需要创建 keras.layers.Layer 类的子类。

class MyDense(keras.layers.Layer):
    def __init__(self, units, activation=None, **kwargs):
        super().__init__(**kwargs)
        self.units = units
        self.activation = keras.activations.get(activation)

    def build(self, batch_input_shape):
        self.kernel = self.add_weight(
            name="kernel", shape=[batch_input_shape[-1], self.units],
            initializer="glorot_normal")
        self.bias = self.add_weight(
            name="bias", shape=[self.units], initializer="zeros")
        super().build(batch_input_shape) # must be at the end

    def call(self, X):
        return self.activation(X @ self.kernel + self.bias)

    def compute_output_shape(self, batch_input_shape):
        return tf.TensorShape(batch_input_shape.as_list()[:-1] + [self.units])

    def get_config(self):
        base_config = super().get_config()
        return {**base_config, "units": self.units,
                "activation": keras.activations.serialize(self.activation)}
    

model = keras.models.Sequential([
    MyDense(30, activation="relu", input_shape=input_shape),
    MyDense(1)
])

model.compile(loss="mse", optimizer="nadam")
model.fit(X_train_scaled, y_train, epochs=2,
          validation_data=(X_valid_scaled, y_valid))
model.evaluate(X_test_scaled, y_test)

此例作为示例:定义了全连接层的简化版本。

自定义模型

创建keras.Model 类的子类,创建层和变量,用call() 方法完成模型想做的任何事

在这里插入图片描述

class ResidualBlock(keras.layers.Layer):
    def __init__(self, n_layers, n_neurons, **kwargs):
        super().__init__(**kwargs)
        self.hidden = [keras.layers.Dense(n_neurons, activation="elu",
                                          kernel_initializer="he_normal")
                       for _ in range(n_layers)]

    def call(self, inputs):
        Z = inputs
        for layer in self.hidden:
            Z = layer(Z)
        return inputs + Z

class ResidualRegressor(keras.models.Model):
    def __init__(self, output_dim, **kwargs):
        super().__init__(**kwargs)
        self.hidden1 = keras.layers.Dense(30, activation="elu",
                                          kernel_initializer="he_normal")
        self.block1 = ResidualBlock(2, 30)
        self.block2 = ResidualBlock(2, 30)
        self.out = keras.layers.Dense(output_dim)

    def call(self, inputs):
        Z = self.hidden1(inputs)
        for _ in range(1 + 3):
            Z = self.block1(Z)
        Z = self.block2(Z)
        return self.out(Z)

此例仅为如上图的创建自定义模型的示例,

使用自动微分计算梯度

用程序求偏导

f ( w 1 , w 2 ) = 3 w 1 2 + 2 w 1 w 2 f(w_1,w_2)=3 w_1^2+2 w_1 w_2 f(w1,w2)=3w12+2w1w2

def f(w1, w2):
    return 3 * w1 ** 2 + 2 * w1 * w2

w1, w2 = 5, 3
eps = 1e-6
dw1=(f(w1 + eps, w2) - f(w1, w2)) / eps
dw2=(f(w1, w2 + eps) - f(w1, w2)) / eps
#dw1:36.000003007075065  
#dw2:10.000000003174137

这种方法很容易实现,但只是大概。重要的是,需要对每个参数至少要调用一次 f (   ) f(~) f( ),对于大神经网络,就不怎么可控。所以,应该使用自动微分。TensorFlow 的实现很简单:不仅结果是正确的(准确度只受浮点误差限制)

w1, w2 = tf.Variable(5.), tf.Variable(3.)

with tf.GradientTape() as tape:
    z = f(w1, w2)

gradients = tape.gradient(z, [w1, w2])
#[<tf.Tensor: shape=(), dtype=float32, numpy=36.0>,
# <tf.Tensor: shape=(), dtype=float32, numpy=10.0>]

先定义了两个变量w1w2,然后创建了一个tf.GradientTape 上下文,它能自动记录变量的每个操作,最后使用它算出结果 z 关于两个变量 [w1, w2] 的梯度

TensorFlow 函数和图

在这里插入图片描述

TensorFlow 是如何生成计算图的呢?

首先分析了 Python 函数源码,找到所有的控制语句,比如: for,while,if ,break、continue、return。这个第一步被称为自动图(AutoGraph

然后,调用函数for_stmt() 会形成运算tf.while_loop(),但没有向其传递参数,而是传递一个符号张量(symbolic tensor),如图,一个没有任何真实值的张量,只有名字、数据类型和形状。

最后的图是跟踪中生成的。节点表示运算,箭头表示张量。

TF 函数规则

大多数时候,将 Python 函数转换为 TF 函数:要用@tf.function装饰,或让Keras 来处理,但有一些规则要遵守:

  • 如果调用任何外部库,包括 NumPy,甚至是标准库,调用只会在跟踪中运行,不会是图的一部分。
  • 可以调用其它 Python 函数或 TF 函数,但是它们要遵守相同的规则,因为 TensorFlow 会在计算图中记录它们的运算。注意,其它函数不需要用@tf.function装饰。
  • 最好在 TF函数的外部创建变量。如果你想将一个新值赋值给变量,要确保调用它的assign()方法,而不是使用=
  • Python 的源码应该可以被 TensorFlow 使用
  • TensorFlow 只能捕获在张量或数据集上迭代的 for循环。因此要确保使用for i in tf.range(x),而不是for i in range(x),否则循环不能在图中捕获,反而会在追踪中运行。

==

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值