深度学习
人工智能>机器学习>深度学习
- 人工智能:努力将通常由人类完成的智力任务自动化。(人们输入的是规则(即程序)和需要根据这些规则进行处理的数据,系统输出的是答案)
- 机器学习:机器学习系统是训练出来的,而不是明确的用程序编写出来的。将与某个任务相关的许多示例输入机器学习系统,它会在这些示例中找到统计结构,从而最终找到规则将任务自动化。(人们输入的是数据和从这些数据中预期得到的答案,系统输出的是规则。这些规则随后可以应用于新的数据,并使计算机自主生成答案。)
- 机器学习的技术定义:在预先定义好的可能性空间中,利用反馈信号的指引来寻找输入数据的有用表示。
- 机器学习三要素:
- 输入数据点
- 预期输出的示例(对于图像标记任务来说,预期输出可能是分类标签)
- 衡量算法效果好坏的方法 (计算算法当前的输出与预期输出的差距)
- 从数据中学习表示:机器学习中衡量结果是一种反馈信号,用于调节算法的工作方式。这个调节步骤就是我们所说的学习。机器学习模型将输入数据变换为有意义的输出,这是一个从已知的输入和输出示例中精细“学习”的过程。因此,机器学习和深度学习的核心问题在于有意义的变换数据,即在于学习输入数据的有用表示(representation)—这种表示可以让数据更接近预期输出。
- 深度学习之深度:又被称为分层表示学习(layered representations learning)和层级表示学习(hierarchical representations learning)。深度学习是机器学习的一个分支领域,它是从数据中学习表示的一种新方法,强调从连续的层中进行学习,这些层对应于越来越有意义的表示。数据模型中包含多少层,这个被称为模型的深度(depth)。
- 深度学习:深度学习是从数据中学习表示的一种数学框架。
- 深度学习的技术定义:学习数据表示的多级方法。
- 深度学习的工作原理:神经网络中每层对输入数据所做的具体操作保存在该层的权重中,其本质是一串数字。用术语来说,每层实现的变换由其权重来参数化。权重有时也被称为该层的参数。学习的意思是为神经网络的所有层找到一组权重值,使得该网络能够将每个示例输入与其目标正确的一一对应。
损失函数的输入是网络预测值与真实值,然后计算一个距离值,衡量该网络在这个示例上的效果好坏。
深度学习的基本技巧是利用这个距离值作为反馈信号来对权重值进行微调,以降低当前示例对应的损失值。这种调节由优化器来完成。实现了反向传播算法,是深度学习的核心算法。
深度学习之前的机器学习
- 概率建模
概率建模(probabilistic modeling)是统计学原理在数据分析中的应用。
最早的机器学习形式:- 朴素贝叶斯算法。朴素贝叶斯是一类基于应用贝叶斯定理的机器学习分类器,它假设输入数据的特征都是独立的。
- logistic回归(logistics regression,简称logreg),是一种分类算法,而不是回归算法。
- 早期的神经网络
- 核方法(kernel method)
- 核方法是一组分类算法,其中最有名的就是支持向量机(SVM,support vector machine)。SVM的目标是通过在属于两个不同类别的两组数据点之间找到良好决策边界(decision boundary)来解决分类问题。决策边界可以看作一条直线或一个平面,将训练数据划分为两块空间,分别对应于两个类别。对于新数据点的分类,只需判断它位于决策边界的哪一侧。
- 将数据映射到高维表示从而使分类问题简化,在实践中通常是难以计算的。需要用到核技巧(kernel trick)。其基本思想是:要想咋新的表示空间中找到良好的决策超平面,不需要在新空间中直接计算点的坐标,只需要在新空间中计算点对之间的距离,而利用核函数(kernel function)可以高效的完成这种计算。核函数是一个在计算上能够实现的操作,将原始空间中的任意两个点映射为这两点在目标表示空间中的距离。完全避免了对新表示进行直接计算。核函数通常是人为选择的,而不是从数据中学习到的—对于SVM来说,只有分割超平面是通过学习得到的。
- SVM很难扩展到大型数据集,并且在图像分类等感知问题上的效果也不好,SVM是一种比较浅层的方法,要想应用于感知问题,首先需要手动提取出有用的表示(特征工程),这一步骤很难,而且不稳定。
- 决策树、随机森林与梯度提升机
- 决策树(decision tree)是类似于流程图的结构,可以对输入数据进行分类或根据给定输入来预测输出值。
- 随机森林(random forest),引入了一种健壮且实用的决策树学习方法,即首先构建许多决策树,然后将它们的输出集成在一起。
- 梯度提升机(gradient boosting machine)是将弱预测模型(通常是决策树)集成的机器学习技术。使用了梯度提升方法,通过迭代训练新模型来专门解决之前模型的弱点,从而改进任何机器学习模型的效果。将梯度提升技术应用于决策树时,得到的模型与随机森林具有相似的性质,但在绝大多数情况下都比随机森林要好。是目前处理非感知数据最好的方法之一。
- 深度学习从数据中进行学习时有两个基本特征:第一,通过渐进的、逐层的方式形成越来越复杂的表示;第二,对中间这些渐进的表示共同进行学习,每一层的变化都需要同时考虑上下两层的需要。
神经网络的数学基础
神经网络
- 类别和标签
在机器学习中,分类问题中的某个类别叫作类(class)。数据点叫作样本(sample)。某个样本对应的类叫作标签(label)。 - 神经网络的核心组件是层(layer),它是一种数据处理模块,可以看出数据过滤器。层从输入数据中提取表示—期望这种表示有助于解决手头的问题。大多数深度学习都是简单的层链接起来,从而实现渐进式的数据蒸馏(data distillation)。深度学习模型就像数据处理的筛子,包含一系列越来越精细的数据过滤器(即层)。
- 训练模型,选择编译步骤的三个参数:
- 损失函数(loss function):网络如何衡量在训练数据上的性能,即网络如何朝着正确的方向前进。
- 优化器(optimizer):基于训练数据和损失函数来更新网络的机制。
- 在训练和测试过程中需要监控的指标(metric):正确分类的图像所占的比例。
#加载keras中的MNIST数据集
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
#网络架构
from keras import models
from keras import layers
network = models.Sequential()
network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,)))
network.add(layers.Dense(10, activation='softmax'))
#编译步骤
network.compile(optimizer='rmsprop', loss='categorical_crossentropy',metrics=['accuracy'])
#准备图像数据
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype('float32') / 255
#准备标签
from keras.utils import to_categorical
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
#训练数据上拟合模型
network.fit(train_images, train_labels, epoches=5, batch_size=128)
神经网络的数据表示
张量是一个数据容器,它包含的数据几乎总是数值数据,因此它是数字的容器。张量是矩阵向任意维度的推广,张量的维度(dimension)通常叫作轴(axis)。
- 标量(0D张量):仅包含一个数字的张量叫作标量(scalar,也叫标量张量、零维张量。0D张量)。
- 向量(1D张量):数字组成的数组叫作向量(vector)或一维张量(1D张量)。一维张量只有一个轴。
- 矩阵(2D张量):向量组成的数组叫作矩阵(matrix)或二维张量(2D张量)。矩阵有两个轴(通常叫作行或列)
- 3D张量与更高维张量:将多个矩阵组合成一个新的数组,可以得到一个3D张量。
- 张量的关键属性:
- 轴的个数(阶)
- 形状
- 数据类型
- 数据批量的概念
通常深度学习中所有数据张量的第一个轴(0轴)都是样本轴(sample axis,也叫样本维度)。深度学习模型不会同时处理整个数据集,而是将数据拆分成小批量,对于这种批量张量,第一个轴(0轴)叫作批量轴(batch axis)或批量维度(batch dimension)。 - 现实世界中的数据张量
- 向量数据:2D张量,形状为(samples, features)
- 时间序列数据或序列数据:3D张量,形状为(samples, timesteps, features)。
- 图像:4D张量,形状为(samples, height, width, channels)或(samples, channels, height, width)。
- 向量数据
对于这种数据集,每个数据点都被编码为一个向量,因此一个数据批量就被编码为2D张量(即向量组成的数组),其中第一个轴就是样本轴,第二个轴是特征轴。 - 时间序列数据或序列数据
当时间(或序列顺序)对于数据很重要时,应该将数据存储在带有时间轴的3D张量中,每个样本可以被编码为一个向量序列(即2D张量),因此一个数据批量就被编码为一个3D张量。 - 图像数据
图像数据通常具有三个维度:高度、宽度和颜色深度。虽然灰度图像只含有一个颜色通道,因此可以保存在2D张量中,但按照惯例,图像张量适中都是3D张量,灰度图像的彩色通道只有一维。因此,如果图像大小为256x256,那么128张灰度图组成的批量可以保存在一个形状为(128, 256, 256, 1)的张量中,而128张彩色图像组成的批量则可以保存在一个形状为(128, 256, 256, 3)的张量中。图像张量的形状有两种约定:通道在后(channel-last)的约定(在TensorFlow中使用)和通道在前(channel-first)的约定(在THeano中使用)。Keras框架同时支持这两种格式。
张量运算
- 逐元素运算
relu运算和加法都是逐元素(element-wise)的运算,即该运算独立的应用于张量中的每个元素,这些运算非常适合大规模并行实现(向量化实现)。
def naive_relu(x):
assert len(x.shape) == 2 #x是一个Numpy的2D张量
x = x.copy()
for i in range(x.shape[0]):
for j in range(x.shape[1]):
x[i, j] = max(x[i, j], 0)
return x
def naive_add(x, y):
assert len(x.shape) == 2
assert x.shape == y.shape
x = x.copy()
for i in range(x.shape[0]):
for j in range(x.shape[1]):
x[i, j] += y[i, j]
return x
在实践中处理Numpy数组时,这些运算都是优化好的Numpy内置函数,这些函数将大量运算交给安装好的基础线性代数子程序(BLAS,basic linear algebra subprograms)实现,BLAS是低层次的、高度并行的、高效的张量操作程序,通常用Fortran或C语言来实现。
因此,在Numpy中可以直接进行下列逐元素运算,速度非常快。
import numpy as np
z = x + y #逐元素相加
z = np.maximum(z, 0.) #逐元素的ReLU
- 广播
将两个形状不同的张量相加,如果没有歧义的话,较小的张量会被广播(broadcast),一匹配较大张量的形状。广播包含以下两个步骤:- 向较小向量添加轴(叫作广播轴),使其ndim与较大的张量相同。
- 将较小的张量沿着新轴重复,使其形状与较大的张量相同。
def naive_add_matrix_and_vector(x, y):
assert len(x.shape) == 2
assert len(y.shape) == 1
x = x.copy()
for i in range(x.shape[0]):
for j in range(x.shape[1]):
x[i, j] += y[j]
return x
import numpy as np
x = np.random.random((64, 3, 32, 10)) #shape:(64, 3, 32, 10)
y = np.random.random((32, 10))#shape:(32, 10)
z = np.maximum(x, y) #shape:(64, 3, 32, 10)
- 张量点积
点积运算,也叫张量积(tensor product),,与逐元素的乘积不同,它将输入张量的元素合并在一起。
两个向量之间的点积是一个标量,而且只有元素个数相同的向量之间才能做点积。
def naive_vector_dot(x, y):
assert len(x.shape) == 1
assert len(y.shape) == 1
assert x.shape[0] == y.shape[0]
z = 0.
for i in range(x.shape[0]):
z += x[i] * y[i]
return z
一个矩阵x和一个向量y做点积,返回值是一个向量,其中每个元素是y和x的每一行之间的点积。
#method1
def naive_matrix_vector_dot(x, y):
assert len(x.shape) == 2
assert len(y.shape) == 1
assert x.shape[1] == y.shape[0]
z = np.zeros(x.shape[0])
for i in range(x.shape[0]):
for j in range(x.shape[1]):
z[i] += x[i, j] * y[j]
return z
#method2
def naive_matrix_vector_dot(x, y):
z = np.zeros(x.shape[0])
for i in range(x.shape[0]):
z[i] = naive_vector_dot(x[i, :], y)
return z
def naive_matrix_dot(x, y):
assert len(x.shape) == 2
assert len(y.shape) == 2
assert x.shape[1] == y.shape[0]
z = np.zeros((x.shape[0], y.shape[1]))
for i in range(x.shape[0]):
for j in range(x.shape[1]):
row_x = x[i, :]
col_y = y[:, j]
z[i, j] = naive_vector_dot(row_x, col_y)
return z
-
张量变形
-
张量运算的几何解释
对于张量运算所操作的张量,其元素可以被解释为某种几何空间内点的坐标,因此所有的张量运算都有几何解释。 -
深度学学习的几何解释
神经网络完全由一系列张量运算组成,而这些张量运算都只是输入数据的几何变换。因此,神经网络技术为高维空间中非常复杂的几何变换,这种变换可以通过许多简单的步骤来实现。
基于梯度的优化
每个神经层都通过output = relu(dot(W, input) + b)
的方法对输入数据进行变换。在这个表达式中W和b都是张量,均为该层的属性。它们被称为该层的权重(weight)或可训练参数(trainable parameter),分别对应kernel和bias属性。这些权重包含网络从观察训练数据中学到的信息。
训练循环过程:
- 抽取训练样本x和对应目标y组成数据批量。
- 在x上运行网络,得到预测值y_pred
- 计算网络在这批数据上的损失,用于衡量y_pred和y之间的距离。
- 更新网络的所有权重,使网络在这批数据上的损失略微下降。
网络中所有运算都是可微(differentiable)的,计算损失相对于网络系数的梯度(gradient),然后向梯度的反方向改变系数,从而使损失降低。
-
导数
假设有一个连续的光滑函数f(x) = y
, 将实数x映射为另一个实数y。由于函数是连续的,x的微小变化只能导致y的微小变化—函数连续性的直观解释。假设x增大了一个很小的因子epsilon_x,这导致y也发生了很小的变化,即epsilon_y:f(x + epsilon_x) = y + epsilon_y
。
此外,由于函数是光滑的(即函数曲线没有突变的角度),在某个点p附近,如果epsilon_x足够小,就可以近似为斜率为a的线性函数,这样epsilon_y就变成了a*epsilon_x:f(x + epsilon_x) = y + a * epsilon_x
,只有在x足够接近p时,这个线性近似才有效。
斜率a被称为f在p点的导数(derivative)。如果a是负的,说明x在p点附近的微小变化将导致f(x)减小;如果a是正的,那么x的微小变化将导致f(x)增大。此外,a的绝对值(导数的大小)表示增大或减小的速度快慢。
对于每个可微函数f(x)(可微的意思是“可以被求导”),都存在一个导数函数f’(x),将x的值映射为f在该点的局部线性近似的斜率。
导数完全描述了改变x后f(x)会如何变化。如果希望减小f(x)的值,只需将x沿着导数的反方向移动一小步。 -
张量运算的梯度:梯度
梯度(gradient)是张量运算的导数。它是导数这一概念向多元函数导数的推广,多元函数是以张量作为输入的函数。
单变量函数f(x)的导数可以看作函数f曲线的斜率。同样,gradient(f)(W0)也可以看作表示f(W)在W0附近曲率(curvature)的张量。 -
随机梯度下降
给定一个可微函数,理论上可以用解析法找到它的最小值:函数的最小值时导数为0的点,因此只需要找到所有导数为0的点,然后计算函数在其中哪个点具有最小值。但是对于实际的神经网络是无法求解的,因为参数的个数不会少于几千个,而且经常有上千万个。 -
链式求导:反向传播算法
将链式法则应用于神经网络梯度值的计算,得到的算法叫作反向传播(backpropagation,也叫反式微分,reverse-mode differentiation)。反向传播从最终损失值开始,从最顶层反向作用至最底层,利用链式法则计算每个参数对损失值的贡献大小。
使用符号微分(symbolic differentiation)框架来实现神经网络,给定一个运算链,并且已知每个运算的导数,这些框架就可以利用链式法则来计算这个运算链的梯度函数,将网络参数值映射为梯度值。对于这样的函数,反向传播就简化为调用这个梯度函数。由于符号微分的出现,无需手动实现反向传播算法。
总结:
- 学习是指找到一组模型参数,使得在给定的训练数据样本和对应目标值上的损失函数最小化。
- 学习过程:随机选取包含数据样本及其目标值的批量,并计算批量损失相对于网络参数的梯度。随后将网络参数沿着梯度的反方向按学习率指定的移动距离移动。
- 整个学习过程之所以能够实现,是因为神经网络是一系列可微分的张量运算,因此可以利用求导的链式法则来得到梯度函数,这个函数将当前参数和当前数据批量映射为一个梯度值。
- 损失是在训练过程中需要最小化的量,因此,它应该能够衡量当前任务是否已成功解决。
- 优化是使用损失梯度更新参数的具体方法。
神经网络入门
神经网络剖析
训练神经网络围绕以下四个方面:
- 层,多个层组合成网络(或模型)。
- 输入数据和相应的目标。
- 损失函数,即用于学习的反馈信号。
- 优化器,决定学习过程如何进行。
- 层:深度学习的基础组件
层是一个数据处理模块,将一个或多个输入张量转换为一个或多个输出张量。有些层是无状态的,但大多数的层是有状态的,即层的权重。权重是利用随机梯度下降学到的一个或多个张量,其中包含网络的知识。
不同的张量格式与不同的数据处理类型需要用到不同的层。 - 模型:层构成的网络
深度学习模型是层构成的有向无环图,最常见的就是层的线性堆叠,将单一输入映射为单一输出。网络拓扑结构定义了一个假设空间(hypothesis space)。 - 损失函数与优化器:配置学习过程的关键
具有多个输出的神经网络可能具有多个损失函数(每个输出对应一个损失函数)。但是,梯度下降过程必须基于单个标量损失值。因此,对于具有多个损失函数的网络,需要将所有损失函数取平均,变为一个标量值。
Keras简介
- Keras、TensorFlow、Theano和CNTK
Keras是一个模型级(model-level)的库,为开发深度学习模型提供了高层次的构建模块。它不处理张量操作、求微分等低层次的运算。它依赖于一个专门的、高度优化的张量库来完成这些运算,这个张量库就是Keras的后端引擎(backend engine)。目前,Keras哟三个后端实现:TensorFlow后端、Theano后端和微软认知工具包(CNTK,Microsoft cognitive toolkit)后端。 - Keras开发:工作流程
- 定义训练数据:输入张量和目标张量。
- 定义层组成的网络(或模型),将输入映射到目标。
- 使用Sequential类(仅用于层的线性堆叠,目前常用的网络架构)。
- 函数式API(functional API,用于层组成的有向无环图,可以构建任意形式的架构)。
- 配置学习过程:选择损失函数、优化器和需要监控的指标。
- 调用模型的fit方法在训练数据上进行迭代。
电影评论分类:二分类问题
- IMDB数据集
IMDB数据集包含来自互联网电影数据库(IMDB)的50000条严重两极分化的评论。数据集被分为用于训练的25000条评论与用于测试的25000条评论,训练集和测试集都包含50%的正面评论和50%负面评论。 - 准备数据
将整数序列转换为张量。- 填充列表,使其具有相同的长度,再将列表转换成形状为(samples, word_indices)的整数张量。
- 对列表进行one-hot编码,将其转换为0和1组成的向量。
- 构建网络
一个隐藏单元(hidden unit)是该层表示空间的一个维度。与权重矩阵做点积相当于将输入数据投影到隐藏单元数表示的维度空间。可以将表示空间的维度直观的理解为‘网络学习内部表示时所拥有的自由度’,隐藏单元越多(即更高维的表示空间),网络越能够学到更加复杂的表示,但网络的代价也变得更大,而且可能会导致学到不好的模式(这种模式会提高训练数据上的性能,但不会提高测试数据上的性能)。
中间层使用relu作为激活函数,最后一层使用sigmoid激活以输出一个0~1范围内的概率值,relu(rectified linear unit,整流线性单元)函数将所有负值归零,而sigmoid函数则将任意值“压缩”到[0,1]区间内,其输出值可以看作概率值。
如果没有relu等激活函数(也叫非线性),Dense层将只包含两个线性运算——点积和加法:
output = dot(W, input) + b
这样Dense层就只能学习输入数据的线性变换(仿射变换):该层的假设空间是从输入数据到16(隐藏单元个数)位空间所有可能的线性变换集合。这种假设空间非常有限,无法利用多个表示层的优势,因为多个线性层堆叠实现的仍是线性运算,添加层数并不会扩展假设空间。
为了得到更丰富的假设空间,从而充分利用多层表示的优势,需要添加非线性或激活函数。
#加载IMDB数据库
from keras.datasets import imdb
#train_data,test_data是评论组成的列表,每条评论又是单词组成的列表(表示一系列单词)
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)
#将一个单词映射为整数索引的字典
word_index = imdb.get_word_index()
#键值颠倒,将整数索引映射为单词
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
#将评论解码,索引减去了3,是因为0/1/2是为‘padding(填充)’、‘start of sequence(序列开始)’、‘unknown(未知词)’分别保留的索引
decoded_review = ''.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])
#将整数序列编码为二进制矩阵
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences):
#将results[i]的指定索引设为1
results[i, sequence] = 1.
return results
#将数据向量化
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
#模型定义
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
#编译模型
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
#配置优化器
from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(lr=0.001),loss='binary_crossentropy', metrics=['accuracy'])
#使用自定义的损失和指标
from keras import losses
from keras import metrics
model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss=losses.binary_crossentropy, metrics=[metrics.binary_accuracy])
#留出验证集
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
#训练模型
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val))
#绘制训练损失和验证损失
import matplotlib.pyplot as plt
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']
epoches = range(1, len(loss_values) + 1)
plt.plot(epochs, loss_values, 'bo', label='Training loss')
plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
#绘制训练精度和验证精度
plt.clf()
acc = history_dict['acc']
val_acc = history_dict['val_acc']
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and Validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
#从头开始重新训练一个模型
model = models.Sequential()
model.add(layers.Dense(16, activation='relu', input_shape=(10000,) ))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=4, batch_size=512)
results = models.evaluate(x_test, y_test)
# 使用训练好的网络在新数据上生成预测结果
model.predict(x_test)
新闻分类:多分类
- 路透社数据集
路透社数据集包含许多短新闻及其对应的主题,是一个简单、广泛使用的文本分类数据集。包括46个不同的主题:某些主题的样本更多,但训练集中每个主题都有至少10个样本。 - 中间层维度足够大的重要性
中间层的隐藏单元个数不应该比分类的类别小太多,否则会造成信息瓶颈。
#加载路透社数据集
from keras.datasets import reuters
(train-data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000)
#将索引解码为新闻文本
word_index = reuters.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoded_newwise = ''.join([reverse_word_index.get(i-3, '?') for i in train_data[0]])
#编码数据
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
for i, sequence in enumerate(sequences);
results[i, sequence] = 1.
return results
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
def to_one_hot(labels, dimensiion=46):
results = np.zeros((len(labels), dimension))
for i, label in enumerate(labels):
results[i, label] = 1.
return results
one_hot_train_labels = to_one_hot(train_labels)
one_hot_test_labels = to_one_hot(test_labels)
# keras 内置方法可以实现这个操作
from keras.utils.np_utils import to_categorical
one_hot_train_labels = to_categorical(trian_labels)
onet_hot_test_labels = to_categorical(test_labels)
#模型定义
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))
#编译模型
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
#留出验证集
x_val = x_train[:1000]
partial_x_train = x_train[1000:]
y_val = one_hot_train_labels[:1000]
partial_y_train = one_hot_train_labels[1000:]
#训练模型
history = model.fit(partial_x_train, partial_y_train, epochs=20,batch_size=512, validation_data=(x_val, y_val))
#绘制训练损失和验证损失
import matplotlib.pyplot as plt
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(loss) + 1)
plt.plt(epochs, loss, 'bo', label='Training loss')
plt.plt(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
#绘制训练精度和验证精度
plt.clf()
acc = history.history['acc']
val_acc = history.history['val_acc']
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and Validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()
#从头开始重新训练一个模型
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(46, activation='softmax'))
model.compile(optimizer='rmsprop', loss='categrical_crossentropy',metrics=['accuracy'])
model.fit(partial_x_train, partial_y_train, epochs=9, batch_size=512, validation_data=(x_val, y_val))
results = model.evaluate(x_test, one_hot_test_labels)
#在新数据上生成预测结果
predictions = model.predict(x_test)
- 总结
- 如果要对N个类别的数据点进行分类,网络的最后一层应该是大小为N的Dense层。
- 对于单标签、多分类问题,网络的最后一层应该使用softmax激活,这样可以输出在N个输出类别上的概率分布。
- 损失函数为分类交叉熵,它将网络输出的概率分布与目标的真实分布之间的距离最小化。
- 处理分类问题的标签有两种方式:
- 通过分类编码(也叫one-hot编码)对标签进行编码,然后使用categorical_crossentropy作为损失函数。
- 将编码标签改为整数,然后使用sparse_categorical_crossentropy损失函数。
- 如果需要将数据划分到许多类别中,应该避免使用太小的中间层,以免在网络中造成信息瓶颈。
预测房价:回归问题
分类问题,其目标是预测输入数据点所对应的单一离散的标签。回归问题,它预测一个连续值而不是离散的标签。例如:根据气象数据预测明天的气温,或者根据软件说明预测完成软件项目所需要的时间。
不用将回归问题和logistics回归算法混为一谈,logistics回归不是回归算法,而是分类算法。
- 波士顿房价数据集
波士顿房价数据集包含的数据点相对较少,只有506个,分别为404个训练样本和102个测试样本。输入数据的每个特征都有不同的取值范围。比如,有些特征是比例,取值范围为0-1;有的取值范围为1-12;还有的范围为0-100,等等。 - 构建网络
网络的最后一层只有一个单元,没有激活,是一个线性层。这是标量回归(标量回归是预测单一连续值的回归)的典型设置。添加激活函数将会限制输出范围。所以最后一层是纯线性额,可以学会预测任意范围内的值。
编译网络用的是mse损失函数,即均方误差(MSE, mean squared error),预测值与目标值之间的平方。这是回归问题常用的损失函数。
在训练过程的指标:平均绝对值误差(MAE, mean absolute error)。是预测值与目标值之差的绝对值。
#加载波士顿房价数据
from keras.datasets import boston_housing
(train_data, train_targets),(test_data, test_targets) = boston_housing.load_data()
#数据标准化
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std
#构建网络
from keras import models
from keras import layers
def build_model():
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(train_data.shape[1],)))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(1))
model.compile(optimizer='rmsprop',loss='mse', metrics=['mae'])
return model
#K折验证
import numpy as np
k = 4
num_val_samples = len(train_data) // k
num_epochs = 100
all_mae_histories = []
#all_scores = []
for i in range(k):
print('processing fold #', i)
val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
partial_train_data = np.concatenate([train_data[:i * num_val_samples], train_data[(i + 1) * num_val_samples:]], axis=0)
partial_train_targets = np.concatenate([train_targets[:i * num_val_samples], train_targets[(i + 1) * num_val_samples:]], axis=0)
model = build_model()
history = model.fit(partial_train_data, partial_train_targets, epochs=num_epochs, batch_size=1, verbose=0)
#val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
#all_scores.append(val_mae)
mae_history = history.history['val_mean_absolute_error']
all_mae_histories.append(mae_history)
#计算所有轮次中的K折验证分数平均值
average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]
#绘制验证分数
import matplotlib.pyplot as plt
plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
#绘制验证分数(删除前10个数据点)
def smooth_curve(points, factor=0.9):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
smooth_mae_history = smooth_curve(average_mae_history[10:])
plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
#训练最终模型
model = build_model()
model.fit(train_data, train_targets, epochs=80, batch_size=16, verbose=0)
test_mae_score, test_mae_score = model.evaluate(test_data, test_targets)
机器学习基础
机器学习的分支
- 监督学习
给定一组样本(通常由人工标注),监督学习可以学会将输入数据映射到已知目标(标注)。监督学习主要包括分类和回归。但还有更多的变体:- 序列生成(sequence generation)。给定一张图像,预测描述图像的文字。序列生成有时候可以被重新表示为一系列分类问题。
- 语法树预测(syntax tree prediction)。给定一个句子,预测其分解生成的语法树。
- 目标检测 (object detection)。给定一张图像,在图像中特定目标的周围画一个边界框。这个问题也可以表示为分类问题(给定多个候选边界框,对每个边框内的目标进行分类)或分类与回归联合问题(用向量回归来预测边界框的坐标)。
- 图像分割(image segmentation)。给定一张图像,在特定图像上画一个像素级的掩码(mask)。
- 无监督学习
无监督学习是指在没有目标额情况下寻找输入数据的有趣变换,其目的在于数据可视化、数据压缩、数据去躁或更好的理解数据中的相关性。降维(dimensionality reduction)和聚类(clustering)都是周所周知的无监督学习方法。 - 自监督学习
自监督学习是监督学习的一个特例。自监督学习没有人工标注的标签的监督学习,可以将它看作没有人类参与的监督学习。标签仍然存在,但它们是从输入数据中生成的,通常是使用启发式算法生成的。自编码器(autoencoder)是典型的自监督学习的例子,其生成的目标就是未经修改的输入。
监督学习、自监督学习和无监督学习之间的区别有时候很模糊,这三个类别更像是没有明确界限的连续体。自监督学习可以被重新解释为监督学习或无监督学习,这取决于关注的是学习机制还是应用场景。 - 强化学习
在强化学习中,智能体(agent)接收有关其环境的信息,并学会选择使用某种奖励最大化的行动。
分类和回归术语:
- 样本(sample)或熟人(input):进入模型的数据点。
- 预测(prediction)或输出(output):从模型出来的结果。
- 目标(target):真实值。对于外部数据源,理想情况下,模型应该能够预测出目标。
- 预测误差(prediction error)或损失值(loss value):模型预测与目标之间的距离。
- 类别(class):分类问题中供选择的一组标签。
- 标签(label):分类问题中类别标注的具体例子。
- 真值(ground-truth)或标注(annotation):数据集的所有目标,通常由人工收集。
- 二分类(binary classification):一种分类任务,每个输入样本都应被划分到两个互斥的类别中。
- 多分类(multiclass classification):一种多分类任务,每个输入样本都应被划分到两个以上的类别中。
- 多标签分类(multilabel classification):一种多分类任务,每个输入样本都可以分配多个标签。
- 标量回归(scalar regression):目标是连续标量值的任务。
- 向量回归(vector regression):目标是一组连续值(比如一个连续向量)的任务。
- 小批量(mini-batch)或批量(batch):模型同时处理的一小部分样本(样本数通常为8-128).样本数通常去2的幂,这样便于GPU上的内存分配。
评估机器学习模型
- 留出验证
留出一定比例的数据作为测试集。在剩余的数据上训练模型,然后在测试集上评估模型。如果可用的数据很少,那么可能验证集和测试集包含的样本就太少,从而无法在统计学上代表数据。 - K折验证
K折验证(K-fold validation)将数据划分为大小相同的K个分区。对于每个分区i,在剩余的K-1个分区上训练模型,然后在分区i上评估模型。最终分数等于K个分数的平均值。 - 带有打乱数据的重复K这验证
如果可用的数据相对较少,而又需要尽可能精确的评估模型,可以选择带有打乱数据的重复K折验证(iterated K-fold validation with shuffling)。具体做法是多次使用K折验证,在每次将数据划分为K个分区之前都将数据打乱。最终分数是每次K这验证分数的平均值。注意,这种方法一种要训练和评估PxK个模型(P是重复次数),计算代价很大。
数据预处理、特征工程和特征学习
- 神经网络的数据预处理
数据预处理的目的是使原始数据更适于用神经网络处理,包括向量化、标准化、处理缺失值和特征提取。 - 特征工程(feature engineering)
特征工程的本质:用更简单的方式表述问题,从而使问题变得更容易。
过拟合与欠拟合
- 减小网络容量
防止过拟合的最简单方法就是减小模型大小,即减小模型中可学习参数的个数(这由层数和每层的单元个数决定)。深度学习模型通常都很擅长拟合训练数据,但真正的挑战在于泛化,而不是拟合。 - 添加权重正则化
简单模型比复杂模型更不容易过拟合。简单模型是指参数值分布的熵更小的模型(或参数更少的模型)。一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,这使得权重值的分布更贱规则(regular),这种方法叫作权重正则化(weight regularization),其实现方法是向网络损失函数中添加与较大权重值相关的成本(cost)。- L1正则化(L1 regularization):添加的成本与权重系数的绝对值成正比。
- L2正则化(L2 regularization):添加的成本与权重系数的平方成正比。神经网络的L2正则化也叫权重衰减(weight decay)。
- 添加dropout正则化
对某一层使用dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为0)。 - 获取更多的训练数据
机器学习的通用工作流程
- 定义问题,收集数据集
- 选择衡量成功的指标
- 确定评估方法
- 准备数据
- 开发比基准更好的模型
- 扩大模型规模:开发过拟合的模型
- 模型正则化与调节超参数
深度学习用于计算机视觉
卷积神经网络简介
-
卷积运算
密集连接层和卷积层的根本区别在于,Dense层从输入特征空间中学到的是全局模式,而卷积层学到的是局部模式。- 卷积神经网络学到的模式具有平移不变性(translation invariant)。卷积神经网络做图像右下角学到某个模式之后,它可以在任何地方识别这个模式,比如左上角。对于密集连接网络来说,如果模式出现在新的位置,它只能重新学习这个模式。这使得卷积神经网络在处理图像时可以高效利用数据(因为视觉世界从根本上具有平移不变性),它只需要更少的训练样本就可以学到具有泛化能力的数据表示。
- 卷积神经网络可以学到模式的空间层次结构(spatial hierarchies of patterns),第一个卷积层将学习小的局部模式(比如边缘),第二个卷积层将学习由第一层特征组成的更大的模式,以此类推。这使得神经网络可以有效的学习越来越复杂、越来越抽象的视觉概念(因为视觉世界从根本上具有空间层次结构)。
-
最大池化
使用下采样的原因,一是减少需要处理的特征图的元素个数,二是通过让连续卷积层的观察窗口越来越大(即窗口覆盖原始输入的比例越来越大),从而引入空间过滤器的层级结构。最大池化不是实现这种下采样的唯一方法。还可以在前一个卷积层中使用步幅来实现。此外,还可以使用平均池化来代替最大池化,其方法是将每个局部输入图块变换Wie取该图块各通道的平均值,而不是最大值。但最大池化的效果往往比这些替代方法更好。因为特征中往往编码了某种模式或概念在特征图的不同位置是否存在,而观察不同特征的最大值而不是平均值能够给出更多的信息。因此,最合理的子采样策略是首先完成密集的特征图(通过无步进的卷积),然后观察特征每个小图块上的最大激活,而不是查看输入的稀疏窗口(通过步进卷积)或对输入图块取平均,因为后两种方法可能导致错过或淡化特征是否存在的信息。
小型数据集上训练卷积神经网络
#将图像复制到训练、验证和预测的目录
import os, shutil
#原始数据集解压目录的路径
orginal_dataset_dir ='kaggle_original_data'
#保存较小数据集的目录
base_dir = 'cats_and_dogs_small'
os.mkdir(base_dir)
#训练、验证和测试的目录
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
#图像复制
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1500,2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
#将猫狗分类的小型卷积神经网络实例化
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
#配置模型用于训练
from keras import optimizers
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])
#使用ImageDataGenerator从目录中读取图像
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150,150), batch_size=20, class_mode='binary')
validation_generator = test_datagen.flow_from_directory(validation_dir, target_size=(150,150), batch_size=20, class_mode='binary')
#利用批量生成器拟合模型
history = model.fit_generator(train_generator, step_per_epoch=100, epochs=30, validation_data=validation_generator, validation_steps=50)
#保存模型
model.save('cats_and_dogs_small_1.h5')
#绘制训练过程中的损失曲线和精度曲线
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validationg loss')
plt.legend()
plt.show()
过拟合的原因是学习样本太少,导致无法训练出能够泛化到新数据的模型。如果有无限的数据,那么模型能够观察到数据分布的所有内容,这样就永远不会过拟合。数据增强是从现有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加样本。其目标是,模型在训练是不会两次查看完全相同的图像。这样模型能够观察到数据的更多内容,从而具有更好的泛化能力。
#利用ImageDataGenerator来设置数据增强
datagen = ImageDataGenerator(rotation_range=40, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode='nearest')
#显示几个随机增强后的训练图像
from keras.preprocessing import image
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]
#选择一张图像进行增强
img_path = fnames[3]
#读取图像并调整大小
img = image.load_img(img_path, target_size=(150,150))
#将其转换为形状(150,150,3)的Numpy数组
x = image.img_to_array(img)
#将其形状改变为(1,150,150,3)
x = x.reshape((1,) + x.shape)
#循环是无限的,需要在某个时刻终止循环
i = 0
for batch in datagen.flow(x, batch_size=1):
plt.figure(i)
imgplot = plt.imshow(image.array_to_img(batch[0]))
i += 1
if i % 4 == 0:
break
plt.show()
#定义一个包含dropout的新卷积神经网络
model = models.Sequential()
model.add(layers.Conv2D(32, (3,3), activation='relu'), input_shape=(150, 150, 3))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(64, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2d(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Conv2D(128, (3,3), activation='relu'))
model.add(layers.MaxPooling2D((2,2)))
model.add(layers.Flatten())
model.add(layes.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])
#利用数据增强生成器训练卷积神经网络
train_datagen = ImageDataGenerator(rescale=1./255, rotation_range=40, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True,)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150,150), batch_size=32, class_mode='binary')
validation_generator = test_datagen.flow_from_directory(validation_dir, target_size=(150,150), batch_size=32, class_mode='binary')
history = model.fit_genrator(train_generator, step_per_epoch=100, epochs=100, validation_data=validation_genarator, validation_steps=50)
#保存模型
model.save('cats_and_dogs_small_2.h5')
使用预训练的卷积神经网络
使用预训练模型的两种方法:
- 特征提取(feature extraction)
特征提取是使用之前网络学到的表示来从新样本中提取出有趣的特征。然后这些特征输入一个新的分类器,从头开始训练。
用于图像分类的卷积神经网络包含两部分:首先是一系列池化层和卷积层,最后是一个密集连接分类器。第一部分叫作模型的卷积基(Convolutional base)。对于卷积神经网络而言,特征提取就是取出之前训练好的网络的卷积基,在上面运行新的数据,然后在输出上面训练一个新的分类器。
卷积神经网络的特征图表示通用概念在图像中是否存在,无论面对什么样的计算机视觉问题,这种特征图都可能很有用。但是,分类器学到的表示必然是针对于模型训练的类别,其中仅包含某个类别出现在整张图像中的概率信息。此外,密集连接层的表示不再包含物体在输入图像中的位置信息。密集连接层舍弃了空间的概念,而物体位置信息仍然由卷积特征图所描述。如果物体位置对于问题很重要,那么密集连接层的特征在很大程度上是无用的。
某个卷积层提取的表示的通用性取决于该层在模型中的深度。模型中更靠近底部的层提取的是局部的、高度通用的特征图(比如视觉边缘、颜色和纹理),而更靠近顶部的层提取的是更加抽象的概念。因此如果新的数据集与原始模型训练的数据有很大的差异,那么最好只使用模型的前几层来做特征提取,而不是用整个卷积基。- 在新的数据集上运行卷积基,将输出保存成硬盘中的Numpy数组,然后用这个数据作为输入,输入到独立的密集连接分类器中。这种方法速度快,计算代价低,因为对于每个输入图像只需运行一次卷积基,而卷积基是目前流程中计算代价最高的。但是该方法不允许使用数据增强。
- 在顶部添加Dense层来扩展已有模型(即conv_base),并在输入数据上端到端的运行整个模型。这样可以使用数据增强,因为每个输入图像进入模型时都会经过卷积基。但这种方法的计算代价比第一种要高很多。
#将VGG16卷积基实例化
from keras.applications import VGG16
#include_top指定模型最后是否包含密集连接分类器。
conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(150,150,3))
#不使用数据增强的快速特征提取
#使用预训练的卷积基提取特征
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = 'cats_and_dogs_small'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')
datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20
def extract_features(directory, sample_count):
features = np.zeros(shape=(sample_count, 4, 4, 512))
labels = np.zeros(shape=(sample_count))
generator = datagen.flow_from_dirctory(directory, target_size=(150, 150), batch_size=batch_size, class_mode='binary')
i = 0
for inputs_batch, labels_batch in generator:
features_batch = conv_base.predict(inputs_batch)
features[i * batch_size : (i + 1) * batch_size] = features_batch
labels[i * batch_size : (i + 1) * batch_size] = labels_batch
i += 1
if i * batch_size >= sample_count:
break
return features, labels
train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)
train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))
#定义并训练密集连接分类器
from keras import models
from keras import layers
from keras import optimizers
model = models.Sequential()
model.add(layers.Dense(256, activation='relu', input_dim=4*4*512))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='simoid'))
model.compile(optimizer=optimizers.RMSprop(lr=1e-4), loss='binary_crossentropy', metrics=['acc'])
history = model.fit(train_features, train_labels, epochs=30, batch_size=20, validation_data=(validation_features, validation_labels))
#绘制结果
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and Validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
#使用数据增强的特征提取
#在卷积基上添加一个密集连接分类器
from keras import models
from keras import layers
model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
#编译和训练模型之前冻结卷积基
conv_base.trainable = False
#利用冻结的卷积基端到端的训练模型
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
train_datagen = ImageDataGenerator(rescale=1./255, rotation_range=40, width_shift_range=0.2, height_shift_range=0.2,shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode='nearest')
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150,150), batch_size=20, class_mode='binary')
validation_generator = test_datagen.flow_from_directory(validation_dir, target_size=(150,150), batch_size=20, class_mode='binary')
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-5), metrics=['acc'])
history = model.fit_generator(train_generator, step_per_epoch=100, epochs=30, validation_data=validation_generator, validation_steps=50)
- 微调模型(fine-tuning)
对于用于特征提取的冻结的模型基,微调是指将其顶部的几层“解冻”,并将这些解冻的几层和新增加的部分联合训练。之所以叫微调,是因为它只是略微调整了所复用模型中更加抽象的表示,以便让这些表示与手头的问题更加相关。
微调网络步骤: - 在已经训练好的基网络(base network)上添加自定义网络。
- 冻结基网络。
- 训练所添加的部分。
- 解冻基网络的一些层。
- 联合训练解冻的这些层和添加的部分。
#冻结知道某一层的所有层
conv_base.trainable = False
set_trainable = False
for layer in conv_base.layers:
if layer.name =='block5_conv1':
set_trainable = True
if set_trainable:
layer.trainable = True
else:
layer.trainable = False
#微调模型
model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-5), metrics=['acc'])
history = model.fit_generator(train_generator, steps_per_epoch=100, epochs=100, validation_data=validation_generator, validation_steps=50)
#使曲线变得平滑
def smooth_curve(points, factor=0.8):
smoothed_points = []
for point in points:
if smoothed_points:
previous = smoothed_points[-1]
smoothed_points.append(previous * factor + point * (1 - factor))
else:
smoothed_points.append(point)
return smoothed_points
plt.plot(epochs, smooth_curve(acc), 'bo', label='Smoothed training acc')
plt.plot(epochs, smooth_curve(val_acc), 'b', label='Smoothed validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, smooth_curve(loss), 'bo', label='Smoothed training loss')
plt.plot(epochs, smooth_curve(val_loss), 'b', label='Smoothed validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
#在测试集上评估模型
test_generator = test_datagen.flow_from_directory(test_dir, target_size=(150,150), batch_size=20, class_mode='binary')
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)
卷积神经网络的可视化
对于某些深度学习模型来说,模型学到的表示很难用人类可以理解的方式来提取和呈现,但是对卷积神经网络来说,卷积神经网络学到的表示非常适合可视化,因为它们是视觉概念的表示。
- 可视化卷积神经网络的中间输出(中间激活):有助于理解卷积神经网络连续的层如何对输入进行变换,也有助于初步了解卷积神经网络每个过滤器的含义。
可视化中间激活,是指对于给定输入,展示网络中各个卷积层和池化层输出的特征图(层的输出通常被称为该层的激活,即激活函数的输出)。可以看到输入如何被分解为网络学到的不同过滤器。在三个维度对特征图进行可视化:宽度、高度和深度(通道)。每个通道都对应相对独立的特征,将每个通道的内容分别绘制成二维图像。
#加载保存的模型
from keras.models import load_model
model = load_model('cats_and_dogs_small_2.h5')
model.summary()
#预处理单张图像
img_path = 'cats_and_dogs_small/test/cats/cat.1700.jpg'
from keras.preprocessing import image
import numpy as np
img = image.load_img(img_path, target_size=(150,150))
img_tensor = image.img_to_array(img)
img_tensor = np.expand_dims(img_tensor, axis=0)
img_tensor /= 255.
#其形状为(1,150,150,3)
print(img_tensor.shape)
#显示测试图像
import matplotlib.pyplot as plt
plt.imshow(img_tensor[0])
plt.show()
#用一个输入张量和一个输出张量列表将模型实例化
from keras import models
#提取前8层的输出
layer_outputs = [layer.output for layer in model.layers[:8]]
#创建一个模型,给定模型输入,可以返回这些输出
activation_model = models.Model(inputs=model.input, outputs=layer_outputs)
#以预测模式运行模型
activations = activation_model.predict(img_tensor)
#第一个卷积层的激活
first_layer_activation = activation[0]
#(1,148,148,32)
print(first_layer_activation.shape)
#将第四个通道可视化
import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0,:,:,4], cmap='viridis')
#将每个中间激活的所有通道可视化
layer_names = []
for layer in model.layers[:8]:
layer_names.append(layer.name)
images_per_row = 16
for layer_name,layer_activation in zip(layer_names, activations):
#特征图中d额特征个数
n_features = layer_activation.shape[-1]
#特征图的形状(1,size,size,n_features)
size = layer_activation.shape[1]
n_cols = n_features // images_per_row
display_grid = np.zeros((size * n_cols, images_per_ros * size))
for col in range(n_cols):
for row in range(images_per_row):
channel_image = layer_activation[0,:,:,col * images_per_row + row]
channel_image -= channel_image.mena()
channel_image /= channel_image.std()
channel_image *= 64
channel_image += 128
channel_image = np.clip(channel_image, 0, 255).astype('uint8')
display_grid[col * size : (col + 1) * size, row * size : (row + 1) * size] = channel_image
scale = 1./ size
plt.figure(figsize=(scale * display_grid.shape[1], scale * display_gred.shape[0]))
plt.title(layer_name)
plt.grid(False)
plt.imshow(display_grid, aspect='auto', cmap='viridis')
- 可视化卷积神经网络的过滤器:有助于精确理解卷积神经网络中每个顾虑器容易接受的视觉模式或视觉概念。
想要观察卷积神经网络学到的过滤器,另一种简单的方法是显示每个过滤器所响应的视觉模式。可以通过在输入空间中进行梯度上升来实现:从空白输入图像开始,将梯度下降应用于卷积神经网络是输入图像的值。其目的是让某个过滤器的响应最大化。得到的输入图像是选定过滤器具有最大响应的图像。
#为过滤器的可视化定义损失张量
from keras.applications import VGG16
from keras import backend as K
model = VGG16(weights='imagenet', include_top=False)
layer_name = 'block3_conv1'
filter_index = 0
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])
#获取损失相对于输入的梯度
grads = K.gradients(loss, model.input)[0]
#梯度标准化技巧
grads /= (K,sqrt(K.mean(K.square(grads))) + 1e-5)
#给定Numpy输入值,得到Numpy输出值
iterate = K.function([model.input], [loss, grads])
import numpy as np
loss_value, grads_value = interate([np.zeros((1,150,150,3))])
#通过随机梯度下降让损失最大化
input_img_data = np.random.random((1, 150, 150, 3)) * 20 + 128
step = 1.
for i in range(40):
#计算损失和梯度
loss_value, grads_value = iterate([input_img_data])
#沿着让损失最大化的方向调节输入图像
input_img_data += grads_value * step
#将张量转换为有效图像的实用函数
def deprocess_image(x):
x -= x.mean()
x /= (x.std() + 1e-5)
x *= 0.1
#将x裁切(clip)到[0,1]区间
x += 0.5
x = np.clip(x, 0, 1)
#将x转换为RGB数组
x *= 255
x = np.clip(x, 0, 255).astype('uint8')
return x
# 生成过滤器可视化的函数
#构建一个损失函数,将该层第n个过滤器的激活最大化
def generate_pattern(layer_name, filter_index, size=150):
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:,:,:,filter_index])
#计算这个损失相对于输入图像的梯度
grads = K.gradients(loss, model.input)[0]
#标准化技巧:将梯度标准化
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
#返回给定输入图像的损失和梯度
interate = K.function([model.input], [loss, grads])
#从带有噪声的灰度图开始
input_img_data = np.random.random((1, size, size, 3)) * 20 + 128.
step = 1.
for i in range(40):
loss_value, grad_value = iterate([input_img_data])
input_img_data += grads_value * step
img = input_img_data[0]
return deprocess_image(img)
#生成某一层中所有过滤器响应模式组成的网格
layer_name = 'block1_conv1'
size = 64
margin = 5
#空图像(全黑色)用于保存结果
results = np.zeros((8 * size + 7 * margin, 8 * size + 7 * margin, 3))
for i in range(8):
for j in range(8):
#生成layer_name层第i+(j*8)个过滤器的模式,将结果f放在results网格第(i,j)个方块中。
filter_img = generate_pattern(layer_name, i + (j * 8), size=size)
horizontal_start = i * size + i * margin
horizontal_end = horizontal_start + size
vertical_start = j * size + j * margin
vertical_end = vertical_start + size
results[horizontal_start : horizontal_end, vertical_start : vertical_end, :] = filter_img
plt.figure(figsize=(20,20))
plt.imshow(results)
- 可视化图像中类激活的热力图:有助于理解图像的哪个部分被识别为属于某个类别,从而可以定位图像中的物体。
这种通用技术叫作类激活图(CAM, class activation map)可视化,它是指对输入图像生成类激活的热力图。类激活热力图是与特定输出类别相关的二维分数网格,对任何输入图像的每个位置都要进行计算,它表示每个位置对该类别的重要程度。理解这一技巧的一种方法是,用“每个通道对类别的重要程度”对“输入图像对不同通道的激活强度”的空间图进行加权,从而得到了“输入图像对类别的激活强度”的空间图。
#加载带有预训练权重的VGG16网络
from keras.applications.vgg16 import VGG16
model = VGG16(weights='imagenet')
#为VGG16模型预处理一张输入图像
from keras.preprocessing import image
from keras.applications.vgg16 import preprocessing_input, decode_predictions
import numpy as np
img_path = 'creative_commons_elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
#对批量进行预处理(按通道进行验收标准化)
x = preprocess_input(x)
#应用Grad-CAM算法
#预测向量o你额“非洲象”元素
african_elephant_output = model.output[:, 386]
last_conv_layer = model.get_layer('block5_conv3')
#"非洲象"类别相对于block5_conv3输出特征图的梯度
grads = K.gradients(african_elephant_output, last_conv_layer.output)[0]
#形状为(512,)的向量,每个元素是特定特征图通道的梯度平均大小
pooled_grads = K.mean(grads, axis=(0, 1, 2))
#访问定义的量:对于给定的样本图像,pooled_grads和block5_conv3层的输出特征图
iterate = K.function([model.input], [pooled_grads, last_conv_layer.output[0]])
pooled_grads_value, conv_layer_output_value = iterate([x])
for i in range(512):
conv_layer_output_value[:,:,i] *= pooled_grads_value[i]
#得到的特征的逐通道平均值即为类激活的热力图
heatmap = np.mean(conv_layer_output_value, axis=-1)
heatmap = np.maximum(heatmap, 0)
heatmap /= np.max(heatmap)
plt.matshow(heatmap)
#将热力图与原始图像叠加
import cv2
img = cv2.imread(img_path)
heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))
heatmap = np.uint8(255 * heatmap)
#将热力图应用于原始图像
heatmap = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
superimposed_img = heatmap * 0.4 + img
cv2.imwrite('elephant_cam.jpg', superimposed_img)
深度学习用于文本和序列
处理文本数据
文本向量化(vectorize)是指将文本转换为数值张量的过程。
- 将文本分割为单词,并将每个单词转换为一个向量
- 将文本分割为字符,并将每个字符转换为一个向量
- 提取单词或字符的n-gram,并将每个n-gram转换为一个向量。n-gram是多个连续单词或字符的激活(n-gram之间可重叠)
将文本分而成的单元(单词、字符或n-gram)叫作标记(token),将文本分解成标记的过程叫作分词(tokenization)。所有文本向量化的过程都是应用某种分词方案,然后将数值向量与生成的标记相关联。
- one-hot编码:对标记做one-hot编码。
- 标记嵌入(token bedding):通常只用于单词,叫作词嵌入(word embedding)
理解循环神经网络
循环神经网络的高级用法
- 循环dropout(recurrent dropout)
- 堆叠循环层(stacking recurrent layers)
- 双向循环层(bidirectional recurrent layer)
用卷积神经网络处理序列
高级的深度学习最佳实践
不用Sequential模型的解决方案:Keras函数式API
- 函数式API简介
使用函数式API,可以直接操作张量,也可以把层当做函数来使用,接收张量并返回张量。
from keras import Input, layers
input_tensor = Input(shape=(32,))
dense = layers.Dense(32, activation='relu')
output_tensor = dense(input_tensor)
from keras.models import Sequential, Model
from keras import layers
from keras import Input
#Sequential method
seq_model = Sequential()
seq_model.add(layers.Dense(32, activation='relu', input_shape=(64,)))
seq_model.add(layers.Dense(32, activation='relu'))
seq_model.add(layers.Dense(10, activation='softmax'))
#api method
input_tensor = Input(shape=(64,))
x = layers.Dense(32, activation='relu')(input_tensor)
x = layers.Dense(32, activation='relu')(x)
output_tensor = layers.Dense(10, activation='softmax')(x)
model = Model(input_tensor, output_tensor)
#查看模型
model.summary()
model.compile(optimizer='remsprop', loss='categorical_crossentropy')
import numpy as np
x_train = np.random.random((1000, 64))
y_train = np.random.random((1000, 10))
model.fit(x_train, y_train, epochs=10, batch_size=128)
score = model.evaluate(x_train, y_train)
- 多输入模型
#用函数式API实现双输入问答模型
from keras.models import Model
from keras import layers
from keras import Input
text_vocabulary_size = 10000
question_vocabulary_size = 10000
answer_vocabulary_size = 500
test_input = Input(shape=(None,), dtype='int32', name='text')
embedded_text = layers.Embedding(text_vocabulary_size, 64)(text_input)
encoded_text = layers.LSTM(32)(embedded_text)
question_input = Input(shape=(None,), dtype='int32', name='question')
embedded_question = layers.Embedding(question_vocalulary_size, 32)(question_input)
encoded_question = layers.LSTM(16)(embedded_question)
concatenated = layers.concatenate([encoded_text, encoded_question], axis=-1)
answer = layers.Dense(answer_vocabulary_size, activation='softmax')(concatenated)
model = Model([text_input, question_input], answer)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['acc'])
#将数据输入到多输入模型中
import numpy as np
num_samples = 1000
max_length = 100
text = np.random.randint(1, text_vocabulary_size, size=(num_samples, max_length))
question = np.random.randint(1, question_vocabulary_size, size=(num_samples, max_length))
answers = np.random.randint(answer_vocabulary_size, size=(num_samples))
answers = keras.utils.to_categorical(answers, answer_vocabulary_size)
model.fit([text, question], answers, epochs=10, batch_size=128)
model.fit({'text': text, 'question': question}, answers, epochs=10, batch_size=128)
- 多输出模型
#用函数API实现一个三输出模型
from keras import layers
from keras import Input
from keras.models import Model
vocabulary_size = 50000
num_income_groups = 10
posts_input = Input(shape=(None,), dtype='int32', name='posts')
embedded_posts = layers.Embedding(256, vocabulary_size)(posts_input)
x = layers.Conv1D(128, 5, activation='relu')(embedded_posts)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.MaxPooling1D(5)(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.Conv1D(256, 5, activation='relu')(x)
x = layers.GlobalMaxPooling1D()(x)
x = layers.Dense(128, actiation='relu')(x)
age_prediction = layers.Dense(1, name='age')(x)
income_prediction = layers.Dense(num_income_groups, activation='softmax', name='income')(x)
gender_prediction = layers.Dense(1, activation='sigmoid', name='gender')(x)
model = Model(posts_input, [age_prediction, income_prediction, gender_prediction])
#多输出模型的编译选项:多重损失
model.compile(optimizer='rmsprop', loss=['mse', 'categorical_crossentropy', 'bianry_crossentropy'])
model.compile(optimizer='rmsprop', loss={'age': 'mse', 'income': 'categorical_crossentropy', 'gender': 'binary_crossentropy'})
#多输出模型的编译选项:损失加权
model.compile(optimizer='rmsprop', loss=['mse', 'categorical_crossentropy', 'binary_crossentropy'], loss_weights=[0.25, 1.0, 10.])
#与上述写法等效(只有输出层具有名称时才能采样这种写法)
model.compile(optimizer='rmsprop', loss={'age': 'mse', 'income': 'categorical_crossentropy', 'gender': 'binary_crossentropy'}, loss_weights={'age': 0.25, 'income': 1., 'gender': 10.})
#将数据输入到多输出模型中
model.fit(posts, [age_targets, income_targets, gender_targets], epochs=10, batch_size=64)
#与上述写法等效(只有输出层具有名称时才能采样这种写法)
model.fit(posts, {'age': age_targets, 'income': income_targets, 'gender': gender_targets}, epochs=10, batch_size=64)
- 层组成的有向无环图
- Inception模块
- 残差连接
- 共享层权重
- 将模型作为层
from keras import layers
from keras import applications
from keras import Input
xception_base = applications.Xception(weights=None, include_top=False)
left_input = Input(shape=(250, 250, 3))
right_input = Input(shape=(250, 250, 3))
left_features = xception_base(left_input)
right_features = xception_base(right_input)
merged_features = layers.concatenate([left_features, right_features], axis=-1)
使用Keras回调函数和TensorBoard来检查并监控深度学习模型
-
训练过程中将回调函数作用于模型
当观测到验证损失不再改善时就停止训练。可以使用Keras回调函数来实现。回调函数(callback)是在调用fit时传入模型的一个对象(即实现特定方法的类实例),它在训练过程中的不同时间点都会被模型调用。他可以访问关于模型状态与性能的所有数据,还可以采取行动:中断训练、保存模型、加载一组不同的权重或改变模型的状态。- 模型检查点(model checkpointing):在训练过程中的不同时间点保存模型的当前权重。
- 提前终止(early stopping):如果验证损失不再改善,则中断训练(同时保存在训练过程中得到的最佳模型)。
- 在训练过程中动态调节某些参数值:比如优化器的学习率。
- 在训练过程中记录训练指标和验证指标,或将模型学到的表示可视化(这些表示也在不断更新)。
keras.callbacks模块中内置的回调函数:
- ModelCheckpoint与EarlyStopping回调函数
import keras
callbacks_list = [keras.callbacks.EarlyStopping(monitor='acc', patience=1,), keras.callbacks.ModelCheckpoint(filepath='my_model.h5', monitor='val_loss', save_best_only=True,)]
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
model.fit(x, y, epoches=10, batch_size=32, callbacks=callbacks_list, validation_data=(x_val, y_val))
- ReduceLROnPlateau回调函数
如果验证损失不再改善,可以使用这个回调函数来降低学习率。在训练过程中如果出现了损失平台(loss plateau),那么增大或减小学习率都是跳出局部最小值的有效策略。
callbacks_list = [keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.1, patience=10,)]
model.fit(x, y, epochs=10, batch_size=32, callbacks=callbacks_list, validation_data=(x_val, y_val))
- 编写自己的回调函数
import keras
import numpy as np
class ActivationLogger(keras.callbacks.Callback):
def set_model(self, model):
self.model = model
layer_outputs = [layer.output for layer in model.layers]
self.activations_model = keras.models.Model(model.input, layer_outputs)
def on_epoch_end(self, epoch, logs=None):
if self.validation_data is None:
raise RuntimeError('Requires validation_data.')
validation_sample = self.validation_data[0][0:1]
activations = self.activations_model.predict(validation_sample)
f = open('activations_at_epoch_' + str(epoch) + '.npz', 'w')
np.savez(f, activations)
f.close()
- TensorBoard简介:TensorFlow的可视化框架
- 在训练过程中以可视化的方式监控指标
- 将模型架构可视化
- 将激活和梯度的直方图可视化
- 以三维的形式研究嵌入
#使用了TensorBoard的文本分类模型
import keras
from keras import layers
from keras.datasets import imdb
from keras.preprocessing import sequence
max_features = 2000
max_len = 500
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pand_sequences(x_test, maxlen=max_len)
model = keras.model.Sequential()
model.add(layers.Embedding(max_features, 128, input_length=max_len, name='embed'))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.MaxPooling1D(5))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
#为TensorBoard日志文件创建一个目录
mkdir my_log_dir
#使用一个TensorBoard回调函数来训练模型
callbacks = [keras.callbacks.TensorBoard(log_dir='my_log_dir', histogram_freq=1, embeddings_freq=1,)]
history = model.fit(x_train, y_train, epochs=20, batch_size=128, validation_split=0.2, callbacks=callbacks)
#启动TensorBoard服务器
tensorboard --logdir=my_log_dir
让模型性能发挥到极致
- 高级架构模式
- 批标准化(batch normalization)
批标准化的工作原理:训练过程中在内部保存已读取每批数据均值和方差的指数移动平均值。批标准化的主要效果是,它有助于梯度传播,incident允许更深的网络。 - 深度可分离卷积
深度可分离卷积层对输入的每个通道分别执行空间卷积,然后通过逐点卷积(1x1卷积)将输出通道混合,相当于将空间特征学习和通道特征学习分开,如果假设输入中的空间位置高度相关,但不同的通道之间相对独立,那么这么做很有意义的,它需要的参数少,计算量小,可以得到更小更快的模型。是一种执行卷积更高效的方法。能够使用更少的数据学到更好的表示,从而得到性能更好的模型。
- 批标准化(batch normalization)
#在小型数据集上构建一个轻量的深度可分离卷积神经网络,用于图像分类任务(softmax多分类)。对于规模更大的模型,深度可分离卷积是Xception架构的基础,Xception是一个高性能的卷积神经网络,内置于Keras中。
from keras.models import Sequential, Model
from keras import layers
height = 64
width = 64
channels = 3
num_classes = 10
model = Sequential()
model.add(layers.SeparableConv2D(32, 3, activation='relu', input_shape=(height, width, channels,)))
model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layers.MaxPooling2D(2))
model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layers.SeparableConv2D(128, 3, activation='relu'))
model.add(layers.MaxPooling2D(2))
model.add(layers.SeparableConv2D(64, 3, activation='relu'))
model.add(layers.SeparableConv2D(128, 3, activation='relu'))
model.add(layers.GlobalAveragePooling2D())
model.add(layers.Dense(32, actiation='relu'))
model.add(layers.Dense(num_classes, activation='softmax'))
model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
-
超参数优化
- 选择一组超参数(自动选择)
- 构建相应的模型
- 将模型在训练数据上拟合,并衡量其在验证数据上的最终性能
- 选择尝试的下一组超参数(自动选择)
- 重复上述过程
- 最后,衡量模型在测试数据上的性能
-
模型集成
集成是指将一系列不同模型的预测结果汇集到一起,从而得到更好的预测结果。分离器集成是将一组分类器的预测结果汇集在一起,将他们的预测结果取平均值作为预测结果。集成的模型应该尽可能好,同时尽可能不同。即使用非常不同的架构,甚至使用不同类型的机器学习方法。
生成式深度学习
人工智能模拟人类思维过程的可能性,并不局限于被动性任务(比如目标识别)和大多数反应性任务(比如驾驶汽车),它还包括创造性活动。
我们的感知模式、语言和艺术作品都具有统计结构。学习这种结构是深度学习算法所擅长的。机器学习模型能够对图像、音乐和故事的统计潜在空间(latent space)进行学习,然后从这个空间中采样(sample),创造出与模型在训练数据中所见到的艺术作品具有相似特征的新作品。当然,这种采样本身并不是艺术创作行为。它只是从一种数学运算,算法并没有关于人类生活、人类情感或我们人生经验的基础知识;相反,它从一种与我们的经验完全不同的经验中进行学习。作为人类旁观者,只能靠我们的解释才能对模型生成的内容赋予意义。但在技艺高超的艺术家手中,算法生成可以变得非常有意义,并且很美。潜在空间采样会变成一支画笔,能够提高艺术家的能力,增强我们的创造力,并拓展我们的想象空间。此外,它也不需要专业技能和练习,从而让艺术创作变得更加容易。它创造了一种纯粹表达的新媒介,将艺术与技巧相分离。
使用LSTM生成文本
-
生成式循环网络简史
序列数据生成式计算机所做的最接近于做梦的事情。循环神经网络被成功应用于音乐生成、对话生成、图像生成、语音合成和分子设计。甚至还被用于制作电影剧本,然后由真人演员来表演。 -
如何生成序列数据
用深度学习生成序列数据的通用方法,就是使用前面的标记作为输入,训练一个网络(通常是循环神经网络或卷积神经网络)来预测序列中接下来的一个或多个标记。标记(token)通常是单词或字符,给定前面的标记,能够对下一个标记的概率进行建模的任何网络都叫语言模型(language model)。语言模型能够捕捉到语言的潜在空间(latent space),即语言的统计结构。
语言模型训练可以从中采样(sample,即生成新序列)。向模型中输入一个初始文本字符串(即条件数据(conditioning data)),要求模型生成下一个字符或下一个单词(甚至可以同时生成多个标记),然后将生成的输出添加到输入数据中,并多次重复这一过程。 -
采样策略的重要性
生成文本时,如何选择一个字符至关重要,一个简单的方法是贪婪采样(greedy sampling),即始终选择可能性最大的下一个字符。但是这种方法会得到重复的、可预测的字符串,看起来不像是连贯的语言。- 纯随机采样,即从均匀概率分布中抽取下一个字符,其中每个字符的概率相同。这种方案有最大的随机性,这种概率分布具有最大的熵。
- 贪婪采样,也不会生成任何有趣的内容,它没有任何随机性,即相应的概率分别具有最小的熵。
- 从“真实”概率分布(即模型softmax函数输出的分布)中进行采样,是这两个极端之间的一个中间点。它可以在某些时候采样到不常见的字符,从而生成看起来更加有趣的句子,而且有时会得到训练数据中没有的、听起来像是真实存在的新单词,从而表现出创造性。但是这种方法有一个问题,就是它在采样过程中无法控制随机性的大小。
在采样的过程中,softmax温度(softmax temperature)控制随机性的大小,用于表示采样概率分布的熵,即表示所选择的下一个字符会有多么出人意料或多么可预测。 给定一个temperature值,将按照下列方法对原始概率分布(即模型的softmax输出)进行重新加权,计算得到一个新的概率分布。
更高的温度得到的是熵更大的采样分布,会生成更加出人意料、更加无结构的生成数据,而更低的温度对应更小的随机性,以及更加可预测的生成数据。
import numpy as np
def reweight_distribution(original_distribution, temperature=0.5):
#original_distribution是概率值组成的一维Numpy数组,这些概率值之和必须等于1.temperature是一个因子,用于定量描述输出分布的熵
#返回原始分布重新加权后的结果,distribution的求和可能不再等于1,因此需要将它除以求和,以得到新的分布。
distribution = np.log(original_distribution) / temperature
distribution = np.exp(distribution)
return distribution / np.sum(distribution)
- 实现字符级的LSTM文本生成
首先需要可用于学习语言模型的大量文本数据。例子中采用的是语言模型是针对于尼采的写作风格和主题的模型。- 准备数据
下载语料,并将其转换为小写。
- 准备数据
# 下载并解析初始文本文件
import keras
import numpy as np
path = keras.utils.get_file('nietzsche.txt', origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt')
text = open(path).read().lower()
print('Corpus length:', len(text))
# 将字符序列向量化
maxlen = 60 #提取60个字符组成的序列
step = 3 #每3个字符采样一个新序列
sentences = [] #保存所提取的序列
next_chars = [] #保存目标(即下一个字符)
for i in range(0, len(text) - maxlen, step):
sentences.append(text[i: i + maxlen])
next_chars.append(text[i + maxlen])
print('Number of sequences:', len(sentences))
chars = sorted(list(set(text))) #语料中唯一字符组成的列表
print('Unique characters:', len(chars))
char_indices = dict((char, chars.index(char)) for cha in chars) #一个字典,将唯一字符映射为它在列表chars中的索引
print('Vectorization...')
x = np.zeros((len(sentences), maxlen, len(chars)), dtypes=np.bool)
y = np.zeros((len(sentences), len(chars)), dtypes=np.bool)
for i, sentence in enumerate(sentences):
for t, char in enumerate(sentence):
x[i, t, char_indices[char]] = 1
y[i, char_indices[next_chars[i]]] = 1
- 构建网络
# 用于预测下一个字符的单层LSTM模型
from keras import layers
model = keras.models.Sequential()
model.add(layers.LSTM(128, input_shape=(maxlen, len(chars))))
model.add(layers.Dense(len(chars), activation='softmax'))
# 模型编译配置
optimizer = keras.optimizers.RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)
- 训练语言模型并从中采样
给定一个训练好的模型和一个种子文本片段,可以通过重复以下操作来生成新的文本。- 给定目前已生成的文本,从模型中得到下一个字符的概率分布。
- 根据某个温度对分布进行重新加权。
- 根据重新加权后的分布对下一个字符进行随机采样。
- 将新字符添加到文本末尾。
# 给定模型预测,采样下一个字符的函数
def sample(preds, temperature=1.0):
preds = np.asarray(preds).astype('float64')
preds = np.log(preds) / temperature
exp_preds = np.exp(preds)
preds = exp_preds / np.sum(exp_preds)
probas = np.random.multinomial(1, preds, 1)
return np.argmax(probas)
# 文本生成循环
import random
import sys
for epoch in range(1, 60):
print('epoch', epoch)
model.fit(x, y, batch_size=128, epoches=1)
start_index = random.randint(0, len(text) - maxlen - 1)
generated_text = text[start_index: start_index + maxlen]
print('--- Generating with seed: "' + generated_text +'"')
for temperature in [0.2, 0.5, 1.0, 1.2]:
print('----- temperature:', temperature)
sys.stdout.write(generated_text)
for i in range(400):
sampled = np.zeros((1, maxlen, len(chars)))
for t, char in enumerate(generated_text):
sampled[0, t, char_indices[char]] = 1.
preds = model.predict(sampled, vervose=0)[0]
next_index = sample(preds, temperature)
next_char = chars[next_index]
generated_text += next_char
generated_text = generated_text[1:]
sys.stdout.write(next_char)
DeepDream
DeepDream是一种艺术性的图像修改技术,它用到了卷积神经网络学到的表示,反向运行一个卷积神经网络:对卷积神经网络的输入做梯度上升,将所有层的激活最大化,从现有的图像开始,所产生的效果能够抓住已经存在的视觉模式,并以某种艺术性的方式将图像元素扭曲。输入图像是在不同的尺度上进行处理的,可以提高可视化的质量。
- 用Keras实现DeepDream
#加载预训练的Inception V3 模型
from keras.applications import inception_v3
from keras imort backend as K
#不需要训练模型,所以这个命令会禁用所有与训练有关的操作
K.set_learning_phase(0)
#构建不包括全连接层的Inception V3网络,使用预训练的ImageNet权重来加载模型
model = inception_v3.InceptionV3(weights='imagenet', include_top=False)
#设置DeepDream配置
layer_contributions = {
'mixed2': 0.2,
'mixed3': 3.,
'mixed4':2.,
'mixed5':1.5,}
#定义需要最大化的损失
#创建一个字典,将层的名称映射为层的实例
layer_dict = dict([(layer.name, layer) for layer in model.layers])
#在定义损失时将层的贡献添加到这个标量变量中
loss = K.variable(0.)
for layer_name in layer_contributions:
coeff = layer_contributions[layer_name]
activation = layer_dict[layer_name].output
scaling = K.prod(K.cast(K.shape(activation), 'float32'))
#将该层特征的L2范数添加到loss中,为了避免出现边界伪影,损失中仅包含非边界的像素
loss += coeff * K.sum(K.square(activation[:, 2:-2, 2:-2, :])) / scaling
#梯度上升过程
#该张量用于保存生成的图像,即梦境图像
dream = model.input
#计算损失相对于梦境图像的梯度
grads = K.gradients(loss, dream)[0]
#将梯度标准化
grads /= K.maximum(K.mean(K.abs(grads)), 1e-7)
outputs = [loss, grads]
fetch_loss_and_grads = K.function([dream], outputs)
def eval_loss_and_grads(x):
outs = fetch_loss_and_grads([x])
loss_value = outs[0]
grad_values = outs[1]
return loss_value, grad_values
#进行iterations次梯度上升
def gradient_ascent(x, iterations, step, max_loss=None):
for i in range(iterations):
loss_value, grad_values = eval_loss_and_grads(x)
if max_loss is not None and loss_value > max_loss:
break
print('... Loss value at', i, ':', loss_value)
x += step * grad_values
return x
#在多个连续尺度上运行梯度上升
#改变这些超参数可以得到新的效果
import numpy as np
step = 0.01 #梯度上升的步长
num_octave = 3 # 运行梯度上的尺度个数
octave_scale = 1.4 #两个尺度之间的大小比例
iterations = 20 #在每个尺度上运行梯度上升的步数
#如果损失增大到大于10,要中断梯度上升过程,以避免得到丑陋的伪影
base_image_path = '..' #要使用的图的路径
img = preprocess_image(base_image_path)
original_shape = img.shape[1:3]
successive_shapes = [original_shape]
#准备一个由形状元组组成的列表,它定义了运行梯度上升的不同尺度
for i in range(1, num_octave):
shape = tuple([int(dim / (octave_scale ** i)) for dim im original_shape])
successive_shapes.append(shape)
successive_shapes = successive_shapes[::-1] #将形状列表反转,变为升序
#将图像数组的大小缩放到最小尺寸
original_img = np.copy(img)
shrunk_original_img = resize_img(img, successive_shapes[0])
for shape in successive_shapes:
print('Processing image shape', shape)
img = resize_img(img, shape) #将梦境图像放大
#运行梯度上升改变梦境
img = gradient_ascent(img, iterations=iterations, step=step, max_loss=maxloss)
#将原始图像的较小版本放大,它会变得像素化。
upscaled_shrunk_original_img = resize_img(shrunk_original_img, shape)
#在这个尺寸上j计算原始图像的高质量版本
same_size_original = resize_img(original_img, shape)
#二者的差别是在放大过程中丢失的细节
loss_detail = same_size_original - upscaled_shrunk_original_img
#将丢失的细节重新注入到梦境图像中
img += lost_detail
shrunk_original_img = reisze_img(original_img, shape)
save_img(img, fname='dream_at_scale_' + str(shape) + '.png')
save_img(img, fname='final_dream.png')
#辅助函数
import scipy
from keras.preprocessing import image
def resize_img(img, size):
img = np.copy(img)
factors = (1, float(size[0]) / img.shape[1], float(size[1] / img.shape[2], 1))
return scipy.ndimage.zoom(img, factors, order=1)
def save_img(img, fname):
pil_img = deprocess_image(np.copy(img))
scipy.misc.imsave(fname, pil_img)
#通用函数,用于打开图像、改变图像大小以及将图像格式转换为Inception V3模型能够处理的张量
def preprocess_image(image_path):
img = image.load_img(image_path)
img = image.img_to_array(img)
img = np.expand_dims(img, axis=0)
img = inception_v3.preprocess_input(img)
return img
#通用函数,将一个张量转换为有效图像
def deprocess_image(x):
if K.image_data_format() == 'channels_first':
x = x.reshape((3, x.shape[2], x.shape[3]))
x = x.transpose((1, 2, 0))
else:
#对inception_v3.preprocess_input所做的预处理进行反向操作
x = x.reshape((x.shape[1], x.shape[2], 3))
x /= 2.
x += 0.5
x *= 255.
x = np.clip(x, 0, 255).astype('uint8')
return x
- 小结
DeepDream的过程是反向运行一个卷积神经网络,基于网络学到的表示来生成输入。得到的结果是很有趣的,类似于通过迷幻剂扰乱视觉皮层而诱发的视觉伪影。这个过程并不局限于图像模型,甚至并不局限于卷积神经网络。它可以应用于语音、音乐等更多的内容。
神经风格迁移
神经风格迁移是指将参考图像的风格应用于目标图像,同时保留目标图像的内容。
在当前语境下,风格(style)是指图像中不同空间尺度的纹理、颜色和视觉图案, 内容(content)是指图像的高级宏观结构。
实现风格迁移的关键概念:定义一个损失函数来指定想要实现的目标,然后将这个损失最小化。
- 内容损失
网络更靠近底部的层激活包含关于图像的局部信息,而更靠近顶部的层则包含更加全局、更加抽象的信息。卷积神经网络不同层的激活用另一种方式提供了图像内容在不同空间尺度上的分解。因此,图像的内容是更加全局和抽象的,能够被卷积神经网络更靠近顶部的层的表示所捕捉到。因此,内容损失的一个很好的候选者就是两个激活函数之间的L2范数,一个激活是预训练的卷积神经网络更靠近顶部 某层在目标图像上计算得到的激活,另一个激活是同一层在生成图像上计算得到的激活。这可以保证,在更靠顶部的层看来,生成图像与原始目标图像看起来很相似。假设卷积神经网络更靠顶部的层看到的就是输入图像的内容,那么这种方法可以保存图像内容。 - 风格损失
内容损失只使用了一个更靠顶部的层,想要捕捉到卷积神经网络在风格参考图像的所有空间尺度上提取的外观,而不是仅仅是单一尺度上。对于风格损失,使用层激活的格拉姆矩阵(Gram matrix),即某一层特征图的内积。这个内积可以被理解成表示该层特征之间相互关系的映射。这些特征相关关系抓住了在特定空间尺度下模式的统计规律,从经验上来看,它对应于这个尺度上找到的纹理外观。
因此,风格损失的目的是在风格参考图像与生成图像之间,在不同的层激活内保存相似的内部相互关系。反过来,这保证了在风格参考图像与生成图像之间,不同空间尺度找到的纹理看起来都很相似。- 在目标内容图像和生成图像之间保持相似的较高层激活,从而能够保留内容。卷积神经应该能够“看到”目标图像和生成图像包含相同的内容。
- 在较低层和io高层的激活中保持类似的相互关系(correlation),从而能够保留风格。特征相互关系捕捉到的是纹理(texture),生成图像和风格参考图像在不同的空间尺度上应该具有相同的纹理。
- 用Keras实现神经风格迁移
神经风格迁移的一般过程:- 创建一个网络,它呢能同时计算风格参考图像、目标图像和生成图像的VG19层激活。
- 使用这三张图像上计算的层激活来定义之前所述的损失函数,为了实现风格迁移,需要将这个损失函数最小化。
- 设置梯度下降过程来将这个损失函数最小化。
#定义初始变量
from keras.preprocessing.image import load_img, img_to_array
target_image_path = 'img/portrait.jpg' #想要变换图像的路径
style_reference_image_path = 'img/transfer_style_reference.jpg' #风格图像的路劲
#生成图像尺寸
width, height = load_img(target_image_path).size
img_height = 400
img_width = int(width * img_height / height)
#辅助函数
import numpy as np
from keras.applications import vgg19
def preprocess_image(image_path):
img = load_img(image_path, target_size=(img_height, img_width))
img = img_to_array(img)
img = np.expand_dims(img, axis=0)
img = vgg19.preprocess_input(img)
return img
def deprocess_image(x):
#vgg19.preprocess_input的作用是减去ImageNet的平均像素值,使其中心为0,这里相当于vgg19.preprocess_input的逆操作
x[:, :, 0] += 103.939
x[:, :, 1] += 116.779
x[:, :, 2] += 123.68
#将图像由BGR格式转换为RGB格式,这也是VGG19.preprocess_input逆操作的一部分。
x = x[:, :, ::-1]
x = np.clip(x, 0, 255).astype('uint8')
return x
#加载预训练的VGG19网络,并将其应用于三张图像
from keras import backend as K
target_image = K.constant(preprocess_image(target_image_path))
style_reference_image = K.constant(preprocess_image(style_reference_image_path))
combinamtion_image = K.placeholder((1, img_height, img_width, 3)) #站位符用于保存生成图像
input_tensor = K.concatenate([target_image, style_reference_image, combination_image], axis=0) #将三张图像合并Wie一个批量
#利用三张图像组成的批量作为输入来构建VGG19网络。加载模型将使用预训练的ImageNet权重
model = vgg19.VGG19(input_tensor=input_tensor, weights='imagenet', include_top=False)
print('Model loaded.')
#内容损失
def content_loss(base, combination):
return K.sum(K.square(combination - base))
#风格损失
def gram_matrix(x):
features = K.batch_flatten(K.permute_dimensions(x, (2, 0, 1)))
gram = K.dot(features, K.transpose(features))
return gram
def style_loss(style, combination):
S = gram_matirx(style)
C = gram_matrix(combination)
channels = 3
size = img_height * img_width
return K.sum(K.square(S - C)) / (4. * (channels ** 2) * (size ** 2))
#总变差损失
def total_variation_loss(x):
a = K.square(x[:, :img_height - 1, :img_width - 1, :] - x[:, 1:, :img_width - 1, :])
b = K.square(x[:, :img_height - 1, :img_width - 1, :] - x[:, :img_height - 1, 1:, :])
return K.sum(K.pow(a + b, 1.25))
#定义需要最小化的最终损失
#将层的名称映射为激活张量的字典
outputs_dict = dict([(layer.name, layer.output) for layer in model.layers])
content_layer = 'block5_conv2' #用于内容损失的层
style_layers = ['block1_conv1', 'block2_conv1', 'block3_conv1', 'block4_conv1', 'block5_conv1'] #用于风格损失的层
#损失分量的加权平均所使用的权重
total_variation_weight = 1e-4
style_weight = 1.
content_weight = 0.025
#添加内容损失,在定义损失时将所有分量添加到这个标量变量中
loss = K.variable(0.)
layer_features = outputs_dict[conten_layer]
target_image_features = layer_features[0, :, :, :]
combination_features = layer_features[2, :, :, :]
loss += content_weight *content_loss(target_image_features, combination_features)
#添加每个模板层的风格损失分量
for layer_name in style_layers:
layer_features = outputs_dict[layer_name]
style_reference_features = layer_features[1, :, :, :]
combination_features = layer_features[2, :, :, :]
s1 = style_loss(style_reference_features, combination_features)
loss += (style_weight / len(style_layers)) * s1
loss += total_variation_weight * total_variation_loss(combination_image)
#设置梯度下降过程
#获取损失相对于生成图像的梯度
grads = K.gradients(loss, combination_image)[0]
#获取当前损失值和当前梯度值的函数
fetch_loss_and_grads = K.function([combination_image], [loss, grads])
#该类将fetch_loss_and_grads包装起来,可以利用两个单独的方法调用来获取损失和梯度
class Evaluator(object):
def __init__(self):
self.loss_value = None
self.grads_values = None
def loss(self, x):
assert self.loss_value is None
x = x.reshape((1, img_height, img_width, 3))
outs = fetch_loss_and_grads([x])
loss_value = outs[0]
grad_values = outs[1].flatten().astype('float64')
self.loss_value = loss_value
self.grad_values = grad_values
return self.loss_value
def grads(self, x):
assert self.loss_value is not None
grad_values = np.copy(self.grad_values)
self.loss_value = None
self.grad_values = None
return grad_values
evaluator = Evaluator()
#风格迁移循环
from scipy.optimize imort fmin_l_bfgs_b
from scipy.misc import imsave
import time
result_prefix = 'my_result'
iterations = 20
#初始状态:目标图像
x = preprocess_image(target_image_path)
#将图像照片,因为scipy.optimize.fmin_l_bfgs_b只能处理展平的向量
x = x.flatten()
for i in range(iterations):
print('Start of iteration', i)
start_time = time.time()
#对生成的图像的像素运行L-BFGS最优化,以将神经风格损失最小化。必须将计算损失的函数和计算梯度的函数作为两个单独的参数传入
x, min_val, info = fmin_l_bfgs_b(evaluator.loss, x, fprime=evaluator.grads, maxfun=20)
print('Current loss value:', min_vla)
img = x.copy().reshape((img_height, img_width, 3))
img = deprocess_image(img)
fname = result_prefix + '_at_iteration_%d.png' % o
imsave(fname, img)
print('Image saved as', fname)
end_time = time.time()
print('Iteration %d completed in %ds' % (i, end_time - start_time))
用变分自编码器生成图像
- 从图像的潜在空间中采样
图像生成的关键思想是找到一个低维的表示潜在空间(latent space,也是一个向量空间),其中任意点都可以被映射为一张逼真的图像。能够实现这种映射的模块,即以潜在点作为输入并输出一张图像(像素网格),叫作生成器(generator, 对于GAN(generative adversarial network)而言)或解码器(decoder,对于VAE(vaiational autoencoder)而言)。一旦找到了这样的潜在空间,就可以从中有意的或随机对点进行采样,并将其映射到图像空间,从而生成前所未见的图像。
要想学习图像表示的这种潜在空间,GAN和VAE是两种不同的策略,每种策略都有各自的特点,VAE非常适合用于学习具有良好结构的潜在空间,其中特定方向表示数据中有意义的变化轴。GAN生成的图像可能非常逼真,但它的潜在空间可能没有良好结构,也没有足够的连续性。 - 图像编辑的概念向量
概念向量(concept vector):给定一个表示的潜在空间或一个嵌入空间,空间中的特点方向可能表示原始数据中有趣的变化轴。一旦找到了这样的向量,就可以用这种方法来编辑图像:将图像投射到潜在空间中,用一种有意义的方式来移动其表示,然后再将其解码到图像空间。在图像空间中任意独立的变化维度都有概念向量。 - 变分自编码器
变分自编码器是一种生成式模型,特别适用于利用概念向量进行图像编辑的任务。它是一种现代化的自编码器,将深度学习的想法与贝叶斯推断结合在一起。自编码器是一种网络类型,其目的是将输入编码到低维潜在空间,然后再解码回来。
VAE不是将输入图像压缩成潜在空间中的固定编码,而是将图像转换为统计分布的参数,即平均值和方差。假设输入图像是由统计过程中生成的,在编码和解码过程中应该考虑这一过程的随机性。然后,VAE使用平均值和方差这两个参数从分布中随机采样一个元素,并将这个元素解码到原始输入。这个过程的随机性提高了其稳健性,并迫使潜在空间的任何未知都对应有意义的表示,即潜在空间采样的每个点都能解码为有效的输出。
VAE原理:- 一个编码器模块将输入样本input_img转换为表示潜在空间中的两个参数z_mean和z_log_variance。
- 假定潜在正太分布能够生成输入图像,并从这个分布中随机采样一个点z:z = z_mean + exp(z_log_variance) * epsilon,其中epsilon是取值很小的随机张量。
- 一个解码器模块将潜在空间的这个点映射回原始输入图像。
#VAE编码器网络
import keras
from keras import layers
from keras import backend as K
from keras.models import Model
import numpy as np
img_shape = (28, 28, 1)
batch_size = 16
#潜在空间的维度:一个二维平面
latent_dim = 2
input_img = keras.Input(shape=img_shape)
x = layers.Conv2D(32, 3, padding='same', activation='relu')(input_img)
x = layers.Conv2D(64, 3, padding='same', activation='relu', strides=(2, 2))(x)
x = layers.Conv2D(64, 3, padding='same', activation='relu')(x)
x = layers.Conv2D(64, 3, padding='same', activation='relu')(x)
shape_before_flattening = K.int_shape(x)
x = layers.Flatten()(x)
x = layers.Dense(32, activation='relu')(x)
#输入图像最终被编码为这两个参数
z_mean = layers.Dense(latent_dim)(x)
z_log_var = layers.Dense(latent_dim)(x)
#潜在空间采样的函数
def sampling(args):
z_mean, z_log_var = args
epsilon = K.random_normal(shape=(K.shape(z_mean)[0], latent_dim), mean=0., stddev=1.)
return z_mean + K.exp(z_log_var) * epsilon
z = layers.Lambda(sampling)([z_eman, z_log_var])
#VAE解码器网络,将潜在空间点映射为图像
#将z输入到这里
decoder_input = layers.Input(K.int_shape(z)[1:])
#对输入进行上采样
x = layers.Dense(np.prod(shape_before_flattening[1:]), activation='relu')(decoder_input)
#将z转换为特征图,使其形状与编码器模型最后一个Flatten层之前的特征图的形状相同
x = layers.Reshape(shape_before_flattening[1:])(x)
#使用一个Conv2DTranspose层和一个Conv2D层,将z解码为与原始输入图像j具有相同尺寸的特征图
x = layers.Conv2DTranspose(32, 3, padding='same', activation='relu', strides=(2, 2))(x)
x = layers.Conv2D(1, 3, padding='same', activation='sigmoid')(x)
#将解码器模型实例化,它将decoder_input转换为解码后的图像
decoder = Model(decoder_input, x)
#将这个实例应用于z,以得到解码后的z
z_decoder = decoder(z)
#用于计算VAE损失的自定义层
class CustomVariationalLayer(keras.layers.Layer):
def vae_loss(self, x, z_decoded):
x = K.flatten(x)
z_decoded = K.flatten(z_decoded)
xent_loss = keras.metrics.binary_crossentropy(x, z_decoded)
kl_loss = -5e-4 * K.mean(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
return K.mean(xent_loss + kl_loss)
def call(self, inputs):
x = inputs[0]
z_decoded = inputs[1]
loss = self.vae_loss(x, z_decoded)
self.add_loss(loss, inputs=inputs)
#不使用这个输出,但是层必须要有返回值
return x
y = CustomVariationalLayer()([input_img, z_decoded])
#训练VAE
from keras.datasets import mnist
vae = Model(input_img, y)
vae.compile(optimizer='rmsprop', loss=None)
vae.summary()
(x_train, _), (x_test, y_test) = mnist.load_data()
x_train = x_train.satype('float32') / 255.
x_train = x_train.reshape(x_trian.shape + (1,))
x_test = x_test.astype('float32') / 255.
x_test = x_test.reshape(x_test.shape + (1,))
vae.fit(x=x_train, y=None, shuffle=True, epochs=10, batch_size=batch_size, validation_data=(x_test, None))
#从二维潜在空间中采样一组点的网格,并将其解码为图像
import matplotlib.pyplot as plt
from scipy.stats import norm
n = 15
digit_size = 28
figure = np.zeros((digit_size * n, digit_size * n))
grid_x = norm.ppf(np.linspace(0.05, 0.95, n))
grid_y = norm.ppf(np.linspace(0.05, 0.95, n))
for i, yi in enumerate(grid_x):
for j, xi in enumerate(grid_y):
z_sample = np.array([xi, yi])
z_sample = np.tile(z_sample, batch_size).reshpae(batch_size, 2)
x_decoded = decoder.predict(z_sample, batch_size=batch_size)
digit = x_decoded[0].reshape(digit_size, digit_size)
figure[i * digit_size: (i + 1) * digit_size, j * digit_size: (j + 1) * digit_size] = digit
plt.figure(figsize=(10, 10))
plt.imshow(figure, cmap='Greys_r')
plt.show()
生成对抗网络简介
与VAE不同,GAN的潜在空间无法保证具有有意义的结构,而且是不连续的。GAN的优化最小值是不固定的,它是一个动态的系统,最优化过程寻找的不是一个最小值,而是两股力量之间的平衡。
- GAN的简要实现流程
- generator网络将形状为(latent_dim,)的向量映射大形状为(32, 32, 3)的图像。
- discriminator网络将形状为(32, 32, 3)的图像映射到一个二进制分数,用于评估图像为真的概率。
- gan网络将generator网络和discriminator网络连接在一起:gan(x)=discriminator(generator(x))。生成器将潜在空间向量解码为图像,判别器对这些图像的真实性进行评估,因此这个gan网络是将这些潜在向量映射到判别器的评估结果。
- 使用带有“真”/“假”标签的真假图像样本来训练判别器,就和训练普通的图像分类模型一样。
- 为了训练生成器,要使用gan模型的损失相对于生成器权重的提取。在每一步都要移动生成器的权重,其移动方向是让判别器更有可能将生成器解码的图像划分为“真”。训练生成器来欺骗判别器。
- 技巧
- 使用tanh作为生成器最后一层的激活,而不是用sigmoid。
- 使用正太分布(高斯分布)对潜在空间中的点进行采样,而不用均匀分布。
- 随机性能够提高稳健性。训练GAN得到的是一个动态平衡,在训练过程中引入随机性防止GAN可能以各种方式“卡住”。通过两种方式引入随机性:一种是在判别器中使用dropout,另一种是像判别器的标签添加随机噪声。
- 稀疏的梯度会妨碍GAN的训练。最大池化运算和ReLU激活可能导致梯度稀疏。推荐使用步进卷积代替最大池化来进行下采样,LeakyReLU层来代替ReLU激活。LeakyReLU和ReLU类似,但它允许较小的负数激活值,从而放宽了稀疏性限制。
- 在生成的图像中,经常会见到棋盘状伪影,这是由生成器中像素空间的不均匀覆盖导致的。为了解决这个问题,每当在生成器和判别器中都使用步进的Conv2DTranspose或Conv2D时,使用的内核大小要能够被步幅大小整除。
- 生成器
生成器(generator模型)将一个向量(来自潜在空间,训练过程中对其随机采样)转换为一张候选图像。GAN常见的诸多问题之一就是生成器“卡在”看似噪声的生成图像上。一种可行的解决方案是在判别器和生成器中都使用dropout。
#GAN生成器网络
import keras
from keras import layers
import numpy as np
latent_dim = 32
height = 32
width = 32
channels = 3
generator_input = keras.Input(shape=(latent_dim,))
#将输入转换为大小为16x16的128个通道的特征图
x = layers.Dense(128 * 16 * 16)(generator_input)
x = layers.LeakyReLU()(x)
x = layers.Reshape((16, 16, 128))(x)
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
#上采样为32x32
x = layers.Conv2DTranspose(256, 4, strides=2, padding='same')(x)
x = layers.LeakReLU()(x)
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(256, 5, padding='same')(x)
x = layers.LeakyReLU()(x)
#生成一个大小为32x32的单通道特征图(即CIFAR10图像的形状)
x = layers.Conv2D(channels, 7, activation='tanh', padding='same')(x)
#将生成器模型实例化,它将形状为(latent_dim, )的输入映射 形状为(32, 32, 3)的图像
generator = keras.models.Model(generator_input, x)
generator.summary()
- 判别器
判别器(discriminatory模型)接收一张候选图像(真实的或合成的)作为输入,并将其划分到这两个类别之一:“生成图像”或“来自训练集的真实图像”。
#GAN判别器网络
discriminator_input = layers.Input(shape=(height, width, channels))
x = layers.Conv2D(128, 3)(discriminator_input)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Conv2D(128, 4, strides=2)(x)
x = layers.LeakyReLU()(x)
x = layers.Flatten()(x)
#一个dropout层:这个是很重要的技巧
x = layers.Dropout(0.4)(x)
#分类层
x = layers.Dense(1, activation='sigmoid')(x)
#将判别器模型实例化,它将形状为(32, 32, 3)的输入转换为一个二进制分类决策(真/假)
discriminator = keras.models.Model(discriminator_input, x)
discriminator.summary()
#在优化器中使用梯度裁剪(限制梯度值的范围)
#为了稳定训练过程,使用学习率衰减
discriminator_optimizer = keras.optimizers.RMSprop(lr=0.0008, clipvalue=1.0, decay=1e-8)
discriminator.compile(optimizer=discriminator_optimizer, loss='bianry_crossentropy')
- 对抗网络
设置GAN,将生成器和判别器连接在一起。训练时,这个模型让生成器向某个方向移动,从而提高它欺骗判别器的能力。这个模型将潜在空间的点转换为一个分类决策(即“真”或“假”),它训练的标签都是“真实图像”。因此,训练gan将会更新generator的权重,使得discriminator在观察假图像时更有可能预测为“真”。在训练过程中需要将判别器设置为冻结(即不可训练),这样在训练gan时它的权重才不会更新。如果在此过程中可以对判别器的权重进行更新,那么在训练判别器始终预测“真”,但这并不是我们想要的。
#对抗网络
#判别器权重设置为不可训练(仅应用于gan模型)
discriminator.trainable = False
gan_input = keras.Input(shape=(latent_dim,))
gan_output = discriminator(generator(gan_input))
gan = keras.models.Model(gan_input, gan_output)
gan_optimizer = keras.optimizers.RMSprop(lr=0.0004, clipvalue=1.0, decay=1e-8)
gan.compile(optimizer=gan_optimizer, loss='binary_crossentropy')
- 如何训练DCGAN(深度卷积生成式对抗网络 Deep Convolutional GAN)
- 从潜在空间中抽取随机的点(随机噪声)。
- 利用这些随机噪声用gen污染投入生成图像。
- 将生成图像与真实图像混合
- 使用这些混合后的图像以及响应的标签(真实图像为“真”,生成图像为“假”)来训练discriminatory。
- 在潜在空间中随机抽取新的点。
- 使用这些随机向量以及全部是“真实图像”的标签来训练gan。这个会更新生成器的权重(只更新生成器的权重,因为判别器在gan中被冻结),其更新方向是使得判别器能够将生成图像预测为“真实图像”这个过程是训练生成器去欺骗判别器。
- 训练时,对抗损失开始大幅度增加,而判别损失则趋向于零,即判别器最终支配了生成器。如果出现这种情况,可以尝试in小判别器的学习率,并增大判别器的dropout比率。
#实现GAN的训练
import os
from keras.preprocessing import image
(x_train, y_train), (_, _) = keras.datasets.cifar10.load_data()
x_train = x_trian[y_train.flatten() == 6] #选择青蛙图像(类别编号为6)
x_train = x_train.reshape((x_trian.shape[0],) + (height, width, channels)).astype('float32') / 255.
iterations = 1000
batch_size = 20
save_dir = 'your_dir'
start = 0
for step in range(iterations):
#潜在空间中采样随机点
random_latent_vectors = np.random.normal(size=(batch_size, latent_dim))
#将这些点解码为虚假图像
generated_images = generator.predict(random_latent_vectors)
#将这些虚假图像与真实图合在一起
stop = start + batch_size
real_images = x_train[start: stop]
combined_images = np.concatenate([generated_images, real_images])
#合并标签,区分真实和虚假的图像
labels = np.concatenate([np.one((batch_size, 1)), np.zeros((batch_size, 1))])
#向标签中添加随机噪声,这是一个很重要的技巧
labels += 0.05 * np.random.random(labels.shape)
#训练判别器
d_loss = discriminator.train_on_batch(combined_images, labels)
#在潜在空间中采样随机点
random_laten_vectors = np.random.normal(size=(batch_size, latent_dim))
#合并标签,全部是“真实图像”(这是在撒谎)
misleading_targets = np.zeros((batch_size, 1))
#通过gan模型来训练生成器(此时冻结判别器权重)
a_loss = gan.train_on_batch(random_latent_vectors, misleading_targets)
start += batch_size
if start > len(x_train) - batch_size:
start = 0
if step % 100 == 0: #每100步保存并绘图
gan.save_weights('gan.h5')
print('discriminator loss:', d_loss)
print('adversarial loss:', a_loss)
img = image.array_to_img(generated_images[0] * 255., scale=False)
img.save(os.path.join(save_dir, 'generated_frog' + str(step) + '.png'))
img = image.array_to_img(real_images[0] * 255., scale=False)
img.save(os.path.join(save_dir, 'real_frog' + str(step) + '.png'))
- 小结
- GAN由一个生成器网络和一个判别器网络组成。判别器网络的训练目的是能够区分生成器的输出与来自训练集的真实图像,生成器的训练目的是欺骗判别器。 值得注意的是,生成器从未直接见过训练集中的图像,它所知道的关于数据的信息都来自于判别器。
- GAN很难训练,因为训练GAN是一个动态过程,而不是具有固定损失的简单梯度下降过程。想要正确的训练GAN,x㔿使用一些启发式的技巧,还需要大量的调节。
- GAN可能会生成非常逼真的图像。但与VAE不同,GAN学习的潜在空间没有整齐的连续结构,因此可能不适用于某些实际应用,比如通过潜在空间概念向量进行图像编辑。
本章总结
借助深度学习的创造性应用,深度网络不仅能够对现有内容进行标注,还能够自己生成新内容。
- 如何生成序列数据,每次生成一个时间步。这可以应用于文本生成,也可应用于逐个音符的音乐生成或其他任何类型的时间序列数据。
- DeepDream的工作原理:通过输入空间中的梯度上升将卷积神经网络的或最大化。
- 风格迁移实现,将内容图像和风格图像组合在一起,并产生有趣的效果。
- 对抗赛生成网络(GAN)、变分自编码器(VAE)创造新的图像,以及使用潜在空间概念向量进行图像编辑。
总结
重点内容
-
人工智能的各种方法
- 人工智能(artificial intelligence):将认知过程自动化的所有尝试。
- 机器学习(machine learning):人工智能的一个特殊子领域,其目标是仅靠观察训练数据来自动开发程序[即模型(model)]。将数据转换为程序 这个过程叫作学习(learning) 。
- 深度学习(deep learning):机器学习的众多分支之一,它的模型是一长串几何函数,一个接一个的作用在数据上。这些运算被组织成模块,叫作层(layer)。深度学习模型通常都是层的堆叠,或者是层组成的图。这些层由权重(weight)来参数化,权重是在训练过程中需要学习的参数。模型的知识(knowledge)保存在它的权重中,学习的过程就是为这些权重找到正确的值。
-
深度学习在机器学习领域中的特殊之处
在机器感知领域,需要从图像、视频、声音等输入中提取有用的信息,给定足够多的训练数据(特别是由人类正确标记的训练数据),深度学习能够从感知数据提取出人类能够提取出的几乎全部信息。 -
如何看待深度学习
在深度学习中,一切都是向量,即一切都是几何空间(geometric space)中的点(point)。首先量模型输入(文本、图像等)和目标向量化(vectorize),即将其转换为初始输入向量空间和目标向量空间。深度学习模型的每一层都对通过它的数据做一个简单的几何变换。模型中的层链共同形成了一个非常复杂的几何变换,它可以分解为一系列简单的几何变换。这个复杂变换试图将输入空间映射到目标空间,每次映射一个点。这个变换由层的权重来参数化,权重根据模型当前表现进行迭代更新。这种几何变换有一个关键性质,它是必须可微的(differentiable),这样才能通过梯度下降i 学习其参数。即从输入到输出的几何变形必须是平滑且连续的,这是很重要的约束条件。
神经网络最初来自于使用图对意义进行编码这一思路,但是这是一个极具误导性的名称,因为它与神经或网络都没有关系,更合适的名称应该是分层表示学习(layered representations learning)或层级表示学习(hierarchical representations learning),甚至还可以叫深度可微模型(deep differentiable model)或链式几何变换(chained geometric transform),以强调其核心在于连续的激活空间操作。 -
关键的推动技术
- 渐进式的算法创新(反向传播算法)
- 大量可用的感知数据
- 快速且高度并行的计算硬件(GPU)
- 复杂的软件栈,使得人类能够利用这些计算能力(TensorFlow, Keras等)
-
机器学习的通用工作流程
- 定义问题
- 找到能够可靠评估目标成功的方法
- 准备用于评估模型的验证过程
- 数据向量化
- 开发第一个模型
- 通过调节超参数和添加正则化来逐步改进模型架构
- 调节超参数时要小心验证集过拟合,即超参数可能会过于针对验证集而优化。
-
关键网络架构
- 密集连接网络、卷积网络和循环网络,每种类型的网络都针对于特定的输入模式,网络架构中包含对数据结构的假设,即搜索良好模型所在的假设空间。某种架构能够解决某个问题,这完全取决于数据结构与网络架构的假设之间的匹配度。输入模式与网络架构之间的对应关系:
- 向量数据:密集连接网络(Dense层)
- 图像数据:而为卷积神经网络
- 声音数据(比如波形):一维卷积神经网络(首选)或循环神经网络
- 文本数据:一为卷积神经网络(首选)或循环神经网络
- 时间序列数据:循环神经网络(首选)或一维卷积神经网络
- 其他类型的序列数据:循环神经网络或一维卷积神经网络。
- 视频数据:三维卷积神经网络
- 立体数据:三维卷积神经网络
- 密集连接网络
是Dense层的堆叠,用于处理向量数据(向量批量)。这种网络加深而输入特征红没有特定结构:Dense层的每个单元都和其他所有单元相连接。-
二分类问题(bianry classification),层堆叠的最后一层是使用sigmoid激活且只有一个单元的Dense层,并使用binary_crossentropy作为损失。目标应该是0或1。
-
单标签多分类问题(single-label categorical classification,每个样本只有一个类别,不会超过一个),层堆叠的最后一层是一个Dense层,它使用softmax激活,其单元个数等于类别个数,如果目标是one-hot编码的,那么使用categorical_crossentropy作为损失;如果目标是整数,那么使用sparse_categorical_crossentropy作为损失。
-
多标签多分类问题(multilabel categorical classification,每个样本可以有多个类别),层堆叠的最后一层是一个Dense层,它使用sigmoid激活,其单元个数等于类别个数,并使用binary_crossentropy作为损失。目标应该是k-hot编码的。
-
连续值向量的回归(regression)问题,层堆叠的最后一层是i个bai激活Dense层,其单元个数等于要预测的值的个数(通常只有一个值)。有几种损失可以用于回归问题,最常见的是mean_squared_error(均方误差,MSE)和mean_absolute_error(平均绝对误差,MAE)。
#二分类问题 from keras import models from keras import layers model = models.Sequential() model.add(layers.Dense(32, activation='relu', input_shape= (num_input_features,))) model.add(layers.Dense(32, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='rmsprop', loss='bianry_crossentropy') #单标签多分类问题 model = models.Sequential() model.add(layers.Dense(32, activation='relu', input_shape=(num_input_features,))) model.add(layers.Dense(32, activation='relu')) model.add(layers.Dense(num_classes, activation='softmax')) model.compile(optimizer='rmsprop', loss='categorical_crossentropy') #多标签多分类问题 model = models.Sequential() model.add(layers.Dense(32, activation='relu', input_shape=(num_input_features,))) model.add(layers.Dense(32, activation='relu')) model.add(layers.Dense(num_calsses, activatin='sigmoid')) model.compile(optimizer='rmsprop',loss='bianry_crossentropy') #连续值向量的回归问题 model = models.Sequential() model.add(layers.Dense(32, activation='relu', input_shape=(num_input_features,))) model.add(layers.Dense(32, activation='relu')) model.add(layers.Dense(num_values)) model.compile(optimizer='rmsprop', loss='mse')
-
- 卷积神经网络
卷积神经网络能够查看空间局部模式,其方法是对输入张量的不同空间位置 (图块)应用相同的几何变换。这样得到的表示具有平移不变性,这使得卷积层能够高效利用数据,并且能够高度模块化。
卷积神经网络是卷积成和最大池化层的堆叠。池化层可以对数据进行空间下采样,这么做的目的:随着特征数量的增大,需要让特征图的尺寸保持在合理范围内;让后面的卷积层能够看到 中更大的空间范围。卷积神经网络的最后通常是一个Flatten运算或全局池化层,将空间特征图转换为向量,然后再是Dense层,用于实现分类或回归。
#多分类 model = models.Sequential() model.add(layers.SeparableConv2D(32, 3, activation='relu', input_shape=(height, width, channels))) model.add(layers.SeparableConv2D(64, 3, activation='relu')) model.add(layers.MaxPooling2D(2)) model.add(layers.SeparableConv2D(64, 3, activation='relu')) model.add(layers.SeparableConv2D(128, 3, activation='relu')) model.add(layers.MaxPooling2D(2)) model.add(layers.SeparableConv2D(64, 3, activation='relu')) model.add(layers.SeparableConv2D(128, 3, activation='relu')) model.add(layers.GlobalAveragePooling2D()) model.add(layers.Dense(32, activation='relu')) model.add(layers.Dense(num_calsses, activation='softmax')) model.compile(optimizer='rmsprop', loss='categorical_crossentropy')
-
循环神经网络
循环神经网络(RNN,recurrent neural network)的工作原理是,对输入序列每次处理一个时间步,并且自始至终保存一个状态(state, 这个状态通常是一个向量或一组向量,即状态几何空间中的点)。如果序列中的模式不具有时间平移不变性(比如时间序列数据,最近的过去比遥远的过去更加重要),那么应该优先使用循环神经网络,而不是一维卷积神经网络。#一个单层RNN层,用于向量序列的二分类 model = models.Sequential() model.add(layers.LSTM(32, input_shape=(num_timesteps, num_features))) model.add(layers.Dense(num_classes, activation='sigmoid')) model.compile(optimizer='rmsprop', loss='binary_crossentropy') #RNN层堆叠,用于向量序列的二分类 model = models.Sequential() model.add(layers.LSTM(32, return_sequences=True, input_shape=(num_timesteps, num_features))) model.add(layers.LSTM(32, return_sequences=True)) model.add(layers.LSTM(32)) model.add(layers.Dense(num_classes, activation='sigmoid')) model.compile(optimizer='rmsprop', loss='binary_crossentropy')
- 密集连接网络、卷积网络和循环网络,每种类型的网络都针对于特定的输入模式,网络架构中包含对数据结构的假设,即搜索良好模型所在的假设空间。某种架构能够解决某个问题,这完全取决于数据结构与网络架构的假设之间的匹配度。输入模式与网络架构之间的对应关系:
-
可能性空间
- 将向量数据映射 向量数据
- 预测性医疗保健:将患者医疗记录映射到患者治疗效果的预测。
- 行为定向:将一组网站属性映射到用户在网站上所花费的时间数据。
- 产品质量控制:将与某件产品制成品相关的一组属性映射到产品明年会坏掉的概率。
- 将图像数据映射到向量数据
- 医生助手:将医学影像幻灯片映射到是否存在肿瘤的预测。
- 自动驾驶车辆:将车载摄像机的视频画面映射到方向盘的角度控制命令。
- 棋盘游戏人工智能:将围棋和象棋棋盘映射到下一步走棋。
- 饮食助手:将食物照片映射到食物的卡路里数量。
- 年龄预测:将自拍照片映射到人的年龄。
- 将时间序列数据映射为向量数据
- 天气预报:将多个地点天气数据的时间序列映射到某地下周的天气数据。
- 脑机接口:将脑礠图(MEG)数据的时间序列映射到计算机命令。
- 行为定向:将网站上用户交互的时间序列映射到用户购买某件商品的概率。
- 将文本映射到文本
- 智能回复:将电子邮件映射到合理的单行回复。
- 回答问题:将常识问题映射到答案。
- 生成摘要:将一篇长文章映射到文章的简短摘要。
- 将图像映射到文本
- 条件图像生成:将简短 文字描述映射到与这段描述相匹配的图像。
- 标识生成/选择:将公司的名称和描述映射到公司标识。
- 将图像映射到图像:
- 超分辨率:将缩小的图像映射到相同图像的更高分辨率版本。
- 视觉深度感知:将室内环境的图像映射到深度预测图。
- 将图像和文本映射到文本:
- 视觉问答:将图像和关于图像内容的然语言问题映射到自然语言答案。
- 将视频和文本映射到文本
- 视频问答:将短视频和关于视频内容的自然语言问题映射到自然语言答案。
深度学习的局限性
- 将向量数据映射 向量数据
深度学习模型只是将一个向量空间映射到另一个向量空间的简单而又连续的几何变换链。它能做的只是将一个数据流形X映射到另一个流形Y,前提是从X到Y存在可以学习的连续变换。深度学习模型可以被看作一种程序,但反过来说,大多数程序都不能被表示为深度学习模型。对大多数任务而言,要么不存在相应的深度神经网络能够解决的任务,要么即使存在这样的网络,它也可能是不可学习的(learnable)。最后一种情况的的原因可能是相应的几何变换过于复杂,也可能是没有合适的数据用于学习。
- 将机器学习模拟人化的风险
人们误解了深度学习模型的作用,并高估了它们的能力。人类的一个基本特征就是我们的心智理论:我们倾向于将意图、信念和知识投射到身边的事物上。将人类的这个特征应用于深度学习,例如,我们能够大致成功训练一个模型来生成描述图像的说明文字,我们就会相信这个模型能够‘理解’图像内容和它生成的说明文字。然后如果某张图像与训练数据中的一类图像略有不同,并导致模型生成非常荒谬的说明文字,我们就会感到惊讶。
对抗样本是深度学习网络的输入样本,其目的在于欺骗模型对它们进行错误归类。比如在输入空间中进行梯度上升,可以生成能够让卷积神经网络某个过滤器激活最大化的输入,所以通过梯度上升,可以读图像稍作修改,使其能够将某一类别的预测最大化。将长臂猿的梯度添加到一张熊猫照片中,可以让神经网络将这个熊猫归类为长臂猿。这表明了这些模型的脆弱性,也表明这些模型从输入OA输出的映射与人类感知之间的深刻差异。
深度学习模型并不理解它们的输入,至少不是人类所说的理解。我们自己对图像、声音和语言的理解是基于我们作为人类的感觉运动体验。机器学习模型无法获得这些体验,因此也就无法用与人类相似的方式来理解它们的输入。通过对输入模型的大量训练本样本进行标记,可以让模型学会一个简单几何变换,这个变换在一组特定样本上将数据映射到人类概念,但这种映射只是我们头脑中原始模型的简化。我们头脑中的原始模型是从我们作为具身主体的体验发展而来的。机器学习模型就像是镜中的模糊图像。 - 局部泛化与极端泛化
深度学习模型从输入到输出的简单几何变形与人类思考和学习的方式之间存在根本性的区别。区别不仅在于人类是从具身体验中自我学习,而不是通过观察显示的训练样例来学习。除了学习过程不同,底层表示的性质也存在根本性的区别。
深度网络能够将即时刺激映射到即时反应,但人类能做的远比这更多。对于现状、对于我们自己、对于其他人,我们都使用一个复杂的抽象模型,并可以利用这些模型来预测各种可能的并执行长期规划。我们可以将已知的概念融合在一起,来表示之前从未体验过的事物,处理假想情况的能力,将我们的心智模型空间扩展到远远超出我们能够直接体验的范围,让我们能够进行抽象和推理,这种能力可以说是人类认知决定性特征。将其称为极端泛化,即只用 很少的数据,甚至没有新数据,就可以适应从未体验过的新情况的能力。
这与深度网络的做法形成了鲜明对比,将后者称为局部泛化,执行从输入到输出的映射,如果新的输入与网络训练时所见到的输入稍有不同,这种映射就会立刻变得没有意义。
模型只能进行局部泛化,只能适应与过去数据类似的新情况,而人类的认知能够进行极端泛化,能够快速适应全新情况并为长期的未来情况做出规划。 - 小结
深度学习唯一的真正成功之后就是,给定大量的人工标注数据,能够使用连续的几何变换将空间X映射到空间Y。做好这一点已经可以引起基本上所有行业的变革,但离达到人类水平的人工智能还有很长的路要走。
想要突破这一局限性,并能创造出能够与人类大脑匹配的人工智能,我们需要抛弃简单的从输入到输出的映射,转而研究推理和抽象。对各种情况和概念进行抽象建模,一个合适的基础可能是计算机程序。机器模型可以定义为可学习的程序。
深度学习的未来
- 模型即程序
- 超越反向传播和可微层
- 自动化机器学习
- 终身学习与模块化子程序复用