《Python深度学习》笔记


2021/04/25: 发布
2021/04/26: 更新第二章后半部分和第三章
2021/04/29: 更新第四、五章

《Python深度学习》笔记

第一章 什么是深度学习

1.1 人工智能、机器学习与深度学习

三者的关系

1.1.1 人工智能

简洁的定义:努力将通常由人类完成的智力任务自动化。

1.1.2 机器学习

虽然符号主义人工智能适合用来解决定义明确的逻辑问题,但它难以给出明确的规则来解决更加复杂、模糊的问题,比如图像分类、语音识别和语言翻译。于是出现了一种新的方法来替代符号主义人工智能,这就是机器学习(machine learning)。

机器学习:一种新的编程范式

机器学习系统是训练出来的,而不是明确地用程序编写出来的。将与某个任务相关的许多示例输入机器学习系统,它会在这些示例中找到统计结构, 从而最终找到规则将任务自动化。举个例子,你想为度假照片添加标签,并且希望将这项任务自动化,那么你可以将许多人工打好标签的照片输入机器学习系统,系统将学会将照片与特定标签联系在一起的统计规则。

1.1.3 从数据中学习表示

我们需要一下三个要素来进行机器学习:

①. 输入数据点。例如,你的任务是语音识别,那么这些数据点可能是记录人们说话的声音文件。如果你的任务是为图像添加标签,那么这些数据点可能是图像。
②. 预期输出的示例。对于语音识别任务来说,这些示例可能是人们根据声音文件整理生成的文本。对于图像标记任务来说,预期输出可能是“狗”“猫”之类的标签。
③. 衡量算法效果好坏的方法。这一衡量方法是为了计算算法的当前输出与预期输出的差距。衡量结果是一种反馈信号,用于调节算法的工作方式。这个调节步骤就是我们所说的学习。

机器学习模型将输入数据变换为有意义的输出,这是一个从已知的输入和输出示例中进行“学习”的过程。机器学习中的学习指的是,寻找更好数据表示的自动搜索过程。

机器学习的技术定义:在预先定义好的可能性空间中,利用反馈信号的指引来寻找输入数据的有用表示。

1.1.4 深度学习之”深度”

深度学习强调从连续的层(layer)中进行学习,这些层对应于越来越有意义的表示。“深度学习”中的“深度”指的是一系列连续的表示层。数据模型中包含多少层,这被称为模型的“深度(depth)”。

用于数字分类的深度神经网络

数字图像分类模型学到的深度表示

这个网络将数字图像转换成与原始图像差别越来越大的表示,而其中关于最终结果的信息却越来越丰富。可以将深度网络看做多级信息蒸馏操作:信息穿过连续的过滤器,其纯度越来越高(即对任务的帮助越来越大)

深度学习的技术定义:学习数据表示的多级方法。

1.1.5 深度学习的工作原理

神经网络中每层对输入数据所做的具体操作保存在该层的权重(weight)中,其本质是一串数字。 用术语来说,每层实现的变换由其权重来参数化(parameterize)学习的意思是为神经网络的所有层找到一组权重值,使得该网络能够将每个示例输入与其目标正确地一一对应。

神经网络是由其权重来参数化

想要控制神经网络的输出,就需要能够衡量该输出与预期值之间的距离。这是神经网络损失函数(loss function)的任务。

损失函数用来衡量网络输出结果的质量

深度学习的基本技巧是利用这个距离值作为反馈信号来对权重值进行微调,以降低当前示例对应的损失值。这种调节由优化器(optimizer)完成, 它实现了所谓的反向传播(backpropagation)算法(下一章介绍),这是深度学习的核心算法。

将损失值作为反馈信号来调节权重

一开始对神经网络的权重随机赋值,因此网络只是实现了一系列随机变换。其输出结果自然也和理想值相去甚远,相应地,损失值也很高。但随着网络处理的示例越来越多,权重值也在向正确的方向逐步微调,损失值也逐渐降低。这就是训练循环(training loop)

第二章 神经网络的数学基础

2.2 神经网络的数据表示

当前所有机器学习系统都使用张量作为基本数据结构。

张量是一个数据容器,包含的数据几乎总是数值数据,是矩阵向任意维度的推广。张量的维度通常叫做轴

2.2.1 标量(0D张量)

仅包含一个数字的张量叫做标量(scalar)或0维张量,有0个轴。

>>> import numpy as np
>>> x = np.array(12)
>>> x
array(12)
>>> x.ndim
0

2.2.2 向量(1D张量)

数字组成的数组叫做向量(vector)或一维张量(1D张量),有1个轴。

>>> x = np.array([12, 3, 6, 14, 7])
>>> x
array([12, 3, 6, 14, 7])
>>> x.ndim
1

2.2.3 矩阵(2D张量)

向量组成的数组叫做矩阵(matrix)或二维张量(2D张量)

>>> x = np.array([[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]])
>>> x.ndim
2

2.2.4 3D张量与更高维张量

将多个矩阵组合成一个新的数组,可以得到一个3D 张量。 将多个3D张量组合成一个数组,可以创建一个4D张量

深度学习处理的一般都是0D到4D的张量,但处理视频数据时可能会遇到5D张量

2.2.5 关键属性

张量是由一下三个关键属性来定义的:

  • 轴的个数(阶,ndim)。3D张量有3个轴,矩阵有2个轴。
  • 形状(shape)。是一个整数元组,表示张量沿每个轴的维度大小(元素个数)。矩阵示例形状:{3,5};3D张量示例形状:{3,3,5}。
  • 数据类型(dtype):可以是float32、uint8、float64等。

2.2.6 在Numpy中操作张量

选择张量的特定元素叫做张量切片(tensor slicing)

例子:选择10-100个数字(不包括100),并将其放在形状为(90,28,28)的数组中。

>>> my_slice = train_images[10:100]
>>> print(my_slice.shape)
(90, 28, 28)
# 等同于:
>>> my_slice = train_images[10:100, :, :]
>>> my_slice.shape
(90, 28, 28)
>>> my_slice = train_images[10:100, 0:28, 0:28]
>>> my_slice.shape
(90, 28, 28)

2.2.7 数据批量的概念

深度学习中数据张量的第一个轴(0轴)都是样本轴(samples axis,样本维度)

深度学习模型不会同时处理整个数据集,而是将数据拆分成小批量。

# 批量大小为128
batch = train_images{:128}
# 然后是下一个批量
batch = train_images{128:256}
# 然后是第n个批量
batch = train_images[128 * n:128 * (n + 1)]

对于这种批量张量。第一个轴(0轴)叫做批量轴(batch axis)批量维度(batch dimension)

2.2.8 现实世界中的数据张量

现实中要处理的数据几乎总是以下类别之一:

  • 向量数据:2D 张量,形状为(samples, features)。

  • 时间序列数据或序列数据:3D 张量,形状为(samples, timesteps,features)。

  • 图像:4D张量,形状为(samples, height, width, channels)或(samples,channels,height, width)。

  • 视频:5D张量,形状为(samples, frames, height, width, channels)或(samples,frames, channels, height, width)。

2.2.9 向量数据

每个数据点都被编码为一个向量,因此一个数据批量就被编码为2D张量,其中第一个轴是样本轴, 第二个轴是特征轴

2.2.10 时间序列数据或序列数据

每个样本可以被编码为一个向量序列(即2D张量),因此一个数据批量就被编码为一个3D张量。

2.2.11 图像数据

图像通常具有三个维度:高度、宽度和颜色深度。图像张量时钟都是3D张量。

128张大小为256×256的灰度图像组成的批量可以保存在大小为{128,256,256,1}张量。

128张彩色图像:{128,256,256,3}。

图像数据组成的4D张量

TensorFlow中遵循通道在后的约定,即将颜色深度轴放在最后:{samples,height,width,color_depth}

2.2.12 视频数据

保存在5D张量中:{samples,frame,height,width,color_depth}

2.3 神经网络的“齿轮”:张量运算

2.3.2 广播

将两个形状不同的张量相加,较小的张量会被广播(broadcast),以匹配较大张量的形状。

步骤:

①. 向较小的张量添加轴(叫作广播轴),使其ndim与较大的张量相同。
②. 将较小的张量沿着新轴重复,使其形状与较大的张量相同。

2.3.3 张量点积

两个向量之间的点积是一个标量,一个矩阵和一个向量做点积的结果是一个向量。

import numpy as np
z = np.dot(x, y)

2.3.4 张量变形

指改变张量的行和列。

>>> x = np.array([[0., 1.],
[2., 3.],
[4., 5.]])
>>> print(x.shape)
(3, 2)
>>> x = x.reshape((6, 1))
>>> x
array([[ 0.],
[ 1.],
[ 2.],
[ 3.],
[ 4.],
[ 5.]])
>>> x = x.reshape((2, 3))
>>> x
array([[ 0., 1., 2.],
[ 3., 4., 5.]])

特殊的张量变形:转置

>>> x = np.zeros((300, 20))
>>> x = np.transpose(x)
>>> print(x.shape)
(20, 300)

2.3.6 深度学习的几何解释

神经网络完全由一系列张量运算组成,而这些张量运算都只是输入数据的几何变换。深度网络的每一层都通过变换使数据解开一点点——许多层堆叠在一起,可以实现非常复杂的解开过程

2.4 神经网络的“引擎”:基于梯度的优化

一个训练循环的具体过程:

①. 抽取训练样本x 和对应目标y 组成的数据批量。
②. 在x 上运行网络[这一步叫作前向传播(forward pass)],得到预测值y_pred。
③. 计算网络在这批数据上的损失,用于衡量y_pred 和y 之间的距离。
④. 计算损失相对于网络参数的梯度[一次反向传播(backward pass)]。
⑤. 将参数沿着梯度的反方向移动一点,比如W -= step * gradient,从而使这批数据上的损失减小一点。

这叫小批量随机梯度下降(mini-batch stochastic gradient descent,小批量SGD)。

此外,SGD 还有多种变体,其区别在于计算下一次权重更新时还要考虑上一次权重更新,而不是仅仅考虑当前梯度值,比如带动量的SGD、Adagrad、RMSProp 等变体。这些变体被称为优化方法(optimization method)或优化器(optimizer)。其中动量的引入可以使优化过程避免进入局部极小点。

第三章 神经网络入门

3.1 神经网络剖析

训练神经网络主要围绕以下四个方面:

①. ,多个层组合成网络(或模型)。
②. 输入数据和相应的 目标
③. 损失函数,即用于学习的反馈信号。
④. 优化器,决定学习过程如何进行。

3.1.1 层:深度学习的基础组件

神经网络的基本数据结构是。层是一个数据处理模块,将一个或多个输入张量转换为一个或多个输出张量。

不同的张量格式与不同的数据处理类型需要用到不同的层。例如,简单的向量数据保存在形状为(samples, features) 的2D 张量中,通常用全连接层(对应于Keras 的Dense 类)来处理。序列数据保存在形状为(samples, timesteps, features) 的3D 张量中,通常用循环层(recurrent layer,比如Keras 的LSTM 层)来处理。图像数据保存在4D 张量中,通常用二维卷积层(Keras 的Conv2D)来处理。

3.1.2 模型:层构成的网络

深度学习模型是层构成的有向无环图。最常见的例子就是层的线性堆叠,将单一输入映射为单一输出。

一些常见的网络拓扑结构:双分支(two-branch)网络、多头(multihead)网络、Inception模块。

网络的拓扑结构定义了一个假设空间(hypothesis space)。选定了网络拓扑结构,意味着将假设空间限定为一系列特定的张量运算,将输入数据映射为输出数据。然后,你需要为这些张量运算的权重张量找到一组合适的值。

3.1.3 损失函数与优化器:配置学习过程的关键

一旦确定了网络架构,还需要选择一下两个参数:

  • 损失函数——在训练过程中需要将其最小化。它能够衡量当前任务是否已经成功完成。
  • 优化器——决定如何基于损失函数对网络进行更新。它执行的是随机梯度下降(SGD)的某个变体。

具有多个输出的神经网络可能具有多个损失函数(每个输出对应一个损失函数)。但是,梯度下降过程必须基于单个标量损失值。因此,对于具有多个损失函数的网络,需要将所有损失函数取平均,变为一个标量值。

可以遵循一些简单的指导原则来选择正确的损失函数:

①. 二分类问题:使用二元交叉熵(binary crossentropy)损失函数;

②. 多分类问题:使用分类交叉熵(categorical crossentropy)损失函数;

③. 回归问题:均方误差(mean-squared error)损失函数;

④. 序列学习问题:联结主义时序分类(CTC, connectionist temporal classification)损失函数。

3.2 Keras简介

3.2.1 Keras、TensorFlow

Keras 是一个模型级(model-level)的库,为开发深度学习模型提供了高层次的构建模块。它不处理张量操作、求微分等低层次的运算。相反,它依赖于一个专门的、高度优化的张量库来完成这些运算,这个张量库就是Keras 的后端引擎(backend engine)。Keras 没有选择单个张量库并将Keras 实现与这个库绑定,而是以模块化的方式处理这个问题。因此,几个不同的后端引擎都可以无缝嵌入到Keras 中。

深度学习的软件栈和硬件栈

通过TensorFlow(或Theano、CNTK),Keras 可以在CPU 和GPU 上无缝运行。在CPU 上运行时,TensorFlow 本身封装了一个低层次的张量运算库,叫作Eigen;在GPU 上运行时,TensorFlow封装了一个高度优化的深度学习运算库,叫作NVIDIA CUDA 深度神经网络库(cuDNN)

3.2.2 使用Keras开发:概述

典型 Keras工作流程:

①. 定义训练数据:输入张量和目标张量。
②. 定义层组成的网络(或模型),将输入映射到目标。
③. 配置学习过程:选择损失函数、优化器和需要监控的指标。
④. 调用模型的fit 方法在训练数据上进行迭代。

定义模型有两种方法:一种是使用Sequential 类(仅用于层的线性堆叠,这是目前最常见的网络架构),另一种是函数式API(functional API,用于层组成的有向无环图,让你可以构建任意形式的架构)。

# 一个利用Sequential类定义的两层模型
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(32, activation='relu', input_shape=(784,)))
model.add(layers.Dense(10, activation='softmax'))

配置学习过程是在编译这一步,你需要指定模型使用的优化器和损失函数,以及训练过程中想要监控的指标。下面是单一损失函数的例子,这也是目前最常见的。

from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),
              loss='mse',
              metrics=['accuracy'])

最后,学习过程就是通过fit() 方法将输入数据的Numpy 数组(和对应的目标数据)传入模型。

model.fit(input_tensor, target_tensor, batch_size=128, epochs=10)

3.5 新闻分类——多分类问题

应该从这个例子中学到的要点:

①. 如果要对N个类别的数据点进行分类,网络的最后一层应该是大小为N的Dense层。
②. 对于单标签、多分类问题,网络的最后一层应该使用softmax激活,这样可以输出在N个输出类别上的概率分布。
③. 这种问题的损失函数几乎总是应该使用分类交叉熵(categorical crossentropy)。它将网络输出的概率分布与目标的真实分布之间的距离最小化。
④. 处理多分类问题的标签有两种方法。
通过分类编码(也叫one-hot 编码)对标签进行编码,然后使用categorical_
crossentropy 作为损失函数。
将标签编码为整数,然后使用sparse_categorical_crossentropy损失函数。
⑤. 如果你需要将数据划分到许多类别中,应该避免使用太小的中间层,以免在网络中造成信息瓶颈。

第四章 机器学习基础

4.1 机器学习的四个分支

4.1.1 监督学习

给定一组样本(通常由人工标注),它可以学会将输入数据映射到已知目标[也叫标注(annotation)]。一般来说,近年来广受关注的深度学习应用几乎都属于监督学习,比如光学字符识别、语音识别、图像分类和语言翻译。

监督学习主要包括分类回归,但也有其它变体(在此先暂不讨论)。

4.1.2 无监督学习

无监督学习是指在没有目标的情况下寻找输入数据的有趣变换,其目的在于数据可视化、数据压缩、数据去噪或更好地理解数据中的相关性。降维 (dimensionality reduction)和聚类(clustering)都是众所周知的无监督学习方法。

4.1.3 自监督学习

自监督学习是没有人工标注的标签的监督学习,标签是从输入数据中生成的,通常是使用启发式算法生成的。

4.1.4 强化学习

在强化学习中,智能体(agent)接收有关其环境的信息,并学会选择使某种奖励最大化的行动。例如,神经网络会“观察”视频游戏的屏幕并输出游戏操作,目的是尽可能得高分,这种神经网络可以通过强化学习来训练。

4.2 评估机器学习模型

随着训练的进行,模型在训练数据上的性能始终在提高,但在前所未见的数据上的性能则可能不再变化或者开始下降,这就是过拟合

机器学习的目的是得到可以泛化(generalize)的模型,即在前所未见的数据上表现很好的模型,而过拟合则是核心难点。

4.2.1 三种经典的评估方法

1. 简单的留出验证

简单的留出验证数据划分

num_validation_samples = 10000

np.random.shuffle(data) # 通常需要打乱数据

validation_data = data[:num_validation_samples] # 定义验证集
data = data[num_validation_samples:]

training_data = data[:] # 定义训练集

# 在训练数据上训练模型,并在验证数据上评估模型
model = get_model()
model.train(training_data)
validation_score = model.evaluate(validation_data)

# 现在你可以调节模型、重新训练、评估,然后再次调节……

# 一旦调节好超参数,通常就在所有非测试数据上从头开始训练最终模型
model = get_model()
model.train(np.concatenate([training_data,validation_data]))
test_score = model.evaluate(test_data)

缺点::如果可用的数据很少,那么可能验证集和测试集包含的样本就太少,从而无法在统计学上代表数据。如果在划分数据前进行不同的随机打乱,最终得到的模型性能差别很大。

2. K折验证

K 折验证(K-fold validation)将数据划分为大小相同的K个分区。对于每个分区i,在剩余的K-1个分区上训练模型,然后在分区i上评估模型。最终分数等于K 个分数的平均值。

3折验证

k = 4
num_validation_samples = len(data) // k

np.random.shuffle(data) # 打乱数据

validation_scores = []
for fold in range(k):
    # 选择验证数据分区
    validation_data = data[num_validation_samples * fold:
    num_validation_samples * (fold + 1)]
    # 使用剩余数据作为训练数据,“+”运算符是列表合并,不是求和
    training_data = data[:num_validation_samples * fold] +
                data[num_validation_samples * (fold + 1):]

    # 创建一个全新的模型实例(未训练)
    model = get_model()
    model.train(training_data)
    validation_score = model.evaluate(validation_data)
    validation_scores.append(validation_score)

# 最终验证分数:K折验证分数的平均值
validation_score = np.average(validation_scores)

# 在所有非测试数据上训练最终模型
model = get_model()
model.train(data)
test_score = model.evaluate(test_data)
3. 带有打乱数据的重复K折验证

是多次使用K 折验证,在每次将数据划分为K 个分区之前都先将数据打乱。最终分数是每次K 折验证分数的平均值。注意,这种方法一共要训练和评估P×K 个模型(P是重复次数),计算代价很大。

4.3 数据预处理、特征工程和特征学习

4.3.1 神经网络的数据预处理

数据预处理的目的是使原始数据更适于用神经网络处理。

1. 向量化

神经网络的所有输入和目标都必须是浮点数张量(在特定情况下可以是整数张量)。无论处理什么数据(声音、图像还是文本),都必须首先将其转换为张量,这一步叫作数据向量化(data vectorization)

2. 值标准化

为了让网络的学习变得更容易,输入数据应该具有以下特征:

  • 取值较小:大部分值都应该在0-1范围内
  • 同质性:所有特征的取值都应该在大致相同的范围内
3. 处理缺失值

如果不是所有样本都具有这个特征的话,那样你的训练数据或测试数据将会有缺失值。一般来说,对于神经网络,将缺失值设置为0是安全的,只要0不是一个有意义的值。网络能够从数据中学到0意味着缺失数据,并且会忽略这个值。

注意,如果测试数据中可能有缺失值,而网络是在没有缺失值的数据上训练的,那么网络不可能学会忽略缺失值。在这种情况下,你应该人为生成一些有缺失项的训练样本:多次复制一些训练样本,然后删除测试数据中可能缺失的某些特征。

4.3.2 特征工程

特征工程(feature engineering)是指将数据输入模型之前,利用你自己关于数据和机器学习算法(这里指神经网络)的知识对数据进行硬编码的变换(不是模型学到的),以改善模型的效果。多数情况下,一个机器学习模型无法从完全任意的数据中进行学习。呈现给模型的数据应该便于模型进行学习。

特征工程的本质:用更简单的方式表述问题,从而使问题变得更容易。它通常需要深入理解问题。

对于现代深度学习,大部分特征工程都是不需要的,因为神经网络能够从原始
数据中自动提取有用的特征

4.4 过拟合与欠拟合

机器学习的根本问题是优化和泛化之间的对立。优化(optimization)是指调节模型以在训练数据上得到最佳性能(即机器学习中的学习), 而泛化(generalization)是指训练好的模型在前所未见的数据上的性能好坏。

训练开始时,优化和泛化是相关的:训练数据上的损失越小,测试数据上的损失也越小,这时的模型是欠拟合(underfit)的。但在训练数据上迭代一定次数之后,泛化不再提高,验证指标先是不变,然后开始变差, 即模型开始过拟合。这时模型开始学习仅和训练数据有关的模式,但这种模式对新数据来说是错误的或无关紧要的。

防止过拟合的最优解决方法是获取更多的训练数据,模型的训练数据越多,泛化能力自然也越好。如果无法获取更多数据,次优解决方法是调节模型允许存储的信息量,或对模型允许存储的信息加以约束。如果一个网络只能记住几个模式,那么优化过程会迫使模型集中学习最重要的模式,这样更可能得到良好的泛化。这种降低过拟合的方法叫做正则化(regularization)。下面介绍几种最常见的正则化方法。

4.4.1 减少网络大小

防止过拟合的最简单的方法就是减小模型大小,即减少模型中可学习参数的个数(这由层数和每层的单元个数决定)。在深度学习中,模型中可学习参数的个数通常被称为模型的容量(capacity)。 直观上来看,参数更多的模型拥有更大的记忆容量(memorization capacity),因此能够在训练样本和目标之间轻松地学会完美的字典式映射,这种映射没有任何泛化能力。

4.4.2 添加权重正则化

一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,这使得权重值的分布更加规则(regular)。这种方法叫作权重正则化(weight regularization),其实现方法是向网络损失函数中添加与较大权重值相关的成本(cost)。这个成本有两种形式。
①.L1正则化(L1 regularization) :添加的成本与权重系数的绝对值[权重的L1 范数(norm)]成正比。
②. L2 正则化(L2 regularization):添加的成本与权重系数的平方(权重的L2 范数)成正比。神经网络的L2 正则化也叫权重衰减(weight decay)。不要被不同的名称搞混,权重衰减与L2 正则化在数学上是完全相同的。

from keras import regularizers

# 向分类网络中添加L2权重正则化
model = models.Sequential()
model.add(layers.Dense(16,kernel_regularizer=regularizers.l2(0.001),activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16,kernel_regularizer=regularizers.l2(0.001),activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

l2(0.001) 的意思是该层权重矩阵的每个系数都会使网络总损失增加0.001 weight_coefficient_value。注意,由于这个惩罚项*只在训练时添加,所以这个网络的训练损失会比测试损失大很多。

还可以用以下这些权重正则化来代替L2正则化:

from keras import regularizers

regularizers.l1(0.001) # L1正则化

regularizers.l1_l2(l1=0.001, l2=0.001) # 同时做L1和L2正则化

4.4.3 添加dropout正则化

dropout 是神经网络最有效也最常用的正则化方法之一。对某一层使用dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为0)。其核心思想是在层的输出值中引入噪声,打破不显著的偶然模式(Hinton 称之为阴谋)。如果没有噪声的话,网络将会记住这些偶然模式。

dropout 比率(dropout rate)是被设为0 的特征所占的比例,通常在0.2~0.5范围内。

向分类网络中添加两个Dropout层,看一下它们降低过拟合的效果。

# 通过Dropout 层向网络中引入dropout,dropout 将被应用于前面一层的输出。
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))

4.5 机器学习的通用工作流程

4.5.1 定义问题、收集数据集

首先,你必须定义所面对的问题。

  • 你的输入数据是什么?你要预测什么?只有拥有可用的训练数据,你才能学习预测某件事情。比如,只有同时拥有电影评论和情感标注,你才能学习对电影评论进行情感分类。因此,数据可用性通常是这一阶段的限制因素。

  • 你面对的是什么类型的问题?是二分类问题、多分类问题、标量回归问题、向量回归问题,还是多分类、多标签问题?或者是其他问题,比如聚类、生成或强化学习?确定问题类型有助于你选择模型架构、损失函数等。

只有明确了输入、输出以及所使用的数据,你才能进入下一阶段。注意你在这一阶段所做的假设。

  • 假设输出是可以根据输入进行预测的。
  • 假设可用数据包含足够多的信息,足以学习输入和输出之间的关系。

4.5.2 选择衡量成功的指标

对于多分类问题,成功的定义就是分类的精度。 可以使用平均准确率均值(mean average precision)作为指标。

4.5.3 确定评估方法

三种常见的评估方法:

  • 留出验证集:数据量很大时采用

  • K折交叉验证:如果留出验证的样本量太少时可以采用

  • 重复的K折验证:如果可用的数据很少,同时模型评估又需要非常准确,那么应该使用这种方法

4.5.4 准备数据

将数据格式化,使其可以输入到机器学习模型中。

  • 数据格式化为张量
  • 张量的取值通常应该缩放为较小的值,比如在$[-1,1]$区间或$[0,1]$区间
  • 如果不同特征具有不同的取值范围,那么应该做数据标准化
  • 可能需要做特征工程,尤其是对于小数据问题

4.5.5 开发比基准更好的模型

开发一个小型模型,它能够打败纯随机的基准,即获得统计功效

想获得统计功效,必须有两个假设:

  • 假设输出是可以根据输入进行预测的
  • 假设可用的数据包含足够多的信息,足以学习输入和输出之间的关系

如果一切顺利,还需要选择三个关键参数来构建第一个构建模型:

①. 最后一层的激活。它对网络输出进行有效的限制。例如,IMDB 分类的例子在最后一层使用了sigmoid,回归的例子在最后一层没有使用激活,等等。
②. 损失函数。它应该匹配你要解决的问题的类型。例如,IMDB 的例子使用binary_crossentropy、回归的例子使用mse,等等。
③. 优化配置。你要使用哪种优化器?学习率是多少?大多数情况下,使用rmsprop 及其默认的学习率是稳妥的。

为模型选择正确的最后一层激活和损失函数

4.5.6 扩大模型规模:开发过拟合的模型

机器学习中无处不在的对立是优化和泛化的对立,理想的模型是刚好在欠拟合和过拟合的界线上,在容量不足和容量过大的界线上。为了找到这条界线,必须穿过它。

要搞清楚需要多大的模型,就必须开发一个过拟合的模型:

①. 添加更多的层。
②. 让每一层变得更大。
③. 训练更多的轮次。

要始终监控训练损失和验证损失,以及你所关心的指标的训练值和验证值。如果你发现模型在验证数据上的性能开始下降,那么就出现了过拟合。

下一阶段开始正则化和调节模型,以便尽可能地接近理想模型,既不过拟合也不欠拟合。

4.5.7 模型正则化与调节超参数

这一步最费时间

你将不断地调节模型、训练、在验证数据上评估(这里不是测试数据)、再次调节模型,然后重复这一过程,直到模型达到最佳性能。你应该尝试以下几项。

①. 添加dropout。
②. 尝试不同的架构:增加或减少层数。
③. 添加L1 和/ 或L2 正则化。
④. 尝试不同的超参数(比如每层的单元个数或优化器的学习率),以找到最佳配置。
⑤.(可选)反复做特征工程:添加新特征或删除没有信息量的特征。

一旦开发出令人满意的模型配置,你就可以在所有可用数据(训练数据+ 验证数据)上训练最终的生产模型,然后在测试集上最后评估一次。如果测试集上的性能比验证集上差很多,那么这可能意味着你的验证流程不可靠,或者你在调节模型参数时在验证数据上出现了过拟合。在这种情况下,你可能需要换用更加可靠的评估方法,比如重复的K折验证。

第五章 深度学习用于计算机视觉

5.1 卷积神经网络简介

5.1.1 卷积运算

密集连接层和卷积层的根本区别在于,Dense层从输入特征空间中学到的是全局模式,而卷积层学到的是局部模式,对于图像来说,学到的就是在输入图像的二维小窗口中发现的模式。

图像可以被分解为局部模式,如边缘、纹理等

这个重要特性使卷积神经网络具有以下两个有趣的性质。

  • 卷积神经网络学到的模式具有平移不变性(translation invariant)。卷积神经网络在图像右下角学到某个模式之后,它可以在任何地方识别这个模式,比如左上角。对于密集连接网络来说,如果模式出现在新的位置,它只能重新学习这个模式。 这使得卷积神经网络在处理图像时可以高效利用数据(因为视觉世界从根本上具有平移不变性),它只需要更少的训练样本就可以学到具有泛化能力的数据表示。
  • 卷积神经网络可以学到模式的空间层次结构(spatial hierarchies of patterns)。第一个卷积层将学习较小的局部模式(比如边缘),第二个卷积层将学习由第一层特征组成的更大的模式,以此类推。这使得卷积神经网络可以有效地学习越来越复杂、越来越抽象的视觉概念 (因为视觉世界从根本上具有空间层次结构)。

对于包含两个空间轴(高度和宽度) 和一个深度轴(也叫通道轴)的3D 张量, 其卷积也叫特征图(feature map)。对于RGB 图像,深度轴的维度大小等于3,因为图像有3 个颜色通道:红色、绿色和蓝色。对于黑白图像(比如MNIST 数字图像),深度等于1(表示灰度等级)。卷积运算从输入特征图中提取图块,并对所有这些图块应用相同的变换, 生成输出特征图(output feature map)。该输出特征图仍是一个3D 张量,具有宽度和高度,其深度可以任意取值,因为输出深度是层的参数,深度轴的不同通道不再像RGB 输入那样代表特定颜色,而是代表 过滤器(filter)。

视觉世界形成了视觉模块的空间层次结构:超局部的边缘组合成局部的对象,
比如眼睛或耳朵,这些局部对象又组合成高级概念,比如“猫”

在MNIST示例中,第一个卷积层接收一个大小为(28, 28, 1)的特征图,并输出一个大小为(26, 26, 32)的特征图,即它在输入上计算32个过滤器。对于这32个输出通道,每个通道都包含一个26×26的数值网格,它是过滤器对输入的响应图(response map),表示这个过滤器模式在输入中不同位置的响应。 这也是特征图这一术语的含义:深度轴的每个维度都是一个特征(或过滤器), 而2D张量output[:, :, n]是这个过滤器在输入上的响应的二维空间(map)。

响应图的概念:某个模式在输入中的不同位置是否存在的二维图

卷积由以下两个关键参数所定义:
①. 从输入中提取的图块尺寸:这些图块的大小通常是3×3 或5×5。
②. 输出特征图的深度:卷积所计算的过滤器的数量。

对于Keras的Conv2D层,这些参数都是向层传入的前几个参数:Conv2D(output_depth,(window_height, window_width))。

卷积的工作原理:在3D输入特征图上滑动(slide)这些3×3或5×5的窗口,在每个可能的位置停止并提取周围特征的3D图块[形状为(window_height, window_width, input_depth)]。然后每个3D图块与学到的同一个权重矩阵[叫作卷积核(convolution kernel)]做张量积,转换成形状为(output_depth,)的1D向量。然后对所有这些向量进行空间重组,使其转换为形状为(height, width, output_depth)的3D输出特征图。输出特征图中的每个空间位置都对应于输入特征图中的相同位置(比如输出的右下角包含了输入右下角的信息)。举个例子,利用3×3的窗口,向量output[i, j, :]来自3D图块input[i-1:i+1,j-1:j+1, :]。

卷积的工作原理

注意,输出的宽度和高度可能与输入的宽度和高度不同。不同的原因可能有两点。
①. 边界效应,可以通过对输入特征图进行填充来抵消。如果你希望输出特征图的空间维度与输入相同,那么可以使用填充(padding)。填充是在输入特征图的每一边添加适当数目的行和列,使得每个输入方块都能作为卷积窗口的中心。对于Conv2D层,可以通过padding参数来设置填充,这个参数有两个取值:”valid”表示不使用填充(只使用有效的窗口位置);”same“表示“填充后输出的宽度和高度与输入相同”。padding 参数的默认值为”valid”。
②. 使用了步幅(stride)。两个连续窗口的距离是卷积的一个参数,叫作步幅,默认值为1。 也可以使用步进卷积(strided convolution),即步幅大于1的卷积。步幅为2 意味着特征图的宽度和高度都被做了2 倍下采样(除了边界效应引起的变化)。

为了对特征图进行下采样,我们不用步幅,而是通常使用最大池化(max-pooling)运算

5.1.2 最大池化运算

最大池化的作用:对特征图进行下采样。最大池化使用硬编码的max张量运算对局部图块进行变换,而不是学到的线性变换(卷积核)。最大池化通常使用2×2的窗口和步幅2,其目的是将特征图下采样2倍。

使用下采样的原因,一是减少需要处理的特征图的元素个数,二是通过让连续
卷积层的观察窗口越来越大(即窗口覆盖原始输入的比例越来越大),从而引入空间过滤器的层级结构。


文章作者: Mat Jenin
文章链接: http://matjenin.xyz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mat Jenin !
  目录