前置知识请见 人工智能导论

初识 TensorFlow

官网: https://www.tensorflow.org

TensorFlow 大框架:

106-1.png

Estimator 属于 High level 的 API,而 Mid-level API 分别是:

  • Layers:用来构建网络结构
  • Datasets:用来构建数据读取 pipeline
  • Metrics:用来评估网络性能

Tensorflow1.0 主要特性:

  • XLA —— Accelerate Linear Algebra
    • 提升训练速度 58 倍
    • 可以在移动设备运行
  • 引入更高级别的 API —— tf.layers / tf.metrics / tf.losses / tf.keras
  • Tensorflow 调试器
  • 支持 docker 镜像,引入 tensorflow serving 服务

Tensorflow2.0 主要特性:

  • 使用 tf.keraseager mode 进行更加简单的模型构建
  • 鲁棒的跨平台模型部署
  • 强大的研究实验
  • 清除不推荐使用的 API 和减少重复来简化 API

使用 Tensorflow 的大致流程:

106-2.png

在虚拟环境中安装 tensorflow 。

一个 helloword 程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tensorflow as tf

print('*******************')
print(tf.__version__)

x = tf.constant(0.) # 常量 张量
y = tf.constant(1.)

for i in range(50):
x = x+y
y = y/2

print(x.numpy()) # 张量和 ndarray 之间可以互相转化
print(x)

在我本地没有 nvidia GPU 的电脑,输出类似于:

1
2
3
4
5
6
7
8
# 前面还有一大堆警告和报错...
This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 AVX_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
*******************
2.19.0
[时间]E external/local_xla/xla/stream_executor/cuda/cuda_platform.cc:51] failed call to cuInit: INTERNAL: CUDA error: Failed call to cuInit: UNKNOWN ERROR (303)
2.0
tf.Tensor(2.0, shape=(), dtype=float32)

在 Colab 中运行该代码,得到输出:

1
2
3
4
*******************
2.18.0
2.0
tf.Tensor(2.0, shape=(), dtype=float32)

另一个例子:

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
import tensorflow as tf

print('*******************')
print(tf.__version__)

x = tf.Variable(0.) # 变量 张量
y = tf.Variable(1.)

print(x)
print(y)

x.assign(x+y)

print(x)
print(y)

'''
前略
*******************
2.19.0
[时间]: E external/local_xla/xla/stream_executor/cuda/cuda_platform.cc:51] failed call to cuInit: INTERNAL: CUDA error: Failed call to cuInit: UNKNOWN ERROR (303)
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.0>
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.0>
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.0>
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.0>
'''

图结构:静态图,效率高;动态图,调试容易。

106-3.png

图(graph) 是 tensorflow 用于表达计算任务的一个核心概念。从前端(Python)描述神经网络的结构,到后端在多机和分布式系统上部署,到底层 Device(CPU、GPU、TPU)上运行,都是基于图来完成。

数据流图:

106-4.png

张量为什么那么多阶?
可以考虑一些典型场景。例如,灰度图、png、视频等。

深度学习中的“张量” 是工程实践中使用的多维数组结构,而 数学中的张量 是抽象代数与几何的核心概念,两者在形式上相似,但本质和用途大不相同

张量属性:

  • graph 张量所属的默认图
  • op 张量的操作名
  • name 张量的字符串描述
  • shape 张量形状

Tensorflow-keras

简介

keras 是基于 python 的高级神经网络 API,以 Tensorflow、CNTK 或 Theano 为后端运行,keras 必须有后端才可以运行。后端可以切换,现在多用 tensorflow. 方便快速实验,帮助用户以最少的时间验证自己的想法。

Tensorflow-keras 是什么?

  • Tensorflow 对 keras API 规范的实现
  • 相对于以 tensorflow 为后端的 keras, Tensorflow-keras 与 Tensorflow 结合更加紧密
  • 实现在 tf.keras 空间下

Tf-keras 和 keras 联系:

  • 基于同一套 API
  • keras 程序可以通过修改导入方式轻松转为 tf.keras 程序
  • 反之可能不成立,因为 tf.keras 有其他特性
  • 相同的 JSON 和 HDF5 模型序列化格式和语义

tf_keras做分类

下面的 ipynb 文件包含以下内容:

  • 一个衣服分类的实验
  • 为什么要分为训练集,验证集,测试集
  • tensorflow 的基本使用
  • 均方误差
  • Cross Entropy Loss Function(交叉熵损失函数)
  • w 的初始分布

下面的 ipynb 文件包含以下内容:

  • 对之前相同的流程,做归一化,看看有无改进

下面的 ipynb 文件包含以下内容:

  • TensorBoard
  • ModelCheckpoint
  • EarlyStopping

tf_keras做回归

下面的 ipynb 文件包含以下内容:

  • 使用 California Housing dataset 做回归

梯度消失、梯度爆炸

问题描述

关于梯度消失,从一个例子入手:

上面文件演示的问题即是梯度消失。

梯度消失
梯度消失问题发生时,接近于输出层的 hidden layer 3 等的权值更新相对正常,但前面的 hidden layer 1 的权值更新会变得很慢,导致前面的层权值几乎不变,仍接近于初始化的权值,这就导致 hidden layer 1 相当于只是一个映射层,这是此深层网络的学习就等价于只有后几层的浅层网络的学习了。

梯度爆炸
在反向传播过程中使用的是链式求导法则,如果每一层偏导数都大于 1,那么连乘起来将以指数形式增加,误差梯度不断累积,就会造成梯度爆炸。梯度爆炸会导致模型权重更新幅度过大,会造成模型不稳定,无法有效学习,还会出现无法再更新的 NaN 权重值

梯度消失和梯度爆炸其实都是因为反向传播中的连乘效应。除此之外,也和激活函数的选取有关。

106-5.png

对于梯度消失:

  • Sigmoid 函数的导数最大值是 0.25(当 $ z = 0 $ 时)。当 $ z $ 的绝对值较大时(即神经元饱和时),$ \sigma’(z) $ 接近于 0 .
  • Tanh 函数,当 $ z $ 的绝对值较大时,导数也接近于 0 .
  • 在反向传播过程中,如果许多层的激活函数导数值都小于1(特别是当它们远小于1时,比如 Sigmoid 导数的最大值0.25),这些导数值连乘起来会使梯度呈指数级衰减。
  • 如果权重被初始化为较小的值(例如,绝对值小于 1),多个较小的权重矩阵与较小的激活函数导数连乘,会进一步加速梯度的消失。

对于梯度爆炸,可以作类似的解释,不再赘述。

批归一化

接下来的行文逻辑:什么是批归一化,批归一化的动机,减均值除以标准差为什么失败,正确的批归一化算法。

批归一化可以缓解梯度消失和梯度爆炸的问题,简单来说即是,每层的激活值都做归一化。

在深度神经网络的训练过程中,每一层的参数都会在反向传播后进行更新。这意味着,对于网络中的某一层,其输入数据的分布(来自前一层的输出)会随着训练的进行而不断发生变化。这种现象被称为内部协变量偏移 (Internal Covariate Shift, ICS)

ICS 会导致以下问题:

  • 训练速度减慢:后续层需要不断适应这种变化的输入分布。
  • 对初始化敏感:网络对参数的初始值更加敏感。
  • 激活函数饱和:输入数据分布的变化可能导致激活函数的输入落入饱和区(例如Sigmoid的两端),从而导致梯度消失。

批归一化的提出就是为了缓解 ICS 问题。

如何实现批归一化呢?我们自然想到经典的“减均值除以标准差”方法:

106-6.png

这有两个好处:

  1. 避免分布数据偏移
  2. 远离导数饱和区

但这个处理对于在 -1~1 之间的梯度变化不大的激活函数,效果不仅不好,反而更差。比如 sigmoid 函数,在 -1~1 之间几乎是线性,BN 变换后就没有达到非线性变换的目的;而对于 relu,效果会更差,因为会有一半的置零。总之,减均值除以标准差后可能削弱网络的性能

改进的思路是:缩放加移位。

106-7.png

这里的 $ y_i $ 就是批归一化层的输出,它将作为下一层或激活函数的输入。$\gamma$ 和 $\beta$ 是与网络的其他参数(如权重 $ W $ 和偏置 $ b $)一样通过梯度下降学习得到的。如果网络发现原始的表示更好,它可以学习到 $\gamma = \sqrt{\sigma_B^2 + \epsilon}$ 和 $\beta = \mu_B$,从而在一定程度上抵消归一化的影响。

tensorflow 中的接口:
https://www.tensorflow.org/api_docs/python/tf/keras/layers/BatchNormalization

下面是使用批归一化的例子:

更改激活函数

relu -> selu, 缓解梯度消失。

实例:

Dropout

106-8.png

Dropout 可以防止过拟合,实现也非常简单:对想要 dropout 的点输出乘 0 .

tensorflow keras 中的实现

The Dropout layer randomly sets input units to 0 with a frequency of rate at each step during training time, which helps prevent overfitting. Inputs not set to 0 are scaled up by 1 / (1 - rate) such that the sum over all inputs is unchanged.

实例:

Wide & Deep 模型(推荐场景)

原始论文: https://arxiv.org/pdf/1606.07792v1

Google 于 16 年发布,用于分类和回归。曾应用在 Google Play 中的应用推荐。

Wide 用于记忆,Deep 用于联想。一个推荐系统的经典矛盾:应该给用户推荐经常买的,还是探索一些用户可能会买的?

相关文章:

尝试搭建简单的 wide and deep 模型(github 链接):

在 Deep 部分,会用到 Word2Vec 技术。简要介绍:

Word2Vec is a technique in natural language processing that represents words as vectors in a high-dimensional space, capturing semantic relationships between them. It essentially translates words into numerical representations, where words with similar meanings are located closer together in the vector space.

可供参考的资料:
https://medium.com/@manansuri/a-dummys-guide-to-word2vec-456444f3c673

Word2Vec utilizes two main architectures:

  • CBOW (Continuous Bag of Words): Predicts a target word based on its surrounding context words.
  • Skip-gram: Predicts surrounding words based on a given input word.

简要的原理介绍:
如何衡量两个词在语义上是否相近?如果两个词的上下文相同(相近),我们可以有较大的把握认为,这两个词的语义也相近。我们构造一个神经网络,输入为上下文,输出为该词,训练之,将隐藏层的数值作为这个词的 vector 元素。在实践中,我们发现语义相近的词,由这种机制产生的 vector 表示也相近。

有了 Word2Vec,我们就可以用向量之间的差距来衡量信息之间的差距。

用面向对象的方法重写之前的代码:github链接

在之前的模型中,我们只有一个输入,现在尝试多输入(github链接):

iframe 嵌多了可能有点卡,之后只放 github 链接了。

尝试搭建更加复杂的模型(github链接):

106-9.png

一个关于 Wide and Deep 的推荐实验:
https://blog.csdn.net/weixin_34268753/article/details/92123569

超参数搜索

为什么要超参数搜索?手工去试耗费人力。

有哪些超参数?

  • 网络结构参数:几层,每层神经元个数,每层激活函数
  • 训练参数: batch_size,学习率,学习率衰减算法

Batch Size

下面链接详细讲解了 Batch Size:
https://blog.csdn.net/qq_34886403/article/details/82558399

注意:Batch Size 增大了,要到达相同的准确度,必须要增大 epoch。

学习率衰减

固定学习率时,当到达收敛状态时,会在最优值附近一个较大的区域内摆动;而当随着迭代轮次的增加而减小学习率,会使得在收敛时,在最优值附近一个更小的区域内摆动(之所以曲线震荡朝向最优值收敛,是因为在每一个 mini-batch 中都存在噪音)。

学习率衰减算法有很多,这里仅举最简单的两例。

离散下降(discrete staircase)

对于深度学习来说,每 t 轮学习,学习率减半。对于监督学习来说,初始设置一个较大的学习率,然后随着迭代次数的增加,减小学习率。

指数减缓(exponential decay)

对于深度学习来说,学习率按训练轮数增长指数差值递减。例如:

又或者公式为:

其中 epochnum 为当前 epoch 的迭代轮数。不过第二种方法会引入另一个超参 k。

网格搜索、随机搜索

机器学习笔记 中已经提过,我们直接实战。

先看一个朴素的例子:github链接

能否配合 sklearn 进行参数搜索?

注意: scikit-learn 在 1.6 版本中对其内部处理 estimator tags (估计器标签) 的 API 进行了现代化更改。这导致一些第三方库(包括早期版本的 XGBoost 和 CausalML)在与 scikit-learn 1.6.x 交互时出现了 AttributeError: 'super' object has no attribute '__sklearn_tags__' 类似的问题。

一种不够优雅的解决方案是降级 scikit-learn, 我就不尝试了。 github链接 .

另外一些可能的方案有:KerasTuner 等。

关于网格搜索、随机搜索:

106-10.png

随机搜索:参数的生成方式为随机,可探索的空间更大